createSecret(); } public static function getQrDataUri(string $label, string $secret): string { return self::tfa()->getQRCodeImageAsDataUri($label, $secret); } public static function verifyCode(string $plainSecret, string $code): bool { $code = trim($code); if (!preg_match('/^\d{6}$/', $code)) { return false; } return self::tfa()->verifyCode($plainSecret, $code); } public static function encryptSecret(string $plainSecret): string { $key = substr(hash('sha256', (string) config('buildadmin.token.key')), 0, 32); $iv = random_bytes(16); $encrypted = openssl_encrypt($plainSecret, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); if ($encrypted === false) { return ''; } return base64_encode($iv . $encrypted); } public static function decryptSecret(string $encryptedSecret): string { if ($encryptedSecret === '') { return ''; } $raw = base64_decode($encryptedSecret, true); if ($raw === false || strlen($raw) < 17) { return ''; } $iv = substr($raw, 0, 16); $encrypted = substr($raw, 16); $key = substr(hash('sha256', (string) config('buildadmin.token.key')), 0, 32); $plain = openssl_decrypt($encrypted, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv); return $plain === false ? '' : $plain; } public static function verifyStoredCode(string $encryptedSecret, string $code): bool { $plain = self::decryptSecret($encryptedSecret); if ($plain === '') { return false; } return self::verifyCode($plain, $code); } public static function createPendingToken(int $adminId, string $type): string { $token = Random::uuid(); Token::set($token, $type, $adminId, self::TOKEN_EXPIRE); return $token; } public static function resolvePendingToken(string $token, string $type): int { $token = trim($token); if ($token === '') { return 0; } $data = Token::get($token); if (!$data || ($data['type'] ?? '') !== $type) { return 0; } Token::tokenExpirationCheck($data); return (int) ($data['user_id'] ?? 0); } public static function deletePendingToken(string $token): void { if ($token !== '') { Token::delete($token); } } }