Skip to content

Commit

Permalink
feature #257 Allow overriding the $resetRequestLifetime when genera…
Browse files Browse the repository at this point in the history
…ting a token (kbond)

This PR was merged into the main branch.

Discussion
----------

Allow overriding the `$resetRequestLifetime` when generating a token

I have a requirement to, under certain conditions, generate reset tokens with a different TTL than the configured default.

Commits
-------

d947f9d Allow overriding the `$resetRequestLifetime` when generating a token
  • Loading branch information
weaverryan committed Feb 2, 2023
2 parents 5bd4df4 + d947f9d commit 6601f15
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 8 deletions.
15 changes: 9 additions & 6 deletions src/ResetPasswordHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,19 @@ public function __construct(ResetPasswordTokenGenerator $generator, ResetPasswor
*
* @throws TooManyPasswordRequestsException
*/
public function generateResetToken(object $user): ResetPasswordToken
public function generateResetToken(object $user, ?int $resetRequestLifetime = null): ResetPasswordToken
{
$this->resetPasswordCleaner->handleGarbageCollection();

if ($availableAt = $this->hasUserHitThrottling($user)) {
throw new TooManyPasswordRequestsException($availableAt);
}

$expiresAt = new \DateTimeImmutable(sprintf('+%d seconds', $this->resetRequestLifetime));
$resetRequestLifetime = $resetRequestLifetime ?: $this->resetRequestLifetime;

$generatedAt = ($expiresAt->getTimestamp() - $this->resetRequestLifetime);
$expiresAt = new \DateTimeImmutable(sprintf('+%d seconds', $resetRequestLifetime));

$generatedAt = ($expiresAt->getTimestamp() - $resetRequestLifetime);

$tokenComponents = $this->tokenGenerator->createToken($expiresAt, $this->repository->getUserIdentifier($user));

Expand Down Expand Up @@ -164,11 +166,12 @@ public function getTokenLifetime(): int
*
* This method should not be used when timing attacks are a concern.
*/
public function generateFakeResetToken(): ResetPasswordToken
public function generateFakeResetToken(?int $resetRequestLifetime = null): ResetPasswordToken
{
$expiresAt = new \DateTimeImmutable(sprintf('+%d seconds', $this->resetRequestLifetime));
$resetRequestLifetime = $resetRequestLifetime ?: $this->resetRequestLifetime;
$expiresAt = new \DateTimeImmutable(sprintf('+%d seconds', $resetRequestLifetime));

$generatedAt = ($expiresAt->getTimestamp() - $this->resetRequestLifetime);
$generatedAt = ($expiresAt->getTimestamp() - $resetRequestLifetime);

return new ResetPasswordToken('fake-token', $expiresAt, $generatedAt);
}
Expand Down
6 changes: 4 additions & 2 deletions src/ResetPasswordHelperInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
* @author Jesse Rushlow <jr@rushlow.dev>
* @author Ryan Weaver <ryan@symfonycasts.com>
*
* @method ResetPasswordToken generateFakeResetToken() Generates a fake ResetPasswordToken.
* @method ResetPasswordToken generateFakeResetToken(?int $resetRequestLifetime = null) Generates a fake ResetPasswordToken.
*/
interface ResetPasswordHelperInterface
{
Expand All @@ -28,9 +28,11 @@ interface ResetPasswordHelperInterface
* and removeResetRequest() can eventually invalidate it by removing it
* from storage.
*
* @param ?int $resetRequestLifetime Override the default (to be added to interface in 2.0)
*
* @throws ResetPasswordExceptionInterface
*/
public function generateResetToken(object $user): ResetPasswordToken;
public function generateResetToken(object $user/*, ?int $resetRequestLifetime = null*/): ResetPasswordToken;

/**
* Validate a reset request and fetch the user from persistence.
Expand Down
68 changes: 68 additions & 0 deletions tests/UnitTests/ResetPasswordHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,74 @@ public function testExpiresAtUsesCurrentTimeZone(): void
self::assertSame(date_default_timezone_get(), $expiresAt->getTimezone()->getName());
}

public function testExpiresAtUsingDefault(): void
{
$helper = new ResetPasswordHelper(
$this->mockTokenGenerator,
$this->mockCleaner,
$this->mockRepo,
60,
99999999
);

$token = $helper->generateResetToken(new \stdClass());
$expiresAt = $token->getExpiresAt();

self::assertGreaterThan(new \DateTimeImmutable('+55 seconds'), $expiresAt);
self::assertLessThan(new \DateTimeImmutable('+65 seconds'), $expiresAt);
}

public function testExpiresAtUsingOverride(): void
{
$helper = new ResetPasswordHelper(
$this->mockTokenGenerator,
$this->mockCleaner,
$this->mockRepo,
60,
99999999
);

$token = $helper->generateResetToken(new \stdClass(), 30);
$expiresAt = $token->getExpiresAt();

self::assertGreaterThan(new \DateTimeImmutable('+25 seconds'), $expiresAt);
self::assertLessThan(new \DateTimeImmutable('+35 seconds'), $expiresAt);
}

public function testFakeTokenExpiresAtUsingDefault(): void
{
$helper = new ResetPasswordHelper(
$this->mockTokenGenerator,
$this->mockCleaner,
$this->mockRepo,
60,
99999999
);

$token = $helper->generateFakeResetToken();
$expiresAt = $token->getExpiresAt();

self::assertGreaterThan(new \DateTimeImmutable('+55 seconds'), $expiresAt);
self::assertLessThan(new \DateTimeImmutable('+65 seconds'), $expiresAt);
}

public function testFakeTokenExpiresAtUsingOverride(): void
{
$helper = new ResetPasswordHelper(
$this->mockTokenGenerator,
$this->mockCleaner,
$this->mockRepo,
60,
99999999
);

$token = $helper->generateFakeResetToken(30);
$expiresAt = $token->getExpiresAt();

self::assertGreaterThan(new \DateTimeImmutable('+25 seconds'), $expiresAt);
self::assertLessThan(new \DateTimeImmutable('+35 seconds'), $expiresAt);
}

private function getPasswordResetHelper(): ResetPasswordHelper
{
return new ResetPasswordHelper(
Expand Down

0 comments on commit 6601f15

Please sign in to comment.