secret = $secret; } public function getUrl(User $user): string { $otpType = self::TYPE; $name = rawurlencode($user->getUsername()); $settings = $user->getConfiguration()->getSettings(); $urlArgs = [ "secret" => $this->secret, "issuer" => $settings->getSiteName(), ]; $urlArgs = http_build_query($urlArgs); return "otpauth://$otpType/$name?$urlArgs"; } public function generateQRCode(User $user) { $options = new QROptions(['outputType' => QRCode::OUTPUT_IMAGE_PNG, "imageBase64" => false]); $qrcode = new QRCode($options); return $qrcode->render($this->getUrl($user)); } public function generate(?int $at = null, int $length = 6, int $period = 30): string { if ($at === null) { $at = time(); } $seed = intval($at / $period); $secret = Base32::decode($this->secret); $hmac = hash_hmac('sha1', pack("J", $seed), $secret, true); $offset = ord($hmac[-1]) & 0xF; $code = (unpack("N", substr($hmac, $offset, 4))[1] & 0x7fffffff) % intval(pow(10, $length)); return substr(str_pad(strval($code), $length, "0", STR_PAD_LEFT), -1 * $length); } public function verify(string $code): bool { return $this->generate() === $code; } public function getData(): string { return $this->secret; } }