summaryrefslogtreecommitdiff
path: root/vendor/spomky-labs/otphp/src
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/spomky-labs/otphp/src')
-rw-r--r--vendor/spomky-labs/otphp/src/Factory.php115
-rw-r--r--vendor/spomky-labs/otphp/src/FactoryInterface.php23
-rw-r--r--vendor/spomky-labs/otphp/src/HOTP.php103
-rw-r--r--vendor/spomky-labs/otphp/src/HOTPInterface.php29
-rw-r--r--vendor/spomky-labs/otphp/src/OTP.php114
-rw-r--r--vendor/spomky-labs/otphp/src/OTPInterface.php97
-rw-r--r--vendor/spomky-labs/otphp/src/ParameterTrait.php196
-rw-r--r--vendor/spomky-labs/otphp/src/TOTP.php159
-rw-r--r--vendor/spomky-labs/otphp/src/TOTPInterface.php36
9 files changed, 872 insertions, 0 deletions
diff --git a/vendor/spomky-labs/otphp/src/Factory.php b/vendor/spomky-labs/otphp/src/Factory.php
new file mode 100644
index 000000000..70df63945
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/Factory.php
@@ -0,0 +1,115 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+use Assert\Assertion;
+use InvalidArgumentException;
+use function Safe\parse_url;
+use function Safe\sprintf;
+use Throwable;
+
+/**
+ * This class is used to load OTP object from a provisioning Uri.
+ */
+final class Factory implements FactoryInterface
+{
+ public static function loadFromProvisioningUri(string $uri): OTPInterface
+ {
+ try {
+ $parsed_url = parse_url($uri);
+ } catch (Throwable $throwable) {
+ throw new InvalidArgumentException('Not a valid OTP provisioning URI', $throwable->getCode(), $throwable);
+ }
+ Assertion::isArray($parsed_url, 'Not a valid OTP provisioning URI');
+ self::checkData($parsed_url);
+
+ $otp = self::createOTP($parsed_url);
+
+ self::populateOTP($otp, $parsed_url);
+
+ return $otp;
+ }
+
+ /**
+ * @param array<string, mixed> $data
+ */
+ private static function populateParameters(OTPInterface &$otp, array $data): void
+ {
+ foreach ($data['query'] as $key => $value) {
+ $otp->setParameter($key, $value);
+ }
+ }
+
+ /**
+ * @param array<string, mixed> $data
+ */
+ private static function populateOTP(OTPInterface &$otp, array $data): void
+ {
+ self::populateParameters($otp, $data);
+ $result = explode(':', rawurldecode(mb_substr($data['path'], 1)));
+
+ if (2 > \count($result)) {
+ $otp->setIssuerIncludedAsParameter(false);
+
+ return;
+ }
+
+ if (null !== $otp->getIssuer()) {
+ Assertion::eq($result[0], $otp->getIssuer(), 'Invalid OTP: invalid issuer in parameter');
+ $otp->setIssuerIncludedAsParameter(true);
+ }
+ $otp->setIssuer($result[0]);
+ }
+
+ /**
+ * @param array<string, mixed> $data
+ */
+ private static function checkData(array &$data): void
+ {
+ foreach (['scheme', 'host', 'path', 'query'] as $key) {
+ Assertion::keyExists($data, $key, 'Not a valid OTP provisioning URI');
+ }
+ Assertion::eq('otpauth', $data['scheme'], 'Not a valid OTP provisioning URI');
+ parse_str($data['query'], $data['query']);
+ Assertion::keyExists($data['query'], 'secret', 'Not a valid OTP provisioning URI');
+ }
+
+ /**
+ * @param array<string, mixed> $parsed_url
+ */
+ private static function createOTP(array $parsed_url): OTPInterface
+ {
+ switch ($parsed_url['host']) {
+ case 'totp':
+ $totp = TOTP::create($parsed_url['query']['secret']);
+ $totp->setLabel(self::getLabel($parsed_url['path']));
+
+ return $totp;
+ case 'hotp':
+ $hotp = HOTP::create($parsed_url['query']['secret']);
+ $hotp->setLabel(self::getLabel($parsed_url['path']));
+
+ return $hotp;
+ default:
+ throw new InvalidArgumentException(sprintf('Unsupported "%s" OTP type', $parsed_url['host']));
+ }
+ }
+
+ private static function getLabel(string $data): string
+ {
+ $result = explode(':', rawurldecode(mb_substr($data, 1)));
+
+ return 2 === \count($result) ? $result[1] : $result[0];
+ }
+}
diff --git a/vendor/spomky-labs/otphp/src/FactoryInterface.php b/vendor/spomky-labs/otphp/src/FactoryInterface.php
new file mode 100644
index 000000000..00acc2d04
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/FactoryInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+interface FactoryInterface
+{
+ /**
+ * This method is the unique public method of the class.
+ * It can load a provisioning Uri and convert it into an OTP object.
+ */
+ public static function loadFromProvisioningUri(string $uri): OTPInterface;
+}
diff --git a/vendor/spomky-labs/otphp/src/HOTP.php b/vendor/spomky-labs/otphp/src/HOTP.php
new file mode 100644
index 000000000..a2f4a2395
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/HOTP.php
@@ -0,0 +1,103 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+use Assert\Assertion;
+
+final class HOTP extends OTP implements HOTPInterface
+{
+ protected function __construct(?string $secret, int $counter, string $digest, int $digits)
+ {
+ parent::__construct($secret, $digest, $digits);
+ $this->setCounter($counter);
+ }
+
+ public static function create(?string $secret = null, int $counter = 0, string $digest = 'sha1', int $digits = 6): HOTPInterface
+ {
+ return new self($secret, $counter, $digest, $digits);
+ }
+
+ protected function setCounter(int $counter): void
+ {
+ $this->setParameter('counter', $counter);
+ }
+
+ public function getCounter(): int
+ {
+ return $this->getParameter('counter');
+ }
+
+ private function updateCounter(int $counter): void
+ {
+ $this->setCounter($counter);
+ }
+
+ public function getProvisioningUri(): string
+ {
+ return $this->generateURI('hotp', ['counter' => $this->getCounter()]);
+ }
+
+ /**
+ * If the counter is not provided, the OTP is verified at the actual counter.
+ */
+ public function verify(string $otp, ?int $counter = null, ?int $window = null): bool
+ {
+ Assertion::greaterOrEqualThan($counter, 0, 'The counter must be at least 0.');
+
+ if (null === $counter) {
+ $counter = $this->getCounter();
+ } elseif ($counter < $this->getCounter()) {
+ return false;
+ }
+
+ return $this->verifyOtpWithWindow($otp, $counter, $window);
+ }
+
+ private function getWindow(?int $window): int
+ {
+ return abs($window ?? 0);
+ }
+
+ private function verifyOtpWithWindow(string $otp, int $counter, ?int $window): bool
+ {
+ $window = $this->getWindow($window);
+
+ for ($i = $counter; $i <= $counter + $window; ++$i) {
+ if ($this->compareOTP($this->at($i), $otp)) {
+ $this->updateCounter($i + 1);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @return array<string, mixed>
+ */
+ protected function getParameterMap(): array
+ {
+ $v = array_merge(
+ parent::getParameterMap(),
+ ['counter' => function ($value): int {
+ Assertion::greaterOrEqualThan((int) $value, 0, 'Counter must be at least 0.');
+
+ return (int) $value;
+ }]
+ );
+
+ return $v;
+ }
+}
diff --git a/vendor/spomky-labs/otphp/src/HOTPInterface.php b/vendor/spomky-labs/otphp/src/HOTPInterface.php
new file mode 100644
index 000000000..336ce1055
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/HOTPInterface.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+interface HOTPInterface extends OTPInterface
+{
+ /**
+ * The initial counter (a positive integer).
+ */
+ public function getCounter(): int;
+
+ /**
+ * Create a new TOTP object.
+ *
+ * If the secret is null, a random 64 bytes secret will be generated.
+ */
+ public static function create(?string $secret = null, int $counter = 0, string $digest = 'sha1', int $digits = 6): self;
+}
diff --git a/vendor/spomky-labs/otphp/src/OTP.php b/vendor/spomky-labs/otphp/src/OTP.php
new file mode 100644
index 000000000..932bcf97e
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/OTP.php
@@ -0,0 +1,114 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+use Assert\Assertion;
+use ParagonIE\ConstantTime\Base32;
+use RuntimeException;
+use function Safe\ksort;
+use function Safe\sprintf;
+
+abstract class OTP implements OTPInterface
+{
+ use ParameterTrait;
+
+ protected function __construct(?string $secret, string $digest, int $digits)
+ {
+ $this->setSecret($secret);
+ $this->setDigest($digest);
+ $this->setDigits($digits);
+ }
+
+ public function getQrCodeUri(string $uri, string $placeholder): string
+ {
+ $provisioning_uri = urlencode($this->getProvisioningUri());
+
+ return str_replace($placeholder, $provisioning_uri, $uri);
+ }
+
+ /**
+ * The OTP at the specified input.
+ */
+ protected function generateOTP(int $input): string
+ {
+ $hash = hash_hmac($this->getDigest(), $this->intToByteString($input), $this->getDecodedSecret(), true);
+
+ $hmac = array_values(unpack('C*', $hash));
+
+ $offset = ($hmac[\count($hmac) - 1] & 0xF);
+ $code = ($hmac[$offset + 0] & 0x7F) << 24 | ($hmac[$offset + 1] & 0xFF) << 16 | ($hmac[$offset + 2] & 0xFF) << 8 | ($hmac[$offset + 3] & 0xFF);
+ $otp = $code % (10 ** $this->getDigits());
+
+ return str_pad((string) $otp, $this->getDigits(), '0', STR_PAD_LEFT);
+ }
+
+ public function at(int $timestamp): string
+ {
+ return $this->generateOTP($timestamp);
+ }
+
+ /**
+ * @param array<string, mixed> $options
+ */
+ protected function filterOptions(array &$options): void
+ {
+ foreach (['algorithm' => 'sha1', 'period' => 30, 'digits' => 6] as $key => $default) {
+ if (isset($options[$key]) && $default === $options[$key]) {
+ unset($options[$key]);
+ }
+ }
+
+ ksort($options);
+ }
+
+ /**
+ * @param array<string, mixed> $options
+ */
+ protected function generateURI(string $type, array $options): string
+ {
+ $label = $this->getLabel();
+ Assertion::string($label, 'The label is not set.');
+ Assertion::false($this->hasColon($label), 'Label must not contain a colon.');
+ $options = array_merge($options, $this->getParameters());
+ $this->filterOptions($options);
+ $params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options));
+
+ return sprintf('otpauth://%s/%s?%s', $type, rawurlencode((null !== $this->getIssuer() ? $this->getIssuer().':' : '').$label), $params);
+ }
+
+ private function getDecodedSecret(): string
+ {
+ try {
+ return Base32::decodeUpper($this->getSecret());
+ } catch (\Exception $e) {
+ throw new RuntimeException('Unable to decode the secret. Is it correctly base32 encoded?');
+ }
+ }
+
+ private function intToByteString(int $int): string
+ {
+ $result = [];
+ while (0 !== $int) {
+ $result[] = \chr($int & 0xFF);
+ $int >>= 8;
+ }
+
+ return str_pad(implode(array_reverse($result)), 8, "\000", STR_PAD_LEFT);
+ }
+
+ protected function compareOTP(string $safe, string $user): bool
+ {
+ return hash_equals($safe, $user);
+ }
+}
diff --git a/vendor/spomky-labs/otphp/src/OTPInterface.php b/vendor/spomky-labs/otphp/src/OTPInterface.php
new file mode 100644
index 000000000..66e163d5d
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/OTPInterface.php
@@ -0,0 +1,97 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+interface OTPInterface
+{
+ /**
+ * @return string Return the OTP at the specified timestamp
+ */
+ public function at(int $timestamp): string;
+
+ /**
+ * Verify that the OTP is valid with the specified input.
+ * If no input is provided, the input is set to a default value or false is returned.
+ */
+ public function verify(string $otp, ?int $input = null, ?int $window = null): bool;
+
+ /**
+ * @return string The secret of the OTP
+ */
+ public function getSecret(): string;
+
+ /**
+ * @param string $label The label of the OTP
+ */
+ public function setLabel(string $label): void;
+
+ /**
+ * @return string|null The label of the OTP
+ */
+ public function getLabel(): ?string;
+
+ /**
+ * @return string|null The issuer
+ */
+ public function getIssuer(): ?string;
+
+ public function setIssuer(string $issuer): void;
+
+ /**
+ * @return bool If true, the issuer will be added as a parameter in the provisioning URI
+ */
+ public function isIssuerIncludedAsParameter(): bool;
+
+ public function setIssuerIncludedAsParameter(bool $issuer_included_as_parameter): void;
+
+ /**
+ * @return int Number of digits in the OTP
+ */
+ public function getDigits(): int;
+
+ /**
+ * @return string Digest algorithm used to calculate the OTP. Possible values are 'md5', 'sha1', 'sha256' and 'sha512'
+ */
+ public function getDigest(): string;
+
+ /**
+ * @return mixed|null
+ */
+ public function getParameter(string $parameter);
+
+ public function hasParameter(string $parameter): bool;
+
+ /**
+ * @return array<string, mixed>
+ */
+ public function getParameters(): array;
+
+ /**
+ * @param mixed|null $value
+ */
+ public function setParameter(string $parameter, $value): void;
+
+ /**
+ * Get the provisioning URI.
+ */
+ public function getProvisioningUri(): string;
+
+ /**
+ * Get the provisioning URI.
+ *
+ * @param string $uri The Uri of the QRCode generator with all parameters. This Uri MUST contain a placeholder that will be replaced by the method.
+ * @param string $placeholder the placeholder to be replaced in the QR Code generator URI
+ */
+ public function getQrCodeUri(string $uri, string $placeholder): string;
+}
diff --git a/vendor/spomky-labs/otphp/src/ParameterTrait.php b/vendor/spomky-labs/otphp/src/ParameterTrait.php
new file mode 100644
index 000000000..69fa774db
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/ParameterTrait.php
@@ -0,0 +1,196 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+use Assert\Assertion;
+use InvalidArgumentException;
+use ParagonIE\ConstantTime\Base32;
+use function Safe\sprintf;
+
+trait ParameterTrait
+{
+ /**
+ * @var array<string, mixed>
+ */
+ private $parameters = [];
+
+ /**
+ * @var string|null
+ */
+ private $issuer;
+
+ /**
+ * @var string|null
+ */
+ private $label;
+
+ /**
+ * @var bool
+ */
+ private $issuer_included_as_parameter = true;
+
+ /**
+ * @return array<string, mixed>
+ */
+ public function getParameters(): array
+ {
+ $parameters = $this->parameters;
+
+ if (null !== $this->getIssuer() && true === $this->isIssuerIncludedAsParameter()) {
+ $parameters['issuer'] = $this->getIssuer();
+ }
+
+ return $parameters;
+ }
+
+ public function getSecret(): string
+ {
+ return $this->getParameter('secret');
+ }
+
+ private function setSecret(?string $secret): void
+ {
+ $this->setParameter('secret', $secret);
+ }
+
+ public function getLabel(): ?string
+ {
+ return $this->label;
+ }
+
+ public function setLabel(string $label): void
+ {
+ $this->setParameter('label', $label);
+ }
+
+ public function getIssuer(): ?string
+ {
+ return $this->issuer;
+ }
+
+ public function setIssuer(string $issuer): void
+ {
+ $this->setParameter('issuer', $issuer);
+ }
+
+ public function isIssuerIncludedAsParameter(): bool
+ {
+ return $this->issuer_included_as_parameter;
+ }
+
+ public function setIssuerIncludedAsParameter(bool $issuer_included_as_parameter): void
+ {
+ $this->issuer_included_as_parameter = $issuer_included_as_parameter;
+ }
+
+ public function getDigits(): int
+ {
+ return $this->getParameter('digits');
+ }
+
+ private function setDigits(int $digits): void
+ {
+ $this->setParameter('digits', $digits);
+ }
+
+ public function getDigest(): string
+ {
+ return $this->getParameter('algorithm');
+ }
+
+ private function setDigest(string $digest): void
+ {
+ $this->setParameter('algorithm', $digest);
+ }
+
+ public function hasParameter(string $parameter): bool
+ {
+ return \array_key_exists($parameter, $this->parameters);
+ }
+
+ public function getParameter(string $parameter)
+ {
+ if ($this->hasParameter($parameter)) {
+ return $this->getParameters()[$parameter];
+ }
+
+ throw new InvalidArgumentException(sprintf('Parameter "%s" does not exist', $parameter));
+ }
+
+ public function setParameter(string $parameter, $value): void
+ {
+ $map = $this->getParameterMap();
+
+ if (true === \array_key_exists($parameter, $map)) {
+ $callback = $map[$parameter];
+ $value = $callback($value);
+ }
+
+ if (property_exists($this, $parameter)) {
+ $this->$parameter = $value;
+ } else {
+ $this->parameters[$parameter] = $value;
+ }
+ }
+
+ /**
+ * @return array<string, mixed>
+ */
+ protected function getParameterMap(): array
+ {
+ return [
+ 'label' => function ($value) {
+ Assertion::false($this->hasColon($value), 'Label must not contain a colon.');
+
+ return $value;
+ },
+ 'secret' => function ($value): string {
+ if (null === $value) {
+ $value = Base32::encodeUpper(random_bytes(64));
+ }
+ $value = trim(mb_strtoupper($value), '=');
+
+ return $value;
+ },
+ 'algorithm' => function ($value): string {
+ $value = mb_strtolower($value);
+ Assertion::inArray($value, hash_algos(), sprintf('The "%s" digest is not supported.', $value));
+
+ return $value;
+ },
+ 'digits' => function ($value): int {
+ Assertion::greaterThan($value, 0, 'Digits must be at least 1.');
+
+ return (int) $value;
+ },
+ 'issuer' => function ($value) {
+ Assertion::false($this->hasColon($value), 'Issuer must not contain a colon.');
+
+ return $value;
+ },
+ ];
+ }
+
+ private function hasColon(string $value): bool
+ {
+ $colons = [':', '%3A', '%3a'];
+ foreach ($colons as $colon) {
+ if (false !== mb_strpos($value, $colon)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/spomky-labs/otphp/src/TOTP.php b/vendor/spomky-labs/otphp/src/TOTP.php
new file mode 100644
index 000000000..588b37f17
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/TOTP.php
@@ -0,0 +1,159 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+use Assert\Assertion;
+use function Safe\ksort;
+
+final class TOTP extends OTP implements TOTPInterface
+{
+ protected function __construct(?string $secret, int $period, string $digest, int $digits, int $epoch = 0)
+ {
+ parent::__construct($secret, $digest, $digits);
+ $this->setPeriod($period);
+ $this->setEpoch($epoch);
+ }
+
+ public static function create(?string $secret = null, int $period = 30, string $digest = 'sha1', int $digits = 6, int $epoch = 0): TOTPInterface
+ {
+ return new self($secret, $period, $digest, $digits, $epoch);
+ }
+
+ protected function setPeriod(int $period): void
+ {
+ $this->setParameter('period', $period);
+ }
+
+ public function getPeriod(): int
+ {
+ return $this->getParameter('period');
+ }
+
+ private function setEpoch(int $epoch): void
+ {
+ $this->setParameter('epoch', $epoch);
+ }
+
+ public function getEpoch(): int
+ {
+ return $this->getParameter('epoch');
+ }
+
+ public function at(int $timestamp): string
+ {
+ return $this->generateOTP($this->timecode($timestamp));
+ }
+
+ public function now(): string
+ {
+ return $this->at(time());
+ }
+
+ /**
+ * If no timestamp is provided, the OTP is verified at the actual timestamp.
+ */
+ public function verify(string $otp, ?int $timestamp = null, ?int $window = null): bool
+ {
+ $timestamp = $this->getTimestamp($timestamp);
+
+ if (null === $window) {
+ return $this->compareOTP($this->at($timestamp), $otp);
+ }
+
+ return $this->verifyOtpWithWindow($otp, $timestamp, $window);
+ }
+
+ private function verifyOtpWithWindow(string $otp, int $timestamp, int $window): bool
+ {
+ $window = abs($window);
+
+ for ($i = 0; $i <= $window; ++$i) {
+ $next = $i * $this->getPeriod() + $timestamp;
+ $previous = -$i * $this->getPeriod() + $timestamp;
+ $valid = $this->compareOTP($this->at($next), $otp) ||
+ $this->compareOTP($this->at($previous), $otp);
+
+ if ($valid) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function getTimestamp(?int $timestamp): int
+ {
+ $timestamp = $timestamp ?? time();
+ Assertion::greaterOrEqualThan($timestamp, 0, 'Timestamp must be at least 0.');
+
+ return $timestamp;
+ }
+
+ public function getProvisioningUri(): string
+ {
+ $params = [];
+ if (30 !== $this->getPeriod()) {
+ $params['period'] = $this->getPeriod();
+ }
+
+ if (0 !== $this->getEpoch()) {
+ $params['epoch'] = $this->getEpoch();
+ }
+
+ return $this->generateURI('totp', $params);
+ }
+
+ private function timecode(int $timestamp): int
+ {
+ return (int) floor(($timestamp - $this->getEpoch()) / $this->getPeriod());
+ }
+
+ /**
+ * @return array<string, mixed>
+ */
+ protected function getParameterMap(): array
+ {
+ $v = array_merge(
+ parent::getParameterMap(),
+ [
+ 'period' => function ($value): int {
+ Assertion::greaterThan((int) $value, 0, 'Period must be at least 1.');
+
+ return (int) $value;
+ },
+ 'epoch' => function ($value): int {
+ Assertion::greaterOrEqualThan((int) $value, 0, 'Epoch must be greater than or equal to 0.');
+
+ return (int) $value;
+ },
+ ]
+ );
+
+ return $v;
+ }
+
+ /**
+ * @param array<string, mixed> $options
+ */
+ protected function filterOptions(array &$options): void
+ {
+ parent::filterOptions($options);
+
+ if (isset($options['epoch']) && 0 === $options['epoch']) {
+ unset($options['epoch']);
+ }
+
+ ksort($options);
+ }
+}
diff --git a/vendor/spomky-labs/otphp/src/TOTPInterface.php b/vendor/spomky-labs/otphp/src/TOTPInterface.php
new file mode 100644
index 000000000..a19fe7c0b
--- /dev/null
+++ b/vendor/spomky-labs/otphp/src/TOTPInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+declare(strict_types=1);
+
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014-2019 Spomky-Labs
+ *
+ * This software may be modified and distributed under the terms
+ * of the MIT license. See the LICENSE file for details.
+ */
+
+namespace OTPHP;
+
+interface TOTPInterface extends OTPInterface
+{
+ /**
+ * Create a new TOTP object.
+ *
+ * If the secret is null, a random 64 bytes secret will be generated.
+ */
+ public static function create(?string $secret = null, int $period = 30, string $digest = 'sha1', int $digits = 6): self;
+
+ /**
+ * Return the TOTP at the current time.
+ */
+ public function now(): string;
+
+ /**
+ * Get the period of time for OTP generation (a non-null positive integer, in second).
+ */
+ public function getPeriod(): int;
+
+ public function getEpoch(): int;
+}