Files
suanming/server/middleware/auth.cjs
patdelphi d9c57dedb7 feat: 完成易经64卦数据补全和本地化改造
- 完全按照logic/yijing.txt补全所有64卦的完整数据结构
- 包含每卦的卦辞、象传、六爻详解和人生指导
- 重建八字、易经、紫微斗数三个核心分析器
- 实现完整的本地SQLite数据库替代Supabase
- 添加本地Express.js后端服务器
- 更新前端API调用为本地接口
- 实现JWT本地认证系统
- 完善历史记录和用户管理功能
2025-08-18 22:34:39 +08:00

162 lines
4.3 KiB
JavaScript
Raw Permalink 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.
const jwt = require('jsonwebtoken');
const { getDB } = require('../database/index.cjs');
const { AppError } = require('./errorHandler.cjs');
// JWT密钥 (在生产环境中应该从环境变量读取)
const JWT_SECRET = process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '7d';
// 生成JWT token
const generateToken = (userId) => {
return jwt.sign({ userId }, JWT_SECRET, {
expiresIn: JWT_EXPIRES_IN
});
};
// 验证JWT token
const verifyToken = (token) => {
try {
return jwt.verify(token, JWT_SECRET);
} catch (error) {
throw new AppError('无效的访问令牌', 401, 'INVALID_TOKEN');
}
};
// 认证中间件
const authenticate = async (req, res, next) => {
try {
// 从请求头获取token
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
throw new AppError('缺少访问令牌', 401, 'MISSING_TOKEN');
}
const token = authHeader.substring(7); // 移除 'Bearer ' 前缀
// 验证token
const decoded = verifyToken(token);
// 从数据库获取用户信息
const db = getDB();
const user = db.prepare('SELECT id, email FROM users WHERE id = ?').get(decoded.userId);
if (!user) {
throw new AppError('用户不存在', 401, 'USER_NOT_FOUND');
}
// 检查会话是否有效
const session = db.prepare(
'SELECT id FROM user_sessions WHERE user_id = ? AND token_hash = ? AND expires_at > ?'
).get(user.id, hashToken(token), new Date().toISOString());
if (!session) {
throw new AppError('会话已过期,请重新登录', 401, 'SESSION_EXPIRED');
}
// 将用户信息添加到请求对象
req.user = user;
req.token = token;
next();
} catch (error) {
next(error);
}
};
// 可选认证中间件(不强制要求登录)
const optionalAuth = async (req, res, next) => {
try {
const authHeader = req.headers.authorization;
if (authHeader && authHeader.startsWith('Bearer ')) {
const token = authHeader.substring(7);
const decoded = verifyToken(token);
const db = getDB();
const user = db.prepare('SELECT id, email FROM users WHERE id = ?').get(decoded.userId);
if (user) {
const session = db.prepare(
'SELECT id FROM user_sessions WHERE user_id = ? AND token_hash = ? AND expires_at > ?'
).get(user.id, hashToken(token), new Date().toISOString());
if (session) {
req.user = user;
req.token = token;
}
}
}
next();
} catch (error) {
// 可选认证失败时不抛出错误,继续执行
next();
}
};
// 创建用户会话
const createSession = (userId, token) => {
const db = getDB();
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7); // 7天后过期
const stmt = db.prepare(
'INSERT INTO user_sessions (user_id, token_hash, expires_at) VALUES (?, ?, ?)'
);
return stmt.run(userId, hashToken(token), expiresAt.toISOString());
};
// 删除用户会话
const deleteSession = (userId, token) => {
const db = getDB();
const stmt = db.prepare(
'DELETE FROM user_sessions WHERE user_id = ? AND token_hash = ?'
);
return stmt.run(userId, hashToken(token));
};
// 删除用户所有会话
const deleteAllSessions = (userId) => {
const db = getDB();
const stmt = db.prepare('DELETE FROM user_sessions WHERE user_id = ?');
return stmt.run(userId);
};
// Token哈希函数简单实现
const hashToken = (token) => {
const crypto = require('crypto');
return crypto.createHash('sha256').update(token).digest('hex');
};
// 清理过期会话
const cleanupExpiredSessions = () => {
const db = getDB();
const stmt = db.prepare('DELETE FROM user_sessions WHERE expires_at < ?');
const result = stmt.run(new Date().toISOString());
if (result.changes > 0) {
console.log(`清理了 ${result.changes} 个过期会话`);
}
return result.changes;
};
// 定期清理过期会话(每小时执行一次)
setInterval(cleanupExpiredSessions, 60 * 60 * 1000);
module.exports = {
generateToken,
verifyToken,
authenticate,
optionalAuth,
createSession,
deleteSession,
deleteAllSessions,
cleanupExpiredSessions,
JWT_SECRET,
JWT_EXPIRES_IN
};