84 lines
2.2 KiB
TypeScript
84 lines
2.2 KiB
TypeScript
import { NextFunction, Request, Response } from "express";
|
|
import { config } from "./config";
|
|
import { csrfMatches, getSession } from "./authService";
|
|
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
auth?: {
|
|
user: {
|
|
id: string;
|
|
email: string;
|
|
emailVerifiedAt: string | null;
|
|
};
|
|
csrfTokenHash: string;
|
|
sessionToken: string;
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
const MUTATING_METHODS = new Set(["POST", "PUT", "PATCH", "DELETE"]);
|
|
|
|
export function setSessionCookie(res: Response, sessionToken: string): void {
|
|
res.cookie(config.session.cookieName, sessionToken, {
|
|
httpOnly: true,
|
|
secure: config.session.secure,
|
|
sameSite: "lax",
|
|
path: "/",
|
|
maxAge: config.session.ttlDays * 24 * 60 * 60 * 1000,
|
|
});
|
|
}
|
|
|
|
export function clearSessionCookie(res: Response): void {
|
|
res.clearCookie(config.session.cookieName, {
|
|
httpOnly: true,
|
|
secure: config.session.secure,
|
|
sameSite: "lax",
|
|
path: "/",
|
|
});
|
|
}
|
|
|
|
export async function loadAuth(req: Request, _res: Response, next: NextFunction): Promise<void> {
|
|
const token = req.cookies?.[config.session.cookieName];
|
|
if (typeof token !== "string" || token.trim() === "") {
|
|
next();
|
|
return;
|
|
}
|
|
try {
|
|
const session = await getSession(token);
|
|
if (session) {
|
|
req.auth = { ...session, sessionToken: token };
|
|
}
|
|
} catch (error) {
|
|
next(error);
|
|
return;
|
|
}
|
|
next();
|
|
}
|
|
|
|
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
|
|
if (!req.auth) {
|
|
res.status(401).json({ error: "unauthorized", details: ["Authentication required"] });
|
|
return;
|
|
}
|
|
if (!req.auth.user.emailVerifiedAt) {
|
|
res.status(403).json({ error: "email_not_verified", details: ["Email verification required"] });
|
|
return;
|
|
}
|
|
next();
|
|
}
|
|
|
|
export function requireCsrf(req: Request, res: Response, next: NextFunction): void {
|
|
if (!MUTATING_METHODS.has(req.method) || !req.auth) {
|
|
next();
|
|
return;
|
|
}
|
|
const token = req.header("X-CSRF-Token");
|
|
if (!token || !csrfMatches(req.auth.csrfTokenHash, token)) {
|
|
res.status(403).json({ error: "csrf_error", details: ["Invalid CSRF token"] });
|
|
return;
|
|
}
|
|
next();
|
|
}
|