Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .trivyignore.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
vulnerabilities:
- id: GHSA-wrw7-89jp-8q8g
expired_at: 2026-10-30
expired_at: 2026-11-30
statement: "glib is a transitive dependency of Tauri which we cannot update ourselves. Waiting for tauri to finish migration to gtk4-rs: https://github.com/tauri-apps/tauri/issues/12563"
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,62 @@ import { Icon } from '../../../../../shared/components/Icon';
import { RadioIndicator } from '../../../../../shared/components/RadioIndicator/RadioIndicator';
import { mfaMethodIcon } from '../../../../../shared/consts';
import { MfaMethod, type MfaMethodValue } from '../../../../../shared/rust-api/types';
import { mfaToText } from '../../../../../shared/utils/mfa';
import {
mfaToText,
type OpenIdInstance,
openIdProviderName,
} from '../../../../../shared/utils/mfa';

type FactorText = {
title: string;
description: string;
};

const factorText = (method: MfaMethodValue): FactorText => {
const factorText = (method: MfaMethodValue, instance?: OpenIdInstance): FactorText => {
switch (method) {
case MfaMethod.Totp:
return {
title: 'Security code from your authenticator app',
description: `Use an app on your phone that generates a temporary code to confirm it's you (Google Authenticator, Microsoft Authenticator, Authy).`,
description: `Use an authenticator app on your phone, such as Google Authenticator, Microsoft Authenticator, or Authy, to generate a temporary code and verify your identity.`,
};
case MfaMethod.Email:
return {
title: `Code from email`,
description: `We'll send a temporary security code to your email. Enter the code to confirm it's you and continue.`,
description: `Enter the temporary security code sent to your email to verify your identity and continue.`,
};
case MfaMethod.Oidc:
return {
title: mfaToText(MfaMethod.Oidc),
description: `Sign in with your identity provider in the browser to confirm it's you.`,
title: `Verify with ${openIdProviderName(instance)}`,
description: `Sign in through your identity provider in the browser to verify your identity.`,
};
case MfaMethod.Biometric:
return { title: '', description: '' };
case MfaMethod.MobileApprove:
return { title: '', description: '' };
case MfaMethod.Fido2:
return {
title: 'Security key',
description: `Use a security key registered to your account, such as a YubiKey, to confirm it's you.`,
title: mfaToText(MfaMethod.Fido2),
description: `Verify your identity using a registered security key, such as a YubiKey.`,
};
}
};

interface Props {
mfaMethod: MfaMethodValue;
instance?: OpenIdInstance;
selected: boolean;
disabled?: boolean;
onClick: () => void;
}

export const ConfigureMfaVerificatorFactorSelector = ({
mfaMethod,
instance,
selected,
disabled = false,
onClick,
}: Props) => {
const { title, description } = factorText(mfaMethod);
const { title, description } = factorText(mfaMethod, instance);

return (
<div
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,22 +117,23 @@ export const ConfigureSelectMethodsStep = ({ onCancel }: Props) => {
const available = isMfaMethodAvailable(method, instance);
const satisfied = configuredMethods.includes(method) && available;
const locked = isMfaMethodLocked(method, instance);
const methodName = mfaToText(method, instance ?? undefined);

let hint: string | null = null;
let badge: string | undefined;
if (!available || locked) {
hint = `${mfaToText(method, instance ?? undefined)} is not available on this Defguard instance.`;
hint = `${methodName} is not available on this Defguard instance.`;
badge = 'Unavailable';
} else if (disabled && !configured) {
if (satisfied) {
// Reached only via the email fallback, which registers the factor as it verifies.
hint = `${mfaToText(method)} is required to continue.`;
hint = `${methodName} is required to continue.`;
badge = 'Required';
} else if (isClientConfigurableMethod(method)) {
hint = `This Defguard instance does not support configuring ${mfaToText(method)} from the desktop client.`;
hint = `This Defguard instance does not support configuring ${methodName} from the desktop client.`;
badge = 'Not configurable';
} else {
hint = `${mfaToText(method)} cannot be configured in the desktop client.`;
hint = `${methodName} cannot be configured in the desktop client.`;
badge = 'Not configurable';
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,20 @@ export const ConfigureSelectVerificationStep = ({ onCancel }: Props) => {
hideScrollContainer
withControls
>
<FullPageTitle title="Additional verification required" />
<FullPageTitle title="Verify your identity to add a new MFA method" />
<p className="description">
<span>{`To continue, please select one of your already set up methods. This is an extra security step to confirm your identity.`}</span>
<span>{`For security, verify your identity using one of your existing MFA methods. You'll be able to add a new method after verification.`}</span>
</p>
{isPresent(error) && <p className="error">{error}</p>}
<div className="label">
<p>Select an existing method</p>
</div>
<div className="methods">
{methods.map((method) => (
<ConfigureMfaVerificatorFactorSelector
key={method}
mfaMethod={method}
instance={instance}
selected={selected === method}
onClick={() => {
setSelected(method);
Expand Down Expand Up @@ -102,7 +106,7 @@ export const ConfigureSelectVerificationStep = ({ onCancel }: Props) => {
)}
{selected !== MfaMethod.Oidc && (
<Button
text="Continue"
text="Verify identity"
variant={ButtonVariant.Primary}
disabled={selected === undefined || isCancelling}
onClick={() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
#configure-select-verification-step {
.label {
padding-bottom: var(--spacing-md);

& > p {
font: var(--t-body-xs-500);
color: var(--fg-white-100);
}
}

.methods {
display: flex;
flex-flow: column;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const ConfigureMfaWizard = ({ onCancel, onSessionExpired }: Props) => {
[ConfigureMfaStep.Fido2]: {
id: ConfigureMfaStep.Fido2,
order: 2,
label: 'Security Key',
label: mfaToText(MfaMethod.Fido2),
hidden: !selectedSteps.includes(ConfigureMfaStep.Fido2),
},
[ConfigureMfaStep.RecoveryCodes]: {
Expand Down
4 changes: 2 additions & 2 deletions new-ui/src/shared/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { MfaMethod, type MfaMethodValue } from './rust-api/types';
export const mfaMethodIcon: Record<MfaMethodValue, IconKindValue> = {
[MfaMethod.Email]: 'mail',
[MfaMethod.MobileApprove]: 'qr',
[MfaMethod.Oidc]: 'token',
[MfaMethod.Totp]: 'lock-closed',
[MfaMethod.Oidc]: 'lock-closed',
[MfaMethod.Totp]: 'mobile-lock',
[MfaMethod.Biometric]: 'biometric',
[MfaMethod.Fido2]: 'software-key',
};
Expand Down
2 changes: 1 addition & 1 deletion new-ui/src/shared/utils/mfa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const mfaMethodLabels: Record<MfaMethodValue, string> = {
[MfaMethod.Oidc]: 'OpenID',
[MfaMethod.Totp]: 'Authenticator app',
[MfaMethod.Biometric]: 'Mobile Biometric Authentication',
[MfaMethod.Fido2]: 'Security key',
[MfaMethod.Fido2]: 'FIDO2 Security Key',
};

export type OpenIdInstance = Pick<
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/core/src/mfa_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ fn method_name(method: MfaMethod) -> &'static str {
MfaMethod::Oidc => "OpenID",
MfaMethod::Biometric => "mobile biometric authentication",
MfaMethod::MobileApprove => "mobile app approval",
MfaMethod::Fido2 => "security key",
MfaMethod::Fido2 => "FIDO2 Security Key",
}
}

Expand Down
1 change: 1 addition & 0 deletions src-tauri/deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ ignore = [
{ id = "RUSTSEC-2026-0381", reason = "webpki-roots <1.0.10 pulled by hyper-rustls; bundles distrusted emSign root" },
{ id = "RUSTSEC-2026-0382", reason = "rustls-webpki <0.103.16 pulled by rustls; may accept distrusted emSign intermediates" },
{ id = "RUSTSEC-2026-0383", reason = "webpki-root-certs <1.0.10 pulled by rustls-platform-verifier; bundles distrusted emSign root" },
{ id = "RUSTSEC-2026-0418", reason = "expires 2026-11-30, libz-sys <1.1.31 pulled by vergen-git2 build dependency via git2/libgit2-sys, already on latest" },
# The "unmaintained" advisories below stem from our use of Tauri v2, which
# depends on GTK3 bindings that are no longer maintained.
{ id = "RUSTSEC-2024-0370", reason = "Tauri v2 GTK3 dependency (unmaintained)" },
Expand Down
Loading