Initial commit

This commit is contained in:
2026-03-08 01:34:54 +08:00
commit 1f104f73c8
441 changed files with 64911 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
const AuthService = require('../services/auth/AuthService');
const TokenManager = require('../services/auth/TokenManager');
const logger = require('../utils/logger');
function extractToken(req) {
if (req.headers.authorization) {
const parts = req.headers.authorization.split(' ');
if (parts.length === 2 && parts[0] === 'Bearer') {
return parts[1];
}
}
if (req.cookies) {
if (req.cookies.token) {
return req.cookies.token;
}
if (req.cookies.auth) {
return req.cookies.auth;
}
}
if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
async function authMiddleware(req, res, next) {
const authService = AuthService.getInstance();
const tokenManager = TokenManager.getInstance();
if (!authService.hasPassword()) {
req.user = { userId: 'default-user' };
res.locals.authenticated = true;
return next();
}
const token = extractToken(req);
if (token) {
const decoded = tokenManager.verifyToken(token);
if (decoded) {
req.user = { userId: decoded.userId };
res.locals.authenticated = true;
logger.debug('Authentication successful via token', { userId: decoded.userId });
return next();
}
}
const password = req.query.password || req.body?.password;
if (password) {
try {
const isValid = await authService.authenticate(password);
if (isValid) {
req.user = { userId: 'default-user' };
res.locals.authenticated = true;
logger.debug('Authentication successful via password');
return next();
}
} catch (error) {
logger.error('Authentication error', { error: error.message });
}
}
logger.warn('Authentication failed', {
ip: req.socket?.remoteAddress,
path: req.path,
hasToken: !!token,
hasPassword: !!password
});
res.status(401).json({
error: 'Authentication required',
code: 'AUTH_REQUIRED'
});
}
module.exports = authMiddleware;

View File

@@ -0,0 +1,78 @@
const logger = require('../utils/logger');
function errorHandler(err, req, res, next) {
const statusCode = err.statusCode || err.status || 500;
const errorCode = err.code || 'INTERNAL_ERROR';
const errorResponse = {
error: err.message || 'Internal Server Error',
code: errorCode
};
if (err.details) {
errorResponse.details = err.details;
}
if (statusCode >= 500) {
logger.error('Server error', {
error: err.message,
code: errorCode,
stack: err.stack,
path: req.path,
method: req.method,
ip: req.socket?.remoteAddress
});
} else {
logger.warn('Client error', {
error: err.message,
code: errorCode,
path: req.path,
method: req.method,
ip: req.socket?.remoteAddress
});
}
if (process.env.NODE_ENV === 'development' && err.stack) {
errorResponse.stack = err.stack;
}
res.status(statusCode).json(errorResponse);
}
class AppError extends Error {
constructor(message, statusCode = 500, code = 'APP_ERROR') {
super(message);
this.statusCode = statusCode;
this.code = code;
this.status = statusCode;
Error.captureStackTrace(this, this.constructor);
}
withDetails(details) {
this.details = details;
return this;
}
}
function createError(message, statusCode = 500, code = 'APP_ERROR') {
return new AppError(message, statusCode, code);
}
function notFoundHandler(req, res, next) {
const error = new AppError(`Not Found - ${req.originalUrl}`, 404, 'NOT_FOUND');
next(error);
}
function asyncHandler(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
module.exports = {
errorHandler,
AppError,
createError,
notFoundHandler,
asyncHandler
};

View File

@@ -0,0 +1,87 @@
const logger = require('../utils/logger');
class RateLimiter {
constructor(options = {}) {
this.windowMs = options.windowMs || 60 * 1000;
this.maxRequests = options.maxRequests || 5;
this.requests = new Map();
this.cleanupInterval = setInterval(() => this.cleanup(), this.windowMs);
}
cleanup() {
const now = Date.now();
for (const [key, data] of this.requests.entries()) {
if (now - data.startTime > this.windowMs) {
this.requests.delete(key);
}
}
}
getKey(req) {
return req.ip || req.socket?.remoteAddress || 'unknown';
}
middleware() {
return (req, res, next) => {
const key = this.getKey(req);
const now = Date.now();
let requestData = this.requests.get(key);
if (!requestData || now - requestData.startTime > this.windowMs) {
requestData = {
count: 0,
startTime: now
};
this.requests.set(key, requestData);
}
requestData.count++;
const remainingTime = Math.ceil((requestData.startTime + this.windowMs - now) / 1000);
res.setHeader('X-RateLimit-Limit', this.maxRequests);
res.setHeader('X-RateLimit-Remaining', Math.max(0, this.maxRequests - requestData.count));
res.setHeader('X-RateLimit-Reset', remainingTime);
if (requestData.count > this.maxRequests) {
logger.warn('Rate limit exceeded', {
ip: key,
path: req.path,
count: requestData.count,
maxRequests: this.maxRequests
});
return res.status(429).json({
error: 'Too many requests, please try again later',
code: 'RATE_LIMIT_EXCEEDED',
retryAfter: remainingTime
});
}
next();
};
}
stop() {
if (this.cleanupInterval) {
clearInterval(this.cleanupInterval);
}
}
}
function createRateLimiter(options = {}) {
const limiter = new RateLimiter(options);
return limiter.middleware();
}
const defaultRateLimiter = createRateLimiter({
windowMs: 60 * 1000,
maxRequests: 5
});
module.exports = {
RateLimiter,
createRateLimiter,
defaultRateLimiter
};