mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
fix(core): validate mandatory mfa (#4639)
This commit is contained in:
parent
bfb1bf6d06
commit
15ab4d587e
4 changed files with 25 additions and 19 deletions
|
@ -51,7 +51,7 @@ const mfaRequiredCtx = {
|
||||||
signInExperience: {
|
signInExperience: {
|
||||||
...mockSignInExperience,
|
...mockSignInExperience,
|
||||||
mfa: {
|
mfa: {
|
||||||
factors: [MfaFactor.TOTP],
|
factors: [MfaFactor.TOTP, MfaFactor.WebAuthn],
|
||||||
policy: MfaPolicy.Mandatory,
|
policy: MfaPolicy.Mandatory,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -79,8 +79,11 @@ describe('validateMandatoryBindMfa', () => {
|
||||||
validateMandatoryBindMfa(tenantContext, mfaRequiredCtx, interaction)
|
validateMandatoryBindMfa(tenantContext, mfaRequiredCtx, interaction)
|
||||||
).rejects.toMatchError(
|
).rejects.toMatchError(
|
||||||
new RequestError(
|
new RequestError(
|
||||||
{ code: 'user.missing_mfa', status: 422 },
|
{
|
||||||
{ missingFactors: [MfaFactor.TOTP] }
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
},
|
||||||
|
{ availableFactors: [MfaFactor.TOTP, MfaFactor.WebAuthn] }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -111,8 +114,11 @@ describe('validateMandatoryBindMfa', () => {
|
||||||
validateMandatoryBindMfa(tenantContext, mfaRequiredCtx, signInInteraction)
|
validateMandatoryBindMfa(tenantContext, mfaRequiredCtx, signInInteraction)
|
||||||
).rejects.toMatchError(
|
).rejects.toMatchError(
|
||||||
new RequestError(
|
new RequestError(
|
||||||
{ code: 'user.missing_mfa', status: 422 },
|
{
|
||||||
{ missingFactors: [MfaFactor.TOTP] }
|
code: 'user.missing_mfa',
|
||||||
|
status: 422,
|
||||||
|
},
|
||||||
|
{ availableFactors: [MfaFactor.TOTP, MfaFactor.WebAuthn] }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,15 +83,14 @@ export const validateMandatoryBindMfa = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event === InteractionEvent.Register) {
|
if (event === InteractionEvent.Register) {
|
||||||
const missingFactors = factors.filter((factor) => factor !== bindMfa?.type);
|
|
||||||
assertThat(
|
assertThat(
|
||||||
missingFactors.length === 0,
|
bindMfa && factors.includes(bindMfa.type),
|
||||||
new RequestError(
|
new RequestError(
|
||||||
{
|
{
|
||||||
code: 'user.missing_mfa',
|
code: 'user.missing_mfa',
|
||||||
status: 422,
|
status: 422,
|
||||||
},
|
},
|
||||||
{ missingFactors }
|
{ availableFactors: factors.map((factor) => factor) }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -99,17 +98,18 @@ export const validateMandatoryBindMfa = async (
|
||||||
if (event === InteractionEvent.SignIn) {
|
if (event === InteractionEvent.SignIn) {
|
||||||
const { accountId } = interaction;
|
const { accountId } = interaction;
|
||||||
const { mfaVerifications } = await tenant.queries.users.findUserById(accountId);
|
const { mfaVerifications } = await tenant.queries.users.findUserById(accountId);
|
||||||
const missingFactors = factors.filter(
|
const hasFactorInBind = Boolean(bindMfa && factors.includes(bindMfa.type));
|
||||||
(factor) => factor !== bindMfa?.type && !mfaVerifications.some(({ type }) => type === factor)
|
const hasFactorInUser = factors.some((factor) =>
|
||||||
|
mfaVerifications.some(({ type }) => type === factor)
|
||||||
);
|
);
|
||||||
assertThat(
|
assertThat(
|
||||||
missingFactors.length === 0,
|
hasFactorInBind || hasFactorInUser,
|
||||||
new RequestError(
|
new RequestError(
|
||||||
{
|
{
|
||||||
code: 'user.missing_mfa',
|
code: 'user.missing_mfa',
|
||||||
status: 422,
|
status: 422,
|
||||||
},
|
},
|
||||||
{ missingFactors }
|
{ availableFactors: factors.map((factor) => factor) }
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,23 +27,23 @@ const useMfaVerificationErrorHandler = ({ replace }: Options = {}) => {
|
||||||
() => ({
|
() => ({
|
||||||
'user.missing_mfa': (error) => {
|
'user.missing_mfa': (error) => {
|
||||||
const [_, data] = validate(error.data, missingMfaFactorsErrorDataGuard);
|
const [_, data] = validate(error.data, missingMfaFactorsErrorDataGuard);
|
||||||
const missingFactors = data?.missingFactors ?? [];
|
const availableFactors = data?.availableFactors ?? [];
|
||||||
|
|
||||||
if (missingFactors.length === 0) {
|
if (availableFactors.length === 0) {
|
||||||
setToast(error.message);
|
setToast(error.message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (missingFactors.length > 1) {
|
if (availableFactors.length > 1) {
|
||||||
const state: MfaFactorsState = { availableFactors: missingFactors };
|
const state: MfaFactorsState = { availableFactors };
|
||||||
navigate({ pathname: `/${UserMfaFlow.MfaBinding}` }, { replace, state });
|
navigate({ pathname: `/${UserMfaFlow.MfaBinding}` }, { replace, state });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const factor = missingFactors[0];
|
const factor = availableFactors[0];
|
||||||
|
|
||||||
if (factor === MfaFactor.TOTP) {
|
if (factor === MfaFactor.TOTP) {
|
||||||
void startTotpBinding(missingFactors);
|
void startTotpBinding(availableFactors);
|
||||||
}
|
}
|
||||||
// Todo: @xiaoyijun handle other factors
|
// Todo: @xiaoyijun handle other factors
|
||||||
},
|
},
|
||||||
|
|
|
@ -72,7 +72,7 @@ const mfaFactorsGuard = s.array(
|
||||||
);
|
);
|
||||||
|
|
||||||
export const missingMfaFactorsErrorDataGuard = s.object({
|
export const missingMfaFactorsErrorDataGuard = s.object({
|
||||||
missingFactors: mfaFactorsGuard,
|
availableFactors: mfaFactorsGuard,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const requireMfaFactorsErrorDataGuard = s.object({
|
export const requireMfaFactorsErrorDataGuard = s.object({
|
||||||
|
|
Loading…
Reference in a new issue