Unverified Commit 55592ed1 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(auth): support public auth bind endpoint

parent 5427bc76
Loading
Loading
Loading
Loading
+20 −0
Changes for panel/dev/mockApi.ts: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -599,6 +599,26 @@ async function handlePublic(ctx: RequestContext): Promise<boolean> {
      sendJSON(ctx.res, 200, out);
      return true;
    }
    case "POST auth/bind": {
      const body = await readJSON<{ code?: string }>(ctx.req);
      const code = body.code?.trim().toUpperCase();
      if (!code) {
        sendError(ctx.res, 400, "bad_request", "code is required");
        return true;
      }
      if (code !== MOCK_LINK_CODE) {
        sendError(ctx.res, 400, "invalid_code", "bind code is invalid or expired");
        return true;
      }
      setSessionCookie(ctx.res, "linked");
      sendJSON(ctx.res, 200, {
        user_id: "mock-linked",
        linked: true,
        mc_uuid: MC_UUID,
        auth_source: "mojang",
      });
      return true;
    }
    case "POST auth/logout":
      clearSessionCookie(ctx.res);
      sendJSON(ctx.res, 200, { ok: true });
+7 −0
Changes for panel/src/i18n/resources/en-US/auth.json: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,13 @@
  "password": "Password",
  "sign_in": "Sign in",
  "signing_in": "Signing in…",
  "tab_password": "Password",
  "tab_bind": "Bind Code",
  "bind_code": "Bind Code",
  "bind_code_placeholder": "e.g., ABCD2345",
  "bind_hint": "Type /login in-game to generate a one-time bind code.",
  "bind_btn": "Verify & Sign In",
  "binding": "Verifying…",
  "change_password_title": "Set a new password",
  "change_password_subtitle_forced": "Your account was issued a one-time password. Choose a new one to continue.",
  "change_password_subtitle_voluntary": "Update your console password.",
+7 −0
Changes for panel/src/i18n/resources/zh-CN/auth.json: 7 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -5,6 +5,13 @@
  "password": "密码",
  "sign_in": "登录",
  "signing_in": "登录中…",
  "tab_password": "账号密码",
  "tab_bind": "游戏绑定码",
  "bind_code": "绑定码",
  "bind_code_placeholder": "例如:ABCD2345",
  "bind_hint": "在游戏内输入 /login 即可获取一次性绑定码",
  "bind_btn": "验证并登录",
  "binding": "验证中…",
  "change_password_title": "设置新密码",
  "change_password_subtitle_forced": "当前为一次性密码,请设置新密码后继续。",
  "change_password_subtitle_voluntary": "修改控制台登录密码。",
+26 −0
Changes for panel/src/lib/api.test.ts: 26 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -124,6 +124,32 @@ describe("local-password auth wire shapes", () => {
    expect((opts as RequestInit).method).toBe("POST");
  });

  it("bind POSTs {code} to /auth/bind", async () => {
    const fetchSpy = fakeFetch({
      user_id: "mock-linked",
      linked: true,
      mc_uuid: "uuid-123",
      auth_source: "mojang",
    });
    vi.stubGlobal("fetch", fetchSpy);
    const res = await api.bind("ABCD2345");
    expect(res.user_id).toBe("mock-linked");
    expect(res.linked).toBe(true);
    expect(res.mc_uuid).toBe("uuid-123");
    expect(res.auth_source).toBe("mojang");

    const [url, opts] = (fetchSpy as unknown as ReturnType<typeof vi.fn>).mock
      .calls[0];
    expect(String(url)).toBe("/auth/bind");
    expect((opts as RequestInit).method).toBe("POST");
    expect((opts as RequestInit).headers).toEqual({
      "Content-Type": "application/json",
    });
    expect(JSON.parse((opts as RequestInit).body as string)).toEqual({
      code: "ABCD2345",
    });
  });

  it("changePassword POSTs {current_password, new_password}", async () => {
    const fetchSpy = fakeFetch({ ok: true });
    vi.stubGlobal("fetch", fetchSpy);
+4 −0
Changes for panel/src/lib/api.ts: 4 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@ import type {
  LinkResult,
  LinkStatus,
  LoginResult,
  BindResult,
  PlayersResult,
  ServerInfo,
  WhitelistImage,
@@ -99,6 +100,9 @@ export const api = {
  // the tier model reads as `unauthenticated` and routes back to /login.
  logout: () => request<{ ok: boolean }>("POST", "/auth/logout"),

  bind: (code: string) =>
    request<BindResult>("POST", "/auth/bind", { code }),

  // changePassword is callable during the first-login lockdown (the route is
  // AllowDuringPasswordChange): the server re-verifies current_password, rejects an
  // unchanged or weak (8–72 byte) new password, writes the new hash, and revokes
Loading