feat(auth): add microsoft auto-remember policy with tenant override and configurable remember TTL

This commit is contained in:
2026-03-10 22:48:10 +01:00
parent f403a6704f
commit 964c07a9de
32 changed files with 1267 additions and 17 deletions

View File

@@ -10,6 +10,7 @@ use MintyPHP\Repository\User\UserReadRepositoryInterface;
use MintyPHP\Repository\User\UserWriteRepositoryInterface;
use MintyPHP\Service\Auth\AuthPermissionGateway;
use MintyPHP\Service\Auth\AuthSessionTenantContextService;
use MintyPHP\Service\Auth\AuthSettingsGateway;
use MintyPHP\Service\Auth\RememberMeService;
use PHPUnit\Framework\TestCase;
@@ -427,6 +428,81 @@ class RememberMeServiceTest extends TestCase
$this->assertSame(15, $service->expireAllTokensByAdmin());
}
// ---------------------------------------------------------------
// Dynamic TTL via AuthSettingsGateway
// ---------------------------------------------------------------
public function testRememberUserUsesConfiguredTtlFromGateway(): void
{
$settingsGateway = $this->createMock(AuthSettingsGateway::class);
$settingsGateway->method('getRememberTokenLifetimeDays')->willReturn(7);
$capturedExpiresAt = '';
$tokenRepo = $this->createMock(RememberTokenRepositoryInterface::class);
$tokenRepo->expects($this->once())
->method('create')
->with(
42,
$this->anything(),
$this->anything(),
$this->callback(function (string $e) use (&$capturedExpiresAt): bool {
$capturedExpiresAt = $e;
return strtotime($e . ' UTC') > time();
})
)
->willReturn(1);
$cookieStore = $this->createMock(CookieStoreInterface::class);
$cookieStore->expects($this->once())
->method('set')
->with(
'remember',
$this->anything(),
$this->callback(fn (array $opts): bool => $opts['expires'] > time()
&& $opts['expires'] <= time() + (7 * 86400) + 10)
);
$service = $this->newService(
tokenRepository: $tokenRepo,
cookieStore: $cookieStore,
authSettingsGateway: $settingsGateway
);
$service->rememberUser(42);
// Verify expiration is ~7 days from now (not 30)
$expiresTimestamp = strtotime($capturedExpiresAt . ' UTC');
$this->assertGreaterThan(time() + (6 * 86400), $expiresTimestamp);
$this->assertLessThanOrEqual(time() + (7 * 86400) + 10, $expiresTimestamp);
}
public function testRememberUserFallsBackTo30DaysWhenNoGateway(): void
{
$capturedExpiresAt = '';
$tokenRepo = $this->createMock(RememberTokenRepositoryInterface::class);
$tokenRepo->expects($this->once())
->method('create')
->with(
42,
$this->anything(),
$this->anything(),
$this->callback(function (string $e) use (&$capturedExpiresAt): bool {
$capturedExpiresAt = $e;
return true;
})
)
->willReturn(1);
$cookieStore = $this->createMock(CookieStoreInterface::class);
$cookieStore->method('set');
$service = $this->newService(tokenRepository: $tokenRepo, cookieStore: $cookieStore);
$service->rememberUser(42);
$expiresTimestamp = strtotime($capturedExpiresAt . ' UTC');
$this->assertGreaterThan(time() + (29 * 86400), $expiresTimestamp);
$this->assertLessThanOrEqual(time() + (30 * 86400) + 10, $expiresTimestamp);
}
// ---------------------------------------------------------------
// Helper
// ---------------------------------------------------------------
@@ -439,7 +515,8 @@ class RememberMeServiceTest extends TestCase
?AuthSessionTenantContextService $authSessionTenantContextService = null,
?SessionStoreInterface $sessionStore = null,
?CookieStoreInterface $cookieStore = null,
?RequestRuntimeInterface $requestRuntime = null
?RequestRuntimeInterface $requestRuntime = null,
?AuthSettingsGateway $authSettingsGateway = null
): RememberMeService {
// RequestRuntimeInterface has a method named "method()" which PHPUnit 12
// cannot mock (reserved name). Use a simple anonymous-class stub instead.
@@ -483,7 +560,8 @@ class RememberMeServiceTest extends TestCase
$authSessionTenantContextService ?? $this->createMock(AuthSessionTenantContextService::class),
$sessionStore ?? $this->createMock(SessionStoreInterface::class),
$cookieStore ?? $this->createMock(CookieStoreInterface::class),
$requestRuntime
$requestRuntime,
$authSettingsGateway
);
}
}