fix: 修正 user-billing 自服务代理鉴权边界 (#1187)
This commit is contained in:
@@ -1262,14 +1262,14 @@ async function maybeHandleUserBillingApiKeysHttp(request, response, url, options
|
|||||||
async function handleUserBillingProfileHttp(request, response, options) {
|
async function handleUserBillingProfileHttp(request, response, options) {
|
||||||
const token = bearerTokenFromRequest(request);
|
const token = bearerTokenFromRequest(request);
|
||||||
const client = options.userBillingClient ?? options.accessController?.userBilling ?? null;
|
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 });
|
const auth = await options.accessController.authenticate(request, { required: true });
|
||||||
if (!auth.ok) return sendJson(response, auth.status, auth);
|
if (!auth.ok) return sendJson(response, auth.status, auth);
|
||||||
if (!String(auth.authMethod ?? "").startsWith("user-billing")) {
|
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" } });
|
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);
|
const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES);
|
||||||
if (!parsed.ok) return sendJson(response, 400, parsed.error);
|
if (!parsed.ok) return sendJson(response, 400, parsed.error);
|
||||||
const result = await client.updateProfile(token, parsed.value);
|
const result = await client.updateProfile(token, parsed.value);
|
||||||
@@ -1279,14 +1279,14 @@ async function handleUserBillingProfileHttp(request, response, options) {
|
|||||||
async function handleUserBillingPasswordHttp(request, response, options) {
|
async function handleUserBillingPasswordHttp(request, response, options) {
|
||||||
const token = bearerTokenFromRequest(request);
|
const token = bearerTokenFromRequest(request);
|
||||||
const client = options.userBillingClient ?? options.accessController?.userBilling ?? null;
|
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 });
|
const auth = await options.accessController.authenticate(request, { required: true });
|
||||||
if (!auth.ok) return sendJson(response, auth.status, auth);
|
if (!auth.ok) return sendJson(response, auth.status, auth);
|
||||||
if (auth.authMethod !== "user-billing-session") {
|
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" } });
|
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);
|
const parsed = await readJsonObject(request, DEFAULT_BODY_LIMIT_BYTES);
|
||||||
if (!parsed.ok) return sendJson(response, 400, parsed.error);
|
if (!parsed.ok) return sendJson(response, 400, parsed.error);
|
||||||
const result = await client.changePassword(token, parsed.value);
|
const result = await client.changePassword(token, parsed.value);
|
||||||
|
|||||||
@@ -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())));
|
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())));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user