feat(auth): currentUser, requireAuth, requireRole middleware

This commit is contained in:
2026-05-20 22:53:31 +02:00
parent c9d593984d
commit 574e3de0e8

View File

@@ -0,0 +1,56 @@
import type { Request, Response, NextFunction } from 'express';
import { eq } from 'drizzle-orm';
import type { Db } from '../db/client.js';
import { users } from '../db/schema.js';
import { validateAuthSession } from '../services/auth/sessions.js';
import { ApiError } from '../lib/errors.js';
import { SID_COOKIE } from '../lib/cookies.js';
import type { Role, User } from '@flashcard/shared';
declare module 'express-serve-static-core' {
interface Request {
user?: User;
sessionId?: string;
}
}
function rowToUser(r: typeof users.$inferSelect): User {
return {
id: r.id,
email: r.email,
displayName: r.displayName,
role: r.role,
isActive: r.isActive,
emailVerifiedAt: r.emailVerifiedAt ?? null,
pendingEmail: r.pendingEmail ?? null,
createdAt: r.createdAt,
updatedAt: r.updatedAt,
};
}
export function currentUserOrNull(db: Db) {
return async (req: Request, _res: Response, next: NextFunction) => {
const sid = req.cookies?.[SID_COOKIE];
if (!sid) return next();
const session = await validateAuthSession(db, sid);
if (!session) return next();
const row = db.select().from(users).where(eq(users.id, session.userId)).get();
if (!row || !row.isActive) return next();
req.user = rowToUser(row);
req.sessionId = sid;
next();
};
}
export function requireAuth(req: Request, _res: Response, next: NextFunction): void {
if (!req.user) return next(new ApiError(401, 'UNAUTHENTICATED', 'Authentication required'));
next();
}
export function requireRole(role: Role) {
return (req: Request, _res: Response, next: NextFunction): void => {
if (!req.user) return next(new ApiError(401, 'UNAUTHENTICATED', 'Authentication required'));
if (req.user.role !== role) return next(new ApiError(403, 'FORBIDDEN', 'Insufficient role'));
next();
};
}