fix(auth): 为登录链路增加有界超时
ci / verify (pull_request) Successful in 10m14s

为 PostgreSQL 连接、就绪检查和本地登录设置分层超时,数据库不可用时返回稳定 503 错误码并记录无凭据的连接池统计。

前端登录在 10 秒后取消请求,Nginx 登录精确路由限制上游为 15 秒,同时更新 OpenAPI 和回归测试。

补充开发工具链安全 override,使高危依赖审计保持为零。
This commit is contained in:
2026-07-21 10:57:51 +08:00
parent 4f45f38b8e
commit bc1ff2b62f
11 changed files with 366 additions and 42 deletions
+27 -14
View File
@@ -119,6 +119,7 @@ export async function loginLocalAccount(input: { account: string; password: stri
auth: false,
body: input,
method: 'POST',
timeoutMs: 10_000,
});
}
@@ -1180,7 +1181,7 @@ export async function deleteFileStorageChannel(token: string, channelId: string)
async function request<T>(
path: string,
options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record<string, string> } = {},
options: { token?: string; auth?: boolean; method?: string; body?: unknown; headers?: Record<string, string>; timeoutMs?: number } = {},
): Promise<T> {
const headers: Record<string, string> = { ...(options.headers ?? {}) };
if (options.auth !== false && options.token && options.token !== OIDC_BROWSER_SESSION_CREDENTIAL) {
@@ -1189,20 +1190,32 @@ async function request<T>(
if (options.body !== undefined) {
headers['Content-Type'] = 'application/json';
}
const response = await fetch(`${API_BASE}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: 'include',
});
if (!response.ok) {
const body = await response.text();
throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`));
const controller = options.timeoutMs ? new AbortController() : undefined;
const timeout = controller ? globalThis.setTimeout(() => controller.abort(), options.timeoutMs) : undefined;
try {
const response = await fetch(`${API_BASE}${path}`, {
method: options.method ?? 'GET',
headers,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
credentials: 'include',
signal: controller?.signal,
});
if (!response.ok) {
const body = await response.text();
throw new GatewayApiError(parseErrorDetails(body, response.status, `Request failed: ${response.status}`));
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
} catch (error) {
if (controller?.signal.aborted) {
throw new GatewayApiError('登录请求超时,请稍后重试');
}
throw error;
} finally {
if (timeout !== undefined) globalThis.clearTimeout(timeout);
}
if (response.status === 204) {
return undefined as T;
}
return response.json() as Promise<T>;
}
function authorizationHeader(token: string): Record<string, string> {