security hardening

This commit is contained in:
Michael Freno
2025-12-28 20:04:29 -05:00
parent aefd467660
commit 1ba20339a8
22 changed files with 5177 additions and 116 deletions

View File

@@ -3,12 +3,111 @@ import { jwtVerify } from "jose";
import { OAuth2Client } from "google-auth-library";
import type { Row } from "@libsql/client/web";
import { env } from "~/env/server";
import { ConnectionFactory } from "./database";
/**
* Extract cookie value from H3Event (works in both production and tests)
* Falls back to manual header parsing if vinxi's getCookie fails
*/
function getCookieValue(event: H3Event, name: string): string | undefined {
try {
// Try vinxi's getCookie first
return getCookie(event, name);
} catch (e) {
// Fallback for tests: parse cookie header manually
try {
const cookieHeader =
event.headers?.get("cookie") || event.node?.req?.headers?.cookie || "";
const cookies = cookieHeader
.split(";")
.map((c) => c.trim())
.reduce(
(acc, cookie) => {
const [key, value] = cookie.split("=");
if (key && value) acc[key] = value;
return acc;
},
{} as Record<string, string>
);
return cookies[name];
} catch {
return undefined;
}
}
}
/**
* Clear cookie (works in both production and tests)
*/
function clearCookie(event: H3Event, name: string): void {
try {
setCookie(event, name, "", {
maxAge: 0,
expires: new Date("2016-10-05")
});
} catch (e) {
// In tests, setCookie might fail silently
}
}
/**
* Validate session and update last_used timestamp
* @param sessionId - Session ID from JWT
* @param userId - User ID from JWT
* @returns true if session is valid, false otherwise
*/
async function validateSession(
sessionId: string,
userId: string
): Promise<boolean> {
try {
const conn = ConnectionFactory();
const result = await conn.execute({
sql: `SELECT revoked, expires_at FROM Session
WHERE id = ? AND user_id = ?`,
args: [sessionId, userId]
});
if (result.rows.length === 0) {
// Session doesn't exist
return false;
}
const session = result.rows[0];
// Check if session is revoked
if (session.revoked === 1) {
return false;
}
// Check if session is expired
const expiresAt = new Date(session.expires_at as string);
if (expiresAt < new Date()) {
return false;
}
// Update last_used timestamp (fire and forget, don't block)
conn
.execute({
sql: "UPDATE Session SET last_used = datetime('now') WHERE id = ?",
args: [sessionId]
})
.catch((err) =>
console.error("Failed to update session last_used:", err)
);
return true;
} catch (e) {
console.error("Session validation error:", e);
return false;
}
}
export async function getPrivilegeLevel(
event: H3Event
): Promise<"anonymous" | "admin" | "user"> {
try {
const userIDToken = getCookie(event, "userIDToken");
const userIDToken = getCookieValue(event, "userIDToken");
if (userIDToken) {
try {
@@ -16,14 +115,23 @@ export async function getPrivilegeLevel(
const { payload } = await jwtVerify(userIDToken, secret);
if (payload.id && typeof payload.id === "string") {
// Validate session if session ID is present
if (payload.sid) {
const isValidSession = await validateSession(
payload.sid as string,
payload.id
);
if (!isValidSession) {
clearCookie(event, "userIDToken");
return "anonymous";
}
}
return payload.id === env.ADMIN_ID ? "admin" : "user";
}
} catch (err) {
// Silently clear invalid token (401s are expected for non-authenticated users)
setCookie(event, "userIDToken", "", {
maxAge: 0,
expires: new Date("2016-10-05")
});
clearCookie(event, "userIDToken");
}
}
} catch (e) {
@@ -34,7 +142,7 @@ export async function getPrivilegeLevel(
export async function getUserID(event: H3Event): Promise<string | null> {
try {
const userIDToken = getCookie(event, "userIDToken");
const userIDToken = getCookieValue(event, "userIDToken");
if (userIDToken) {
try {
@@ -42,14 +150,23 @@ export async function getUserID(event: H3Event): Promise<string | null> {
const { payload } = await jwtVerify(userIDToken, secret);
if (payload.id && typeof payload.id === "string") {
// Validate session if session ID is present
if (payload.sid) {
const isValidSession = await validateSession(
payload.sid as string,
payload.id
);
if (!isValidSession) {
clearCookie(event, "userIDToken");
return null;
}
}
return payload.id;
}
} catch (err) {
// Silently clear invalid token (401s are expected for non-authenticated users)
setCookie(event, "userIDToken", "", {
maxAge: 0,
expires: new Date("2016-10-05")
});
clearCookie(event, "userIDToken");
}
}
} catch (e) {