feat: add registration and authentication

This commit is contained in:
Vaka.pro
2026-05-21 00:01:35 +03:00
parent 13dd8fa426
commit 35c3554742
37 changed files with 2162 additions and 81 deletions

View File

@@ -0,0 +1,83 @@
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();
}