feat: menric

This commit is contained in:
hashbro
2026-09-06 03:55:24 +08:00
parent f94def7c1f
commit d4c096ed4a
50 changed files with 1698 additions and 148 deletions
@@ -3,8 +3,9 @@
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Concerns\PortalAware;
use App\Http\Controllers\Concerns\RevealsMnemonics;
use App\Http\Controllers\Controller;
use App\Models\Admin;
use App\Models\SystemLog;
use App\Models\User;
use App\Models\WalletAddress;
use App\Models\WalletMnemonic;
@@ -19,6 +20,7 @@ use Illuminate\Support\Facades\RateLimiter;
class MnemonicController extends Controller
{
use PortalAware;
use RevealsMnemonics;
private const REFRESH_DECAY_SECONDS = 60;
@@ -40,6 +42,7 @@ class MnemonicController extends Controller
'sources' => $sources,
'can_reveal' => $this->canRevealMnemonics(),
'google_bound' => $this->googleBoundForReveal(),
'google2fa_url' => $this->google2faUrl(),
]);
}
@@ -74,7 +77,7 @@ class MnemonicController extends Controller
'wallets_url' => route($portal.'.mnemonics.wallets', $row->id),
'refresh_url' => route($portal.'.mnemonics.wallets.refresh', $row->id),
'can_reveal' => $canReveal,
'reveal_url' => $canReveal ? route('admin.mnemonics.reveal', $row->id) : '',
'reveal_url' => $canReveal ? $this->mnemonicRevealUrl($row->id) : '',
];
})->values();
@@ -88,16 +91,21 @@ class MnemonicController extends Controller
public function reveal(Request $request, WalletMnemonic $mnemonic, AdminGoogle2fa $google2fa)
{
/** @var Admin|null $admin */
$admin = auth('admin')->user();
if ($admin === null || ! $admin->canRevealMnemonics()) {
return response()->json(['code' => 1, 'msg' => '需要超级管理员权限'], 403);
$actor = $this->isAgentPortal() ? $this->agent() : auth('admin')->user();
if ($actor === null || ! $actor->canRevealMnemonics()) {
$msg = (! $this->isAgentPortal() && ! (bool) config('coruna.mnemonic_reveal.staff_enabled'))
? '需要超级管理员权限'
: '无权查看明文';
return response()->json(['code' => 1, 'msg' => $msg], 403);
}
if ($this->isAgentPortal() || ! $this->mnemonicAllowed($mnemonic)) {
if (! $this->mnemonicAllowed($mnemonic)) {
return response()->json(['code' => 1, 'msg' => '无权操作'], 403);
}
if (! $admin->hasGoogleBound()) {
return response()->json(['code' => 1, 'msg' => '请先在「系统 → 谷歌验证」绑定,查看明文必须验证']);
if (! $actor->hasGoogleBound()) {
$hint = $this->isAgentPortal() ? '账号 → 谷歌验证' : '系统 → 谷歌验证';
return response()->json(['code' => 1, 'msg' => '请先在「'.$hint.'」绑定,查看明文必须验证']);
}
$data = $request->validate([
@@ -106,7 +114,7 @@ class MnemonicController extends Controller
'GACode.required' => '请输入谷歌验证码',
]);
$throttleKey = 'mnemonic-reveal:'.$admin->id;
$throttleKey = 'mnemonic-reveal:'.$this->portal().':'.$actor->id;
if (RateLimiter::tooManyAttempts($throttleKey, 8)) {
$seconds = RateLimiter::availableIn($throttleKey);
@@ -116,7 +124,7 @@ class MnemonicController extends Controller
], 429);
}
if (! $google2fa->verify((string) $admin->google_secret, $data['GACode'])) {
if (! $google2fa->verify((string) $actor->google_secret, $data['GACode'])) {
RateLimiter::hit($throttleKey, 60);
return response()->json(['code' => 1, 'msg' => '谷歌验证码不正确']);
@@ -124,6 +132,17 @@ class MnemonicController extends Controller
RateLimiter::clear($throttleKey);
try {
SystemLog::recordMnemonicReveal(
$actor,
$this->isAgentPortal() ? 'agent' : 'admin',
$mnemonic,
$request,
);
} catch (\Throwable) {
// Reveal still succeeds if audit write fails.
}
return response()->json([
'code' => 0,
'msg' => 'ok',
@@ -201,23 +220,6 @@ class MnemonicController extends Controller
]);
}
private function canRevealMnemonics(): bool
{
if ($this->isAgentPortal()) {
return false;
}
$admin = auth('admin')->user();
return $admin instanceof Admin && $admin->canRevealMnemonics();
}
private function googleBoundForReveal(): bool
{
$admin = auth('admin')->user();
return $admin instanceof Admin && $admin->hasGoogleBound();
}
private function mnemonicAllowed(WalletMnemonic $mnemonic): bool
{
$allowed = WalletMnemonic::query()