From ee78ed9fdccc9f276402480fb810c4b9be89a51c Mon Sep 17 00:00:00 2001 From: Lyon <88232613+pikasTech@users.noreply.github.com> Date: Sun, 14 Jun 2026 10:17:56 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20user-billing=20?= =?UTF-8?q?=E8=87=AA=E6=9C=8D=E5=8A=A1=E4=BB=A3=E7=90=86=E9=89=B4=E6=9D=83?= =?UTF-8?q?=E8=BE=B9=E7=95=8C=20(#1187)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/cloud/server.ts | 12 +++++------ .../cloud/user-billing-integration.test.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/internal/cloud/server.ts b/internal/cloud/server.ts index f487b38d..cfff7325 100644 --- a/internal/cloud/server.ts +++ b/internal/cloud/server.ts @@ -1262,14 +1262,14 @@ async function maybeHandleUserBillingApiKeysHttp(request, response, url, options async function handleUserBillingProfileHttp(request, response, options) { const token = bearerTokenFromRequest(request); const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; - if (!token || !client?.configured || typeof client.updateProfile !== "function") { - return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing profile API is not configured", { operation: "PATCH /v1/users/me/profile", target: { type: "service", id: "hwlab-user-billing" } }); - } const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) return sendJson(response, auth.status, auth); if (!String(auth.authMethod ?? "").startsWith("user-billing")) { return sendRestError(request, response, 403, "user_billing_auth_required", "profile update requires a user-billing bearer token", { operation: "PATCH /v1/users/me/profile", target: { type: "route", id: "/v1/users/me/profile" } }); } + if (!token || !client?.configured || typeof client.updateProfile !== "function") { + return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing profile API is not configured", { operation: "PATCH /v1/users/me/profile", target: { type: "service", id: "hwlab-user-billing" } }); + } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.updateProfile(token, parsed.value); @@ -1279,14 +1279,14 @@ async function handleUserBillingProfileHttp(request, response, options) { async function handleUserBillingPasswordHttp(request, response, options) { const token = bearerTokenFromRequest(request); const client = options.userBillingClient ?? options.accessController?.userBilling ?? null; - if (!token || !client?.configured || typeof client.changePassword !== "function") { - return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing password API is not configured", { operation: "POST /v1/users/me/password", target: { type: "service", id: "hwlab-user-billing" } }); - } const auth = await options.accessController.authenticate(request, { required: true }); if (!auth.ok) return sendJson(response, auth.status, auth); if (auth.authMethod !== "user-billing-session") { return sendRestError(request, response, 403, "user_billing_session_required", "password update requires a user-billing session token", { operation: "POST /v1/users/me/password", target: { type: "route", id: "/v1/users/me/password" } }); } + if (!token || !client?.configured || typeof client.changePassword !== "function") { + return sendRestError(request, response, 503, "user_billing_not_configured", "HWLAB user-billing password API is not configured", { operation: "POST /v1/users/me/password", target: { type: "service", id: "hwlab-user-billing" } }); + } const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES); if (!parsed.ok) return sendJson(response, 400, parsed.error); const result = await client.changePassword(token, parsed.value); diff --git a/internal/cloud/user-billing-integration.test.ts b/internal/cloud/user-billing-integration.test.ts index c8ff606e..d5228d57 100644 --- a/internal/cloud/user-billing-integration.test.ts +++ b/internal/cloud/user-billing-integration.test.ts @@ -517,3 +517,24 @@ test("cloud api proxies R1 user-billing API key and profile self-service routes" await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); } }); + +test("cloud api reports auth_required before user-billing profile/password proxy config errors", async () => { + const userBillingClient = { + configured: true, + async updateProfile() { throw new Error("profile proxy should not run without auth"); }, + async changePassword() { throw new Error("password proxy should not run without auth"); } + }; + const server = createCloudApiServer({ env: { HWLAB_ACCESS_CONTROL_REQUIRED: "1" }, userBillingClient }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + try { + const { port } = server.address(); + const profile = await fetch(`http://127.0.0.1:${port}/v1/users/me/profile`, { method: "PATCH", headers: { "content-type": "application/json" }, body: JSON.stringify({ displayName: "Missing Auth" }) }); + assert.equal(profile.status, 401); + assert.equal((await profile.json()).error.code, "auth_required"); + const password = await fetch(`http://127.0.0.1:${port}/v1/users/me/password`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ currentPassword: "old-password", newPassword: "new-password" }) }); + assert.equal(password.status, 401); + assert.equal((await password.json()).error.code, "auth_required"); + } finally { + await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve()))); + } +});