Files
easynode/server/app/utils/verify-auth.js
2026-04-26 18:06:40 +08:00

104 lines
3.6 KiB
JavaScript

const jwt = require('jsonwebtoken')
const { AESDecryptAsync } = require('./encrypt')
const { isAllowedIp, getClientIP } = require('../utils/tools')
const { SHA256Encrypt } = require('../utils/encrypt')
const { KeyDB, SessionDB } = require('./db-class')
const keyDB = new KeyDB().getInstance()
const sessionDB = new SessionDB().getInstance()
const enumLoginCode = {
SUCCESS: 'SUCCESS',
EXPIRES: 'TOKEN_EXPIRES',
ERROR_TOKEN: 'TOKEN_ERROR',
ERROR_UID: 'USER_ID_ERROR',
REVOKED_TOKEN: 'REVOKED_TOKEN',
SID_EXPIRES: 'SID_EXPIRES'
}
// 解析 Cookie
const parseCookies = (cookieString) => {
if (!cookieString) return {}
return Object.fromEntries(
cookieString.split(';').map(c => {
const [key, ...v] = c.trim().split('=')
return [key, decodeURIComponent(v.join('='))]
})
)
}
// 校验token&session
const verifyAuthSync = async (token, session) => {
try {
const { jwtToken, _id: userId } = await keyDB.findOneAsync({})
token = await AESDecryptAsync(token)
jwt.verify(token, `${ jwtToken }-${ userId }`)
const sessionRecord = await sessionDB.findOneAsync({ session })
// 是否无效/注销/过期的token
if (!session || !sessionRecord || sessionRecord.revoked !== false) {
return { code: enumLoginCode.REVOKED_TOKEN, success: false }
}
// session是否过期
if (sessionRecord.expireAt < Date.now()) {
await sessionDB.updateAsync({ session }, { $set: { revoked: true } }) // 标记为已撤销
return { code: enumLoginCode.SID_EXPIRES, success: false } //sid过期
}
// 验证token是否匹配session
const currentTokenHash = SHA256Encrypt(token)
if (sessionRecord.tokenHash !== currentTokenHash) {
logger.warn('⚠: Token 哈希不匹配,可能的安全威胁')
return { code: enumLoginCode.ERROR_TOKEN, success: false }
}
return { code: enumLoginCode.SUCCESS, success: true } // 验证成功
} catch (err) {
if (err.name === 'TokenExpiredError') return { code: enumLoginCode.EXPIRES, success: false }
logger.error('用户身份校验失败: ', err.message)
return { code: enumLoginCode.ERROR_TOKEN, success: false }
}
}
const verifyWsAuthSync = async (socket, next) => {
const requestIP = getClientIP(socket.conn.remoteAddress, socket.handshake.headers['x-forwarded-for'])
// console.log('ws terminal requestIP:', requestIP)
// IP 白名单检查
if (!isAllowedIp(requestIP)) {
logger.warn('ws终端连接IP不在白名单中: ', requestIP)
return next(new Error('IP not allowed')) // ✅ 使用 next(error)
}
// Cookie 检查
const cookies = socket.handshake.headers.cookie
if (!cookies) {
logger.warn('终端连接缺少 Cookie')
return next(new Error('No Cookie'))
}
// Session 检查
const { session } = parseCookies(cookies)
// console.log('ws terminal session:', session)
if (!session) {
logger.warn('终端连接缺少 Session Cookie')
return next(new Error('No Session Cookie'))
}
// Token 检查
const { token } = socket.handshake.auth || {}
// console.log('ws terminal token:', token)
if (!token) {
logger.warn('终端连接缺少 Token')
return next(new Error('No Token'))
}
// 验证身份
const { success, code } = await verifyAuthSync(token, session)
if (!success) {
logger.warn('ws终端连接身份验证失败, code:', code)
return next(new Error('Authentication Failed'))
}
// console.log('🤓 ws terminal auth success')
next()
}
module.exports = {
enumLoginCode,
verifyAuthSync,
verifyWsAuthSync,
parseCookies
}