secret = $secret; $this->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 = $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($token)); if (strlen($token) !== self::TOKEN_LENGTH) { 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; } }