diff --git a/packages/core/src/routes/experience/classes/mfa.ts b/packages/core/src/routes/experience/classes/mfa.ts index b90e0e0f5567..15565bb18215 100644 --- a/packages/core/src/routes/experience/classes/mfa.ts +++ b/packages/core/src/routes/experience/classes/mfa.ts @@ -72,16 +72,6 @@ const filterOutEmptyBackupCodes = ( return true; }); -const assertPendingCodes = (codes: string[], pendingCodes: string[]) => { - assertThat( - codes.length === pendingCodes.length && codes.every((code) => pendingCodes.includes(code)), - new RequestError({ - code: 'session.mfa.pending_info_not_found', - status: 422, - }) - ); -}; - /** * This class stores all the pending new MFA settings for a user. */ @@ -235,11 +225,26 @@ export class Mfa { /** * Generates new backup codes for the user. + * * @throws {RequestError} with status 422 if Backup Code is not enabled in the sign-in experience - * */ + * @throws {RequestError} with status 422 if the backup code is the only MFA factor + **/ async generateBackupCodes() { await this.checkMfaFactorsEnabledInSignInExperience([MfaFactor.BackupCode]); + const { mfaVerifications } = await this.interactionContext.getIdentifierUser(); + + const userHasOtherMfa = mfaVerifications.some((mfa) => mfa.type !== MfaFactor.BackupCode); + const hasOtherNewMfa = Boolean(this.#totp ?? this.#webAuthn?.length); + + assertThat( + userHasOtherMfa || hasOtherNewMfa, + new RequestError({ + code: 'session.mfa.backup_code_can_not_be_alone', + status: 422, + }) + ); + const codes = generateBackupCodes(); this.#pendingBackupCodes = codes; @@ -247,37 +252,28 @@ export class Mfa { } /** - * * Add backup codes to the user account. * - * @throws {RequestError} with status 422 if Backup Code is not enabled in the sign-in experience - * @throws {RequestError} with status 422 if the backup code is the only MFA factor - * @throws {RequestError} with status 422 if the codes is not the same as the pending backup codes - * + * - This is to ensure the user has received the backup codes before adding them to the account. * - Any existing backup code factor will be replaced with the new one. + * + * @throws {RequestError} with status 404 if no pending backup codes are found */ - async addBackupCodes(codes: string[]) { - await this.checkMfaFactorsEnabledInSignInExperience([MfaFactor.BackupCode]); - const { mfaVerifications } = await this.interactionContext.getIdentifierUser(); - - const userHasOtherMfa = mfaVerifications.some((mfa) => mfa.type !== MfaFactor.BackupCode); - const hasOtherNewMfa = Boolean(this.#totp ?? this.#webAuthn?.length); - + async addBackupCodes() { assertThat( - userHasOtherMfa || hasOtherNewMfa, + this.#pendingBackupCodes?.length, new RequestError({ - code: 'session.mfa.backup_code_can_not_be_alone', - status: 422, + code: 'session.mfa.pending_info_not_found', + status: 404, }) ); - assertPendingCodes(codes, this.#pendingBackupCodes ?? []); - - this.#pendingBackupCodes = undefined; this.#backupCode = { type: MfaFactor.BackupCode, - codes, + codes: this.#pendingBackupCodes, }; + + this.#pendingBackupCodes = undefined; } /** diff --git a/packages/core/src/routes/experience/profile-routes.ts b/packages/core/src/routes/experience/profile-routes.ts index 80db3483b26c..1358208fd052 100644 --- a/packages/core/src/routes/experience/profile-routes.ts +++ b/packages/core/src/routes/experience/profile-routes.ts @@ -218,14 +218,10 @@ export default function interactionProfileRoutes( router.post( `${experienceRoutes.mfa}/backup-codes`, koaGuard({ - body: z.object({ - codes: z.array(z.string()), - }), status: [204, 400, 403, 404, 422], }), async (ctx, next) => { const { experienceInteraction } = ctx; - const { codes } = ctx.guard.body; // Guard current interaction event is not ForgotPassword assertThat( @@ -238,8 +234,7 @@ export default function interactionProfileRoutes( // Guard current interaction event is identified and MFA verified await experienceInteraction.guardMfaVerificationStatus(); - - await experienceInteraction.mfa.addBackupCodes(codes); + await experienceInteraction.mfa.addBackupCodes(); await experienceInteraction.save(); ctx.status = 204;