Permet aux utilisateurs de visualiser et gérer leurs sessions actives sur différents appareils, avec la possibilité de révoquer des sessions à distance en cas de suspicion d'activité non autorisée. Fonctionnalités : - Liste des sessions actives avec métadonnées (appareil, navigateur, localisation) - Identification de la session courante - Révocation individuelle d'une session - Révocation de toutes les autres sessions - Déconnexion avec nettoyage des cookies sur les deux chemins (legacy et actuel) Sécurité : - Cache frontend scopé par utilisateur pour éviter les fuites entre comptes - Validation que le refresh token appartient à l'utilisateur JWT authentifié - TTL des sessions Redis aligné sur l'expiration du refresh token - Événements d'audit pour traçabilité (SessionInvalidee, ToutesSessionsInvalidees) @see Story 1.6 - Gestion des sessions
134 lines
4.6 KiB
PHP
134 lines
4.6 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Tests\Unit\Administration\Domain\Model\Session;
|
|
|
|
use App\Administration\Domain\Model\RefreshToken\TokenFamilyId;
|
|
use App\Administration\Domain\Model\Session\DeviceInfo;
|
|
use App\Administration\Domain\Model\Session\Location;
|
|
use App\Administration\Domain\Model\Session\Session;
|
|
use App\Administration\Domain\Model\User\UserId;
|
|
use App\Shared\Domain\Tenant\TenantId;
|
|
use DateTimeImmutable;
|
|
use PHPUnit\Framework\Attributes\Test;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class SessionTest extends TestCase
|
|
{
|
|
private const string TENANT_ID = '550e8400-e29b-41d4-a716-446655440002';
|
|
|
|
#[Test]
|
|
public function createGeneratesSessionWithCorrectData(): void
|
|
{
|
|
$familyId = TokenFamilyId::generate();
|
|
$userId = UserId::generate();
|
|
$tenantId = TenantId::fromString(self::TENANT_ID);
|
|
$deviceInfo = DeviceInfo::fromUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0');
|
|
$location = Location::fromIp('192.168.1.1', 'France', 'Paris');
|
|
$createdAt = new DateTimeImmutable('2026-01-31 10:00:00');
|
|
|
|
$session = Session::create(
|
|
$familyId,
|
|
$userId,
|
|
$tenantId,
|
|
$deviceInfo,
|
|
$location,
|
|
$createdAt,
|
|
);
|
|
|
|
self::assertTrue($session->familyId->equals($familyId));
|
|
self::assertTrue($session->userId->equals($userId));
|
|
self::assertTrue($session->tenantId->equals($tenantId));
|
|
self::assertSame($deviceInfo, $session->deviceInfo);
|
|
self::assertSame($location, $session->location);
|
|
self::assertEquals($createdAt, $session->createdAt);
|
|
self::assertEquals($createdAt, $session->lastActivityAt);
|
|
}
|
|
|
|
#[Test]
|
|
public function updateActivityUpdatesLastActivityTimestamp(): void
|
|
{
|
|
$session = $this->createSession();
|
|
$newActivityAt = new DateTimeImmutable('2026-01-31 14:30:00');
|
|
|
|
$updatedSession = $session->updateActivity($newActivityAt);
|
|
|
|
self::assertEquals($newActivityAt, $updatedSession->lastActivityAt);
|
|
self::assertEquals($session->createdAt, $updatedSession->createdAt);
|
|
self::assertTrue($session->familyId->equals($updatedSession->familyId));
|
|
}
|
|
|
|
#[Test]
|
|
public function isCurrentReturnsTrueForMatchingFamilyId(): void
|
|
{
|
|
$familyId = TokenFamilyId::generate();
|
|
$session = Session::create(
|
|
$familyId,
|
|
UserId::generate(),
|
|
TenantId::fromString(self::TENANT_ID),
|
|
DeviceInfo::fromUserAgent('Mozilla/5.0'),
|
|
Location::unknown(),
|
|
new DateTimeImmutable(),
|
|
);
|
|
|
|
self::assertTrue($session->isCurrent($familyId));
|
|
self::assertFalse($session->isCurrent(TokenFamilyId::generate()));
|
|
}
|
|
|
|
#[Test]
|
|
public function belongsToUserReturnsTrueForMatchingUserId(): void
|
|
{
|
|
$userId = UserId::generate();
|
|
$session = Session::create(
|
|
TokenFamilyId::generate(),
|
|
$userId,
|
|
TenantId::fromString(self::TENANT_ID),
|
|
DeviceInfo::fromUserAgent('Mozilla/5.0'),
|
|
Location::unknown(),
|
|
new DateTimeImmutable(),
|
|
);
|
|
|
|
self::assertTrue($session->belongsToUser($userId));
|
|
self::assertFalse($session->belongsToUser(UserId::generate()));
|
|
}
|
|
|
|
#[Test]
|
|
public function reconstituteRestoresSessionFromStorage(): void
|
|
{
|
|
$familyId = TokenFamilyId::generate();
|
|
$userId = UserId::generate();
|
|
$tenantId = TenantId::fromString(self::TENANT_ID);
|
|
$deviceInfo = DeviceInfo::fromUserAgent('Mozilla/5.0');
|
|
$location = Location::fromIp('10.0.0.1', 'Germany', 'Berlin');
|
|
$createdAt = new DateTimeImmutable('2026-01-20 08:00:00');
|
|
$lastActivityAt = new DateTimeImmutable('2026-01-31 16:00:00');
|
|
|
|
$session = Session::reconstitute(
|
|
$familyId,
|
|
$userId,
|
|
$tenantId,
|
|
$deviceInfo,
|
|
$location,
|
|
$createdAt,
|
|
$lastActivityAt,
|
|
);
|
|
|
|
self::assertTrue($session->familyId->equals($familyId));
|
|
self::assertEquals($createdAt, $session->createdAt);
|
|
self::assertEquals($lastActivityAt, $session->lastActivityAt);
|
|
}
|
|
|
|
private function createSession(): Session
|
|
{
|
|
return Session::create(
|
|
TokenFamilyId::generate(),
|
|
UserId::generate(),
|
|
TenantId::fromString(self::TENANT_ID),
|
|
DeviceInfo::fromUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0'),
|
|
Location::fromIp('192.168.1.1', 'France', 'Paris'),
|
|
new DateTimeImmutable('2026-01-31 10:00:00'),
|
|
);
|
|
}
|
|
}
|