Files
suanming/server/middleware/auth.js

217 lines
4.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { authService } from '../services/authService.js';
/**
* JWT认证中间件
*/
export const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({
error: {
code: 'UNAUTHORIZED',
message: '缺少访问令牌'
}
});
}
try {
const decoded = authService.verifyToken(token);
req.user = decoded;
next();
} catch (error) {
return res.status(403).json({
error: {
code: 'FORBIDDEN',
message: '无效的访问令牌'
}
});
}
};
/**
* 可选认证中间件(用于可选登录的接口)
*/
export const optionalAuth = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (token) {
try {
const decoded = authService.verifyToken(token);
req.user = decoded;
} catch (error) {
// 忽略token验证错误继续执行
req.user = null;
}
} else {
req.user = null;
}
next();
};
/**
* 错误处理中间件
*/
export const errorHandler = (err, req, res, next) => {
console.error('API Error:', err);
// 数据库错误
if (err.code && err.code.startsWith('SQLITE_')) {
return res.status(500).json({
error: {
code: 'DATABASE_ERROR',
message: '数据库操作失败'
}
});
}
// 验证错误
if (err.name === 'ValidationError') {
return res.status(400).json({
error: {
code: 'VALIDATION_ERROR',
message: err.message
}
});
}
// JWT错误
if (err.name === 'JsonWebTokenError') {
return res.status(401).json({
error: {
code: 'INVALID_TOKEN',
message: '无效的访问令牌'
}
});
}
if (err.name === 'TokenExpiredError') {
return res.status(401).json({
error: {
code: 'TOKEN_EXPIRED',
message: '访问令牌已过期'
}
});
}
// 默认错误
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: err.message || '内部服务器错误'
}
});
};
/**
* 请求日志中间件
*/
export const requestLogger = (req, res, next) => {
const start = Date.now();
const { method, url, ip } = req;
res.on('finish', () => {
const duration = Date.now() - start;
const { statusCode } = res;
console.log(`${new Date().toISOString()} - ${method} ${url} - ${statusCode} - ${duration}ms - ${ip}`);
});
next();
};
/**
* 输入验证中间件
*/
export const validateInput = (schema) => {
return (req, res, next) => {
const { error } = schema.validate(req.body);
if (error) {
return res.status(400).json({
error: {
code: 'INVALID_INPUT',
message: error.details[0].message
}
});
}
next();
};
};
/**
* 速率限制中间件(简单实现)
*/
const rateLimitStore = new Map();
export const rateLimit = (options = {}) => {
const {
windowMs = 15 * 60 * 1000, // 15分钟
max = 100, // 最大请求数
message = '请求过于频繁,请稍后再试'
} = options;
return (req, res, next) => {
const key = req.ip || req.connection.remoteAddress;
const now = Date.now();
if (!rateLimitStore.has(key)) {
rateLimitStore.set(key, { count: 1, resetTime: now + windowMs });
return next();
}
const record = rateLimitStore.get(key);
if (now > record.resetTime) {
// 重置计数
record.count = 1;
record.resetTime = now + windowMs;
return next();
}
if (record.count >= max) {
return res.status(429).json({
error: {
code: 'RATE_LIMIT_EXCEEDED',
message
}
});
}
record.count++;
next();
};
};
/**
* CORS中间件配置
*/
export const corsOptions = {
origin: function (origin, callback) {
// 允许的域名列表
const allowedOrigins = [
'http://localhost:5173',
'http://localhost:3000',
'http://127.0.0.1:5173',
'http://127.0.0.1:3000'
];
// 开发环境允许所有来源
if (process.env.NODE_ENV === 'development') {
return callback(null, true);
}
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('不允许的CORS来源'));
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
};