66 */
77
88import { defaults , parseAffiliateData } from '@logto/affiliate' ;
9- import {
10- adminTenantId ,
11- MfaFactor ,
12- VerificationType ,
13- type User ,
14- type Mfa ,
15- type MfaVerification ,
16- } from '@logto/schemas' ;
9+ import { adminTenantId , MfaFactor , VerificationType , type User , type Mfa } from '@logto/schemas' ;
1710import { conditional , trySafe } from '@silverhand/essentials' ;
1811import { type IRouterContext } from 'koa-router' ;
1912
@@ -202,10 +195,33 @@ export const getAllUserEnabledMfaVerifications = (
202195 mfaSettings : Mfa ,
203196 user : User ,
204197 currentProfile ?: InteractionProfile
205- ) : MfaVerification [ ] => {
206- const storedVerifications = filterOutEmptyBackupCodes ( user . mfaVerifications ) . filter (
207- ( verification ) => mfaSettings . factors . includes ( verification . type )
208- ) ;
198+ ) : MfaFactor [ ] => {
199+ const storedVerifications = filterOutEmptyBackupCodes ( user . mfaVerifications )
200+ . filter ( ( verification ) => mfaSettings . factors . includes ( verification . type ) )
201+ // Filter out backup codes if all the codes are used
202+ . filter ( ( verification ) => {
203+ if ( verification . type !== MfaFactor . BackupCode ) {
204+ return true ;
205+ }
206+ return verification . codes . some ( ( code ) => ! code . usedAt ) ;
207+ } )
208+ . slice ( )
209+ // Sort by last used time, the latest used factor is the first one, backup code is always the last one
210+ . sort ( ( verificationA , verificationB ) => {
211+ if ( verificationA . type === MfaFactor . BackupCode ) {
212+ return 1 ;
213+ }
214+
215+ if ( verificationB . type === MfaFactor . BackupCode ) {
216+ return - 1 ;
217+ }
218+
219+ return (
220+ new Date ( verificationB . lastUsedAt ?? 0 ) . getTime ( ) -
221+ new Date ( verificationA . lastUsedAt ?? 0 ) . getTime ( )
222+ ) ;
223+ } )
224+ . map ( ( { type } ) => type ) ;
209225
210226 if ( ! EnvSet . values . isDevFeaturesEnabled ) {
211227 return storedVerifications ;
@@ -217,23 +233,11 @@ export const getAllUserEnabledMfaVerifications = (
217233 const implicitVerifications = [
218234 // Email MFA Factor: user has primaryEmail + Email factor enabled in SIE
219235 ...( mfaSettings . factors . includes ( MfaFactor . EmailVerificationCode ) && email
220- ? ( [
221- {
222- id : 'implicit-email-mfa' , // Fake ID for capability
223- type : MfaFactor . EmailVerificationCode ,
224- createdAt : new Date ( user . createdAt ) . toISOString ( ) ,
225- } ,
226- ] satisfies MfaVerification [ ] )
236+ ? [ MfaFactor . EmailVerificationCode ]
227237 : [ ] ) ,
228238 // Phone MFA Factor: user has primaryPhone + Phone factor enabled in SIE
229239 ...( mfaSettings . factors . includes ( MfaFactor . PhoneVerificationCode ) && phone
230- ? ( [
231- {
232- id : 'implicit-phone-mfa' , // Fake ID for capability
233- type : MfaFactor . PhoneVerificationCode ,
234- createdAt : new Date ( user . createdAt ) . toISOString ( ) ,
235- } ,
236- ] satisfies MfaVerification [ ] )
240+ ? [ MfaFactor . PhoneVerificationCode ]
237241 : [ ] ) ,
238242 ] ;
239243
0 commit comments