109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import { Request, Response, NextFunction } from 'express';
|
|
import jwt from 'jsonwebtoken';
|
|
import { User } from '../models/User';
|
|
import { ssoConfig } from '../config/sso';
|
|
import { ResponseHandler } from '../utils/responseHandler';
|
|
|
|
interface JwtPayload {
|
|
userId: string;
|
|
employeeId: string;
|
|
email: string;
|
|
role: string;
|
|
iat: number;
|
|
exp: number;
|
|
}
|
|
|
|
export const authenticateToken = async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<void> => {
|
|
try {
|
|
// Try to get token from Authorization header first
|
|
const authHeader = req.headers.authorization;
|
|
let token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
|
|
|
// Fallback to cookie if available (requires cookie-parser middleware)
|
|
if (!token && req.cookies?.accessToken) {
|
|
token = req.cookies.accessToken;
|
|
}
|
|
|
|
if (!token) {
|
|
ResponseHandler.unauthorized(res, 'Access token is required');
|
|
return;
|
|
}
|
|
|
|
// Verify JWT token
|
|
const decoded = jwt.verify(token, ssoConfig.jwtSecret) as JwtPayload;
|
|
|
|
// Fetch user from database to ensure they still exist and are active
|
|
const user = await User.findByPk(decoded.userId);
|
|
|
|
if (!user || !user.isActive) {
|
|
ResponseHandler.unauthorized(res, 'User not found or inactive');
|
|
return;
|
|
}
|
|
|
|
// Attach user info to request object
|
|
req.user = {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
employeeId: user.employeeId || null, // Optional - schema not finalized
|
|
role: user.role // Keep uppercase: USER, MANAGEMENT, ADMIN
|
|
};
|
|
|
|
next();
|
|
} catch (error: any) {
|
|
if (error?.name === 'TokenExpiredError') {
|
|
ResponseHandler.unauthorized(res, 'Token has expired');
|
|
} else if (error?.name === 'JsonWebTokenError') {
|
|
ResponseHandler.unauthorized(res, 'Invalid token');
|
|
} else {
|
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
ResponseHandler.error(res, 'Authentication error', 500, errorMessage);
|
|
}
|
|
}
|
|
};
|
|
|
|
export const requireAdmin = (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): void => {
|
|
if (req.user?.role !== 'ADMIN') {
|
|
ResponseHandler.forbidden(res, 'Admin access required');
|
|
return;
|
|
}
|
|
next();
|
|
};
|
|
|
|
export const optionalAuth = async (
|
|
req: Request,
|
|
res: Response,
|
|
next: NextFunction
|
|
): Promise<void> => {
|
|
try {
|
|
const authHeader = req.headers.authorization;
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (token) {
|
|
const decoded = jwt.verify(token, ssoConfig.jwtSecret) as JwtPayload;
|
|
const user = await User.findByPk(decoded.userId);
|
|
|
|
if (user && user.isActive) {
|
|
req.user = {
|
|
userId: user.userId,
|
|
email: user.email,
|
|
employeeId: user.employeeId || null, // Optional - schema not finalized
|
|
role: user.role // Keep uppercase: USER, MANAGEMENT, ADMIN
|
|
};
|
|
}
|
|
}
|
|
|
|
next();
|
|
} catch (error) {
|
|
// For optional auth, we don't throw errors, just continue without user
|
|
next();
|
|
}
|
|
};
|