diff options
author | Mario <mario@mariovavti.com> | 2023-03-08 10:04:29 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2023-03-08 10:04:29 +0000 |
commit | 234bb6425021b72f0db71667191b2c36dc593791 (patch) | |
tree | 2966d68516cebae70d4a75aace9962a809532339 /vendor/spomky-labs/otphp/src/TOTP.php | |
parent | d43a56614cd93982d19f4f82aae6e62f9ca533a9 (diff) | |
download | volse-hubzilla-234bb6425021b72f0db71667191b2c36dc593791.tar.gz volse-hubzilla-234bb6425021b72f0db71667191b2c36dc593791.tar.bz2 volse-hubzilla-234bb6425021b72f0db71667191b2c36dc593791.zip |
port totp mfa from streams with some adjustions
Diffstat (limited to 'vendor/spomky-labs/otphp/src/TOTP.php')
-rw-r--r-- | vendor/spomky-labs/otphp/src/TOTP.php | 172 |
1 files changed, 172 insertions, 0 deletions
diff --git a/vendor/spomky-labs/otphp/src/TOTP.php b/vendor/spomky-labs/otphp/src/TOTP.php new file mode 100644 index 000000000..e9bce9e14 --- /dev/null +++ b/vendor/spomky-labs/otphp/src/TOTP.php @@ -0,0 +1,172 @@ +<?php + +declare(strict_types=1); + +namespace OTPHP; + +use InvalidArgumentException; +use function is_int; + +/** + * @see \OTPHP\Test\TOTPTest + */ +final class TOTP extends OTP implements TOTPInterface +{ + 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 + ): self { + $totp = $secret !== null + ? self::createFromSecret($secret) + : self::generate() + ; + $totp->setPeriod($period); + $totp->setDigest($digest); + $totp->setDigits($digits); + $totp->setEpoch($epoch); + + return $totp; + } + + public static function createFromSecret(string $secret): self + { + $totp = new self($secret); + $totp->setPeriod(self::DEFAULT_PERIOD); + $totp->setDigest(self::DEFAULT_DIGEST); + $totp->setDigits(self::DEFAULT_DIGITS); + $totp->setEpoch(self::DEFAULT_EPOCH); + + return $totp; + } + + public static function generate(): self + { + return self::createFromSecret(self::generateSecret()); + } + + public function getPeriod(): int + { + $value = $this->getParameter('period'); + is_int($value) || throw new InvalidArgumentException('Invalid "period" parameter.'); + + return $value; + } + + public function getEpoch(): int + { + $value = $this->getParameter('epoch'); + is_int($value) || throw new InvalidArgumentException('Invalid "epoch" parameter.'); + + return $value; + } + + public function expiresIn(): int + { + $period = $this->getPeriod(); + + return $period - (time() % $this->getPeriod()); + } + + public function at(int $input): string + { + return $this->generateOTP($this->timecode($input)); + } + + public function now(): string + { + return $this->at(time()); + } + + /** + * 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. + */ + public function verify(string $otp, null|int $timestamp = null, null|int $leeway = null): bool + { + $timestamp ??= time(); + $timestamp >= 0 || throw new InvalidArgumentException('Timestamp must be at least 0.'); + + if ($leeway === null) { + return $this->compareOTP($this->at($timestamp), $otp); + } + + $leeway = abs($leeway); + $leeway < $this->getPeriod() || throw new InvalidArgumentException( + 'The leeway must be lower than the TOTP period' + ); + + return $this->compareOTP($this->at($timestamp - $leeway), $otp) + || $this->compareOTP($this->at($timestamp), $otp) + || $this->compareOTP($this->at($timestamp + $leeway), $otp); + } + + public function getProvisioningUri(): string + { + $params = []; + if ($this->getPeriod() !== 30) { + $params['period'] = $this->getPeriod(); + } + + if ($this->getEpoch() !== 0) { + $params['epoch'] = $this->getEpoch(); + } + + return $this->generateURI('totp', $params); + } + + public function setPeriod(int $period): void + { + $this->setParameter('period', $period); + } + + public function setEpoch(int $epoch): void + { + $this->setParameter('epoch', $epoch); + } + + /** + * @return array<string, callable> + */ + 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; + }, + ] + ); + } + + /** + * @param array<string, mixed> $options + */ + protected function filterOptions(array &$options): void + { + parent::filterOptions($options); + + if (isset($options['epoch']) && $options['epoch'] === 0) { + unset($options['epoch']); + } + + ksort($options); + } + + private function timecode(int $timestamp): int + { + return (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod()); + } +} |