Skip to content

Commit

Permalink
refactor(core): refactor bind backup codes
Browse files Browse the repository at this point in the history
refactor bind backup codes
  • Loading branch information
simeng-li committed Jul 25, 2024
1 parent 5ddddbc commit ddae22a
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 36 deletions.
56 changes: 26 additions & 30 deletions packages/core/src/routes/experience/classes/mfa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,6 @@ const filterOutEmptyBackupCodes = (
return true;

Check warning on line 72 in packages/core/src/routes/experience/classes/mfa.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/mfa.ts#L66-L72

Added lines #L66 - L72 were not covered by tests
});

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.
*/
Expand Down Expand Up @@ -235,49 +225,55 @@ 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;

return this.#pendingBackupCodes;
}

Check warning on line 252 in packages/core/src/routes/experience/classes/mfa.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/mfa.ts#L233-L252

Added lines #L233 - L252 were not covered by tests

/**
*
* 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;
}

Check warning on line 277 in packages/core/src/routes/experience/classes/mfa.ts

View check run for this annotation

Codecov / codecov/patch

packages/core/src/routes/experience/classes/mfa.ts#L263-L277

Added lines #L263 - L277 were not covered by tests

/**
Expand Down
7 changes: 1 addition & 6 deletions packages/core/src/routes/experience/profile-routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,14 +218,10 @@ export default function interactionProfileRoutes<T extends WithLogContext>(
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(
Expand All @@ -238,8 +234,7 @@ export default function interactionProfileRoutes<T extends WithLogContext>(

// 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;
Expand Down

0 comments on commit ddae22a

Please sign in to comment.