@@ -28,6 +28,7 @@ import assertThat from '#src/utils/assert-that.js';
2828
2929import { type InteractionContext } from '../types.js' ;
3030
31+ import { getAllUserEnabledMfaVerifications } from './helpers.js' ;
3132import { SignInExperienceValidator } from './libraries/sign-in-experience-validator.js' ;
3233
3334export type MfaData = {
@@ -73,19 +74,6 @@ const isMfaSkipped = (logtoConfig: JsonObject): boolean => {
7374 return parsed . success ? parsed . data [ userMfaDataKey ] . skipped === true : false ;
7475} ;
7576
76- /**
77- * Filter out backup codes mfa verifications that have been used
78- */
79- const filterOutEmptyBackupCodes = (
80- mfaVerifications : User [ 'mfaVerifications' ]
81- ) : User [ 'mfaVerifications' ] =>
82- mfaVerifications . filter ( ( mfa ) => {
83- if ( mfa . type === MfaFactor . BackupCode ) {
84- return mfa . codes . some ( ( code ) => ! code . usedAt ) ;
85- }
86- return true ;
87- } ) ;
88-
8977/**
9078 * This class stores all the pending new MFA settings for a user.
9179 */
@@ -261,11 +249,11 @@ export class Mfa {
261249
262250 await this . checkMfaFactorsEnabledInSignInExperience ( [ MfaFactor . BackupCode ] ) ;
263251
264- const { mfaVerifications } = await this . interactionContext . getIdentifiedUser ( ) ;
265- const userHasOtherMfa = mfaVerifications . some ( ( mfa ) => mfa . type !== MfaFactor . BackupCode ) ;
266- const hasOtherNewMfa = Boolean ( this . #totp ?? this . #webAuthn ?. length ) ;
252+ const userFactors = await this . getUserMfaFactors ( ) ;
253+ const hasOtherMfaFactors = userFactors . some ( ( factor ) => factor !== MfaFactor . BackupCode ) ;
254+
267255 assertThat (
268- userHasOtherMfa || hasOtherNewMfa ,
256+ hasOtherMfaFactors ,
269257 new RequestError ( {
270258 code : 'session.mfa.backup_code_can_not_be_alone' ,
271259 status : 422 ,
@@ -298,11 +286,7 @@ export class Mfa {
298286 return ;
299287 }
300288
301- const {
302- mfaVerifications,
303- logtoConfig,
304- id : userId ,
305- } = await this . interactionContext . getIdentifiedUser ( ) ;
289+ const { logtoConfig, id : userId } = await this . interactionContext . getIdentifiedUser ( ) ;
306290
307291 const isMfaRequiredByUserOrganizations = await this . isMfaRequiredByUserOrganizations (
308292 mfaSettings ,
@@ -334,7 +318,7 @@ export class Mfa {
334318
335319 const availableFactors = factors . filter ( ( factor ) => factor !== MfaFactor . BackupCode ) ;
336320
337- const factorsInUser = filterOutEmptyBackupCodes ( mfaVerifications ) . map ( ( { type } ) => type ) ;
321+ const factorsInUser = await this . getUserMfaFactors ( ) ;
338322 const factorsInBind = this . bindMfaFactorsArray . map ( ( { type } ) => type ) ;
339323 const linkedFactors = deduplicate ( [ ...factorsInUser , ...factorsInBind ] ) ;
340324
@@ -395,4 +379,21 @@ export class Mfa {
395379
396380 return organizations . some ( ( { isMfaRequired } ) => isMfaRequired ) ;
397381 }
382+
383+ private async getUserMfaFactors ( ) : Promise < MfaFactor [ ] > {
384+ const mfaSettings = await this . signInExperienceValidator . getMfaSettings ( ) ;
385+ const user = await this . interactionContext . getIdentifiedUser ( ) ;
386+ const currentProfile = this . interactionContext . getCurrentProfile ( ) ;
387+
388+ const existingVerifications = getAllUserEnabledMfaVerifications (
389+ mfaSettings ,
390+ user ,
391+ currentProfile
392+ ) ;
393+ return [
394+ ...existingVerifications . map ( ( { type } ) => type ) ,
395+ ...( this . #totp ? [ MfaFactor . TOTP ] : [ ] ) ,
396+ ...( this . #webAuthn?. length ? [ MfaFactor . WebAuthn ] : [ ] ) ,
397+ ] . filter ( Boolean ) ;
398+ }
398399}
0 commit comments