first commit
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { User } = require('../models');
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const authenticate = async (req, res, next) => {
|
||||
try {
|
||||
const token = req.header('Authorization')?.replace('Bearer ', '');
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Access denied. No token provided.'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findByPk(decoded.userId);
|
||||
|
||||
if (!user || !user.is_active) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid token or user not found'
|
||||
});
|
||||
}
|
||||
|
||||
req.user = { userId: user.id, role: user.role };
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Invalid token'
|
||||
});
|
||||
}
|
||||
if (error.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Token expired'
|
||||
});
|
||||
}
|
||||
|
||||
logger.error('Authentication error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const authorize = (...roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
error: 'Access denied. Authentication required.'
|
||||
});
|
||||
}
|
||||
|
||||
if (!roles.includes(req.user.role)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
error: 'Access denied. Insufficient permissions.'
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
authenticate,
|
||||
authorize
|
||||
};
|
||||
@@ -0,0 +1,58 @@
|
||||
const logger = require('../utils/logger');
|
||||
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
let error = { ...err };
|
||||
error.message = err.message;
|
||||
|
||||
// Log error
|
||||
logger.error(err);
|
||||
|
||||
// Mongoose bad ObjectId
|
||||
if (err.name === 'CastError') {
|
||||
const message = 'Resource not found';
|
||||
error = { message, statusCode: 404 };
|
||||
}
|
||||
|
||||
// Mongoose duplicate key
|
||||
if (err.code === 11000) {
|
||||
const message = 'Duplicate field value entered';
|
||||
error = { message, statusCode: 400 };
|
||||
}
|
||||
|
||||
// Mongoose validation error
|
||||
if (err.name === 'ValidationError') {
|
||||
const message = Object.values(err.errors).map(val => val.message).join(', ');
|
||||
error = { message, statusCode: 400 };
|
||||
}
|
||||
|
||||
// Sequelize validation error
|
||||
if (err.name === 'SequelizeValidationError') {
|
||||
const message = err.errors.map(e => e.message).join(', ');
|
||||
error = { message, statusCode: 400 };
|
||||
}
|
||||
|
||||
// Sequelize unique constraint error
|
||||
if (err.name === 'SequelizeUniqueConstraintError') {
|
||||
const message = 'Duplicate field value entered';
|
||||
error = { message, statusCode: 400 };
|
||||
}
|
||||
|
||||
// JWT errors
|
||||
if (err.name === 'JsonWebTokenError') {
|
||||
const message = 'Invalid token';
|
||||
error = { message, statusCode: 401 };
|
||||
}
|
||||
|
||||
if (err.name === 'TokenExpiredError') {
|
||||
const message = 'Token expired';
|
||||
error = { message, statusCode: 401 };
|
||||
}
|
||||
|
||||
res.status(error.statusCode || 500).json({
|
||||
success: false,
|
||||
error: error.message || 'Server Error',
|
||||
...(process.env.NODE_ENV === 'development' && { stack: err.stack })
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = errorHandler;
|
||||
@@ -0,0 +1,24 @@
|
||||
const { RateLimiterMemory } = require('rate-limiter-flexible');
|
||||
|
||||
const rateLimiter = new RateLimiterMemory({
|
||||
keyPrefix: 'middleware',
|
||||
points: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || 100,
|
||||
duration: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 900, // 15 minutes
|
||||
});
|
||||
|
||||
const rateLimiterMiddleware = async (req, res, next) => {
|
||||
try {
|
||||
const key = req.ip;
|
||||
await rateLimiter.consume(key);
|
||||
next();
|
||||
} catch (rejRes) {
|
||||
const secs = Math.round(rejRes.msBeforeNext / 1000) || 1;
|
||||
res.set('Retry-After', String(secs));
|
||||
res.status(429).json({
|
||||
error: 'Too Many Requests',
|
||||
retryAfter: secs
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = rateLimiterMiddleware;
|
||||
@@ -0,0 +1,87 @@
|
||||
const Joi = require('joi');
|
||||
|
||||
const validate = (schema) => {
|
||||
return (req, res, next) => {
|
||||
const { error } = schema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: error.details[0].message
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// Validation schemas
|
||||
const schemas = {
|
||||
register: Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().min(6).required(),
|
||||
firstName: Joi.string().min(2).required(),
|
||||
lastName: Joi.string().min(2).required(),
|
||||
role: Joi.string().valid('user', 'admin', 'expert').optional()
|
||||
}),
|
||||
|
||||
login: Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
password: Joi.string().required()
|
||||
}),
|
||||
|
||||
updateProfile: Joi.object({
|
||||
firstName: Joi.string().min(2).optional(),
|
||||
lastName: Joi.string().min(2).optional(),
|
||||
preferences: Joi.object().optional()
|
||||
}),
|
||||
|
||||
createConversation: Joi.object({
|
||||
title: Joi.string().min(1).optional()
|
||||
}),
|
||||
|
||||
sendMessage: Joi.object({
|
||||
conversationId: Joi.string().uuid().required(),
|
||||
content: Joi.string().min(1).required(),
|
||||
messageType: Joi.string().valid('text', 'plan', 'execution', 'feedback').optional()
|
||||
}),
|
||||
|
||||
submitFeedback: Joi.object({
|
||||
messageId: Joi.string().uuid().optional(),
|
||||
feedbackType: Joi.string().valid('positive', 'negative', 'correction', 'suggestion').required(),
|
||||
rating: Joi.number().min(1).max(5).optional(),
|
||||
comment: Joi.string().optional(),
|
||||
correctedContent: Joi.string().optional()
|
||||
}),
|
||||
|
||||
createModelVersion: Joi.object({
|
||||
modelName: Joi.string().min(1).required(),
|
||||
modelType: Joi.string().valid('MODEL1', 'QUERYMODEL').required(),
|
||||
baseModel: Joi.string().min(1).required(),
|
||||
fineTuningMethod: Joi.string().valid('SFT', 'DPO', 'PPO', 'LoRA', 'QLoRA').optional(),
|
||||
hyperparameters: Joi.object().optional()
|
||||
}),
|
||||
|
||||
executeTool: Joi.object({
|
||||
planId: Joi.string().uuid().required(),
|
||||
toolName: Joi.string().min(1).required(),
|
||||
toolType: Joi.string().valid('query_expander', 'extraction', 'report1', 'report2', 'web_search', 'encyclopedia_pdf').required(),
|
||||
inputParameters: Joi.object().required()
|
||||
}),
|
||||
|
||||
generatePlan: Joi.object({
|
||||
query: Joi.string().min(1).required(),
|
||||
conversationId: Joi.string().uuid().optional(),
|
||||
context: Joi.object().optional()
|
||||
}),
|
||||
|
||||
executePlan: Joi.object({
|
||||
planId: Joi.string().uuid().required(),
|
||||
options: Joi.object().optional()
|
||||
})
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
validate,
|
||||
schemas
|
||||
};
|
||||
Reference in New Issue
Block a user