Files
pos-system/services/iam-service/src/middlewares/auth.middleware.ts

292 lines
9.0 KiB
TypeScript

import { logger } from '@goodgo/logger';
import { ApiResponse } from '@goodgo/types';
import { Request, Response, NextFunction } from 'express';
import { jwtService } from '../modules/token/jwt.service';
import { getErrorMessage } from '../utils/error-utils';
/**
* EN: Extended Request interface with user information
* VI: Interface Request mở rộng với thông tin người dùng
*/
declare global {
namespace Express {
interface Request {
user?: {
id?: string;
userId?: string;
sub?: string;
email: string;
role?: string;
roles?: string[];
permissions?: string[];
iat?: number;
exp?: number;
};
}
}
}
/**
* EN: Authentication middleware - verifies JWT tokens
* VI: Middleware xác thực - xác minh JWT tokens
*
* @param options - Configuration options / Tùy chọn cấu hình
*/
export const authenticate = (_options: {
secret?: string;
ignoreExpiration?: boolean;
} = {}) => {
return async (req: Request, res: Response, next: NextFunction) => {
try {
// EN: Extract token from Authorization header or cookie
// VI: Trích xuất token từ header Authorization hoặc cookie
let token: string | null = null;
// Try Authorization header first
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
token = authHeader.substring(7);
}
// Try cookie if no header token
if (!token && req.cookies) {
token = req.cookies.access_token || null;
}
if (!token) {
logger.warn('No authentication token provided / Không có token xác thực được cung cấp', {
path: req.path,
method: req.method,
});
const response: ApiResponse = {
success: false,
error: {
code: 'AUTH_001',
message: 'Authentication required / Yêu cầu xác thực',
},
timestamp: new Date().toISOString(),
};
res.status(401).json(response);
return;
}
// EN: Verify token using JWT service
// VI: Xác minh token sử dụng JWT service
const payload = await jwtService.verifyAccessToken(token);
// EN: Attach user information to request
// VI: Gắn thông tin người dùng vào request
req.user = {
id: payload.sub,
userId: payload.sub,
sub: payload.sub,
email: payload.email,
role: payload.roles?.[0], // Primary role
roles: payload.roles,
permissions: payload.permissions,
iat: payload.iat,
exp: payload.exp,
};
logger.debug('User authenticated successfully / Người dùng đã được xác thực thành công', {
userId: payload.sub,
email: payload.email,
roles: payload.roles,
});
next();
} catch (error: unknown) {
logger.warn('Authentication failed / Xác thực thất bại', {
error: getErrorMessage(error),
path: req.path,
method: req.method,
});
const response: ApiResponse = {
success: false,
error: {
code: 'AUTH_002',
message: 'Invalid or expired token / Token không hợp lệ hoặc hết hạn',
},
timestamp: new Date().toISOString(),
};
res.status(401).json(response);
}
};
};
/**
* EN: Role-based authorization middleware
* VI: Middleware phân quyền dựa trên vai trò
*
* @param allowedRoles - Array of roles that can access the resource / Mảng các vai trò được phép truy cập tài nguyên
*/
export const authorize = (...allowedRoles: string[]) => {
return (req: Request, res: Response, next: NextFunction) => {
// EN: Check if user is authenticated
// VI: Kiểm tra người dùng đã được xác thực chưa
if (!req.user) {
logger.warn('Authorization attempted without authentication / Phân quyền được thử mà không xác thực', {
path: req.path,
method: req.method,
});
const response: ApiResponse = {
success: false,
error: {
code: 'AUTH_003',
message: 'Authentication required / Yêu cầu xác thực',
},
timestamp: new Date().toISOString(),
};
res.status(401).json(response);
return;
}
// EN: Check if user has required role
// VI: Kiểm tra người dùng có vai trò cần thiết không
const userRoles = req.user.roles || (req.user.role ? [req.user.role] : []);
const hasRole = userRoles.some(role => allowedRoles.includes(role));
if (!hasRole) {
logger.warn('Access denied - insufficient permissions / Truy cập bị từ chối - không đủ quyền', {
userId: req.user.userId || req.user.id,
userRoles,
requiredRoles: allowedRoles,
path: req.path,
method: req.method,
});
const response: ApiResponse = {
success: false,
error: {
code: 'AUTH_004',
message: 'Insufficient permissions / Không đủ quyền',
},
timestamp: new Date().toISOString(),
};
res.status(403).json(response);
return;
}
logger.debug('Authorization successful / Phân quyền thành công', {
userId: req.user.userId || req.user.id,
userRoles,
path: req.path,
method: req.method,
});
next();
};
};
/**
* EN: Combined auth and authorization middleware
* VI: Middleware kết hợp xác thực và phân quyền
*
* @param secret - JWT secret key / Khóa bí mật JWT
* @param allowedRoles - Array of allowed roles / Mảng vai trò được phép
*/
export const requireAuth = (
secret?: string,
...allowedRoles: string[]
) => {
return [authenticate({ secret }), authorize(...allowedRoles)];
};
/**
* EN: Optional authentication middleware - doesn't fail if no token provided
* VI: Middleware xác thực tùy chọn - không thất bại nếu không có token
*
* @param options - Configuration options / Tùy chọn cấu hình
*/
export const optionalAuth = (_options: {
secret?: string;
ignoreExpiration?: boolean;
} = {}) => {
return async (req: Request, _res: Response, next: NextFunction) => {
try {
let token: string | null = null;
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
token = authHeader.substring(7);
}
if (!token && req.cookies) {
token = req.cookies.access_token || null;
}
if (token) {
const payload = await jwtService.verifyAccessToken(token);
req.user = {
id: payload.sub,
userId: payload.sub,
sub: payload.sub,
email: payload.email,
role: payload.roles?.[0],
roles: payload.roles,
permissions: payload.permissions,
iat: payload.iat,
exp: payload.exp,
};
logger.debug('Optional authentication successful / Xác thực tùy chọn thành công', {
userId: payload.sub,
});
}
next();
} catch (error: unknown) {
// EN: For optional auth, just continue without user info
// VI: Với optional auth, chỉ tiếp tục mà không có thông tin user
logger.debug('Optional authentication skipped / Xác thực tùy chọn bị bỏ qua', {
reason: getErrorMessage(error),
});
next();
}
};
};
/**
* EN: Check if user has specific role (utility function)
* VI: Kiểm tra người dùng có vai trò cụ thể (hàm tiện ích)
*
* @param user - User object from request / Đối tượng user từ request
* @param role - Role to check / Vai trò cần kiểm tra
* @returns True if user has the role / True nếu user có vai trò
*/
export const hasRole = (user: Express.Request['user'], role: string): boolean => {
if (!user) return false;
const userRoles = user.roles || (user.role ? [user.role] : []);
return userRoles.includes(role);
};
/**
* EN: Check if user has any of the specified roles (utility function)
* VI: Kiểm tra người dùng có bất kỳ vai trò nào trong danh sách (hàm tiện ích)
*
* @param user - User object from request / Đối tượng user từ request
* @param roles - Array of roles to check / Mảng vai trò cần kiểm tra
* @returns True if user has any of the roles / True nếu user có bất kỳ vai trò nào
*/
export const hasAnyRole = (user: Express.Request['user'], roles: string[]): boolean => {
if (!user) return false;
const userRoles = user.roles || (user.role ? [user.role] : []);
return userRoles.some(r => roles.includes(r));
};
/**
* EN: Check if user is authenticated (utility function)
* VI: Kiểm tra người dùng đã được xác thực (hàm tiện ích)
*
* @param user - User object from request / Đối tượng user từ request
* @returns True if user is authenticated / True nếu user đã được xác thực
*/
export const isAuthenticated = (user: Express.Request['user']): boolean => {
return !!user;
};