修复认证挑战与可信代理安全边界(07-17-auth-challenge-trusted-proxy-hardening)

这个提交包含在:
2026-07-17 14:24:58 +08:00
父节点 bbb5c89825
当前提交 6ddedf1e0e
共修改 31 个文件,包含 3801 行新增和 419 行删除
@@ -0,0 +1,991 @@
const crypto = require("node:crypto");
const { Client } = require("pg");
const { createPrismaClient } = require("../prismaClient");
const {
generateSixDigitCode,
hashAuthCode,
randomRefreshToken,
sha256Hex,
verifyPassword,
} = require("../utils/crypto");
const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
const EMPLOYEE_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
class AuthConcurrencyCheckError extends Error {
constructor(code) {
super(code);
this.name = "AuthConcurrencyCheckError";
this.safeCode = code;
}
}
function fail(code) {
throw new AuthConcurrencyCheckError(code);
}
function requireValue(env, name) {
const value = String(env[name] ?? "").trim();
if (!value) fail(`${name.toLowerCase()}_required`);
return value;
}
function validateAuthRegressionEnvironment(env = process.env) {
if (env.RUN_AUTH_SECURITY_REGRESSION !== "1") fail("regression_opt_in_required");
if (env.NODE_ENV !== "test") fail("test_environment_required");
if (env.RATE_LIMIT_ENABLED !== "false") fail("rate_limit_must_be_disabled");
if (env.DEV_SHOW_PASSWORD_RESET_CODE !== "false") fail("code_logging_forbidden");
if (env.TRUST_PROXY_HOPS !== "0") fail("trust_proxy_must_be_disabled");
if (env.EMAIL_ENABLED !== "false") fail("email_provider_must_be_disabled");
if (String(env.SMTP_HOST ?? "").trim()) fail("smtp_host_forbidden");
if (String(env.SMTP_USER ?? "").trim() || String(env.SMTP_PASS ?? "").trim()) {
fail("smtp_credentials_forbidden");
}
if (env.IM_NOTIFIER_ENABLED !== "false") fail("im_notifier_must_be_disabled");
if (env.WECOM_ENABLED !== "false") fail("wecom_must_be_disabled");
if (String(env.WECOM_AGENT_SECRET ?? "").trim()) fail("wecom_secret_forbidden");
if (env.OIDC_ENABLED !== "false") fail("oidc_must_be_disabled");
if (String(env.OIDC_CLIENT_SECRET ?? "").trim()) fail("oidc_secret_forbidden");
const databaseUrl = requireValue(env, "DATABASE_URL");
let parsedDatabaseUrl;
try {
parsedDatabaseUrl = new URL(databaseUrl);
} catch {
fail("database_url_invalid");
}
if (!["postgres:", "postgresql:"].includes(parsedDatabaseUrl.protocol)) {
fail("database_protocol_invalid");
}
const pgConfig = new Client({ connectionString: databaseUrl });
const databaseHost = String(pgConfig.host ?? "").toLowerCase();
const databaseName = String(pgConfig.database ?? "");
if (!LOOPBACK_HOSTS.has(databaseHost)) fail("database_host_not_loopback");
if (!/^hyb_oa_auth_[A-Za-z0-9_]+$/.test(databaseName)) fail("database_name_not_isolated");
if (requireValue(env, "AUTH_SECURITY_DATABASE_CONFIRM") !== databaseName) {
fail("database_confirmation_mismatch");
}
const baseUrl = requireValue(env, "BASE_URL");
let parsedBaseUrl;
try {
parsedBaseUrl = new URL(baseUrl);
} catch {
fail("base_url_invalid");
}
if (parsedBaseUrl.protocol !== "http:" || !LOOPBACK_HOSTS.has(parsedBaseUrl.hostname)) {
fail("base_url_not_loopback");
}
const pepper = requireValue(env, "AUTH_CODE_PEPPER");
if (/^<[^>]+>$/.test(pepper) || Buffer.byteLength(pepper, "utf8") < 32) {
fail("auth_code_pepper_weak");
}
const jwtSecret = requireValue(env, "JWT_SECRET");
if (jwtSecret === pepper) fail("auth_code_pepper_not_independent");
requireValue(env, "FIELD_ENCRYPTION_KEY");
return {
baseUrl: parsedBaseUrl.origin,
databaseUrl,
pepper,
};
}
function validateAuthConcurrencyEnvironment(env = process.env) {
if (env.RUN_AUTH_POSTGRES_CONCURRENCY !== "1") fail("explicit_opt_in_required");
const baseContext = validateAuthRegressionEnvironment(env);
const employeeId = requireValue(env, "AUTH_SECURITY_EMPLOYEE_ID");
if (!EMPLOYEE_ID_PATTERN.test(employeeId)) fail("employee_id_invalid");
return {
...baseContext,
employeeId,
employeeEmail: requireValue(env, "AUTH_SECURITY_EMPLOYEE_EMAIL"),
currentPassword: requireValue(env, "AUTH_SECURITY_CURRENT_PASSWORD"),
};
}
function createStrongPassword(prefix) {
return `${prefix}A1!${crypto.randomBytes(12).toString("hex")}`;
}
function assertCondition(condition, code) {
if (!condition) fail(code);
}
async function postJson(baseUrl, path, payload, { headers = {}, signal } = {}) {
let response;
try {
response = await fetch(`${baseUrl}${path}`, {
method: "POST",
headers: {
"content-type": "application/json",
...headers,
},
body: JSON.stringify(payload),
signal,
});
} catch {
fail("http_request_failed");
}
let error = null;
if (response.status !== 200) {
try {
const body = await response.json();
error = {
code: body?.error?.code ?? null,
detailsCode: body?.error?.details?.code ?? null,
};
} catch {
error = null;
}
}
return {
status: response.status,
error,
setCookie: Boolean(response.headers.get("set-cookie")),
};
}
function extractRefreshCookie(response) {
const setCookie = String(response.headers.get("set-cookie") ?? "");
const match = setCookie.match(/(?:^|,\s*)refresh_token=([^;,\s]+)/);
return match ? `refresh_token=${match[1]}` : null;
}
async function loginForRace({ baseUrl, email, password, fingerprint, signal }) {
let response;
try {
response = await fetch(`${baseUrl}/api/auth/login`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-device-fingerprint": fingerprint,
},
body: JSON.stringify({ identifier: email, password }),
signal,
});
} catch {
fail("login_http_request_failed");
}
let accessToken = null;
let error = null;
try {
const body = await response.json();
if (response.status === 200) {
accessToken = typeof body?.data?.accessToken === "string" ? body.data.accessToken : null;
} else {
error = {
code: body?.error?.code ?? null,
detailsCode: body?.error?.details?.code ?? null,
};
}
} catch {
error = null;
}
return {
status: response.status,
accessToken,
refreshCookie: extractRefreshCookie(response),
error,
};
}
async function getWithAccessToken(baseUrl, path, accessToken) {
let response;
try {
response = await fetch(`${baseUrl}${path}`, {
headers: { authorization: `Bearer ${accessToken}` },
});
} catch {
fail("access_token_probe_failed");
}
let errorCode = null;
if (response.status !== 200) {
try {
const body = await response.json();
errorCode = body?.error?.code ?? null;
} catch {
errorCode = null;
}
}
return { status: response.status, errorCode };
}
async function preparePasswordResetChallenge({
prisma,
employeeId,
pepper,
includeSession,
}) {
const now = new Date();
const expiresAt = new Date(now.getTime() + 10 * 60 * 1000);
const code = generateSixDigitCode();
const emailCode = generateSixDigitCode();
const codeHash = hashAuthCode({
employeeId,
code,
purpose: "password_reset",
pepper,
});
const emailCodeHash = hashAuthCode({
employeeId,
code: emailCode,
purpose: "email_login",
pepper,
});
const refreshToken = includeSession ? randomRefreshToken() : null;
const created = await prisma.$transaction(async (tx) => {
await tx.employee.update({
where: { id: employeeId },
data: {
password_reset_failed_count: 0,
password_reset_failed_first_at: null,
password_reset_locked_until: null,
password_reset_request_count: 0,
password_reset_request_first_at: null,
password_reset_last_requested_at: null,
email_login_code_failed_count: 0,
email_login_code_failed_first_at: null,
email_login_code_locked_until: null,
email_login_code_last_requested_at: null,
},
});
await tx.passwordResetCode.updateMany({
where: { employee_id: employeeId, invalidated_at: null },
data: { invalidated_at: now },
});
await tx.emailLoginCode.updateMany({
where: { employee_id: employeeId, used_at: null },
data: { used_at: now },
});
const resetCode = await tx.passwordResetCode.create({
data: {
employee_id: employeeId,
code_hash: codeHash,
expires_at: expiresAt,
created_ip: "127.0.0.1",
created_user_agent: "auth-concurrency-check",
},
select: { id: true },
});
const loginCode = await tx.emailLoginCode.create({
data: {
employee_id: employeeId,
code_hash: emailCodeHash,
expires_at: expiresAt,
created_ip: "127.0.0.1",
created_user_agent: "auth-concurrency-check",
},
select: { id: true },
});
let refreshSessionId = null;
if (refreshToken) {
const refreshSession = await tx.authSession.create({
data: {
employee_id: employeeId,
refresh_token_hash: sha256Hex(refreshToken),
device_fingerprint: "auth-concurrency-check",
ip_address: "127.0.0.1",
user_agent: "auth-concurrency-check",
expires_at: new Date(now.getTime() + 60 * 60 * 1000),
},
select: { id: true },
});
refreshSessionId = refreshSession.id;
}
return { resetCodeId: resetCode.id, emailCodeId: loginCode.id, refreshSessionId };
});
return { ...created, code, refreshToken };
}
async function getEmployeeSecurityState(prisma, employeeId) {
return prisma.employee.findUnique({
where: { id: employeeId },
select: {
id: true,
email: true,
status: true,
password_hash: true,
token_version: true,
password_reset_failed_count: true,
password_reset_locked_until: true,
},
});
}
async function countAudit(prisma, employeeId, action) {
return prisma.auditLog.count({
where: { actor_id: employeeId, action },
});
}
async function runConcurrentForgotPasswordRequests({ prisma, context }) {
const preparedAt = new Date();
await prisma.$transaction(async (tx) => {
await tx.employee.update({
where: { id: context.employeeId },
data: {
password_reset_failed_count: 0,
password_reset_failed_first_at: null,
password_reset_locked_until: null,
password_reset_request_count: 0,
password_reset_request_first_at: null,
password_reset_last_requested_at: null,
},
});
await tx.passwordResetCode.updateMany({
where: { employee_id: context.employeeId, invalidated_at: null },
data: { invalidated_at: preparedAt },
});
});
const requestedAuditBefore = await countAudit(
prisma,
context.employeeId,
"AUTH_PASSWORD_RESET_REQUESTED",
);
const responses = await Promise.all(
Array.from({ length: 20 }, () =>
postJson(context.baseUrl, "/api/auth/forgot-password", {
email: context.employeeEmail,
}),
),
);
assertCondition(
responses.every((response) => response.status === 200 && !response.setCookie),
"forgot_concurrent_response_invalid",
);
const [employee, activeCodes, requestedAuditAfter] = await Promise.all([
prisma.employee.findUnique({
where: { id: context.employeeId },
select: {
password_reset_failed_count: true,
password_reset_locked_until: true,
password_reset_request_count: true,
password_reset_request_first_at: true,
password_reset_last_requested_at: true,
},
}),
prisma.passwordResetCode.count({
where: {
employee_id: context.employeeId,
used_at: null,
invalidated_at: null,
expires_at: { gt: new Date() },
},
}),
countAudit(prisma, context.employeeId, "AUTH_PASSWORD_RESET_REQUESTED"),
]);
assertCondition(activeCodes === 1, "forgot_concurrent_active_code_count_invalid");
assertCondition(employee?.password_reset_request_count === 1, "forgot_request_count_invalid");
assertCondition(
employee?.password_reset_request_first_at instanceof Date &&
employee?.password_reset_last_requested_at instanceof Date,
"forgot_request_timestamps_missing",
);
assertCondition(employee?.password_reset_failed_count === 0, "forgot_failure_count_changed");
assertCondition(employee?.password_reset_locked_until === null, "forgot_account_locked");
assertCondition(
requestedAuditAfter === requestedAuditBefore + 1,
"forgot_request_audit_count_invalid",
);
}
async function runSameCodeConcurrency({ prisma, context }) {
const before = await getEmployeeSecurityState(prisma, context.employeeId);
assertCondition(before?.email === context.employeeEmail, "employee_identity_mismatch");
assertCondition(before.status === "active", "employee_not_active");
assertCondition(
await verifyPassword(context.currentPassword, before.password_hash),
"current_password_mismatch",
);
const challenge = await preparePasswordResetChallenge({
prisma,
employeeId: context.employeeId,
pepper: context.pepper,
includeSession: true,
});
const successAuditBefore = await countAudit(
prisma,
context.employeeId,
"AUTH_PASSWORD_RESET_SUCCEEDED",
);
const newPassword = createStrongPassword("Concurrent");
const responses = await Promise.all(
Array.from({ length: 20 }, () =>
postJson(context.baseUrl, "/api/auth/reset-password", {
email: context.employeeEmail,
code: challenge.code,
newPassword,
}),
),
);
const successes = responses.filter((response) => response.status === 200);
const failures = responses.filter((response) => response.status === 400);
if (successes.length !== 1) {
if (
responses.every(
(response) =>
response.status === 400 &&
response.error?.code === "VALID_001" &&
response.error?.detailsCode === "invalid_or_expired",
)
) {
const [rejectedCode, rejectedEmployee, activeCodeCount] = await Promise.all([
prisma.passwordResetCode.findUnique({
where: { id: challenge.resetCodeId },
select: { attempts_count: true, used_at: true, invalidated_at: true },
}),
prisma.employee.findUnique({
where: { id: context.employeeId },
select: {
password_reset_failed_count: true,
password_reset_locked_until: true,
},
}),
prisma.passwordResetCode.count({
where: {
employee_id: context.employeeId,
used_at: null,
invalidated_at: null,
expires_at: { gt: new Date() },
},
}),
]);
if (Number(rejectedCode?.attempts_count ?? 0) > 0) {
fail("concurrent_code_hash_mismatch");
}
if (rejectedEmployee?.password_reset_locked_until instanceof Date) {
fail("concurrent_account_prelocked");
}
if (activeCodeCount === 0) {
fail("concurrent_code_not_active");
}
fail("concurrent_active_code_not_selected");
}
if (
responses.every(
(response) =>
response.status === 400 &&
response.error?.code === "VALID_001" &&
response.error?.detailsCode === null,
)
) {
fail("concurrent_all_payloads_rejected");
}
fail("concurrent_success_count_invalid");
}
assertCondition(failures.length === 19, "concurrent_failure_count_invalid");
assertCondition(
failures.every(
(response) =>
response.error?.code === "VALID_001" &&
response.error?.detailsCode === "invalid_or_expired" &&
!response.setCookie,
),
"concurrent_failure_contract_invalid",
);
const after = await getEmployeeSecurityState(prisma, context.employeeId);
assertCondition(after.token_version === before.token_version + 1, "token_version_increment_invalid");
assertCondition(await verifyPassword(newPassword, after.password_hash), "new_password_not_active");
assertCondition(
!(await verifyPassword(context.currentPassword, after.password_hash)),
"old_password_still_active",
);
assertCondition(after.password_reset_failed_count === 0, "concurrent_loser_failure_count_changed");
assertCondition(after.password_reset_locked_until === null, "concurrent_losers_locked_account");
const [resetCode, emailCode, activeResetCodes, activeSessions, successAuditAfter] =
await Promise.all([
prisma.passwordResetCode.findUnique({
where: { id: challenge.resetCodeId },
select: { used_at: true, invalidated_at: true },
}),
prisma.emailLoginCode.findUnique({
where: { id: challenge.emailCodeId },
select: { used_at: true },
}),
prisma.passwordResetCode.count({
where: {
employee_id: context.employeeId,
used_at: null,
invalidated_at: null,
expires_at: { gt: new Date() },
},
}),
prisma.authSession.count({
where: {
employee_id: context.employeeId,
revoked_at: null,
expires_at: { gt: new Date() },
},
}),
countAudit(prisma, context.employeeId, "AUTH_PASSWORD_RESET_SUCCEEDED"),
]);
assertCondition(resetCode?.used_at instanceof Date, "reset_code_not_consumed");
assertCondition(resetCode?.invalidated_at instanceof Date, "reset_code_not_invalidated");
assertCondition(emailCode?.used_at instanceof Date, "email_code_not_invalidated");
assertCondition(activeResetCodes === 0, "active_reset_code_remains");
assertCondition(activeSessions === 0, "active_session_remains");
assertCondition(successAuditAfter === successAuditBefore + 1, "success_audit_count_invalid");
return newPassword;
}
async function waitForBlockedConnections(observer, excludedPids, expectedCount, timeoutMs) {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
const result = await observer.query(
`SELECT COUNT(*)::int AS count
FROM pg_stat_activity
WHERE datname = current_database()
AND pid <> $1
AND pid <> $2
AND cardinality(pg_blocking_pids(pid)) > 0`,
excludedPids,
);
if (Number(result.rows[0]?.count ?? 0) >= expectedCount) return;
await new Promise((resolve) => setTimeout(resolve, 50));
}
fail("blocked_connection_order_timeout");
}
async function withTimeout(promise, timeoutMs, code) {
let timeout;
try {
return await Promise.race([
promise,
new Promise((_, reject) => {
timeout = setTimeout(() => reject(new AuthConcurrencyCheckError(code)), timeoutMs);
}),
]);
} finally {
clearTimeout(timeout);
}
}
async function runLoginResetRace({ prisma, context, currentPassword }) {
const before = await getEmployeeSecurityState(prisma, context.employeeId);
assertCondition(
await verifyPassword(currentPassword, before.password_hash),
"race_current_password_mismatch",
);
const challenge = await preparePasswordResetChallenge({
prisma,
employeeId: context.employeeId,
pepper: context.pepper,
includeSession: false,
});
const successAuditBefore = await countAudit(
prisma,
context.employeeId,
"AUTH_PASSWORD_RESET_SUCCEEDED",
);
const loginAuditBefore = await countAudit(prisma, context.employeeId, "AUTH_LOGIN_SUCCESS");
const racePassword = createStrongPassword("RaceReset");
const holder = new Client({ connectionString: context.databaseUrl });
const observer = new Client({ connectionString: context.databaseUrl });
const resetAbort = new AbortController();
const loginAbort = new AbortController();
let holderInTransaction = false;
let resetPromise = null;
let loginPromise = null;
try {
await holder.connect();
await observer.connect();
const holderPid = holder.processID;
const observerPid = observer.processID;
await holder.query("BEGIN");
holderInTransaction = true;
await holder.query('SELECT id FROM "employees" WHERE id = $1::uuid FOR UPDATE', [
context.employeeId,
]);
resetPromise = postJson(
context.baseUrl,
"/api/auth/reset-password",
{
email: context.employeeEmail,
code: challenge.code,
newPassword: racePassword,
},
{ signal: resetAbort.signal },
);
await waitForBlockedConnections(observer, [holderPid, observerPid], 1, 15_000);
loginPromise = postJson(
context.baseUrl,
"/api/auth/login",
{
identifier: context.employeeEmail,
password: currentPassword,
},
{
headers: { "x-device-fingerprint": "auth-reset-race" },
signal: loginAbort.signal,
},
);
await waitForBlockedConnections(observer, [holderPid, observerPid], 2, 20_000);
await holder.query("COMMIT");
holderInTransaction = false;
const [resetResponse, loginResponse] = await Promise.all([
withTimeout(resetPromise, 30_000, "race_reset_timeout"),
withTimeout(loginPromise, 30_000, "race_login_timeout"),
]);
assertCondition(resetResponse.status === 200, "race_reset_failed");
assertCondition(
loginResponse.status === 401 &&
loginResponse.error?.code === "AUTH_001" &&
!loginResponse.setCookie,
"stale_login_was_accepted",
);
} finally {
resetAbort.abort();
loginAbort.abort();
if (holderInTransaction) {
await holder.query("ROLLBACK").catch(() => undefined);
}
await Promise.allSettled([resetPromise, loginPromise].filter(Boolean));
await holder.end().catch(() => undefined);
await observer.end().catch(() => undefined);
}
const after = await getEmployeeSecurityState(prisma, context.employeeId);
const [activeSessions, successAuditAfter, loginAuditAfter] = await Promise.all([
prisma.authSession.count({
where: {
employee_id: context.employeeId,
revoked_at: null,
expires_at: { gt: new Date() },
},
}),
countAudit(prisma, context.employeeId, "AUTH_PASSWORD_RESET_SUCCEEDED"),
countAudit(prisma, context.employeeId, "AUTH_LOGIN_SUCCESS"),
]);
assertCondition(after.token_version === before.token_version + 1, "race_token_version_invalid");
assertCondition(await verifyPassword(racePassword, after.password_hash), "race_password_not_active");
assertCondition(
!(await verifyPassword(currentPassword, after.password_hash)),
"race_old_password_still_active",
);
assertCondition(activeSessions === 0, "race_active_session_remains");
assertCondition(successAuditAfter === successAuditBefore + 1, "race_success_audit_invalid");
assertCondition(loginAuditAfter === loginAuditBefore, "race_login_success_audit_created");
return racePassword;
}
async function runLoginFirstResetRace({ prisma, context, currentPassword }) {
const before = await getEmployeeSecurityState(prisma, context.employeeId);
assertCondition(
await verifyPassword(currentPassword, before.password_hash),
"login_first_current_password_mismatch",
);
const challenge = await preparePasswordResetChallenge({
prisma,
employeeId: context.employeeId,
pepper: context.pepper,
includeSession: false,
});
const successAuditBefore = await countAudit(
prisma,
context.employeeId,
"AUTH_PASSWORD_RESET_SUCCEEDED",
);
const loginAuditBefore = await countAudit(prisma, context.employeeId, "AUTH_LOGIN_SUCCESS");
const nextPassword = createStrongPassword("LoginFirstReset");
const fingerprint = "auth-login-first-reset";
const holder = new Client({ connectionString: context.databaseUrl });
const observer = new Client({ connectionString: context.databaseUrl });
const loginAbort = new AbortController();
const resetAbort = new AbortController();
let holderInTransaction = false;
let loginPromise = null;
let resetPromise = null;
let loginResponse = null;
let resetResponse = null;
try {
await holder.connect();
await observer.connect();
const holderPid = holder.processID;
const observerPid = observer.processID;
await holder.query("BEGIN");
holderInTransaction = true;
await holder.query('SELECT id FROM "employees" WHERE id = $1::uuid FOR UPDATE', [
context.employeeId,
]);
loginPromise = loginForRace({
baseUrl: context.baseUrl,
email: context.employeeEmail,
password: currentPassword,
fingerprint,
signal: loginAbort.signal,
});
await waitForBlockedConnections(observer, [holderPid, observerPid], 1, 15_000);
resetPromise = postJson(
context.baseUrl,
"/api/auth/reset-password",
{
email: context.employeeEmail,
code: challenge.code,
newPassword: nextPassword,
},
{ signal: resetAbort.signal },
);
await waitForBlockedConnections(observer, [holderPid, observerPid], 2, 20_000);
await holder.query("COMMIT");
holderInTransaction = false;
[loginResponse, resetResponse] = await Promise.all([
withTimeout(loginPromise, 30_000, "login_first_login_timeout"),
withTimeout(resetPromise, 30_000, "login_first_reset_timeout"),
]);
assertCondition(
loginResponse.status === 200 &&
typeof loginResponse.accessToken === "string" &&
typeof loginResponse.refreshCookie === "string",
"login_first_login_failed",
);
assertCondition(resetResponse.status === 200, "login_first_reset_failed");
} finally {
loginAbort.abort();
resetAbort.abort();
if (holderInTransaction) {
await holder.query("ROLLBACK").catch(() => undefined);
}
await Promise.allSettled([loginPromise, resetPromise].filter(Boolean));
await holder.end().catch(() => undefined);
await observer.end().catch(() => undefined);
}
const [accessProbe, refreshProbe] = await Promise.all([
getWithAccessToken(context.baseUrl, "/api/me", loginResponse.accessToken),
postJson(
context.baseUrl,
"/api/auth/refresh",
{},
{
headers: {
cookie: loginResponse.refreshCookie,
origin: context.baseUrl,
"x-device-fingerprint": fingerprint,
},
},
),
]);
assertCondition(
accessProbe.status === 401 && accessProbe.errorCode === "AUTH_004",
"login_first_old_access_token_active",
);
assertCondition(
refreshProbe.status === 401 && refreshProbe.error?.code === "AUTH_005",
"login_first_old_refresh_token_active",
);
const after = await getEmployeeSecurityState(prisma, context.employeeId);
const [activeSessions, successAuditAfter, loginAuditAfter] = await Promise.all([
prisma.authSession.count({
where: {
employee_id: context.employeeId,
revoked_at: null,
expires_at: { gt: new Date() },
},
}),
countAudit(prisma, context.employeeId, "AUTH_PASSWORD_RESET_SUCCEEDED"),
countAudit(prisma, context.employeeId, "AUTH_LOGIN_SUCCESS"),
]);
assertCondition(
after.token_version === before.token_version + 1,
"login_first_token_version_invalid",
);
assertCondition(await verifyPassword(nextPassword, after.password_hash), "login_first_password_not_active");
assertCondition(activeSessions === 0, "login_first_session_not_revoked");
assertCondition(successAuditAfter === successAuditBefore + 1, "login_first_reset_audit_invalid");
assertCondition(loginAuditAfter === loginAuditBefore + 1, "login_first_login_audit_invalid");
return nextPassword;
}
async function runRefreshResetRace({ prisma, context, currentPassword }) {
const before = await getEmployeeSecurityState(prisma, context.employeeId);
assertCondition(
await verifyPassword(currentPassword, before.password_hash),
"refresh_race_current_password_mismatch",
);
const challenge = await preparePasswordResetChallenge({
prisma,
employeeId: context.employeeId,
pepper: context.pepper,
includeSession: true,
});
assertCondition(
typeof challenge.refreshToken === "string" && typeof challenge.refreshSessionId === "string",
"refresh_race_session_missing",
);
const successAuditBefore = await countAudit(
prisma,
context.employeeId,
"AUTH_PASSWORD_RESET_SUCCEEDED",
);
const nextPassword = createStrongPassword("RefreshReset");
const holder = new Client({ connectionString: context.databaseUrl });
const observer = new Client({ connectionString: context.databaseUrl });
const resetAbort = new AbortController();
const refreshAbort = new AbortController();
let holderInTransaction = false;
let resetPromise = null;
let refreshPromise = null;
try {
await holder.connect();
await observer.connect();
const holderPid = holder.processID;
const observerPid = observer.processID;
await holder.query("BEGIN");
holderInTransaction = true;
await holder.query('SELECT id FROM "employees" WHERE id = $1::uuid FOR UPDATE', [
context.employeeId,
]);
resetPromise = postJson(
context.baseUrl,
"/api/auth/reset-password",
{
email: context.employeeEmail,
code: challenge.code,
newPassword: nextPassword,
},
{ signal: resetAbort.signal },
);
await waitForBlockedConnections(observer, [holderPid, observerPid], 1, 15_000);
refreshPromise = postJson(
context.baseUrl,
"/api/auth/refresh",
{},
{
headers: {
cookie: `refresh_token=${challenge.refreshToken}`,
origin: context.baseUrl,
"x-device-fingerprint": "auth-concurrency-check",
},
signal: refreshAbort.signal,
},
);
await waitForBlockedConnections(observer, [holderPid, observerPid], 2, 20_000);
await holder.query("COMMIT");
holderInTransaction = false;
const [resetResponse, refreshResponse] = await Promise.all([
withTimeout(resetPromise, 30_000, "refresh_race_reset_timeout"),
withTimeout(refreshPromise, 30_000, "refresh_race_refresh_timeout"),
]);
assertCondition(resetResponse.status === 200, "refresh_race_reset_failed");
assertCondition(
refreshResponse.status === 401 && refreshResponse.error?.code === "AUTH_005",
"refresh_rotated_after_reset",
);
} finally {
resetAbort.abort();
refreshAbort.abort();
if (holderInTransaction) {
await holder.query("ROLLBACK").catch(() => undefined);
}
await Promise.allSettled([resetPromise, refreshPromise].filter(Boolean));
await holder.end().catch(() => undefined);
await observer.end().catch(() => undefined);
}
const after = await getEmployeeSecurityState(prisma, context.employeeId);
const [session, activeSessions, successAuditAfter] = await Promise.all([
prisma.authSession.findUnique({
where: { id: challenge.refreshSessionId },
select: { revoked_at: true },
}),
prisma.authSession.count({
where: {
employee_id: context.employeeId,
revoked_at: null,
expires_at: { gt: new Date() },
},
}),
countAudit(prisma, context.employeeId, "AUTH_PASSWORD_RESET_SUCCEEDED"),
]);
assertCondition(after.token_version === before.token_version + 1, "refresh_race_token_version_invalid");
assertCondition(await verifyPassword(nextPassword, after.password_hash), "refresh_race_password_not_active");
assertCondition(session?.revoked_at instanceof Date, "refresh_race_original_session_active");
assertCondition(activeSessions === 0, "refresh_race_active_session_remains");
assertCondition(successAuditAfter === successAuditBefore + 1, "refresh_race_audit_invalid");
return nextPassword;
}
async function runAuthPostgresConcurrencyCheck(env = process.env) {
const context = validateAuthConcurrencyEnvironment(env);
const { prisma, pool } = createPrismaClient();
try {
console.log("[AUTH-CONCURRENCY] forgot-password-20: start");
await runConcurrentForgotPasswordRequests({ prisma, context });
console.log("[AUTH-CONCURRENCY] forgot-password-20: pass");
console.log("[AUTH-CONCURRENCY] same-code-20: start");
let currentPassword = await runSameCodeConcurrency({ prisma, context });
console.log("[AUTH-CONCURRENCY] same-code-20: pass");
console.log("[AUTH-CONCURRENCY] reset-first-login-race: start");
currentPassword = await runLoginResetRace({ prisma, context, currentPassword });
console.log("[AUTH-CONCURRENCY] reset-first-login-race: pass");
console.log("[AUTH-CONCURRENCY] login-first-reset-race: start");
currentPassword = await runLoginFirstResetRace({ prisma, context, currentPassword });
console.log("[AUTH-CONCURRENCY] login-first-reset-race: pass");
console.log("[AUTH-CONCURRENCY] refresh-reset-race: start");
await runRefreshResetRace({ prisma, context, currentPassword });
console.log("[AUTH-CONCURRENCY] refresh-reset-race: pass");
} finally {
await prisma.$disconnect().catch(() => undefined);
await pool.end().catch(() => undefined);
}
}
if (require.main === module) {
runAuthPostgresConcurrencyCheck().catch((error) => {
const code =
error instanceof AuthConcurrencyCheckError
? error.safeCode
: "auth_concurrency_check_failed";
console.error(`[AUTH-CONCURRENCY][FAIL] ${code}`);
process.exitCode = 1;
});
}
module.exports = {
AuthConcurrencyCheckError,
runAuthPostgresConcurrencyCheck,
validateAuthConcurrencyEnvironment,
validateAuthRegressionEnvironment,
};
@@ -0,0 +1,50 @@
require("dotenv/config");
const {
validateAuthRegressionEnvironment,
} = require("./check-auth-postgres-concurrency");
try {
validateAuthRegressionEnvironment(process.env);
} catch {
console.error("[AUTH-TEST-SERVER][FAIL] environment_guard_rejected");
process.exit(1);
}
const { buildApp } = require("../app");
const disabledExternalEmailService = Object.freeze({
resolveEmailDeliveryStatus() {
return { ready: true, reasons: [] };
},
async sendNotificationEmailSafe() {
return { email: { skipped: true, sent: 0, failed: 0 } };
},
});
async function main() {
const app = await buildApp({ authEmailService: disabledExternalEmailService });
const { host, port } = app.config;
await app.listen({ host, port });
let shuttingDown = false;
const shutdown = async () => {
if (shuttingDown) return;
shuttingDown = true;
try {
await app.close();
process.exit(0);
} catch {
console.error("[AUTH-TEST-SERVER][FAIL] shutdown_failed");
process.exit(1);
}
};
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
}
main().catch(() => {
console.error("[AUTH-TEST-SERVER][FAIL] startup_failed");
process.exit(1);
});