idk h3 at all

This commit is contained in:
Michael Freno
2025-12-28 21:56:34 -05:00
parent 4892dff38e
commit f5abf77454
2 changed files with 190 additions and 148 deletions

View File

@@ -30,6 +30,7 @@ import {
setCSRFToken, setCSRFToken,
csrfProtection, csrfProtection,
getClientIP, getClientIP,
getUserAgent,
getAuditContext, getAuditContext,
rateLimitLogin, rateLimitLogin,
rateLimitPasswordReset, rateLimitPasswordReset,
@@ -37,6 +38,22 @@ import {
rateLimitEmailVerification rateLimitEmailVerification
} from "~/server/security"; } from "~/server/security";
import { logAuditEvent } from "~/server/audit"; import { logAuditEvent } from "~/server/audit";
import type { H3Event } from "vinxi/http";
import type { Context } from "../utils";
/**
* Safely extract H3Event from Context
* In production: ctx.event is APIEvent, H3Event is at ctx.event.nativeEvent
* In development: ctx.event might be H3Event directly
*/
function getH3Event(ctx: Context): H3Event {
// Check if nativeEvent exists (production)
if (ctx.event && 'nativeEvent' in ctx.event && ctx.event.nativeEvent) {
return ctx.event.nativeEvent as H3Event;
}
// Otherwise, assume ctx.event is H3Event (development)
return ctx.event as unknown as H3Event;
}
/** /**
* Create JWT with session tracking * Create JWT with session tracking
@@ -115,7 +132,7 @@ function setAuthCookies(
const cookieOptions: any = { const cookieOptions: any = {
path: "/", path: "/",
httpOnly: true, httpOnly: true,
secure: true, // Always enforce secure cookies secure: env.NODE_ENV === "production",
sameSite: "lax", sameSite: "lax",
...options ...options
}; };
@@ -292,9 +309,9 @@ export const authRouter = createTRPCRouter({
} }
// Create session with client info // Create session with client info
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
const userAgent = const userAgent =
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown"; getUserAgent(getH3Event(ctx));
const sessionId = await createSession( const sessionId = await createSession(
userId, userId,
"14d", "14d",
@@ -304,16 +321,16 @@ export const authRouter = createTRPCRouter({
const token = await createJWT(userId, sessionId); const token = await createJWT(userId, sessionId);
setCookie(ctx.event.nativeEvent, "userIDToken", token, { setCookie(getH3Event(ctx), "userIDToken", token, {
maxAge: 60 * 60 * 24 * 14, // 14 days maxAge: 60 * 60 * 24 * 14, // 14 days
path: "/", path: "/",
httpOnly: true, httpOnly: true,
secure: true, // Always enforce secure cookies secure: env.NODE_ENV === "production",
sameSite: "lax" sameSite: "lax"
}); });
// Set CSRF token for authenticated session // Set CSRF token for authenticated session
setCSRFToken(ctx.event.nativeEvent); setCSRFToken(getH3Event(ctx));
// Log successful OAuth login // Log successful OAuth login
await logAuditEvent({ await logAuditEvent({
@@ -331,7 +348,7 @@ export const authRouter = createTRPCRouter({
}; };
} catch (error) { } catch (error) {
// Log failed OAuth login // Log failed OAuth login
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.login.failed", eventType: "auth.login.failed",
eventData: { eventData: {
@@ -475,9 +492,9 @@ export const authRouter = createTRPCRouter({
} }
// Create session with client info // Create session with client info
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
const userAgent = const userAgent =
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown"; getUserAgent(getH3Event(ctx));
const sessionId = await createSession( const sessionId = await createSession(
userId, userId,
"14d", "14d",
@@ -487,16 +504,16 @@ export const authRouter = createTRPCRouter({
const token = await createJWT(userId, sessionId); const token = await createJWT(userId, sessionId);
setCookie(ctx.event.nativeEvent, "userIDToken", token, { setCookie(getH3Event(ctx), "userIDToken", token, {
maxAge: 60 * 60 * 24 * 14, // 14 days maxAge: 60 * 60 * 24 * 14, // 14 days
path: "/", path: "/",
httpOnly: true, httpOnly: true,
secure: true, // Always enforce secure cookies secure: env.NODE_ENV === "production",
sameSite: "lax" sameSite: "lax"
}); });
// Set CSRF token for authenticated session // Set CSRF token for authenticated session
setCSRFToken(ctx.event.nativeEvent); setCSRFToken(getH3Event(ctx));
// Log successful OAuth login // Log successful OAuth login
await logAuditEvent({ await logAuditEvent({
@@ -514,7 +531,7 @@ export const authRouter = createTRPCRouter({
}; };
} catch (error) { } catch (error) {
// Log failed OAuth login // Log failed OAuth login
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.login.failed", eventType: "auth.login.failed",
eventData: { eventData: {
@@ -595,9 +612,9 @@ export const authRouter = createTRPCRouter({
const userId = (res.rows[0] as unknown as User).id; const userId = (res.rows[0] as unknown as User).id;
// Create session with client info // Create session with client info
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
const userAgent = const userAgent =
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown"; getUserAgent(getH3Event(ctx));
const expiresIn = rememberMe ? "14d" : "12h"; const expiresIn = rememberMe ? "14d" : "12h";
const sessionId = await createSession( const sessionId = await createSession(
userId, userId,
@@ -611,7 +628,7 @@ export const authRouter = createTRPCRouter({
const cookieOptions: any = { const cookieOptions: any = {
path: "/", path: "/",
httpOnly: true, httpOnly: true,
secure: true, // Always enforce secure cookies secure: env.NODE_ENV === "production",
sameSite: "lax" sameSite: "lax"
}; };
@@ -620,14 +637,14 @@ export const authRouter = createTRPCRouter({
} }
setCookie( setCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"userIDToken", "userIDToken",
userToken, userToken,
cookieOptions cookieOptions
); );
// Set CSRF token for authenticated session // Set CSRF token for authenticated session
setCSRFToken(ctx.event.nativeEvent); setCSRFToken(getH3Event(ctx));
// Log successful email link login // Log successful email link login
await logAuditEvent({ await logAuditEvent({
@@ -645,7 +662,7 @@ export const authRouter = createTRPCRouter({
}; };
} catch (error) { } catch (error) {
// Log failed email link login // Log failed email link login
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.login.failed", eventType: "auth.login.failed",
eventData: { eventData: {
@@ -704,10 +721,10 @@ export const authRouter = createTRPCRouter({
await conn.execute({ sql: query, args: params }); await conn.execute({ sql: query, args: params });
// Log successful email verification // Log successful email verification
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
userId, userId,
eventType: "auth.email_verified", eventType: "auth.email.verify.complete",
eventData: { email }, eventData: { email },
ipAddress, ipAddress,
userAgent, userAgent,
@@ -720,9 +737,9 @@ export const authRouter = createTRPCRouter({
}; };
} catch (error) { } catch (error) {
// Log failed email verification // Log failed email verification
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.email_verified", eventType: "auth.email.verify.complete",
eventData: { eventData: {
email, email,
reason: error instanceof TRPCError ? error.message : "unknown" reason: error instanceof TRPCError ? error.message : "unknown"
@@ -749,8 +766,8 @@ export const authRouter = createTRPCRouter({
const { email, password, passwordConfirmation } = input; const { email, password, passwordConfirmation } = input;
// Apply rate limiting // Apply rate limiting
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
rateLimitRegistration(clientIP, ctx.event.nativeEvent); rateLimitRegistration(clientIP, getH3Event(ctx));
// Schema already validates password match, but double check // Schema already validates password match, but double check
if (password !== passwordConfirmation) { if (password !== passwordConfirmation) {
@@ -771,9 +788,9 @@ export const authRouter = createTRPCRouter({
}); });
// Create session with client info // Create session with client info
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
const userAgent = const userAgent =
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown"; getUserAgent(getH3Event(ctx));
const sessionId = await createSession( const sessionId = await createSession(
userId, userId,
"14d", "14d",
@@ -783,21 +800,21 @@ export const authRouter = createTRPCRouter({
const token = await createJWT(userId, sessionId); const token = await createJWT(userId, sessionId);
setCookie(ctx.event.nativeEvent, "userIDToken", token, { setCookie(getH3Event(ctx), "userIDToken", token, {
maxAge: 60 * 60 * 24 * 14, // 14 days maxAge: 60 * 60 * 24 * 14, // 14 days
path: "/", path: "/",
httpOnly: true, httpOnly: true,
secure: true, // Always enforce secure cookies secure: env.NODE_ENV === "production",
sameSite: "lax" sameSite: "lax"
}); });
// Set CSRF token for authenticated session // Set CSRF token for authenticated session
setCSRFToken(ctx.event.nativeEvent); setCSRFToken(getH3Event(ctx));
// Log successful registration // Log successful registration
await logAuditEvent({ await logAuditEvent({
userId, userId,
eventType: "auth.registration.success", eventType: "auth.register.success",
eventData: { email, method: "email" }, eventData: { email, method: "email" },
ipAddress: clientIP, ipAddress: clientIP,
userAgent, userAgent,
@@ -807,9 +824,9 @@ export const authRouter = createTRPCRouter({
return { success: true, message: "success" }; return { success: true, message: "success" };
} catch (e) { } catch (e) {
// Log failed registration // Log failed registration
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.registration.failed", eventType: "auth.register.failed",
eventData: { eventData: {
email, email,
method: "email", method: "email",
@@ -831,107 +848,131 @@ export const authRouter = createTRPCRouter({
emailPasswordLogin: publicProcedure emailPasswordLogin: publicProcedure
.input(loginUserSchema) .input(loginUserSchema)
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { email, password, rememberMe } = input; try {
const { email, password, rememberMe } = input;
// Apply rate limiting // Apply rate limiting
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
rateLimitLogin(email, clientIP, ctx.event.nativeEvent); rateLimitLogin(email, clientIP, getH3Event(ctx));
const conn = ConnectionFactory(); const conn = ConnectionFactory();
const res = await conn.execute({ const res = await conn.execute({
sql: "SELECT * FROM User WHERE email = ?", sql: "SELECT * FROM User WHERE email = ?",
args: [email] args: [email]
}); });
// Always run password check to prevent timing attacks // Always run password check to prevent timing attacks
const user = const user =
res.rows.length > 0 ? (res.rows[0] as unknown as User) : null; res.rows.length > 0 ? (res.rows[0] as unknown as User) : null;
const passwordHash = user?.password_hash || null; const passwordHash = user?.password_hash || null;
const passwordMatch = await checkPasswordSafe(password, passwordHash); const passwordMatch = await checkPasswordSafe(password, passwordHash);
// Check all conditions after password verification // Check all conditions after password verification
if (!user || !passwordHash || !passwordMatch) { if (!user || !passwordHash || !passwordMatch) {
// Log failed login attempt (wrap in try-catch to ensure it never blocks auth flow) // Debug logging (remove after fixing)
console.log("Login failed for:", email);
console.log("User found:", !!user);
console.log("Password hash exists:", !!passwordHash);
console.log("Password match:", passwordMatch);
// Log failed login attempt (wrap in try-catch to ensure it never blocks auth flow)
try {
const { ipAddress, userAgent } = getAuditContext(
getH3Event(ctx)
);
await logAuditEvent({
eventType: "auth.login.failed",
eventData: {
email,
method: "password",
reason: "invalid_credentials"
},
ipAddress,
userAgent,
success: false
});
} catch (auditError) {
console.error("Audit logging failed:", auditError);
}
throw new TRPCError({
code: "UNAUTHORIZED",
message: "no-match"
});
}
if (
!user.provider ||
!["email", "google", "github", "apple"].includes(user.provider)
) {
await conn.execute({
sql: "UPDATE User SET provider = ? WHERE id = ?",
args: ["email", user.id]
});
}
const expiresIn = rememberMe ? "14d" : "12h";
// Create session with client info (reuse clientIP from rate limiting)
const userAgent =
getUserAgent(getH3Event(ctx));
const sessionId = await createSession(
user.id,
expiresIn,
clientIP,
userAgent
);
const token = await createJWT(user.id, sessionId, expiresIn);
const cookieOptions: any = {
path: "/",
httpOnly: true,
secure: env.NODE_ENV === "production",
sameSite: "lax"
};
if (rememberMe) {
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days
}
setCookie(getH3Event(ctx), "userIDToken", token, cookieOptions);
// Set CSRF token for authenticated session
setCSRFToken(getH3Event(ctx));
// Log successful login (wrap in try-catch to ensure it never blocks auth flow)
try { try {
const { ipAddress, userAgent } = getAuditContext(
ctx.event.nativeEvent
);
await logAuditEvent({ await logAuditEvent({
eventType: "auth.login.failed", userId: user.id,
eventData: { eventType: "auth.login.success",
email, eventData: { method: "password", rememberMe: rememberMe || false },
method: "password", ipAddress: clientIP,
reason: "invalid_credentials"
},
ipAddress,
userAgent, userAgent,
success: false success: true
}); });
} catch (auditError) { } catch (auditError) {
console.error("Audit logging failed:", auditError); console.error("Audit logging failed:", auditError);
} }
return { success: true, message: "success" };
} catch (error) {
// Log the actual error for debugging
console.error("emailPasswordLogin error:", error);
console.error("Error stack:", error instanceof Error ? error.stack : "no stack");
// Re-throw TRPCErrors as-is
if (error instanceof TRPCError) {
throw error;
}
// Wrap other errors
throw new TRPCError({ throw new TRPCError({
code: "UNAUTHORIZED", code: "INTERNAL_SERVER_ERROR",
message: "no-match" message: "An error occurred during login",
cause: error
}); });
} }
if (
!user.provider ||
!["email", "google", "github", "apple"].includes(user.provider)
) {
await conn.execute({
sql: "UPDATE User SET provider = ? WHERE id = ?",
args: ["email", user.id]
});
}
const expiresIn = rememberMe ? "14d" : "12h";
// Create session with client info (reuse clientIP from rate limiting)
const userAgent =
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown";
const sessionId = await createSession(
user.id,
expiresIn,
clientIP,
userAgent
);
const token = await createJWT(user.id, sessionId, expiresIn);
const cookieOptions: any = {
path: "/",
httpOnly: true,
secure: true, // Always enforce secure cookies
sameSite: "lax"
};
if (rememberMe) {
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days
}
setCookie(ctx.event.nativeEvent, "userIDToken", token, cookieOptions);
// Set CSRF token for authenticated session
setCSRFToken(ctx.event.nativeEvent);
// Log successful login (wrap in try-catch to ensure it never blocks auth flow)
try {
await logAuditEvent({
userId: user.id,
eventType: "auth.login.success",
eventData: { method: "password", rememberMe: rememberMe || false },
ipAddress: clientIP,
userAgent,
success: true
});
} catch (auditError) {
console.error("Audit logging failed:", auditError);
}
return { success: true, message: "success" };
}), }),
requestEmailLinkLogin: publicProcedure requestEmailLinkLogin: publicProcedure
@@ -946,7 +987,7 @@ export const authRouter = createTRPCRouter({
try { try {
const requested = getCookie( const requested = getCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"emailLoginLinkRequested" "emailLoginLinkRequested"
); );
if (requested) { if (requested) {
@@ -1025,7 +1066,7 @@ export const authRouter = createTRPCRouter({
const exp = new Date(Date.now() + 2 * 60 * 1000); const exp = new Date(Date.now() + 2 * 60 * 1000);
setCookie( setCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"emailLoginLinkRequested", "emailLoginLinkRequested",
exp.toUTCString(), exp.toUTCString(),
{ {
@@ -1066,12 +1107,12 @@ export const authRouter = createTRPCRouter({
const { email } = input; const { email } = input;
// Apply rate limiting // Apply rate limiting
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
rateLimitPasswordReset(clientIP, ctx.event.nativeEvent); rateLimitPasswordReset(clientIP, getH3Event(ctx));
try { try {
const requested = getCookie( const requested = getCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"passwordResetRequested" "passwordResetRequested"
); );
if (requested) { if (requested) {
@@ -1145,7 +1186,7 @@ export const authRouter = createTRPCRouter({
const exp = new Date(Date.now() + 5 * 60 * 1000); const exp = new Date(Date.now() + 5 * 60 * 1000);
setCookie( setCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"passwordResetRequested", "passwordResetRequested",
exp.toUTCString(), exp.toUTCString(),
{ {
@@ -1155,10 +1196,10 @@ export const authRouter = createTRPCRouter({
); );
// Log password reset request // Log password reset request
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
userId: user.id, userId: user.id,
eventType: "auth.password_reset.requested", eventType: "auth.password.reset.request",
eventData: { email }, eventData: { email },
ipAddress, ipAddress,
userAgent, userAgent,
@@ -1172,10 +1213,10 @@ export const authRouter = createTRPCRouter({
!(error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS") !(error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS")
) { ) {
const { ipAddress, userAgent } = getAuditContext( const { ipAddress, userAgent } = getAuditContext(
ctx.event.nativeEvent getH3Event(ctx)
); );
await logAuditEvent({ await logAuditEvent({
eventType: "auth.password_reset.requested", eventType: "auth.password.reset.request",
eventData: { eventData: {
email: input.email, email: input.email,
reason: error instanceof TRPCError ? error.message : "unknown" reason: error instanceof TRPCError ? error.message : "unknown"
@@ -1266,20 +1307,20 @@ export const authRouter = createTRPCRouter({
}); });
} }
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(getH3Event(ctx), "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
}); });
setCookie(ctx.event.nativeEvent, "userIDToken", "", { setCookie(getH3Event(ctx), "userIDToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
}); });
// Log successful password reset // Log successful password reset
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
userId: payload.id, userId: payload.id,
eventType: "auth.password_reset.completed", eventType: "auth.password.reset.complete",
eventData: {}, eventData: {},
ipAddress, ipAddress,
userAgent, userAgent,
@@ -1289,9 +1330,9 @@ export const authRouter = createTRPCRouter({
return { success: true, message: "success" }; return { success: true, message: "success" };
} catch (error) { } catch (error) {
// Log failed password reset // Log failed password reset
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
eventType: "auth.password_reset.completed", eventType: "auth.password.reset.complete",
eventData: { eventData: {
reason: error instanceof TRPCError ? error.message : "unknown" reason: error instanceof TRPCError ? error.message : "unknown"
}, },
@@ -1317,12 +1358,12 @@ export const authRouter = createTRPCRouter({
const { email } = input; const { email } = input;
// Apply rate limiting // Apply rate limiting
const clientIP = getClientIP(ctx.event.nativeEvent); const clientIP = getClientIP(getH3Event(ctx));
rateLimitEmailVerification(clientIP, ctx.event.nativeEvent); rateLimitEmailVerification(clientIP, getH3Event(ctx));
try { try {
const requested = getCookie( const requested = getCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"emailVerificationRequested" "emailVerificationRequested"
); );
if (requested) { if (requested) {
@@ -1399,7 +1440,7 @@ export const authRouter = createTRPCRouter({
await sendEmail(email, "freno.me email verification", htmlContent); await sendEmail(email, "freno.me email verification", htmlContent);
setCookie( setCookie(
ctx.event.nativeEvent, getH3Event(ctx),
"emailVerificationRequested", "emailVerificationRequested",
Date.now().toString(), Date.now().toString(),
{ {
@@ -1409,10 +1450,10 @@ export const authRouter = createTRPCRouter({
); );
// Log email verification request // Log email verification request
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
userId: user.id, userId: user.id,
eventType: "auth.email_verification.requested", eventType: "auth.email.verify.request",
eventData: { email }, eventData: { email },
ipAddress, ipAddress,
userAgent, userAgent,
@@ -1426,10 +1467,10 @@ export const authRouter = createTRPCRouter({
!(error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS") !(error instanceof TRPCError && error.code === "TOO_MANY_REQUESTS")
) { ) {
const { ipAddress, userAgent } = getAuditContext( const { ipAddress, userAgent } = getAuditContext(
ctx.event.nativeEvent getH3Event(ctx)
); );
await logAuditEvent({ await logAuditEvent({
eventType: "auth.email_verification.requested", eventType: "auth.email.verify.request",
eventData: { eventData: {
email: input.email, email: input.email,
reason: error instanceof TRPCError ? error.message : "unknown" reason: error instanceof TRPCError ? error.message : "unknown"
@@ -1468,7 +1509,7 @@ export const authRouter = createTRPCRouter({
// Try to get user ID for audit log before clearing cookies // Try to get user ID for audit log before clearing cookies
let userId: string | null = null; let userId: string | null = null;
try { try {
const token = getCookie(ctx.event.nativeEvent, "userIDToken"); const token = getCookie(getH3Event(ctx), "userIDToken");
if (token) { if (token) {
const secret = new TextEncoder().encode(env.JWT_SECRET_KEY); const secret = new TextEncoder().encode(env.JWT_SECRET_KEY);
const { payload } = await jwtVerify(token, secret); const { payload } = await jwtVerify(token, secret);
@@ -1478,17 +1519,17 @@ export const authRouter = createTRPCRouter({
// Ignore token verification errors during signout // Ignore token verification errors during signout
} }
setCookie(ctx.event.nativeEvent, "userIDToken", "", { setCookie(getH3Event(ctx), "userIDToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
}); });
setCookie(ctx.event.nativeEvent, "emailToken", "", { setCookie(getH3Event(ctx), "emailToken", "", {
maxAge: 0, maxAge: 0,
path: "/" path: "/"
}); });
// Log signout // Log signout
const { ipAddress, userAgent } = getAuditContext(ctx.event.nativeEvent); const { ipAddress, userAgent } = getAuditContext(getH3Event(ctx));
await logAuditEvent({ await logAuditEvent({
userId, userId,
eventType: "auth.logout", eventType: "auth.logout",

View File

@@ -3,6 +3,7 @@ import { getCookie, setCookie } from "vinxi/http";
import type { H3Event } from "vinxi/http"; import type { H3Event } from "vinxi/http";
import { t } from "~/server/api/utils"; import { t } from "~/server/api/utils";
import { logAuditEvent } from "~/server/audit"; import { logAuditEvent } from "~/server/audit";
import { env } from "~/env/server";
/** /**
* Extract cookie value from H3Event (works in both production and tests) * Extract cookie value from H3Event (works in both production and tests)
@@ -108,7 +109,7 @@ export function setCSRFToken(event: H3Event): string {
maxAge: 60 * 60 * 24 * 14, // 14 days - same as session maxAge: 60 * 60 * 24 * 14, // 14 days - same as session
path: "/", path: "/",
httpOnly: false, // Must be readable by client JS httpOnly: false, // Must be readable by client JS
secure: true, // Always enforce secure secure: env.NODE_ENV === "production",
sameSite: "lax" sameSite: "lax"
}); });
return token; return token;