fix: harden authentication security
Some checks failed
CI / build-and-test (pull_request) Has been cancelled

This commit is contained in:
Vaka.pro
2026-05-24 14:27:22 +03:00
parent 35c3554742
commit fb246e2e55
9 changed files with 371 additions and 51 deletions

View File

@@ -101,6 +101,24 @@ function createMockPool(): Pool {
return result([{ count: String(users.size) } as unknown as T]);
}
if (sql.includes("SELECT COUNT(*)::text AS count FROM sessions")) {
const tokenHash = p[0] != null ? String(p[0]) : null;
const count = Array.from(sessions.values()).filter((row) => !tokenHash || row.token_hash === tokenHash).length;
return result([{ count: String(count) } as unknown as T]);
}
if (sql.includes("SELECT COUNT(*)::text AS count FROM email_verification_tokens")) {
const tokenHash = p[0] != null ? String(p[0]) : null;
const count = Array.from(verificationTokens.values()).filter((row) => !tokenHash || row.token_hash === tokenHash).length;
return result([{ count: String(count) } as unknown as T]);
}
if (sql.includes("SELECT COUNT(*)::text AS count FROM password_reset_tokens")) {
const tokenHash = p[0] != null ? String(p[0]) : null;
const count = Array.from(resetTokens.values()).filter((row) => !tokenHash || row.token_hash === tokenHash).length;
return result([{ count: String(count) } as unknown as T]);
}
if (sql.includes("SELECT id FROM users WHERE LOWER(BTRIM(email))")) {
const email = String(p[0] ?? "");
const user = Array.from(users.values()).find((item) => item.email.trim().toLowerCase() === email);
@@ -114,6 +132,13 @@ function createMockPool(): Pool {
}
if (sql.includes("INSERT INTO users")) {
const email = String(p[0] ?? "").trim().toLowerCase();
const existing = Array.from(users.values()).find((item) => item.email.trim().toLowerCase() === email);
if (existing) {
const err = new Error("duplicate key") as Error & { code?: string };
err.code = "23505";
throw err;
}
const id = crypto.randomUUID();
const row = {
id,
@@ -141,16 +166,37 @@ function createMockPool(): Pool {
return result([row as unknown as T], "INSERT");
}
if (sql.includes("UPDATE email_verification_tokens SET used_at = NOW() WHERE id")) {
const id = String(p[0] ?? "");
const row = verificationTokens.get(id);
if (!row || row.used_at != null) {
return result<T>([], "UPDATE");
}
row.used_at = new Date();
return result([{ id: row.id } as unknown as T], "UPDATE");
}
if (sql.includes("UPDATE email_verification_tokens SET used_at = NOW() WHERE user_id")) {
const userId = String(p[0] ?? "");
const exceptId = p[1] != null ? String(p[1]) : null;
for (const row of verificationTokens.values()) {
if (row.user_id === userId && row.used_at == null) {
if (row.user_id === userId && row.id !== exceptId && row.used_at == null) {
row.used_at = new Date();
}
}
return result<T>([], "UPDATE");
}
if (sql.includes("FROM email_verification_tokens") && sql.includes("token_hash =")) {
const tokenHash = String(p[0] ?? "");
const now = Date.now();
return result(
Array.from(verificationTokens.values()).filter(
(row) => row.token_hash === tokenHash && !row.used_at && new Date(row.expires_at).getTime() > now,
) as T[],
);
}
if (sql.includes("FROM email_verification_tokens") && sql.includes("WHERE used_at IS NULL")) {
const now = Date.now();
return result(
@@ -172,16 +218,37 @@ function createMockPool(): Pool {
return result([row as unknown as T], "INSERT");
}
if (sql.includes("UPDATE password_reset_tokens SET used_at = NOW() WHERE id")) {
const id = String(p[0] ?? "");
const row = resetTokens.get(id);
if (!row || row.used_at != null) {
return result<T>([], "UPDATE");
}
row.used_at = new Date();
return result([{ id: row.id } as unknown as T], "UPDATE");
}
if (sql.includes("UPDATE password_reset_tokens SET used_at = NOW() WHERE user_id")) {
const userId = String(p[0] ?? "");
const exceptId = p[1] != null ? String(p[1]) : null;
for (const row of resetTokens.values()) {
if (row.user_id === userId && row.used_at == null) {
if (row.user_id === userId && row.id !== exceptId && row.used_at == null) {
row.used_at = new Date();
}
}
return result<T>([], "UPDATE");
}
if (sql.includes("FROM password_reset_tokens") && sql.includes("token_hash =")) {
const tokenHash = String(p[0] ?? "");
const now = Date.now();
return result(
Array.from(resetTokens.values()).filter(
(row) => row.token_hash === tokenHash && !row.used_at && new Date(row.expires_at).getTime() > now,
) as T[],
);
}
if (sql.includes("FROM password_reset_tokens") && sql.includes("WHERE used_at IS NULL")) {
const now = Date.now();
return result(
@@ -273,6 +340,44 @@ function createMockPool(): Pool {
return result<T>([], "UPDATE");
}
if (sql.includes("DELETE FROM sessions WHERE expires_at <= NOW()")) {
const now = Date.now();
let deleted = 0;
for (const [id, row] of sessions.entries()) {
const revokedAt = row.revoked_at ? new Date(row.revoked_at).getTime() : null;
const staleRevoked = revokedAt != null && revokedAt < now - 30 * 24 * 60 * 60 * 1000;
if (new Date(row.expires_at).getTime() <= now || staleRevoked) {
sessions.delete(id);
deleted += 1;
}
}
return result<T>(Array.from({ length: deleted }, () => ({} as unknown as T)), "DELETE");
}
if (sql.includes("DELETE FROM email_verification_tokens WHERE expires_at <= NOW()")) {
const now = Date.now();
let deleted = 0;
for (const [id, row] of verificationTokens.entries()) {
if (new Date(row.expires_at).getTime() <= now || row.used_at != null) {
verificationTokens.delete(id);
deleted += 1;
}
}
return result<T>(Array.from({ length: deleted }, () => ({} as unknown as T)), "DELETE");
}
if (sql.includes("DELETE FROM password_reset_tokens WHERE expires_at <= NOW()")) {
const now = Date.now();
let deleted = 0;
for (const [id, row] of resetTokens.entries()) {
if (new Date(row.expires_at).getTime() <= now || row.used_at != null) {
resetTokens.delete(id);
deleted += 1;
}
}
return result<T>(Array.from({ length: deleted }, () => ({} as unknown as T)), "DELETE");
}
if (sql.includes("SELECT value FROM app_settings")) {
const value = appSettings.get("orphan_races_claimed_by_user_id");
return value ? result([{ value } as unknown as T]) : emptyResult();