Prashikshak / API /src /middleware /auth.middleware.ts
Abhisingh-18's picture
Initial commit: Prashikshak - disaster management training platform
9a92a42
Raw
History Blame Contribute Delete
2.79 kB
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.' });
}
// Check if session exists in Redis (for logout/invalidation)
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.' });
}
// Fetch user to check 2FA status
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;
// Enforce 2FA
// Allow 2FA setup routes even if 2FA is not enabled
const is2FASetupRoute = req.originalUrl.includes('/auth/2fa/');
if (!user.isTwoFactorEnabled && !is2FASetupRoute) {
return res.status(403).json({
message: '2FA setup required.',
code: '2fa_setup_required'
});
}
// Enforce document upload for trainers and organizations
// Allow document upload routes and verification status check routes
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'
});
}
// Extend session TTL on each request (sliding expiration)
await redis.expire(sessionKey, 7 * 24 * 60 * 60); // 7 days
next();
} catch (err) {
return res.status(401).json({ message: 'Invalid token.' });
}
};
export default auth;