Migrate project from typing_practiceweb

This commit is contained in:
2026-05-14 16:04:14 +08:00
parent dbc5425706
commit 0e87d5546d
225 changed files with 92811 additions and 0 deletions

205
server/middleware/auth.ts Normal file
View File

@@ -0,0 +1,205 @@
// server/middleware/auth.ts
import jwt from 'jsonwebtoken';
import { Request, Response, NextFunction } from 'express';
import { config } from '../config';
// 用户接口
interface UserPayload {
_id: string;
username: string;
fullname: string;
email: string;
isAdmin: boolean;
exp?: number;
}
// 扩展 Request 类型
declare global {
namespace Express {
interface Request {
user?: UserPayload;
token?: string;
}
}
}
// 认证错误类
class AuthError extends Error {
constructor(
message: string,
public statusCode: number = 401,
public code: string = 'AUTH_ERROR'
) {
super(message);
this.name = 'AuthError';
}
}
/**
* 验证并解析 JWT token
* @param token JWT token
* @returns 解析后的用户信息
*/
const verifyToken = (token: string): UserPayload => {
try {
const decoded = jwt.verify(token, config.JWT_SECRET) as UserPayload;
console.debug('Token verification:', {
userId: decoded._id,
username: decoded.username,
expiresIn: decoded.exp ? new Date(decoded.exp * 1000).toISOString() : 'unknown'
});
return decoded;
} catch (error) {
if (error instanceof jwt.TokenExpiredError) {
console.debug('Token expired:', { error: error.message, expiredAt: error.expiredAt });
throw new AuthError('认证令牌已过期', 401, 'TOKEN_EXPIRED');
}
if (error instanceof jwt.JsonWebTokenError) {
console.debug('Invalid token:', { error: error.message });
throw new AuthError('无效的认证令牌', 401, 'INVALID_TOKEN');
}
console.error('Unexpected token verification error:', error);
throw new AuthError('认证失败', 401, 'AUTH_FAILED');
}
};
/**
* 认证中间件
*/
export const auth = async (req: Request, res: Response, next: NextFunction) => {
try {
const authHeader = req.header('Authorization');
if (!authHeader) {
return res.status(401).json({
error: '未提供认证令牌',
code: 'NO_TOKEN'
});
}
if (!authHeader.startsWith('Bearer ')) {
return res.status(401).json({
error: '认证令牌格式无效',
code: 'INVALID_TOKEN_FORMAT'
});
}
const token = authHeader.replace('Bearer ', '').trim();
if (!token) {
return res.status(401).json({
error: '认证令牌为空',
code: 'EMPTY_TOKEN'
});
}
try {
const decoded = verifyToken(token);
if (!decoded._id || !decoded.username) {
return res.status(401).json({
error: '认证令牌信息不完整',
code: 'INCOMPLETE_TOKEN'
});
}
if (decoded.exp) {
const now = Math.floor(Date.now() / 1000);
const timeLeft = decoded.exp - now;
if (timeLeft < 300) {
console.debug('Token expiring soon:', {
timeLeft: `${timeLeft}s`,
expireAt: new Date(decoded.exp * 1000).toISOString()
});
}
}
req.user = decoded;
req.token = token;
console.debug('Auth successful:', {
userId: decoded._id,
username: decoded.username,
path: req.path
});
next();
} catch (error) {
if (error instanceof AuthError) {
return res.status(error.statusCode).json({
error: error.message,
code: error.code
});
}
throw error;
}
} catch (error) {
console.error('Auth error:', error);
return res.status(401).json({
error: '认证失败',
code: 'AUTH_FAILED'
});
}
};
/**
* 可选认证中间件
* 不强制要求认证,但如果提供了有效的 token 则解析用户信息
*/
export const optionalAuth = async (req: Request, res: Response, next: NextFunction) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (token) {
console.debug('Optional auth: token present');
const decoded = verifyToken(token);
req.user = decoded;
req.token = token;
} else {
console.debug('Optional auth: no token');
}
next();
} catch (error) {
console.debug('Optional auth failed:', {
error: error instanceof Error ? error.message : 'Unknown error',
code: error instanceof AuthError ? error.code : 'OPTIONAL_AUTH_FAILED'
});
next();
}
};
/**
* 管理员认证中间件
*/
export const adminAuth = async (req: Request, res: Response, next: NextFunction) => {
try {
await auth(req, res, () => {
if (!req.user || !req.user.isAdmin) {
console.debug('Admin auth failed:', {
userId: req.user?._id,
username: req.user?.username,
isAdmin: req.user?.isAdmin
});
return res.status(403).json({
error: '需要管理员权限',
code: 'ADMIN_REQUIRED'
});
}
console.debug('Admin auth successful:', {
userId: req.user._id,
username: req.user.username
});
next();
});
} catch (error) {
console.error('Admin auth unexpected error:', error);
res.status(500).json({
error: '服务器内部错误',
code: 'SERVER_ERROR'
});
}
};
// 导出类型和错误类
export type { UserPayload };
export { AuthError };

View File

@@ -0,0 +1,58 @@
// server/middleware/practiceValidation.ts
import { Request, Response, NextFunction } from 'express';
import crypto from 'crypto';
export const validatePracticeSubmission = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const { signature, timestamp, ...practiceData } = req.body;
const currentServerTime = Date.now();
// 1. 验证时间戳
if (Math.abs(currentServerTime - timestamp) > 2000) {
return res.status(400).json({
error: '提交时间异111常',
code: 'INVALID_TIMESTAMP'
});
}
// 2. 使用相同的方式序列化数据
const orderedData = JSON.stringify(practiceData, Object.keys(practiceData).sort());
// 3. 验证签名
const expectedSignature = crypto
.createHash('sha256')
.update(orderedData + timestamp)
.digest('hex');
console.log('Debug info:', {
receivedSignature: signature,
expectedSignature: expectedSignature,
data: orderedData,
timestamp: timestamp
});
if (signature !== expectedSignature) {
return res.status(400).json({
error: '数据签名无效',
code: 'INVALID_SIGNATURE',
debug: {
received: signature,
expected: expectedSignature
}
});
}
// ... 其他验证代码保持不变
next();
} catch (error) {
console.error('Validation error:', error);
res.status(400).json({
error: '数据验证失败'+error,
code: 'VALIDATION_ERROR'
});
}
};