googleActor(); return view('admin.security.google2fa', [ 'enabled' => $actor->requiresLoginGoogle(), 'bound' => $actor->hasGoogleBound(), 'routes' => $this->googleRoutes(), ]); } public function prepare(Request $request, AdminGoogle2fa $google2fa): JsonResponse { $actor = $this->googleActor(); $data = $request->validate([ 'password' => ['required', 'string'], ], [ 'password.required' => '登陆密码不能为空', ]); if (! Hash::check($data['password'], $actor->password)) { return response()->json(['code' => 1, 'msg' => '登陆密码不正确']); } if ((int) $actor->google_auth_open === 1 || filled($actor->google_secret)) { return response()->json(['code' => 201, 'msg' => '您已绑定谷歌验证,可直接开启或关闭']); } $secret = $google2fa->generateSecret(); $request->session()->put($this->pendingSecretKey(), $secret); $otpAuthUrl = $google2fa->otpAuthUrl($actor->username, $secret, $this->isAgentPortal() ? 'agent' : 'admin'); return response()->json([ 'code' => 0, 'msg' => 'ok', 'secret' => $secret, 'qr_svg' => $google2fa->qrSvg($otpAuthUrl), ]); } public function bind(Request $request, AdminGoogle2fa $google2fa): JsonResponse { $actor = $this->googleActor(); $data = $request->validate([ 'GAKey' => ['required', 'string', 'max:16'], 'GASecret' => ['required', 'string', 'max:64'], 'login_verify' => ['nullable', 'integer', 'in:0,1'], ], [ 'GAKey.required' => '请输入谷歌验证码', 'GASecret.required' => '参数不完整', ]); $pending = (string) $request->session()->get($this->pendingSecretKey(), ''); if ($pending === '' || ! hash_equals($pending, $data['GASecret'])) { return response()->json(['code' => 1, 'msg' => '绑定已过期,请重新获取二维码']); } if (! $google2fa->verify($data['GASecret'], $data['GAKey'])) { return response()->json(['code' => 1, 'msg' => '绑定失败,验证码不正确']); } $loginVerify = (int) ($data['login_verify'] ?? 0); $actor->forceFill([ 'google_auth_open' => $loginVerify, 'google_secret' => $data['GASecret'], ])->save(); $request->session()->forget($this->pendingSecretKey()); return response()->json([ 'code' => 0, 'msg' => $loginVerify === 1 ? '绑定成功,下次登录需要输入谷歌验证码' : '绑定成功。登录不校验谷歌验证码;查看助记词明文仍需验证', ]); } public function toggle(Request $request, AdminGoogle2fa $google2fa): JsonResponse { $actor = $this->googleActor(); $data = $request->validate([ 'password' => ['required', 'string'], 'open' => ['required', 'integer', 'in:0,1'], 'GACode' => ['nullable', 'string', 'max:16'], ]); if (! Hash::check($data['password'], $actor->password)) { return response()->json(['code' => 1, 'msg' => '登陆密码不正确']); } if (! filled($actor->google_secret)) { return response()->json(['code' => 1, 'msg' => '您未绑定谷歌验证']); } $open = (int) $data['open']; $code = (string) ($data['GACode'] ?? ''); if (! $google2fa->verify((string) $actor->google_secret, $code)) { return response()->json(['code' => 1, 'msg' => '谷歌验证码不正确']); } $actor->forceFill(['google_auth_open' => $open])->save(); return response()->json([ 'code' => 0, 'msg' => $open === 1 ? '已开启登录谷歌验证' : '已关闭登录谷歌验证(绑定仍保留,查看助记词仍需验证)', ]); } public function unbind(Request $request, AdminGoogle2fa $google2fa): JsonResponse { $actor = $this->googleActor(); $data = $request->validate([ 'password' => ['required', 'string'], 'GACode' => ['required', 'string', 'max:16'], ]); if (! Hash::check($data['password'], $actor->password)) { return response()->json(['code' => 1, 'msg' => '登陆密码不正确']); } if (! filled($actor->google_secret)) { return response()->json(['code' => 1, 'msg' => '您未绑定谷歌验证']); } if (! $google2fa->verify((string) $actor->google_secret, $data['GACode'])) { return response()->json(['code' => 1, 'msg' => '谷歌验证码不正确']); } $actor->forceFill([ 'google_auth_open' => 0, 'google_secret' => null, ])->save(); $request->session()->forget($this->pendingSecretKey()); return response()->json(['code' => 0, 'msg' => '已解除谷歌验证绑定']); } private function googleActor(): Admin|User { if ($this->isAgentPortal()) { /** @var User $user */ $user = auth('agent')->user(); return $user; } /** @var Admin $admin */ $admin = auth('admin')->user(); return $admin; } /** @return array{prepare: string, bind: string, toggle: string, unbind: string} */ private function googleRoutes(): array { $portal = $this->portal(); return [ 'prepare' => route($portal.'.security.google2fa.prepare'), 'bind' => route($portal.'.security.google2fa.bind'), 'toggle' => route($portal.'.security.google2fa.toggle'), 'unbind' => route($portal.'.security.google2fa.unbind'), ]; } private function pendingSecretKey(): string { return $this->isAgentPortal() ? 'agent_google2fa_pending_secret' : 'admin_google2fa_pending_secret'; } }