224 lines
6.2 KiB
JavaScript
Raw Permalink Normal View History

/**
* /api/auth
*
* 开发者输入编号 DEV-002即可直接进入对话界面
* 系统自动匹配团队配置的 API人不需要设置任何东西
* API Key 只在服务器端使用前端永远看不到
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var crypto = require('crypto');
var router = express.Router();
// 团队共享密钥(存在服务器环境变量,前端不可见)
var TEAM_API_KEY = process.env.TEAM_API_KEY || '';
// 会话 token 签名密钥
var SESSION_SECRET = process.env.SESSION_SECRET || crypto.randomBytes(32).toString('hex');
// 简易速率限制(防暴力枚举)
var loginAttempts = new Map();
var RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1分钟窗口
var RATE_LIMIT_MAX = 10; // 每分钟最多10次
function checkRateLimit(ip) {
var now = Date.now();
var entry = loginAttempts.get(ip);
if (!entry || now - entry.windowStart > RATE_LIMIT_WINDOW_MS) {
loginAttempts.set(ip, { windowStart: now, count: 1 });
return true;
}
entry.count++;
if (entry.count > RATE_LIMIT_MAX) {
return false;
}
return true;
}
// 开发者数据库(后续对接 Notion
var DEV_DATABASE = {
'DEV-000': { name: '冰朔', level: 3, channel: 'system' },
'DEV-001': { name: '页页', level: 1, channel: '小坍缩核线' },
'DEV-002': { name: '肥猫', level: 2, channel: '男频' },
'DEV-003': { name: 'Awen', level: 1, channel: '知秋线' },
'DEV-004': { name: '之之', level: 2, channel: '秋秋线' },
'DEV-005': { name: '时雨', level: 1, channel: '知秋线' },
'DEV-006': { name: '匆匆那年', level: 1, channel: '霜砚线' },
'DEV-007': { name: '小兴', level: 1, channel: '霜砚线' },
'DEV-008': { name: '花尔', level: 1, channel: '糖星云线' },
'DEV-009': { name: '小草莓', level: 1, channel: '欧诺弥亚线' },
'DEV-010': { name: '桔子', level: 2, channel: '女频' },
'DEV-011': { name: '燕樊', level: 1, channel: '寂曜线' }
};
// 活跃会话存储(内存级,重启清空)
var activeSessions = new Map();
/**
* 生成会话 token有效期 24h
* @param {string} devId
* @returns {string}
*/
function generateSessionToken(devId) {
var payload = devId + ':' + Date.now();
var hmac = crypto.createHmac('sha256', SESSION_SECRET);
hmac.update(payload);
var token = payload + ':' + hmac.digest('hex');
var tokenBase64 = Buffer.from(token).toString('base64');
activeSessions.set(tokenBase64, {
devId: devId,
createdAt: Date.now(),
expiresAt: Date.now() + 24 * 60 * 60 * 1000
});
return tokenBase64;
}
/**
* 验证会话 token
* @param {string} token
* @returns {{ valid: boolean, devId?: string }}
*/
function verifySessionToken(token) {
var session = activeSessions.get(token);
if (!session) {
return { valid: false };
}
if (Date.now() > session.expiresAt) {
activeSessions.delete(token);
return { valid: false };
}
return { valid: true, devId: session.devId };
}
/**
* 会话验证中间件
*/
function requireSession(req, res, next) {
var authHeader = req.headers['authorization'] || '';
var token = authHeader.replace(/^Bearer\s+/i, '');
if (!token) {
return res.status(401).json({
error: true,
code: 'SESSION_REQUIRED',
message: '请先登录。需要携带有效的会话 token。'
});
}
var result = verifySessionToken(token);
if (!result.valid) {
return res.status(401).json({
error: true,
code: 'SESSION_EXPIRED',
message: '会话已过期,请重新登录。'
});
}
req.sessionDevId = result.devId;
req.sessionDev = DEV_DATABASE[result.devId] || null;
next();
}
/**
* POST /api/auth/team-login
* 开发者编号免配置登录
*/
router.post('/auth/team-login', function(req, res) {
// 速率限制检查
var clientIp = req.ip || req.connection.remoteAddress || 'unknown';
if (!checkRateLimit(clientIp)) {
return res.status(429).json({
success: false,
message: '⚠️ 登录请求过于频繁,请稍后再试。'
});
}
var devId = (req.body.devId || '').trim().toUpperCase();
if (!devId || !/^DEV-\d{3}$/.test(devId)) {
return res.json({
success: false,
message: '❌ 无效的开发者编号格式。请使用 DEV-XXX 格式。'
});
}
var dev = DEV_DATABASE[devId];
if (!dev) {
return res.json({
success: false,
message: '❌ 开发者编号 ' + devId + ' 不存在。请确认你的编号。'
});
}
// 生成会话 token有效期 24h
var sessionToken = generateSessionToken(devId);
res.json({
success: true,
devName: dev.name,
level: dev.level,
channel: dev.channel,
sessionToken: sessionToken
});
// 审计日志
console.log('[AUTH] ' + devId + ' (' + dev.name + ') logged in at ' + new Date().toISOString());
});
/**
* GET /api/auth/verify
* 验证当前会话是否有效
*/
router.get('/auth/verify', function(req, res) {
var clientIp = req.ip || req.connection.remoteAddress || 'unknown';
if (!checkRateLimit(clientIp)) {
return res.status(429).json({ valid: false, message: '请求过于频繁' });
}
var authHeader = req.headers['authorization'] || '';
var token = authHeader.replace(/^Bearer\s+/i, '');
var result = verifySessionToken(token);
if (result.valid) {
var dev = DEV_DATABASE[result.devId] || {};
res.json({
valid: true,
devId: result.devId,
devName: dev.name || '',
level: dev.level || 0,
channel: dev.channel || ''
});
} else {
res.json({ valid: false });
}
});
/**
* POST /api/auth/logout
* 注销会话
*/
router.post('/auth/logout', function(req, res) {
var authHeader = req.headers['authorization'] || '';
var token = authHeader.replace(/^Bearer\s+/i, '');
if (token && activeSessions.has(token)) {
var session = activeSessions.get(token);
activeSessions.delete(token);
console.log('[AUTH] ' + session.devId + ' logged out at ' + new Date().toISOString());
}
res.json({ success: true, message: '已注销' });
});
module.exports = router;
module.exports.requireSession = requireSession;
module.exports.verifySessionToken = verifySessionToken;
module.exports.DEV_DATABASE = DEV_DATABASE;