feat(auth): currentUser, requireAuth, requireRole middleware
This commit is contained in:
56
packages/backend/src/middleware/auth.ts
Normal file
56
packages/backend/src/middleware/auth.ts
Normal 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();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user