idk h3 at all
This commit is contained in:
@@ -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,11 +848,12 @@ export const authRouter = createTRPCRouter({
|
|||||||
emailPasswordLogin: publicProcedure
|
emailPasswordLogin: publicProcedure
|
||||||
.input(loginUserSchema)
|
.input(loginUserSchema)
|
||||||
.mutation(async ({ input, ctx }) => {
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
try {
|
||||||
const { email, password, rememberMe } = input;
|
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({
|
||||||
@@ -851,10 +869,16 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
// Check all conditions after password verification
|
// Check all conditions after password verification
|
||||||
if (!user || !passwordHash || !passwordMatch) {
|
if (!user || !passwordHash || !passwordMatch) {
|
||||||
|
// 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)
|
// Log failed login attempt (wrap in try-catch to ensure it never blocks auth flow)
|
||||||
try {
|
try {
|
||||||
const { ipAddress, userAgent } = getAuditContext(
|
const { ipAddress, userAgent } = getAuditContext(
|
||||||
ctx.event.nativeEvent
|
getH3Event(ctx)
|
||||||
);
|
);
|
||||||
await logAuditEvent({
|
await logAuditEvent({
|
||||||
eventType: "auth.login.failed",
|
eventType: "auth.login.failed",
|
||||||
@@ -891,7 +915,7 @@ export const authRouter = createTRPCRouter({
|
|||||||
|
|
||||||
// Create session with client info (reuse clientIP from rate limiting)
|
// Create session with client info (reuse clientIP from rate limiting)
|
||||||
const userAgent =
|
const userAgent =
|
||||||
ctx.event.nativeEvent.request.headers.get("user-agent") || "unknown";
|
getUserAgent(getH3Event(ctx));
|
||||||
const sessionId = await createSession(
|
const sessionId = await createSession(
|
||||||
user.id,
|
user.id,
|
||||||
expiresIn,
|
expiresIn,
|
||||||
@@ -904,7 +928,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"
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -912,10 +936,10 @@ export const authRouter = createTRPCRouter({
|
|||||||
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days
|
cookieOptions.maxAge = 60 * 60 * 24 * 14; // 14 days
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie(ctx.event.nativeEvent, "userIDToken", token, cookieOptions);
|
setCookie(getH3Event(ctx), "userIDToken", token, cookieOptions);
|
||||||
|
|
||||||
// Set CSRF token for authenticated session
|
// Set CSRF token for authenticated session
|
||||||
setCSRFToken(ctx.event.nativeEvent);
|
setCSRFToken(getH3Event(ctx));
|
||||||
|
|
||||||
// Log successful login (wrap in try-catch to ensure it never blocks auth flow)
|
// Log successful login (wrap in try-catch to ensure it never blocks auth flow)
|
||||||
try {
|
try {
|
||||||
@@ -932,6 +956,23 @@ export const authRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, message: "success" };
|
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({
|
||||||
|
code: "INTERNAL_SERVER_ERROR",
|
||||||
|
message: "An error occurred during login",
|
||||||
|
cause: error
|
||||||
|
});
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
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",
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user