| import { Request, Response, NextFunction } from 'express'; |
| import jwt from 'jsonwebtoken'; |
| import { JWT_CONFIG } from '../config/env.config'; |
| import { getRedisClient } from '../util/redis.util'; |
|
|
| declare global { |
| namespace Express { |
| interface Request { |
| userId?: string; |
| user?: any; |
| } |
| } |
| } |
|
|
| const auth = async (req: Request, res: Response, next: NextFunction) => { |
| const authHeader = req.headers.authorization; |
|
|
| if (!authHeader || !authHeader.startsWith('Bearer ')) { |
| return res.status(401).json({ message: 'No token provided.' }); |
| } |
|
|
| const token = authHeader.split(' ')[1]; |
|
|
| try { |
| const decoded = jwt.verify(token, JWT_CONFIG.secret); |
|
|
| if (typeof decoded === 'string' || !('userId' in decoded)) { |
| return res.status(401).json({ message: 'Invalid token structure.' }); |
| } |
|
|
| |
| const redis = await getRedisClient(); |
| const sessionKey = `session:${decoded.userId}:${token.substring(0, 10)}`; |
| const sessionExists = await redis.exists(sessionKey); |
|
|
| if (!sessionExists) { |
| return res.status(401).json({ message: 'Session expired or invalid.' }); |
| } |
|
|
| |
| const User = (await import('../model/user.model')).default; |
| const user = await User.findById(decoded.userId); |
|
|
| if (!user) { |
| return res.status(401).json({ message: 'User not found.' }); |
| } |
|
|
| req.userId = decoded.userId; |
| req.user = user; |
|
|
| |
| |
| const is2FASetupRoute = req.originalUrl.includes('/auth/2fa/'); |
|
|
| if (!user.isTwoFactorEnabled && !is2FASetupRoute) { |
| return res.status(403).json({ |
| message: '2FA setup required.', |
| code: '2fa_setup_required' |
| }); |
| } |
|
|
| |
| |
| const isDocumentUploadRoute = req.originalUrl.includes('/users/upload-documents'); |
| const isVerificationRoute = req.originalUrl.includes('/users/me') || |
| req.originalUrl.includes('/users/reapply-verification'); |
|
|
| const requiresDocumentUpload = (user.role === 'trainer' || user.role === 'organization') && !user.documentsUploaded; |
|
|
| if (requiresDocumentUpload && !isDocumentUploadRoute && !isVerificationRoute && !is2FASetupRoute) { |
| return res.status(403).json({ |
| message: 'Document upload required.', |
| code: 'document_upload_required' |
| }); |
| } |
|
|
| |
| await redis.expire(sessionKey, 7 * 24 * 60 * 60); |
|
|
| next(); |
| } catch (err) { |
| return res.status(401).json({ message: 'Invalid token.' }); |
| } |
| }; |
|
|
| export default auth; |