Skip to content

Commit 1e7dd2e

Browse files
committed
fix(passport): delete one-time state on callback
1 parent da961e2 commit 1e7dd2e

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/passport.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,13 @@ export class Strategy implements passport.Strategy {
541541
): Promise<void> {
542542
try {
543543
const sessionKey = this._sessionKey
544-
const stateData: StateData = (req as any).session[sessionKey]
544+
const session: Record<string, StateData | undefined> = (req as any)
545+
.session
546+
const stateData = session[sessionKey]
547+
548+
// Consume the one-time transaction state as soon as the callback is
549+
// handled so it cannot be reused by a later request.
550+
delete session[sessionKey]
545551

546552
if (!stateData?.code_verifier) {
547553
return this.fail({

test/passport.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,3 +1395,45 @@ test('custom sessionKey isolates state from another strategy', async (t) => {
13951395
t.truthy(harness1.session['strategy-b'])
13961396
t.not(harness1.session['strategy-a'], harness1.session['strategy-b'])
13971397
})
1398+
1399+
// --- Session state cleanup ---
1400+
1401+
test('successful callback calls success and clears session state', async (t) => {
1402+
const { port, server } = await startTokenEndpoint()
1403+
t.teardown(() => close(server))
1404+
1405+
const harness = createStrategyHarness(port)
1406+
const redirectTo = await doAuthorizationRequest(harness)
1407+
1408+
await doAuthorizationCodeGrant(
1409+
harness,
1410+
`${new URL(redirectTo).origin}/cb?code=ok`,
1411+
)
1412+
1413+
t.truthy(harness.results.successUser)
1414+
t.deepEqual(harness.session, {})
1415+
})
1416+
1417+
test('replayed callback fails because state was consumed', async (t) => {
1418+
const { port, server } = await startTokenEndpoint()
1419+
t.teardown(() => close(server))
1420+
1421+
const harness = createStrategyHarness(port)
1422+
const redirectTo = await doAuthorizationRequest(harness)
1423+
const callbackUrl = `${new URL(redirectTo).origin}/cb?code=ok`
1424+
1425+
// First callback succeeds
1426+
await doAuthorizationCodeGrant(harness, callbackUrl)
1427+
t.truthy(harness.results.successUser)
1428+
1429+
// Reset results
1430+
harness.results.successUser = undefined
1431+
harness.results.failInfo = undefined
1432+
1433+
// Second callback with same session should fail
1434+
await doAuthorizationCodeGrant(harness, callbackUrl)
1435+
t.truthy(harness.results.failInfo)
1436+
t.like(harness.results.failInfo as Record<string, unknown>, {
1437+
message: 'Unable to verify authorization request state',
1438+
})
1439+
})

0 commit comments

Comments
 (0)