aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/spomky-labs/otphp/src/TOTP.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/spomky-labs/otphp/src/TOTP.php')
-rw-r--r--vendor/spomky-labs/otphp/src/TOTP.php92
1 files changed, 63 insertions, 29 deletions
diff --git a/vendor/spomky-labs/otphp/src/TOTP.php b/vendor/spomky-labs/otphp/src/TOTP.php
index 3a7d72870..035e04f95 100644
--- a/vendor/spomky-labs/otphp/src/TOTP.php
+++ b/vendor/spomky-labs/otphp/src/TOTP.php
@@ -4,8 +4,9 @@ declare(strict_types=1);
namespace OTPHP;
-use function assert;
use InvalidArgumentException;
+use Psr\Clock\ClockInterface;
+use function assert;
use function is_int;
/**
@@ -13,16 +14,34 @@ use function is_int;
*/
final class TOTP extends OTP implements TOTPInterface
{
+ private readonly ClockInterface $clock;
+
+ public function __construct(string $secret, ?ClockInterface $clock = null)
+ {
+ parent::__construct($secret);
+ if ($clock === null) {
+ trigger_deprecation(
+ 'spomky-labs/otphp',
+ '11.3.0',
+ 'The parameter "$clock" will become mandatory in 12.0.0. Please set a valid PSR Clock implementation instead of "null".'
+ );
+ $clock = new InternalClock();
+ }
+
+ $this->clock = $clock;
+ }
+
public static function create(
null|string $secret = null,
int $period = self::DEFAULT_PERIOD,
string $digest = self::DEFAULT_DIGEST,
int $digits = self::DEFAULT_DIGITS,
- int $epoch = self::DEFAULT_EPOCH
+ int $epoch = self::DEFAULT_EPOCH,
+ ?ClockInterface $clock = null
): self {
$totp = $secret !== null
- ? self::createFromSecret($secret)
- : self::generate()
+ ? self::createFromSecret($secret, $clock)
+ : self::generate($clock)
;
$totp->setPeriod($period);
$totp->setDigest($digest);
@@ -32,9 +51,9 @@ final class TOTP extends OTP implements TOTPInterface
return $totp;
}
- public static function createFromSecret(string $secret): self
+ public static function createFromSecret(string $secret, ?ClockInterface $clock = null): self
{
- $totp = new self($secret);
+ $totp = new self($secret, $clock);
$totp->setPeriod(self::DEFAULT_PERIOD);
$totp->setDigest(self::DEFAULT_DIGEST);
$totp->setDigits(self::DEFAULT_DIGITS);
@@ -43,9 +62,9 @@ final class TOTP extends OTP implements TOTPInterface
return $totp;
}
- public static function generate(): self
+ public static function generate(?ClockInterface $clock = null): self
{
- return self::createFromSecret(self::generateSecret());
+ return self::createFromSecret(self::generateSecret(), $clock);
}
public function getPeriod(): int
@@ -68,9 +87,14 @@ final class TOTP extends OTP implements TOTPInterface
{
$period = $this->getPeriod();
- return $period - (time() % $this->getPeriod());
+ return $period - ($this->clock->now()->getTimestamp() % $this->getPeriod());
}
+ /**
+ * The OTP at the specified input.
+ *
+ * @param 0|positive-int $input
+ */
public function at(int $input): string
{
return $this->generateOTP($this->timecode($input));
@@ -78,16 +102,24 @@ final class TOTP extends OTP implements TOTPInterface
public function now(): string
{
- return $this->at(time());
+ $timestamp = $this->clock->now()
+ ->getTimestamp();
+ assert($timestamp >= 0, 'The timestamp must return a positive integer.');
+
+ return $this->at($timestamp);
}
/**
* If no timestamp is provided, the OTP is verified at the actual timestamp. When used, the leeway parameter will
* allow time drift. The passed value is in seconds.
+ *
+ * @param 0|positive-int $timestamp
+ * @param null|0|positive-int $leeway
*/
public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool
{
- $timestamp ??= time();
+ $timestamp ??= $this->clock->now()
+ ->getTimestamp();
$timestamp >= 0 || throw new InvalidArgumentException('Timestamp must be at least 0.');
if ($leeway === null) {
@@ -98,8 +130,12 @@ final class TOTP extends OTP implements TOTPInterface
$leeway < $this->getPeriod() || throw new InvalidArgumentException(
'The leeway must be lower than the TOTP period'
);
+ $timestampMinusLeeway = $timestamp - $leeway;
+ $timestampMinusLeeway >= 0 || throw new InvalidArgumentException(
+ 'The timestamp must be greater than or equal to the leeway.'
+ );
- return $this->compareOTP($this->at($timestamp - $leeway), $otp)
+ return $this->compareOTP($this->at($timestampMinusLeeway), $otp)
|| $this->compareOTP($this->at($timestamp), $otp)
|| $this->compareOTP($this->at($timestamp + $leeway), $otp);
}
@@ -133,23 +169,21 @@ final class TOTP extends OTP implements TOTPInterface
*/
protected function getParameterMap(): array
{
- return array_merge(
- parent::getParameterMap(),
- [
- 'period' => static function ($value): int {
- (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.');
-
- return (int) $value;
- },
- 'epoch' => static function ($value): int {
- (int) $value >= 0 || throw new InvalidArgumentException(
- 'Epoch must be greater than or equal to 0.'
- );
-
- return (int) $value;
- },
- ]
- );
+ return [
+ ...parent::getParameterMap(),
+ 'period' => static function ($value): int {
+ (int) $value > 0 || throw new InvalidArgumentException('Period must be at least 1.');
+
+ return (int) $value;
+ },
+ 'epoch' => static function ($value): int {
+ (int) $value >= 0 || throw new InvalidArgumentException(
+ 'Epoch must be greater than or equal to 0.'
+ );
+
+ return (int) $value;
+ },
+ ];
}
/**