fix(auth): distinguish Endorser unavailability from auth rejection
Return 503 when Endorser cannot be reached and keep 401 for rejected JWTs, with clearer server-side diagnostics and generic client messages.
This commit is contained in:
+29
-6
@@ -132,15 +132,38 @@ export async function requireEndorserAuth(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const endorsed = await checkAuth(jwt);
|
const result = await checkAuth(jwt);
|
||||||
if (!endorsed) {
|
if (result.ok) {
|
||||||
console.log("[Auth] Endorser verification failed");
|
next();
|
||||||
res.status(401).json({
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorTime = new Date().toISOString();
|
||||||
|
const did = req.did ?? "(unknown)";
|
||||||
|
|
||||||
|
if (result.reason === "unavailable") {
|
||||||
|
console.log("[Auth] Endorser unavailable");
|
||||||
|
console.error(
|
||||||
|
"[Auth] Endorser auth check unavailable at",
|
||||||
|
errorTime + ", did:",
|
||||||
|
did
|
||||||
|
);
|
||||||
|
res.status(503).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: "Unauthorized",
|
message:
|
||||||
|
"Authentication service unavailable. See server logs at " + errorTime,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
next();
|
console.log("[Auth] Endorser verification failed");
|
||||||
|
console.error(
|
||||||
|
"[Auth] Endorser rejected JWT at",
|
||||||
|
errorTime + ", did:",
|
||||||
|
did
|
||||||
|
);
|
||||||
|
res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: "Unauthorized. See server logs at " + errorTime,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ import { errorMessage } from "../util/formatElapsed.js";
|
|||||||
|
|
||||||
const RATE_LIMITS_PATH = "/api/report/rateLimits";
|
const RATE_LIMITS_PATH = "/api/report/rateLimits";
|
||||||
|
|
||||||
|
export type EndorserAuthResult =
|
||||||
|
| { ok: true }
|
||||||
|
| { ok: false; reason: "unauthorized" | "unavailable" };
|
||||||
|
|
||||||
function rateLimitsUrl(): string {
|
function rateLimitsUrl(): string {
|
||||||
const base = ENDORSER_URL.replace(/\/+$/, "");
|
const base = ENDORSER_URL.replace(/\/+$/, "");
|
||||||
return `${base}${RATE_LIMITS_PATH}`;
|
return `${base}${RATE_LIMITS_PATH}`;
|
||||||
@@ -12,13 +16,15 @@ function rateLimitsUrl(): string {
|
|||||||
* Confirms a JWT is accepted by the Endorser server.
|
* Confirms a JWT is accepted by the Endorser server.
|
||||||
*
|
*
|
||||||
* Calls GET /api/report/rateLimits with the JWT as a Bearer token.
|
* Calls GET /api/report/rateLimits with the JWT as a Bearer token.
|
||||||
* Returns true on success, false on auth or request failure.
|
* Distinguishes auth rejection from Endorser unavailability.
|
||||||
* Does not expose HTTP status or response bodies to callers.
|
* Does not expose HTTP status or response bodies to callers.
|
||||||
*/
|
*/
|
||||||
export async function checkAuth(jwt: string): Promise<boolean> {
|
export async function checkAuth(jwt: string): Promise<EndorserAuthResult> {
|
||||||
|
const url = rateLimitsUrl();
|
||||||
|
|
||||||
let response: Response;
|
let response: Response;
|
||||||
try {
|
try {
|
||||||
response = await fetch(rateLimitsUrl(), {
|
response = await fetch(url, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${jwt}`,
|
Authorization: `Bearer ${jwt}`,
|
||||||
@@ -26,19 +32,32 @@ export async function checkAuth(jwt: string): Promise<boolean> {
|
|||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
"[Endorser] Auth check request failed:",
|
"[Endorser] Auth check request failed for",
|
||||||
|
url + ":",
|
||||||
errorMessage(err)
|
errorMessage(err)
|
||||||
);
|
);
|
||||||
return false;
|
return { ok: false, reason: "unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (response.ok) {
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5xx: Endorser is up but unhealthy; treat as unavailable.
|
||||||
|
if (response.status >= 500) {
|
||||||
console.error(
|
console.error(
|
||||||
"[Endorser] Auth check failed with status",
|
"[Endorser] Auth check unavailable for",
|
||||||
|
url + ", status",
|
||||||
response.status
|
response.status
|
||||||
);
|
);
|
||||||
return false;
|
return { ok: false, reason: "unavailable" };
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
// 4xx: JWT rejected or user not registered on Endorser.
|
||||||
|
console.error(
|
||||||
|
"[Endorser] Auth check rejected for",
|
||||||
|
url + ", status",
|
||||||
|
response.status
|
||||||
|
);
|
||||||
|
return { ok: false, reason: "unauthorized" };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user