mirror of
https://github.com/logto-io/logto.git
synced 2024-12-30 20:33:54 -05:00
fix(core): should not be able to skip mandatory MFA (#6892)
This commit is contained in:
parent
10766d4517
commit
cf3aa1a40e
4 changed files with 70 additions and 3 deletions
|
@ -285,7 +285,7 @@ export class Mfa {
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the policy is not mandatory and the user has skipped MFA, then there is nothing to check
|
// If the policy is not mandatory and the user has skipped MFA, then there is nothing to check
|
||||||
if ((policy !== MfaPolicy.Mandatory && this.#mfaSkipped) ?? isMfaSkipped(logtoConfig)) {
|
if (policy !== MfaPolicy.Mandatory && (this.#mfaSkipped ?? isMfaSkipped(logtoConfig))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,7 @@ export const validateMandatoryBindMfa = async (
|
||||||
return interaction;
|
return interaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the policy is not mandatory and the user has skipped MFA, skip check
|
// If the policy is not mandatory and the user has skipped MFA (in the current interaction), skip check
|
||||||
const { mfaSkipped } = interaction;
|
const { mfaSkipped } = interaction;
|
||||||
if (policy !== MfaPolicy.Mandatory && mfaSkipped) {
|
if (policy !== MfaPolicy.Mandatory && mfaSkipped) {
|
||||||
return interaction;
|
return interaction;
|
||||||
|
@ -177,7 +177,8 @@ export const validateMandatoryBindMfa = async (
|
||||||
const { accountId } = interaction;
|
const { accountId } = interaction;
|
||||||
const { mfaVerifications, logtoConfig } = await tenant.queries.users.findUserById(accountId);
|
const { mfaVerifications, logtoConfig } = await tenant.queries.users.findUserById(accountId);
|
||||||
|
|
||||||
if (isMfaSkipped(logtoConfig)) {
|
// If the policy is not mandatory and the user has skipped MFA (not in the current interaction), skip check
|
||||||
|
if (policy !== MfaPolicy.Mandatory && isMfaSkipped(logtoConfig)) {
|
||||||
return interaction;
|
return interaction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,35 @@ describe('Bind MFA APIs happy path', () => {
|
||||||
await deleteUser(userId);
|
await deleteUser(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should reject if user has not binded MFA even if it is skipped', async () => {
|
||||||
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
|
await userApi.create({ username, password });
|
||||||
|
|
||||||
|
// Set to skipable policy first to update the user's mfa skipped state
|
||||||
|
await enableUserControlledMfaWithTotp();
|
||||||
|
const client = await initExperienceClient();
|
||||||
|
await identifyUserWithUsernamePassword(client, username, password);
|
||||||
|
await expectRejects(client.submitInteraction(), {
|
||||||
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
});
|
||||||
|
await client.skipMfaBinding();
|
||||||
|
const { redirectTo } = await client.submitInteraction();
|
||||||
|
const userId = await processSession(client, redirectTo);
|
||||||
|
await logoutClient(client);
|
||||||
|
|
||||||
|
// Set to mandatory policy to check the user's mfa skipped state
|
||||||
|
await enableMandatoryMfaWithTotpAndBackupCode();
|
||||||
|
const client2 = await initExperienceClient();
|
||||||
|
await identifyUserWithUsernamePassword(client2, username, password);
|
||||||
|
await expectRejects(client2.submitInteraction(), {
|
||||||
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteUser(userId);
|
||||||
|
});
|
||||||
|
|
||||||
it('should bind backup codes on sign-in', async () => {
|
it('should bind backup codes on sign-in', async () => {
|
||||||
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
const { username, password } = generateNewUserProfile({ username: true, password: true });
|
||||||
const user = await userApi.create({ username, password });
|
const user = await userApi.create({ username, password });
|
||||||
|
|
|
@ -157,6 +157,43 @@ describe('sign in and fulfill mfa (mandatory TOTP)', () => {
|
||||||
await deleteUser(user.id);
|
await deleteUser(user.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fail with missing_mfa error even if mfa is skipped', async () => {
|
||||||
|
// Set to skipable policy first to update the user's mfa skipped state
|
||||||
|
await enableUserControlledMfaWithTotp();
|
||||||
|
const { userProfile, user } = await generateNewUser({ username: true, password: true });
|
||||||
|
const client = await initClient();
|
||||||
|
await client.successSend(putInteraction, {
|
||||||
|
event: InteractionEvent.SignIn,
|
||||||
|
identifier: {
|
||||||
|
username: userProfile.username,
|
||||||
|
password: userProfile.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await expectRejects(client.submitInteraction(), {
|
||||||
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
});
|
||||||
|
await client.successSend(skipMfaBinding);
|
||||||
|
await client.submitInteraction();
|
||||||
|
|
||||||
|
// Set to mandatory policy to check the user's mfa skipped state
|
||||||
|
await enableMandatoryMfaWithTotp();
|
||||||
|
const client2 = await initClient();
|
||||||
|
await client2.successSend(putInteraction, {
|
||||||
|
event: InteractionEvent.SignIn,
|
||||||
|
identifier: {
|
||||||
|
username: userProfile.username,
|
||||||
|
password: userProfile.password,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await expectRejects(client2.submitInteraction(), {
|
||||||
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
});
|
||||||
|
|
||||||
|
await deleteUser(user.id);
|
||||||
|
});
|
||||||
|
|
||||||
it('should sign in and fulfill totp', async () => {
|
it('should sign in and fulfill totp', async () => {
|
||||||
const { id } = await registerWithMfa();
|
const { id } = await registerWithMfa();
|
||||||
await deleteUser(id);
|
await deleteUser(id);
|
||||||
|
|
Loading…
Reference in a new issue