Loading panel/dev/mockApi.ts +20 −0 Changes for panel/dev/mockApi.ts: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 }); Loading panel/src/i18n/resources/en-US/auth.json +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 Loading @@ -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.", Loading panel/src/i18n/resources/zh-CN/auth.json +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 Loading @@ -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": "修改控制台登录密码。", Loading panel/src/lib/api.test.ts +26 −0 Changes for panel/src/lib/api.test.ts: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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); Loading panel/src/lib/api.ts +4 −0 Changes for panel/src/lib/api.ts: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import type { LinkResult, LinkStatus, LoginResult, BindResult, PlayersResult, ServerInfo, WhitelistImage, Loading Loading @@ -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 Loading
panel/dev/mockApi.ts +20 −0 Changes for panel/dev/mockApi.ts: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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 }); Loading
panel/src/i18n/resources/en-US/auth.json +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 Loading @@ -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.", Loading
panel/src/i18n/resources/zh-CN/auth.json +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 Loading @@ -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": "修改控制台登录密码。", Loading
panel/src/lib/api.test.ts +26 −0 Changes for panel/src/lib/api.test.ts: 26 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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); Loading
panel/src/lib/api.ts +4 −0 Changes for panel/src/lib/api.ts: 4 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -12,6 +12,7 @@ import type { LinkResult, LinkStatus, LoginResult, BindResult, PlayersResult, ServerInfo, WhitelistImage, Loading Loading @@ -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