Unverified Commit 9079a2cd authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(panel): implement email otp and passkey login interface

parent 7becb384
Loading
Loading
Loading
Loading
+78 −0
Changes for panel/dev/mockApi.ts: 78 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -667,6 +667,84 @@ async function handlePublic(ctx: RequestContext): Promise<boolean> {
      });
      return true;
    }
    case "POST auth/passkey/login/discoverable/begin": {
      sendJSON(ctx.res, 200, {
        publicKey: {
          challenge: "c29tZV9kaXNjb3ZlcmFibGVfY2hhbGxlbmdl",
          rp: { name: "Felis Dev", id: "dev.felis.localhost" },
          user: { id: "bW9ja191c2VyX2lk", name: "[email protected]", displayName: "owner" },
          pubKeyCredParams: [{ type: "public-key", alg: -7 }],
          allowCredentials: [],
          timeout: 60000,
        },
        login_id: "mock_login_id_1234"
      });
      return true;
    }
    case "POST auth/passkey/login/discoverable/finish": {
      const body = await readJSON<{ login_id?: string; assertion?: any }>(ctx.req);
      if (!body.login_id || !body.assertion) {
        sendError(ctx.res, 400, "bad_request", "login_id and assertion are required");
        return true;
      }
      setSessionCookie(ctx.res, "owner");
      sendJSON(ctx.res, 200, {
        user_id: "mock-owner",
        role: "owner"
      });
      return true;
    }
    case "POST auth/passkey/login/begin": {
      const body = await readJSON<{ email?: string }>(ctx.req);
      if (!body.email || !body.email.includes("@")) {
        sendError(ctx.res, 400, "bad_request", "email is required");
        return true;
      }
      sendJSON(ctx.res, 200, {
        challenge: "c29tZV9wYXNza2V5X2NoYWxsZW5nZQ",
        rp: { name: "Felis Dev", id: "dev.felis.localhost" },
        user: { id: "bW9ja191c2VyX2lk", name: body.email, displayName: "owner" },
        pubKeyCredParams: [{ type: "public-key", alg: -7 }],
        allowCredentials: [{ type: "public-key", id: "cGstMQ" }],
        timeout: 60000,
      });
      return true;
    }
    case "POST auth/passkey/login/finish": {
      const body = await readJSON<{ email?: string; assertion?: any }>(ctx.req);
      if (!body.email || !body.assertion) {
        sendError(ctx.res, 400, "bad_request", "email and assertion are required");
        return true;
      }
      setSessionCookie(ctx.res, "owner");
      sendJSON(ctx.res, 200, {
        user_id: "mock-owner",
        role: "owner"
      });
      return true;
    }
    case "POST auth/email/start": {
      const body = await readJSON<{ email?: string }>(ctx.req);
      if (!body.email || !body.email.includes("@")) {
        sendError(ctx.res, 400, "bad_request", "email is required");
        return true;
      }
      sendJSON(ctx.res, 202, { sent: true, expires_at: new Date(Date.now() + 600000).toISOString() });
      return true;
    }
    case "POST auth/email/verify": {
      const body = await readJSON<{ email?: string; code?: string }>(ctx.req);
      if (!body.email || body.code !== "123456") {
        sendError(ctx.res, 400, "invalid_code", "email code is invalid or expired");
        return true;
      }
      setSessionCookie(ctx.res, "owner");
      sendJSON(ctx.res, 200, {
        user_id: "mock-owner",
        role: "owner"
      });
      return true;
    }
    case "POST auth/logout":
      clearSessionCookie(ctx.res);
      sendJSON(ctx.res, 200, { ok: true });
+0 −1
Changes for panel/src/components/EditServerDialog.tsx: 0 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ import {
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { api, humanizeError } from "@/lib/api";
+17 −1
Changes for panel/src/i18n/resources/en-US/auth.json: 17 added lines, 1 removed line.
Original line number Diff line number Diff line
{
  "login_title": "Sign in to Felis",
  "login_subtitle": "Operator console",
  "username": "Username",
  "username": "Username or Email",
  "password": "Password",
  "sign_in": "Sign in",
  "signing_in": "Signing in…",
  "tab_password": "Password",
  "tab_bind": "Bind Code",
  "tab_email": "Email OTP",
  "other_login_methods": "Other sign-in options",
  "or_divider": "or",
  "tab_email_btn": "Sign in with Email OTP",
  "tab_bind_btn": "Sign in with Bind Code",
  "back_to_password": "Back to password login",
  "email_address": "Email Address",
  "email_placeholder": "Enter your registered email",
  "otp_code": "Verification Code",
  "otp_placeholder": "Enter 6-digit code",
  "send_otp": "Send Code",
  "sending_otp": "Sending…",
  "otp_sent": "Verification code sent to your email.",
  "otp_btn": "Verify & Sign In",
  "resend_in": "s",
  "passkey_btn": "Sign in with Passkey",
  "bind_code": "Bind Code",
  "bind_code_placeholder": "e.g., ABCD2345",
  "bind_hint": "Type /login in-game to generate a one-time bind code.",
+3 −1
Changes for panel/src/i18n/resources/en-US/errors.json: 3 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -25,5 +25,7 @@
  "passkey_challenge_invalid": "Passkey challenge is invalid or expired, please try again.",
  "invalid_attestation": "Could not verify this Passkey, please try again.",
  "passkey_already_bound": "This Passkey is already bound to another account.",
  "passkey_unavailable": "Passkey subsystem is not available right now."
  "passkey_unavailable": "Passkey subsystem is not available right now.",
  "passkey_not_allowed": "The operation was cancelled by the user or browser.",
  "passkey_aborted": "Passkey registration was aborted."
}
+17 −1
Changes for panel/src/i18n/resources/zh-CN/auth.json: 17 added lines, 1 removed line.
Original line number Diff line number Diff line
{
  "login_title": "登录 Felis",
  "login_subtitle": "运维控制台",
  "username": "用户名",
  "username": "用户名或邮箱",
  "password": "密码",
  "sign_in": "登录",
  "signing_in": "登录中…",
  "tab_password": "账号密码",
  "tab_bind": "游戏绑定码",
  "tab_email": "邮箱验证码",
  "other_login_methods": "其他登录方式",
  "or_divider": "或",
  "tab_email_btn": "使用邮箱验证码登录",
  "tab_bind_btn": "使用游戏绑定码登录",
  "back_to_password": "返回密码登录",
  "email_address": "邮箱地址",
  "email_placeholder": "请输入绑定的邮箱",
  "otp_code": "验证码",
  "otp_placeholder": "请输入 6 位验证码",
  "send_otp": "发送验证码",
  "sending_otp": "发送中…",
  "otp_sent": "验证码已发送至您的邮箱,请查收",
  "otp_btn": "验证并登录",
  "resend_in": "秒后重试",
  "passkey_btn": "使用 Passkey 登录",
  "bind_code": "绑定码",
  "bind_code_placeholder": "例如:ABCD2345",
  "bind_hint": "在游戏内输入 /login 即可获取一次性绑定码",
Loading