feat: add online device management and desktop settings integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const { UserDB } = require('./database');
|
||||
const { UserDB, DeviceSessionDB } = require('./database');
|
||||
const { decryptSecret } = require('./utils/encryption');
|
||||
const DEFAULT_LOCAL_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB
|
||||
const DEFAULT_OSS_STORAGE_QUOTA_BYTES = 1024 * 1024 * 1024; // 1GB
|
||||
@@ -64,13 +64,16 @@ if (JWT_SECRET.length < 32) {
|
||||
console.log('[安全] ✓ JWT密钥验证通过');
|
||||
|
||||
// 生成Access Token(短期)
|
||||
function generateToken(user) {
|
||||
function generateToken(user, sessionId = null) {
|
||||
const safeSessionId = typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
is_admin: user.is_admin,
|
||||
type: 'access'
|
||||
type: 'access',
|
||||
sid: safeSessionId,
|
||||
jti: crypto.randomBytes(12).toString('hex')
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: ACCESS_TOKEN_EXPIRES }
|
||||
@@ -78,11 +81,13 @@ function generateToken(user) {
|
||||
}
|
||||
|
||||
// 生成Refresh Token(长期)
|
||||
function generateRefreshToken(user) {
|
||||
function generateRefreshToken(user, sessionId = null) {
|
||||
const safeSessionId = typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
type: 'refresh',
|
||||
sid: safeSessionId,
|
||||
// 添加随机标识,使每次生成的refresh token不同
|
||||
jti: crypto.randomBytes(16).toString('hex')
|
||||
},
|
||||
@@ -91,8 +96,26 @@ function generateRefreshToken(user) {
|
||||
);
|
||||
}
|
||||
|
||||
function decodeAccessToken(token) {
|
||||
if (!token || typeof token !== 'string') return null;
|
||||
try {
|
||||
return jwt.verify(token, JWT_SECRET);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function decodeRefreshToken(token) {
|
||||
if (!token || typeof token !== 'string') return null;
|
||||
try {
|
||||
return jwt.verify(token, REFRESH_SECRET);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证Refresh Token并返回新的Access Token
|
||||
function refreshAccessToken(refreshToken) {
|
||||
function refreshAccessToken(refreshToken, context = {}) {
|
||||
try {
|
||||
const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
|
||||
|
||||
@@ -100,6 +123,18 @@ function refreshAccessToken(refreshToken) {
|
||||
return { success: false, message: '无效的刷新令牌类型' };
|
||||
}
|
||||
|
||||
const sessionId = typeof decoded.sid === 'string' ? decoded.sid.trim() : '';
|
||||
if (sessionId) {
|
||||
const activeSession = DeviceSessionDB.findActiveBySessionId(sessionId);
|
||||
if (!activeSession || Number(activeSession.user_id) !== Number(decoded.id)) {
|
||||
return { success: false, message: '当前设备会话已失效,请重新登录' };
|
||||
}
|
||||
DeviceSessionDB.touch(sessionId, {
|
||||
ipAddress: context.ipAddress,
|
||||
userAgent: context.userAgent
|
||||
});
|
||||
}
|
||||
|
||||
const user = UserDB.findById(decoded.id);
|
||||
|
||||
if (!user) {
|
||||
@@ -115,11 +150,12 @@ function refreshAccessToken(refreshToken) {
|
||||
}
|
||||
|
||||
// 生成新的access token
|
||||
const newAccessToken = generateToken(user);
|
||||
const newAccessToken = generateToken(user, sessionId || null);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
token: newAccessToken,
|
||||
sessionId: sessionId || null,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
@@ -148,6 +184,29 @@ function authMiddleware(req, res, next) {
|
||||
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
if (decoded.type !== 'access') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '无效的令牌类型'
|
||||
});
|
||||
}
|
||||
|
||||
const sessionId = typeof decoded.sid === 'string' ? decoded.sid.trim() : '';
|
||||
if (sessionId) {
|
||||
const activeSession = DeviceSessionDB.findActiveBySessionId(sessionId);
|
||||
if (!activeSession || Number(activeSession.user_id) !== Number(decoded.id)) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '当前设备已下线,请重新登录'
|
||||
});
|
||||
}
|
||||
DeviceSessionDB.touch(sessionId, {
|
||||
ipAddress: req.ip || req.socket?.remoteAddress || '',
|
||||
userAgent: req.get('user-agent') || ''
|
||||
});
|
||||
}
|
||||
|
||||
const user = UserDB.findById(decoded.id);
|
||||
|
||||
if (!user) {
|
||||
@@ -217,6 +276,8 @@ function authMiddleware(req, res, next) {
|
||||
// 主题偏好
|
||||
theme_preference: user.theme_preference || null
|
||||
};
|
||||
req.authSessionId = sessionId || null;
|
||||
req.authTokenJti = typeof decoded.jti === 'string' ? decoded.jti : null;
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
@@ -329,6 +390,8 @@ module.exports = {
|
||||
JWT_SECRET,
|
||||
generateToken,
|
||||
generateRefreshToken,
|
||||
decodeAccessToken,
|
||||
decodeRefreshToken,
|
||||
refreshAccessToken,
|
||||
authMiddleware,
|
||||
adminMiddleware,
|
||||
|
||||
Reference in New Issue
Block a user