@@ -10,6 +10,7 @@ import { MongoDBOIDCError } from './types';
10
10
import {
11
11
errorString ,
12
12
getRefreshTokenId ,
13
+ getStableTokenSetId ,
13
14
messageFromError ,
14
15
normalizeObject ,
15
16
throwIfAborted ,
@@ -69,6 +70,10 @@ interface UserOIDCAuthState {
69
70
// A cached Client instance that uses the issuer metadata as discovered
70
71
// through serverOIDCMetadata.
71
72
client ?: Client ;
73
+ // A set of refresh token IDs which are currently being rejected, i.e.
74
+ // where the driver has called our callback indicating that the corresponding
75
+ // access token has become invalid.
76
+ discardingTokenSets ?: string [ ] ;
72
77
}
73
78
74
79
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
@@ -239,6 +244,7 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
239
244
lastIdTokenClaims : serializedState . lastIdTokenClaims
240
245
? { ...serializedState . lastIdTokenClaims }
241
246
: undefined ,
247
+ discardingTokenSets : serializedState . discardingTokenSets ,
242
248
} ;
243
249
this . updateStateWithTokenSet (
244
250
state ,
@@ -274,6 +280,7 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
274
280
lastIdTokenClaims : state . lastIdTokenClaims
275
281
? { ...state . lastIdTokenClaims }
276
282
: undefined ,
283
+ discardingTokenSets : state . discardingTokenSets ?? [ ] ,
277
284
} ,
278
285
] as const ;
279
286
} ) ,
@@ -652,6 +659,7 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
652
659
this . logger . emit ( 'mongodb-oidc-plugin:state-updated' , {
653
660
updateId,
654
661
timerDuration,
662
+ tokenSetId : getStableTokenSetId ( tokenSet ) ,
655
663
} ) ;
656
664
}
657
665
@@ -821,7 +829,8 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
821
829
822
830
private async initiateAuthAttempt (
823
831
state : UserOIDCAuthState ,
824
- driverAbortSignal ?: OIDCAbortSignal
832
+ driverAbortSignal ?: OIDCAbortSignal ,
833
+ { forceRefreshOrReauth = false } = { }
825
834
) : Promise < IdPServerResponse > {
826
835
throwIfAborted ( this . options . signal ) ;
827
836
throwIfAborted ( driverAbortSignal ) ;
@@ -843,11 +852,12 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
843
852
try {
844
853
get_tokens: {
845
854
if (
855
+ ! forceRefreshOrReauth &&
846
856
tokenExpiryInSeconds (
847
857
state . currentTokenSet ?. set ,
848
858
passIdTokenAsAccessToken
849
859
) >
850
- 5 * 60
860
+ 5 * 60
851
861
) {
852
862
this . logger . emit ( 'mongodb-oidc-plugin:skip-auth-attempt' , {
853
863
reason : 'not-expired' ,
@@ -915,6 +925,13 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
915
925
916
926
const { token_type, expires_at, access_token, id_token, refresh_token } =
917
927
state . currentTokenSet . set ;
928
+ const tokenSetId = getStableTokenSetId ( state . currentTokenSet . set ) ;
929
+
930
+ // We would not want to return the access token or ID token of a token set whose
931
+ // accompanying refresh token was passed to us by
932
+ const willRetryWithForceRefreshOrReauth =
933
+ ! forceRefreshOrReauth &&
934
+ ! ! state . discardingTokenSets ?. includes ( tokenSetId ) ;
918
935
919
936
this . logger . emit ( 'mongodb-oidc-plugin:auth-succeeded' , {
920
937
tokenType : token_type ?? null , // DPoP or Bearer
@@ -926,11 +943,20 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
926
943
idToken : id_token ,
927
944
refreshToken : refresh_token ,
928
945
} ,
946
+ tokenSetId,
947
+ forceRefreshOrReauth,
948
+ willRetryWithForceRefreshOrReauth,
929
949
} ) ;
930
950
951
+ if ( willRetryWithForceRefreshOrReauth ) {
952
+ return await this . initiateAuthAttempt ( state , driverAbortSignal , {
953
+ forceRefreshOrReauth : true ,
954
+ } ) ;
955
+ }
956
+
931
957
return {
932
958
accessToken : passIdTokenAsAccessToken ? id_token || '' : access_token ,
933
- refreshToken : refresh_token ,
959
+ refreshToken : tokenSetId ,
934
960
// Passing `expiresInSeconds: 0` results in the driver not caching the token.
935
961
// We perform our own caching here inside the plugin, so interactions with the
936
962
// cache of the driver are not really required or necessarily helpful.
@@ -969,20 +995,39 @@ export class MongoDBOIDCPluginImpl implements MongoDBOIDCPlugin {
969
995
username : params . username ,
970
996
} ) ;
971
997
972
- if ( state . currentAuthAttempt ) {
973
- return await state . currentAuthAttempt ;
998
+ // If the driver called us with a refresh token, that means that its corresponding
999
+ // access token has become invalid and we should always return a new one.
1000
+ if ( params . refreshToken ) {
1001
+ ( state . discardingTokenSets ??= [ ] ) . push ( params . refreshToken ) ;
1002
+ this . logger . emit ( 'mongodb-oidc-plugin:discarding-token-set' , {
1003
+ tokenSetId : params . refreshToken ,
1004
+ } ) ;
974
1005
}
975
1006
976
- const newAuthAttempt = this . initiateAuthAttempt (
977
- state ,
978
- params . timeoutContext
979
- ) ;
980
1007
try {
981
- state . currentAuthAttempt = newAuthAttempt ;
982
- return await newAuthAttempt ;
1008
+ if ( state . currentAuthAttempt ) {
1009
+ return await state . currentAuthAttempt ;
1010
+ }
1011
+
1012
+ const newAuthAttempt = this . initiateAuthAttempt (
1013
+ state ,
1014
+ params . timeoutContext
1015
+ ) ;
1016
+ try {
1017
+ state . currentAuthAttempt = newAuthAttempt ;
1018
+ return await newAuthAttempt ;
1019
+ } finally {
1020
+ if ( state . currentAuthAttempt === newAuthAttempt )
1021
+ state . currentAuthAttempt = null ;
1022
+ }
983
1023
} finally {
984
- if ( state . currentAuthAttempt === newAuthAttempt )
985
- state . currentAuthAttempt = null ;
1024
+ if ( params . refreshToken ) {
1025
+ const index =
1026
+ state . discardingTokenSets ?. indexOf ( params . refreshToken ) ?? - 1 ;
1027
+ if ( index > 0 ) {
1028
+ state . discardingTokenSets ?. splice ( index , 1 ) ;
1029
+ }
1030
+ }
986
1031
}
987
1032
}
988
1033
0 commit comments