fix: harden authentication security
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
Some checks failed
CI / build-and-test (pull_request) Has been cancelled
This commit is contained in:
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user