mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-15 08:40:46 +08:00
fix(uds-auth): keep sessions until manual sign-out; refresh expired UAC tokens by QR (0.3.20)
Users were asked to sign in again every ~30 minutes, even while active. Session lifetime - Browser: UDS_SESSION kept its login-time Max-Age and was never re-issued, so the cookie vanished 30 min after login. Re-issue it on use (throttled by slidingInterval). - Desktop: remote.mux WS traffic never passed the HTTP auth middleware, so the idle TTL never slid. assertPrincipalLive now reports activity and slides the session and the account's bridges (throttled). - New setting sessionIdleTimeoutHours (Settings page + config). Default 0 = no automatic sign-out; only a manual sign-out ends a session, so conversations and night-time cron are never cut off. 0.5-168 enables an idle timeout with the 7-day cap. The TTL is read live. - Stores support long TTLs: the absolute cap follows the TTL, older 7-day sessions are lifted on activity, and expiry timers re-arm instead of overflowing setTimeout (~24.8 days). UAC token check - Every 2 hours (and 1 min after start) each QR session's token is checked against icenterapi. A rejected token is only flagged, never a sign-out. Network errors, 5xx and bad JSON keep the previous verdict (the old 6h check revoked all of an account's sessions on any failure, including outages). - GET /uds-auth/session-status (non-sliding) returns alive + tokenValid. The client probes it every minute and on focus: a gone session signs the UI out with a notice; a rejected token opens a refresh QR once. - Scanning in refresh mode writes the new token into every live session of the account and the skill credential cache in place: no new session, no reload. Other accounts are rejected (token_refresh_emp_mismatch). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
dfdf298184
commit
0e6de055a9
20 changed files with 904 additions and 119 deletions
|
|
@ -274,3 +274,62 @@ test('bearer hash is not reversible from store maps', async () => {
|
|||
assert.ok(store._byBearer.has(hash))
|
||||
assert.equal([...store._byBearer.keys()].includes(bearer), false)
|
||||
})
|
||||
|
||||
test('active cookie session re-issues UDS_SESSION so the browser cookie slides too', async () => {
|
||||
const store = new MemoryStore()
|
||||
const roles = mockRoles({ '12345': ROLES.USER })
|
||||
const mw = createAuthMiddleware({
|
||||
userSearchUrl: 'http://127.0.0.1:9/never',
|
||||
udsAuth: { baseUrl: 'http://127.0.0.1:9' },
|
||||
session: { cookieMaxAge: 1800000, slidingExpiration: true, slidingInterval: 300000 },
|
||||
}, store, roles, {})
|
||||
const { sessionId, bearer } = await store.create({ empNo: '12345', isAuthenticated: true }, 1800)
|
||||
const call = async () => {
|
||||
const headers = {}
|
||||
const res = { getHeader: (k) => headers[k], setHeader: (k, v) => { headers[k] = v } }
|
||||
const ctx = { req: { headers: { cookie: `${SESSION_COOKIE}=${bearer}` }, socket: {} }, res }
|
||||
await mw(ctx, async () => {})
|
||||
assert.equal(ctx.empNo, '12345')
|
||||
return [].concat(headers['Set-Cookie'] || []).find((c) => c.startsWith(`${SESSION_COOKIE}=`))
|
||||
}
|
||||
|
||||
// Fresh session (within slidingInterval of login): no Set-Cookie churn.
|
||||
assert.equal(await call(), undefined)
|
||||
|
||||
// 20 min later the idle window has drifted past slidingInterval → cookie re-issued.
|
||||
store._sessions.get(sessionId).expiresAt = Date.now() + 10 * 60 * 1000
|
||||
const cookie = await call()
|
||||
assert.ok(cookie, 'UDS_SESSION re-issued')
|
||||
assert.match(cookie, /Max-Age=1800/)
|
||||
assert.match(cookie, /HttpOnly/)
|
||||
})
|
||||
|
||||
test('idle TTL follows the live session config (sessionIdleTimeoutHours) without restart', async () => {
|
||||
const store = new MemoryStore()
|
||||
const roles = mockRoles({ '12345': ROLES.USER })
|
||||
let hours = 24
|
||||
const session = {
|
||||
get cookieMaxAge() { return hours * 60 * 60 * 1000 },
|
||||
slidingExpiration: true,
|
||||
slidingInterval: 300000,
|
||||
}
|
||||
const mw = createAuthMiddleware({
|
||||
userSearchUrl: 'http://127.0.0.1:9/never',
|
||||
udsAuth: { baseUrl: 'http://127.0.0.1:9' },
|
||||
session,
|
||||
}, store, roles, {})
|
||||
const { sessionId, bearer } = await store.create({ empNo: '12345', isAuthenticated: true }, 60)
|
||||
const call = async () => {
|
||||
const headers = {}
|
||||
const res = { getHeader: (k) => headers[k], setHeader: (k, v) => { headers[k] = v } }
|
||||
await mw({ req: { headers: { cookie: `${SESSION_COOKIE}=${bearer}` }, socket: {} }, res }, async () => {})
|
||||
return [].concat(headers['Set-Cookie'] || []).find((c) => c.startsWith(`${SESSION_COOKIE}=`))
|
||||
}
|
||||
assert.match(await call(), /Max-Age=86400/)
|
||||
assert.ok(store._sessions.get(sessionId).expiresAt > Date.now() + 23 * 60 * 60_000)
|
||||
|
||||
hours = 2
|
||||
store._sessions.get(sessionId).expiresAt = Date.now() + 60_000
|
||||
assert.match(await call(), /Max-Age=7200/)
|
||||
assert.ok(store._sessions.get(sessionId).expiresAt <= Date.now() + 2 * 60 * 60_000)
|
||||
})
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue