/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
import assert from 'node:assert/strict'
import { afterEach, describe, test } from 'node:test'
import { QueryClient } from '@tanstack/react-query'
import { useAuthStore, type AuthBundle } from '../stores/auth-store'
import {
applyAuthRotation,
bootstrapAuthentication,
clearAuthenticatedClientState,
createRefreshRunner,
isAuthBundle,
type AuthRefreshRuntime,
} from './auth-session'
const bundle: AuthBundle = {
access_token: 'access-token',
token_type: 'Bearer',
access_expires_at: Math.floor(Date.now() / 1000) + 600,
user: {
id: 42,
username: 'test-user',
role: 1,
},
session: {
sid: 'session-a',
current: true,
login_method: 'password',
ip: '127.0.0.1',
user_agent: 'test',
created_at: 100,
last_active_at: 100,
expires_at: 1000,
},
}
afterEach(() => {
useAuthStore.getState().auth.reset('idle')
})
describe('authentication session coordination', () => {
test('bootstrap distinguishes a completed anonymous check from an active session', async () => {
useAuthStore.getState().auth.reset('complete')
assert.deepEqual(await bootstrapAuthentication(), { kind: 'anonymous' })
useAuthStore.getState().auth.setBundle(bundle)
assert.deepEqual(await bootstrapAuthentication(), {
kind: 'authenticated',
bundle,
})
})
test('a session mismatch clears only local state and retries without the stale SID', async () => {
let expectedSID: string | undefined = bundle.session.sid
const requestedSIDs: Array = []
const clears: Array<[boolean, string | undefined]> = []
const accepted: AuthBundle[] = []
const runtime: AuthRefreshRuntime = {
request: async (sid) => {
requestedSIDs.push(sid)
if (requestedSIDs.length === 1) {
return {
status: 409,
data: { code: 'AUTH_SESSION_MISMATCH' },
}
}
return { status: 200, data: { success: true, data: bundle } }
},
getExpectedSID: () => expectedSID,
parseBundle: (value) => (isAuthBundle(value) ? value : null),
acceptBundle: (acceptedBundle) => accepted.push(acceptedBundle),
clear: (synchronizeTabs, bootstrapState) => {
clears.push([synchronizeTabs, bootstrapState])
expectedSID = undefined
},
markTransient: () => undefined,
wait: async () => undefined,
}
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'authenticated')
assert.deepEqual(requestedSIDs, [bundle.session.sid, undefined])
assert.deepEqual(clears, [[false, 'idle']])
assert.deepEqual(accepted, [bundle])
})
test('a rejected refresh confirms anonymous state and synchronizes sign-out', async () => {
const clears: Array<[boolean, string | undefined]> = []
const runtime: AuthRefreshRuntime = {
request: async () => ({ status: 401 }),
getExpectedSID: () => bundle.session.sid,
parseBundle: () => null,
acceptBundle: () => undefined,
clear: (synchronizeTabs, bootstrapState) => {
clears.push([synchronizeTabs, bootstrapState])
},
markTransient: () => undefined,
wait: async () => undefined,
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
kind: 'anonymous',
})
assert.deepEqual(clears, [[true, undefined]])
})
test('a temporary refresh failure remains retryable without clearing the session', async () => {
let transientCount = 0
let clearCount = 0
const runtime: AuthRefreshRuntime = {
request: async () => ({ status: 503, error: new Error('unavailable') }),
getExpectedSID: () => bundle.session.sid,
parseBundle: () => null,
acceptBundle: () => undefined,
clear: () => {
clearCount += 1
},
markTransient: () => {
transientCount += 1
},
wait: async () => undefined,
}
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(clearCount, 0)
assert.equal(transientCount, 1)
})
test('a rate limited refresh remains retryable without clearing the session', async () => {
let transientCount = 0
let clearCount = 0
const runtime: AuthRefreshRuntime = {
request: async () => ({ status: 429 }),
getExpectedSID: () => bundle.session.sid,
parseBundle: () => null,
acceptBundle: () => undefined,
clear: () => {
clearCount += 1
},
markTransient: () => {
transientCount += 1
},
wait: async () => undefined,
}
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(clearCount, 0)
assert.equal(transientCount, 1)
})
test('an exhausted refresh race clears the unusable local session', async () => {
const requestedDelays: number[] = []
const clears: Array<[boolean, string | undefined]> = []
const runtime: AuthRefreshRuntime = {
request: async () => ({
status: 409,
data: { code: 'AUTH_REFRESH_RACE' },
}),
getExpectedSID: () => bundle.session.sid,
parseBundle: () => null,
acceptBundle: () => undefined,
clear: (synchronizeTabs, bootstrapState) => {
clears.push([synchronizeTabs, bootstrapState])
},
markTransient: () => undefined,
wait: async (delay) => {
requestedDelays.push(delay)
},
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
kind: 'out_of_sync',
code: 'AUTH_REFRESH_RACE',
})
assert.deepEqual(requestedDelays, [80, 200, 500])
assert.deepEqual(clears, [[false, undefined]])
})
test('an unexpected successful response is treated as out of sync', async () => {
let cleared = false
const runtime: AuthRefreshRuntime = {
request: async () => ({ status: 200, data: { success: true } }),
getExpectedSID: () => bundle.session.sid,
parseBundle: () => null,
acceptBundle: () => undefined,
clear: () => {
cleared = true
},
markTransient: () => undefined,
wait: async () => undefined,
}
assert.deepEqual(await createRefreshRunner(runtime)(), {
kind: 'out_of_sync',
code: 'AUTH_INVALID_REFRESH_RESPONSE',
})
assert.equal(cleared, true)
})
test('a refresh response cannot restore credentials after a newer auth operation', async () => {
let current = true
let accepted = false
const runtime: AuthRefreshRuntime = {
request: async () => {
current = false
return { status: 200, data: { success: true, data: bundle } }
},
getExpectedSID: () => bundle.session.sid,
parseBundle: (value) => (isAuthBundle(value) ? value : null),
acceptBundle: () => {
accepted = true
},
clear: () => undefined,
markTransient: () => undefined,
wait: async () => undefined,
isCurrent: () => current,
}
const outcome = await createRefreshRunner(runtime)()
assert.equal(outcome.kind, 'transient_error')
assert.equal(accepted, false)
})
test('explicit rotations update only the current session', () => {
useAuthStore.getState().auth.setBundle(bundle)
applyAuthRotation({
access_token: 'rotated-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 60,
session: { ...bundle.session, last_active_at: 200 },
})
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
assert.strictEqual(useAuthStore.getState().auth.user, bundle.user)
assert.throws(
() =>
applyAuthRotation({
access_token: 'non-bearer-token',
token_type: 'Custom',
access_expires_at: bundle.access_expires_at + 120,
session: bundle.session,
}),
/Invalid authentication rotation response/
)
assert.throws(
() =>
applyAuthRotation({
access_token: 'non-current-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, current: false },
}),
/Invalid authentication rotation response/
)
assert.throws(
() =>
applyAuthRotation({
access_token: 'wrong-session-token',
token_type: 'Bearer',
access_expires_at: bundle.access_expires_at + 120,
session: { ...bundle.session, sid: 'session-b' },
}),
/session mismatch/
)
assert.equal(useAuthStore.getState().auth.accessToken, 'rotated-token')
})
test('sign-out clears user-scoped query, mutation, and authentication state', () => {
const queryClient = new QueryClient()
queryClient.setQueryData(['account', bundle.user.id], {
username: bundle.user.username,
})
queryClient.getMutationCache().build(queryClient, {
mutationKey: ['account', bundle.user.id, 'update'],
mutationFn: async () => undefined,
})
useAuthStore.getState().auth.setBundle(bundle)
useAuthStore.getState().auth.setPending2FAFlowToken('pending-flow')
clearAuthenticatedClientState(queryClient, false)
assert.equal(queryClient.getQueryCache().getAll().length, 0)
assert.equal(queryClient.getMutationCache().getAll().length, 0)
assert.equal(useAuthStore.getState().auth.user, null)
assert.equal(useAuthStore.getState().auth.accessToken, null)
assert.equal(useAuthStore.getState().auth.session, null)
assert.equal(useAuthStore.getState().auth.pending2FAFlowToken, null)
assert.equal(useAuthStore.getState().auth.bootstrapState, 'complete')
const nextBundle: AuthBundle = {
...bundle,
access_token: 'next-user-token',
user: { id: 84, username: 'next-user', role: 1 },
session: { ...bundle.session, sid: 'session-b' },
}
useAuthStore.getState().auth.setBundle(nextBundle)
assert.equal(
queryClient.getQueryData(['account', bundle.user.id]),
undefined
)
})
})