1.增加谷歌验证器

This commit is contained in:
2026-06-24 16:25:31 +08:00
parent 5187330c38
commit 3e88a5dd91
21 changed files with 822 additions and 46 deletions

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace app\common\library;
use ba\Random;
use app\common\facade\Token;
use RobThree\Auth\TwoFactorAuth;
use RobThree\Auth\Providers\Qr\BaconQrCodeProvider;
/**
* 管理员谷歌验证器TOTP
*/
class AdminTotp
{
public const TOKEN_TYPE_BIND = 'admin-totp-bind';
public const TOKEN_TYPE_VERIFY = 'admin-totp-verify';
public const TOKEN_EXPIRE = 300;
private static ?TwoFactorAuth $tfa = null;
private static function tfa(): TwoFactorAuth
{
if (self::$tfa === null) {
$issuer = (string) get_sys_config('site_name');
if ($issuer === '') {
$issuer = 'BuildAdmin';
}
self::$tfa = new TwoFactorAuth(new BaconQrCodeProvider(), $issuer);
}
return self::$tfa;
}
public static function isBound(?string $encryptedSecret): bool
{
return is_string($encryptedSecret) && $encryptedSecret !== '';
}
public static function generateSecret(): string
{
return self::tfa()->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);
}
}
}