@@ -3,6 +3,10 @@ import {
33 account ,
44 auditLog ,
55 member ,
6+ oauthAccessToken ,
7+ oauthClient ,
8+ oauthConsent ,
9+ oauthTokenFamily ,
610 organization ,
711 session ,
812 ssoProvider ,
@@ -309,6 +313,84 @@ describe('authentication audit history in PostgreSQL', () => {
309313 ) . toHaveLength ( 0 )
310314 } )
311315
316+ it ( 'ends every pre-reset session and OAuth token when a password reset completes' , async ( ) => {
317+ await login ( )
318+ const [ current ] = await db
319+ . select ( { id : session . id } )
320+ . from ( session )
321+ . where ( eq ( session . userId , userId ) )
322+ const clientId = generateId ( )
323+ const familyId = generateId ( )
324+ const now = new Date ( )
325+ const expiresAt = new Date ( now . getTime ( ) + 86_400_000 )
326+ await db . insert ( oauthClient ) . values ( {
327+ id : clientId ,
328+ clientId,
329+ name : 'Password reset OAuth fixture' ,
330+ public : true ,
331+ tokenEndpointAuthMethod : 'none' ,
332+ grantTypes : [ 'authorization_code' , 'refresh_token' ] ,
333+ redirectUris : [ 'http://127.0.0.1/callback' ] ,
334+ } )
335+ try {
336+ await db . insert ( oauthConsent ) . values ( {
337+ id : generateId ( ) ,
338+ clientId,
339+ userId,
340+ scopes : [ 'api:read' ] ,
341+ createdAt : now ,
342+ updatedAt : now ,
343+ } )
344+ await db . insert ( oauthTokenFamily ) . values ( {
345+ id : familyId ,
346+ clientId,
347+ userId,
348+ sessionId : current . id ,
349+ createdAt : now ,
350+ expiresAt,
351+ } )
352+ await db . insert ( oauthAccessToken ) . values ( {
353+ id : generateId ( ) ,
354+ token : generateId ( ) ,
355+ clientId,
356+ userId,
357+ scopes : [ 'api:read' ] ,
358+ createdAt : now ,
359+ expiresAt,
360+ } )
361+
362+ const requested = await auth . handler (
363+ request ( '/request-password-reset' , {
364+ email,
365+ redirectTo : 'http://localhost:3000/reset-password' ,
366+ } )
367+ )
368+ expect ( requested . status ) . toBe ( 200 )
369+ const [ resetToken ] = await db
370+ . select ( { identifier : verification . identifier } )
371+ . from ( verification )
372+ . where ( eq ( verification . value , userId ) )
373+ const completed = await auth . handler (
374+ request ( '/reset-password' , {
375+ token : resetToken . identifier . replace ( 'reset-password:' , '' ) ,
376+ newPassword : 'Audit-fixture-new-password-37!' ,
377+ } )
378+ )
379+ expect ( completed . status ) . toBe ( 200 )
380+
381+ expect ( await sessionCount ( ) ) . toBe ( 0 )
382+ expect (
383+ await db . select ( ) . from ( oauthTokenFamily ) . where ( eq ( oauthTokenFamily . clientId , clientId ) )
384+ ) . toHaveLength ( 0 )
385+ expect (
386+ await db . select ( ) . from ( oauthAccessToken ) . where ( eq ( oauthAccessToken . clientId , clientId ) )
387+ ) . toHaveLength ( 0 )
388+ await expectEvent ( 'password.reset' , userId )
389+ } finally {
390+ await db . delete ( oauthClient ) . where ( eq ( oauthClient . clientId , clientId ) )
391+ }
392+ } )
393+
312394 it ( 'scopes an SSO admission refusal to the configured provider organization when the account belongs elsewhere' , async ( ) => {
313395 const providerOrganizationId = generateId ( )
314396 const providerId = generateId ( )
0 commit comments