fix(auth): revoke captured client sessions safely on logout

This commit is contained in:
2026-10-05 17:13:46 +08:00
parent e139c5c87b
commit 3703d0a677
8 changed files with 175 additions and 28 deletions
+15 -1
View File
@@ -35,7 +35,7 @@ function harness(t, expired = false) {
class ApiError extends Error { constructor(status) { super('failed'); this.status = status } }
const module = compile('./auth.ts', {
vue, pinia, '@/api/session': session,
'@/modules/auth/api/auth': { logoutMerchant: async refresh => behavior.logout?.(refresh) },
'@/modules/auth/api/auth': { logoutMerchant: async (access, refresh) => behavior.logout?.(refresh, access) },
'@/modules/auth/utils/jwt': compile('../utils/jwt.ts'),
'@/api/http': { ApiError, refreshAuthenticatedSession: async () => {
session.persistTokenPair({ accessToken: token(1, 1, 1, 'refreshed'), refreshToken: 'refresh-new' })
@@ -152,3 +152,17 @@ test('store logout clears immediately and late revocation failure preserves a la
assert.equal(h.auth.isAuthenticated, true)
assert.equal(h.session.getRefreshToken(), 'refresh-b')
})
test('duplicate logout joins captured pair revocation and does not send again',async t=>{
const h=harness(t);const remote=deferred();let count=0,sent
h.behavior.logout=(refresh,access)=>{count++;sent={refresh,access};return remote.promise}
const a=h.auth.logout(),b=h.auth.logout();assert.equal(h.auth.isAuthenticated,false);assert.equal(sent.access,token());assert.equal(sent.refresh,'refresh-a');assert.equal(count,1);remote.resolve();await Promise.all([a,b])
})
test('B logout during A pending revokes B; A finally cannot release B deduplication',async t=>{
const h=harness(t);const a=deferred(),b=deferred(),calls=[]
h.behavior.logout=(refresh,access)=>{calls.push({refresh,access});return calls.length===1?a.promise:b.promise}
const leavingA=h.auth.logout();h.rotate(token(2,2,2));await flush();assert.equal(h.auth.isAuthenticated,true)
const leavingB=h.auth.logout();assert.equal(h.auth.isAuthenticated,false);assert.equal(h.session.getAccessToken(),'');assert.equal(calls.length,2);assert.equal(calls[1].access,token(2,2,2))
a.resolve();await leavingA;const duplicateB=h.auth.logout();assert.equal(calls.length,2);let duplicateFinished=false;void duplicateB.then(()=>{duplicateFinished=true});await flush();assert.equal(duplicateFinished,false);b.resolve();await Promise.all([leavingB,duplicateB])
})