logger = $logger; } public function generate(User $user, \DateTimeInterface $expiration) { $token = \random_bytes(self::TOKEN_LENGTH); $username = $user->getUsernameCanonical(); if (empty($username)) { throw new \UnexpectedValueException('username should not be empty to generate a token'); } $timestamp = (string) $expiration->getTimestamp(); $hash = \hash('sha1', $token.$username.$timestamp.$this->secret); return [ self::HASH => $hash, self::TOKEN => \bin2hex($token), self::TIMESTAMP => $timestamp, self::USERNAME_CANONICAL => $username, ]; } public function verify($hash, $token, User $user, string $timestamp) { $token = \hex2bin(\trim((string) $token)); if (self::TOKEN_LENGTH !== \strlen($token)) { return false; } $username = $user->getUsernameCanonical(); $date = \DateTimeImmutable::createFromFormat('U', $timestamp); if ($date < new \DateTime('now')) { $this->logger->info('receiving a password recover token with expired ' .'validity'); return false; } $expected = \hash('sha1', $token.$username.$timestamp.$this->secret); if ($expected !== $hash) { return false; } return true; } }