feat: 优化 session 管理

This commit is contained in:
chaoszhu
2026-08-29 20:07:06 +08:00
parent 932fb708c4
commit cf8a5eae50
11 changed files with 166 additions and 60 deletions
+21 -8
View File
@@ -1,8 +1,12 @@
import { KeyDB, SessionDB } from '../utils/db-class.js'
import { cookieSecure } from '../config/index.js'
import { disconnectAllSessionConnections, revokeAllSessions } from '../utils/auth-session.js'
import { pruneLoginLogs } from '../utils/login-log.js'
const keyDB = new KeyDB().getInstance()
const sessionDB = new SessionDB().getInstance()
async function getLog({ res }) {
await pruneLoginLogs(sessionDB)
let sessionList = await sessionDB.findAsync({})
let { ipWhiteList = [] } = await keyDB.findOneAsync({}) || {}
ipWhiteList = ipWhiteList.filter(ip => typeof ip === 'string' && ip.trim() !== '')
@@ -24,13 +28,6 @@ const saveIpWhiteList = async ({ res, request }) => {
res.success({ msg: 'success' })
}
const removeSomeLoginRecords = async ({ res }) => {
const sevenDaysAgo = Date.now() - 7 * 24 * 60 * 60 * 1000
const result = await sessionDB.removeAsync({ create: { $lt: sevenDaysAgo } }, { multi: true })
if (result === 0) return res.success({ msg: '没有符合条件的登录日志' })
res.success({ msg: `已成功移除 ${ result } 条登录日志` })
}
const revokeLoginSid = async (ctx) => {
const { res, request, cookies } = ctx
let { params: { id } } = request
@@ -50,9 +47,25 @@ const revokeLoginSid = async (ctx) => {
res.success({ msg: '注销凭证成功' })
}
const revokeAllLoginSessions = async (ctx) => {
try {
await revokeAllSessions(sessionDB)
} finally {
// 已建立的长连接不会再次经过鉴权,需要主动断开。
disconnectAllSessionConnections()
}
ctx.cookies.set('session', '', {
httpOnly: true,
expires: new Date(0),
sameSite: 'strict',
secure: cookieSecure
})
ctx.res.success({ msg: '已注销所有会话,请重新登录' })
}
export {
getLog,
saveIpWhiteList,
removeSomeLoginRecords,
revokeAllLoginSessions,
revokeLoginSid
}
+6 -6
View File
@@ -7,7 +7,7 @@ import { getGroupList, addGroupList, updateGroupList, removeGroup } from '../con
import { getScriptList, getLocalScriptList, addScript, updateScriptList, removeScript, batchRemoveScript, importScript } from '../controller/scripts.js'
import { getScriptGroupList, addScriptGroup, removeScriptGroup, updateScriptGroup } from '../controller/script-group.js'
import { getOnekeyRecord, removeOnekeyRecord } from '../controller/onekey.js'
import { getLog, saveIpWhiteList, removeSomeLoginRecords, revokeLoginSid } from '../controller/sessionLog.js'
import { getLog, saveIpWhiteList, revokeAllLoginSessions, revokeLoginSid } from '../controller/sessionLog.js'
import { getAIConfig, saveAIConfig, getAIModels, updateAIPreferences } from '../controller/chat.js'
import { getAgentSessions, getAgentSessionDetail, updateAgentSession, forkAgentSession, removeAgentSession, clearAgentSessions, editAgentSessionMessage } from '../controller/agent-session.js'
import { getAgentMcpServers, addAgentMcpServer, editAgentMcpServer, removeAgentMcpServer, testAgentMcpConnection, discoverAgentMcpServer } from '../controller/agent-mcp.js'
@@ -294,11 +294,11 @@ const log = [
path: '/ip-white-list',
controller: saveIpWhiteList
},
{
method: 'delete',
path: '/remove-some-login-records',
controller: removeSomeLoginRecords
},
{
method: 'delete',
path: '/revoke-all-sessions',
controller: revokeAllLoginSessions
},
{
method: 'delete',
path: '/revoke-login/:id',
+12 -4
View File
@@ -1,10 +1,12 @@
import schedule from 'node-schedule'
import { sendNoticeAsync } from '../utils/notify.js'
import { formatTimestamp } from '../utils/tools.js'
import { HostListDB } from '../utils/db-class.js'
import { HostListDB, SessionDB } from '../utils/db-class.js'
import { LOGIN_LOG_RETENTION_DAYS, pruneLoginLogs } from '../utils/login-log.js'
const hostListDB = new HostListDB().getInstance()
const sessionDB = new SessionDB().getInstance()
const expiredNotifyJob = async () => {
const expiredNotifyJob = async () => {
logger.info('=====开始检测服务器到期时间=====', new Date())
const hostList = await hostListDB.findAsync({})
for (const item of hostList) {
@@ -25,8 +27,14 @@ const expiredNotifyJob = async () => {
sendNoticeAsync('host_expired', title, temp + content)
}
}
}
}
const loginLogRetentionJob = async () => {
const removed = await pruneLoginLogs(sessionDB)
if (removed > 0) logger.info(`已清理 ${ removed } 条超过 ${ LOGIN_LOG_RETENTION_DAYS } 天的登录日志`)
}
export default () => {
schedule.scheduleJob('0 0 12 1/1 * ?', expiredNotifyJob)
schedule.scheduleJob('0 0 3 * * *', loginLogRetentionJob)
}
+15
View File
@@ -0,0 +1,15 @@
const LOGIN_LOG_RETENTION_DAYS = 90
const DAY_MS = 24 * 60 * 60 * 1000
const pruneLoginLogs = (sessionStore, now = Date.now()) => {
const cutoff = now - LOGIN_LOG_RETENTION_DAYS * DAY_MS
return sessionStore.removeAsync(
{ create: { $lt: cutoff } },
{ multi: true }
)
}
export {
LOGIN_LOG_RETENTION_DAYS,
pruneLoginLogs
}
+14
View File
@@ -6,6 +6,7 @@ import {
registerSocketServer,
revokeAllSessions
} from '../app/utils/auth-session.js'
import { LOGIN_LOG_RETENTION_DAYS, pruneLoginLogs } from '../app/utils/login-log.js'
const calls = []
const sessionStore = {
@@ -35,6 +36,19 @@ const storedSessions = await realSessionStore.findAsync({})
assert.equal(storedSessions.length, 3)
assert.ok(storedSessions.every(session => session.revoked === true))
const now = Date.UTC(2026, 7, 29)
const loginLogStore = new Datastore()
await loginLogStore.insertAsync([
{ session: 'old', create: now - (LOGIN_LOG_RETENTION_DAYS + 1) * 24 * 60 * 60 * 1000 },
{ session: 'boundary', create: now - LOGIN_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000 },
{ session: 'recent', create: now - 10 * 24 * 60 * 60 * 1000 }
])
assert.equal(await pruneLoginLogs(loginLogStore, now), 1)
assert.deepEqual(
(await loginLogStore.findAsync({})).map(item => item.session).sort(),
['boundary', 'recent']
)
let disconnectCalls = 0
let destroyCalls = 0
const closeListeners = []