EternalAI/src/lib/auth.js

154 lines
4.4 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.

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const prisma = require('./prisma');
const JWT_EXPIRES_IN = '7d';
const ADMIN_JWT_EXPIRES_IN = '7d';
// 安全:生产环境必须配置 JWT_SECRET杜绝硬编码密钥
const JWT_SECRET = process.env.JWT_SECRET;
if (!JWT_SECRET) {
if (process.env.NODE_ENV === 'production') {
throw new Error(
'JWT_SECRET 环境变量未设置。请在 .env 文件中配置一个随机密钥(可用 `openssl rand -hex 32` 生成)。'
);
}
console.warn('[安全警告] JWT_SECRET 未设置,使用开发环境临时密钥。请勿在生产环境使用。');
}
const SECRET = JWT_SECRET || 'dev_only_insecure_secret_do_not_use_in_production';
// Admin JWT 使用独立 secret防止跨角色伪造
const ADMIN_JWT_SECRET = process.env.ADMIN_JWT_SECRET || (process.env.NODE_ENV === 'production'
? null
: 'dev_only_admin_insecure_secret');
// 哈希密码
function hashPassword(password) {
return bcrypt.hashSync(password, 10);
}
// 验证密码
function verifyPassword(password, hash) {
return bcrypt.compareSync(password, hash);
}
// 生成 JWT
function signToken(userId) {
return jwt.sign({ userId }, SECRET, { expiresIn: JWT_EXPIRES_IN });
}
// 验证 JWT 并提取 userId
function verifyToken(token) {
try {
const decoded = jwt.verify(token, SECRET);
return decoded.userId;
} catch {
return null;
}
}
// Express 中间件:验证 JWT
function authMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '未登录' });
}
const token = authHeader.slice(7);
const userId = verifyToken(token);
if (!userId) {
return res.status(401).json({ error: '登录已过期,请重新登录' });
}
req.userId = userId;
next();
}
// Express 中间件:验证 API Key用于 Hermes 配置拉取)
// 支持 JWT token 和 API Keyeak_ 前缀)两种认证方式
async function apiKeyMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '需要认证' });
}
const token = authHeader.slice(7);
// 如果是 API Keyeak_ 前缀),查数据库验证
if (token.startsWith('eak_')) {
try {
const keyHash = crypto.createHash('sha256').update(token).digest('hex');
const apiKey = await prisma.apiKey.findUnique({
where: { keyHash },
select: { id: true, userId: true },
});
if (!apiKey) {
return res.status(401).json({ error: 'API Key 无效' });
}
// 更新最后使用时间(不阻塞请求)
prisma.apiKey.update({
where: { id: apiKey.id },
data: { lastUsedAt: new Date() },
}).catch((err) => console.warn('更新 API Key lastUsedAt 失败:', err));
req.userId = apiKey.userId;
req.authMethod = 'apikey';
return next();
} catch (err) {
console.error('API Key 验证失败:', err);
return res.status(500).json({ error: '认证失败' });
}
}
// 否则尝试 JWT 认证
const userId = verifyToken(token);
if (!userId) {
return res.status(401).json({ error: '认证无效' });
}
req.userId = userId;
req.authMethod = 'jwt';
next();
}
// ===== Admin 认证 =====
// 生成 Admin JWT
function adminSignToken(adminId) {
return jwt.sign({ adminId, role: 'admin' }, ADMIN_JWT_SECRET, { expiresIn: ADMIN_JWT_EXPIRES_IN });
}
// 验证 Admin JWT
function adminVerifyToken(token) {
try {
const decoded = jwt.verify(token, ADMIN_JWT_SECRET);
if (decoded.role !== 'admin') return null;
return decoded.adminId;
} catch {
return null;
}
}
// Express 中间件:验证 Admin JWT
function adminAuthMiddleware(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: '管理员未登录' });
}
const token = authHeader.slice(7);
const adminId = adminVerifyToken(token);
if (!adminId) {
return res.status(401).json({ error: '管理员登录已过期,请重新登录' });
}
req.adminId = adminId;
next();
}
module.exports = {
hashPassword,
verifyPassword,
signToken,
verifyToken,
authMiddleware,
apiKeyMiddleware,
adminSignToken,
adminVerifyToken,
adminAuthMiddleware,
};