Files
pos-system/services/iam-service/src/middlewares/error.middleware.ts
Ho Ngoc Hai 8cc2f66df6 Update IAM Service with various enhancements and fixes
- Added `xmlchars` dependency to `pnpm-lock.yaml` for improved XML character handling.
- Updated IAM Service audit plan to streamline post-deployment monitoring tasks.
- Enhanced Dockerfile to prune development dependencies after build for a leaner production image.
- Introduced a new encryption key configuration in the environment example for better security practices.
- Refactored multiple service files to improve import organization and maintainability.
- Improved error handling in seed scripts to provide more detailed logging on failures.
- Updated various controllers and services to ensure consistent import statements and enhance readability.

These changes aim to improve the overall functionality, security, and maintainability of the IAM Service.
2026-01-02 16:13:36 +07:00

206 lines
6.8 KiB
TypeScript

import { logger } from '@goodgo/logger';
import express from 'express';
import { ErrorCode, getStatusFromErrorCode, isOperationalError } from '../errors/error-codes';
import { HttpError } from '../errors/http-error';
/**
* EN: Global error handler middleware with enhanced error handling
* VI: Middleware xử lý lỗi toàn cục với enhanced error handling
*/
export const errorHandler = (
err: any,
req: express.Request,
res: express.Response,
_next: express.NextFunction
): void => {
let statusCode = 500;
let errorCode = ErrorCode.INTERNAL_ERROR;
let message = 'Internal server error / Lỗi máy chủ nội bộ';
let details: any = undefined;
let isOperational = false;
// EN: Handle HttpError instances (our custom errors)
// VI: Xử lý HttpError instances (custom errors của chúng ta)
if (err instanceof HttpError) {
statusCode = err.statusCode;
errorCode = err.errorCode as ErrorCode;
message = err.message;
details = err.details;
isOperational = err.isOperational;
}
// EN: Handle Prisma errors
// VI: Xử lý Prisma errors
else if (err.code && typeof err.code === 'string') {
if (err.code === 'P2002') {
// Unique constraint violation
statusCode = 409;
errorCode = ErrorCode.CONSTRAINT_VIOLATION;
message = 'Resource already exists / Tài nguyên đã tồn tại';
isOperational = true;
} else if (err.code.startsWith('P1')) {
// Database connection/query errors
statusCode = 500;
errorCode = ErrorCode.DATABASE_ERROR;
message = 'Database operation failed / Thao tác database thất bại';
isOperational = false;
} else if (err.code.startsWith('P2')) {
// Data validation errors
statusCode = 422;
errorCode = ErrorCode.VALIDATION_ERROR;
message = 'Data validation failed / Validation dữ liệu thất bại';
isOperational = true;
}
}
// EN: Handle JWT errors
// VI: Xử lý JWT errors
else if (err.name === 'JsonWebTokenError') {
statusCode = 401;
errorCode = ErrorCode.INVALID_TOKEN;
message = 'Invalid authentication token / Token xác thực không hợp lệ';
isOperational = true;
} else if (err.name === 'TokenExpiredError') {
statusCode = 401;
errorCode = ErrorCode.TOKEN_EXPIRED;
message = 'Authentication token expired / Token xác thực đã hết hạn';
isOperational = true;
}
// EN: Handle Zod validation errors
// VI: Xử lý Zod validation errors
else if (err.name === 'ZodError') {
statusCode = 422;
errorCode = ErrorCode.VALIDATION_ERROR;
message = 'Validation failed / Validation thất bại';
details = err.errors.map((e: any) => ({
field: e.path.join('.'),
message: e.message,
code: e.code,
}));
isOperational = true;
}
// EN: Handle Express/Multer file upload errors
// VI: Xử lý Express/Multer file upload errors
else if (err.name === 'MulterError') {
statusCode = 400;
errorCode = ErrorCode.INVALID_FORMAT;
message = 'File upload error / Lỗi upload file';
isOperational = true;
}
// EN: Handle rate limiting errors
// VI: Xử lý rate limiting errors
else if (err.message && err.message.includes('Too many requests')) {
statusCode = 429;
errorCode = ErrorCode.RATE_LIMIT_EXCEEDED;
message = 'Rate limit exceeded / Vượt quá giới hạn tốc độ';
isOperational = true;
}
// EN: Handle generic errors
// VI: Xử lý generic errors
else {
// EN: Try to map error message patterns
// VI: Thử map error message patterns
const errorMessage = err.message?.toLowerCase() || '';
if (errorMessage.includes('not found')) {
statusCode = 404;
errorCode = ErrorCode.NOT_FOUND;
message = err.message;
isOperational = true;
} else if (errorMessage.includes('unauthorized') || errorMessage.includes('not authenticated')) {
statusCode = 401;
errorCode = ErrorCode.UNAUTHORIZED;
message = err.message;
isOperational = true;
} else if (errorMessage.includes('forbidden') || errorMessage.includes('not allowed')) {
statusCode = 403;
errorCode = ErrorCode.FORBIDDEN;
message = err.message;
isOperational = true;
} else if (errorMessage.includes('validation') || errorMessage.includes('invalid')) {
statusCode = 422;
errorCode = ErrorCode.VALIDATION_ERROR;
message = err.message;
isOperational = true;
}
}
// EN: Prepare error details for logging
// VI: Chuẩn bị chi tiết lỗi để logging
const errorDetails = {
message: err.message,
name: err.name,
code: err.code,
statusCode,
errorCode,
isOperational,
stack: err.stack,
url: req.url,
method: req.method,
userAgent: req.get('User-Agent'),
ip: req.ip,
userId: (req as any).user?.userId,
details,
};
// EN: Log error with appropriate level
// VI: Log lỗi với level phù hợp
if (!isOperational || statusCode >= 500) {
logger.error('Unhandled error occurred / Lỗi không mong muốn xảy ra', errorDetails);
} else {
logger.warn('Operational error occurred / Lỗi operational xảy ra', errorDetails);
}
// EN: Prepare response based on environment
// VI: Chuẩn bị response dựa trên environment
const isProduction = process.env.NODE_ENV === 'production';
const response = {
success: false,
error: {
code: errorCode,
message: isProduction && statusCode >= 500 ? 'Internal server error / Lỗi máy chủ nội bộ' : message,
...(details && !isProduction && { details }),
},
timestamp: new Date().toISOString(),
};
res.status(statusCode).json(response);
};
/**
* EN: 404 Not Found handler with enhanced error details
* VI: Handler 404 Not Found với enhanced error details
*/
export const notFoundHandler = (
req: express.Request,
_res: express.Response,
next: express.NextFunction
): void => {
const error = new HttpError(
`Route ${req.originalUrl} not found / Route ${req.originalUrl} không tìm thấy`,
404,
ErrorCode.NOT_FOUND
);
next(error);
};
/**
* EN: Async error wrapper to catch promise rejections
* VI: Async error wrapper để catch promise rejections
*/
export const asyncHandler = (fn: (req: express.Request, res: express.Response, next: express.NextFunction) => Promise<any> | any) => {
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
/**
* EN: Create HttpError from error code
* VI: Tạo HttpError từ error code
*/
export const createHttpError = (errorCode: ErrorCode, message?: string, details?: any): HttpError => {
const statusCode = getStatusFromErrorCode(errorCode);
const isOperational = isOperationalError(errorCode);
return new HttpError(message || `${errorCode}`, statusCode, errorCode, isOperational, details);
};