@@ -88,6 +88,8 @@ export class IdpLoginService {
8888 @inject ( AuthServiceBindings . MarkUserActivity , { optional : true } )
8989 private readonly userActivity ?: IUserActivity ,
9090 ) { }
91+
92+ private readonly msInSecond = 1000 ;
9193
9294 /**
9395 * Retrieves OpenID Connect configuration settings.
@@ -165,13 +167,27 @@ export class IdpLoginService {
165167 }
166168 try {
167169 const resultCode = await this . codeReader ( request . code ) ;
170+
171+ // Check if the authorization code has already been used (one-time-use enforcement)
172+ const isCodeRevoked = await this . revokedTokensRepo . get ( resultCode ) ;
173+ if ( isCodeRevoked ) {
174+ throw new HttpErrors . Unauthorized ( AuthErrorKeys . CodeExpired ) ;
175+ }
176+
168177 const payload = ( await this . jwtVerifier ( resultCode , {
169178 audience : request . clientId ,
170179 } ) ) as ClientAuthCode < User , typeof User . prototype . id > ;
171180 if ( payload . mfa ) {
172181 throw new HttpErrors . Unauthorized ( AuthErrorKeys . UserVerificationFailed ) ;
173182 }
174183
184+ // Revoke the auth code immediately after verification to prevent reuse
185+ await this . revokedTokensRepo . set (
186+ resultCode ,
187+ { token : resultCode } ,
188+ { ttl : authClient . authCodeExpiration * this . msInSecond } ,
189+ ) ;
190+
175191 if (
176192 payload . user ?. id &&
177193 ! ( await this . userRepo . firstTimeUser ( payload . user . id ) )
@@ -211,7 +227,6 @@ export class IdpLoginService {
211227 accessToken : string ,
212228 data : AnyObject ,
213229 ) : Promise < void > {
214- const ms = 1000 ;
215230 await this . refreshTokenRepo . set (
216231 refreshToken ,
217232 {
@@ -223,7 +238,7 @@ export class IdpLoginService {
223238 externalRefreshToken : ( user as AuthUser ) . externalRefreshToken ,
224239 tenantId : data . tenantId ,
225240 } ,
226- { ttl : authClient . refreshTokenExpiration * ms } ,
241+ { ttl : authClient . refreshTokenExpiration * this . msInSecond } ,
227242 ) ;
228243 }
229244
@@ -292,7 +307,6 @@ export class IdpLoginService {
292307 tenantId ?: string ,
293308 ) : Promise < TokenResponse > {
294309 const size = 32 ;
295- const ms = 1000 ;
296310 try {
297311 const user = await this . getUser ( payload ) ;
298312 const data : AnyObject = await this . getJwtPayload (
@@ -323,7 +337,7 @@ export class IdpLoginService {
323337 publicKey : key . publicKey ,
324338 } ,
325339 {
326- ttl : authClient . accessTokenExpiration * ms ,
340+ ttl : authClient . accessTokenExpiration * this . msInSecond ,
327341 } ,
328342 ) ;
329343 }
@@ -602,7 +616,6 @@ export class IdpLoginService {
602616 this . currentUser ?. authClientId ,
603617 ) ;
604618
605- const ms = 1000 ;
606619 // Save the public key to the cache for retrieval on facade
607620 await this . publicKeyRepo . set (
608621 key . kid ,
@@ -611,7 +624,7 @@ export class IdpLoginService {
611624 publicKey,
612625 } ,
613626 {
614- ttl : authClient . accessTokenExpiration * ms ,
627+ ttl : authClient . accessTokenExpiration * this . msInSecond ,
615628 } ,
616629 ) ;
617630 }
@@ -637,10 +650,9 @@ export class IdpLoginService {
637650 */
638651 private decodeAndGetExpiry ( token : string ) : number | null {
639652 const tokenData = jwt . decode ( token ) as TokenPayload | null ; // handle null result from decode
640- const ms = 1000 ;
641653
642654 if ( tokenData ?. exp ) {
643- return tokenData . exp * ms ;
655+ return tokenData . exp * this . msInSecond ;
644656 }
645657
646658 // If tokenData or exp is missing, return null to indicate no expiry
0 commit comments