fix: multi-factor authentication section shows while MFA is off (#32980)

Every user saw a "Multi-factor authentication" section under Account, reading "Authenticator not configured" and "Managed by your administrator.", although MFA is off by default and there is nothing to set up. The section now only shows when MFA applies to the signed-in user, so everyone who has to use an authenticator still sees it.

Refs #32830
This commit is contained in:
Classic298 2026-10-23 03:58:07 +02:00 • committed by GitHub
parent b17ee7d839
commit 35ee76cebb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 74 additions and 67 deletions

View file

@ -385,7 +385,7 @@
</UserSettingSection>
{/if}
<UserSettingSection title={$i18n.t('Multi-factor authentication')}><Mfa /></UserSettingSection>
<Mfa />
{#if canUseApiKeys({ user: $user, config: $config })}
<UserSettingSection title={$i18n.t('settings.personal.account.sections.apiKeys.title')}>

View file

@ -1,5 +1,6 @@
<script lang="ts">
import UserSettingRow from '../UserSettingRow.svelte';
import UserSettingSection from '../UserSettingSection.svelte';
import { onMount, getContext } from 'svelte';
import { mfaRequest } from '$lib/apis/auths/mfa';
import type { MfaManagementFlow } from '$lib/components/auth/MfaManagement.svelte';
@ -52,70 +53,76 @@
};
</script>
<div class="space-y-2.5 text-xs">
{#if status}
<UserSettingRow let:labelId>
<span slot="label"
>{status.enabled
? $i18n.t('Authenticator configured')
: $i18n.t('Authenticator not configured')}</span
>
{#if status.required && status.enabled}<button
id={`${labelId}-control`}
type="button"
class={actionButtonClass}
on:click={() => {
show = !show;
}}>{show ? $i18n.t('Hide') : $i18n.t('Manage')}</button
>{/if}
</UserSettingRow>
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{status.enabled
? $i18n.t('{{COUNT}} recovery codes remaining', { COUNT: status.recovery_codes_remaining })
: $i18n.t('Managed by your administrator.')}
</p>
{#if show && status.required && status.enabled}
<div class="w-full sm:max-w-md space-y-2.5 py-1">
<p class="text-[0.6875rem] leading-4 text-gray-400 dark:text-gray-500">
{$i18n.t('Verify with a fresh code. Changes sign out all devices.')}
</p>
<label class="block text-gray-600 dark:text-gray-400"
>{recovery ? $i18n.t('Recovery code') : $i18n.t('Authenticator code')}<input
class="mt-1 h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
autocomplete="one-time-code"
bind:value={code}
maxlength={recovery ? 128 : 6}
inputmode={recovery ? 'text' : 'numeric'}
/></label
>
<label class="flex items-center gap-2 text-[0.6875rem] text-gray-500"
><input
class="size-3 rounded-sm"
type="checkbox"
bind:checked={recovery}
on:change={() => {
code = '';
}}
/>{$i18n.t('Use a recovery code')}</label
>
<div class="flex flex-wrap gap-4">
<button
type="button"
class={actionButtonClass}
disabled={busy || !code}
on:click={() => manage(true)}>{$i18n.t('Replace authenticator')}</button
><button
type="button"
class={actionButtonClass}
disabled={busy || !code}
on:click={() => manage(false)}>{$i18n.t('Generate recovery codes')}</button
{#if status?.required || error}
<UserSettingSection title={$i18n.t('Multi-factor authentication')}>
<div class="space-y-2.5 text-xs">
{#if status}
<UserSettingRow let:labelId>
<span slot="label"
>{status.enabled
? $i18n.t('Authenticator configured')
: $i18n.t('Authenticator not configured')}</span
>
</div>
</div>
{/if}
{/if}
{#if error}<p role="alert" class="text-[0.6875rem] text-red-500">{error}</p>{/if}
{#if reauthenticate}<button type="button" class={actionButtonClass} on:click={signIn}
>{$i18n.t('Sign in again')}</button
>{/if}
</div>
{#if status.required && status.enabled}<button
id={`${labelId}-control`}
type="button"
class={actionButtonClass}
on:click={() => {
show = !show;
}}>{show ? $i18n.t('Hide') : $i18n.t('Manage')}</button
>{/if}
</UserSettingRow>
<p class="text-[0.6875rem] text-gray-400 dark:text-gray-600">
{status.enabled
? $i18n.t('{{COUNT}} recovery codes remaining', {
COUNT: status.recovery_codes_remaining
})
: $i18n.t('Managed by your administrator.')}
</p>
{#if show && status.required && status.enabled}
<div class="w-full sm:max-w-md space-y-2.5 py-1">
<p class="text-[0.6875rem] leading-4 text-gray-400 dark:text-gray-500">
{$i18n.t('Verify with a fresh code. Changes sign out all devices.')}
</p>
<label class="block text-gray-600 dark:text-gray-400"
>{recovery ? $i18n.t('Recovery code') : $i18n.t('Authenticator code')}<input
class="mt-1 h-7 w-full rounded-lg border border-gray-100/50 bg-gray-50/40 px-2 text-xs text-gray-700 outline-hidden transition-colors focus:border-blue-400 dark:border-white/[0.04] dark:bg-white/[0.03] dark:text-gray-300 dark:focus:border-blue-500"
autocomplete="one-time-code"
bind:value={code}
maxlength={recovery ? 128 : 6}
inputmode={recovery ? 'text' : 'numeric'}
/></label
>
<label class="flex items-center gap-2 text-[0.6875rem] text-gray-500"
><input
class="size-3 rounded-sm"
type="checkbox"
bind:checked={recovery}
on:change={() => {
code = '';
}}
/>{$i18n.t('Use a recovery code')}</label
>
<div class="flex flex-wrap gap-4">
<button
type="button"
class={actionButtonClass}
disabled={busy || !code}
on:click={() => manage(true)}>{$i18n.t('Replace authenticator')}</button
><button
type="button"
class={actionButtonClass}
disabled={busy || !code}
on:click={() => manage(false)}>{$i18n.t('Generate recovery codes')}</button
>
</div>
</div>
{/if}
{/if}
{#if error}<p role="alert" class="text-[0.6875rem] text-red-500">{error}</p>{/if}
{#if reauthenticate}<button type="button" class={actionButtonClass} on:click={signIn}
>{$i18n.t('Sign in again')}</button
>{/if}
</div>
</UserSettingSection>
{/if}