feat: add ability to force email verification (#1837)

This commit is contained in:
Elias Schneider
2026-10-13 22:04:33 +02:00
committed by GitHub
parent eb8ea6bed2
commit 0ffeb170cf
81 changed files with 2310 additions and 263 deletions
@@ -46,6 +46,18 @@
}
});
const emailVerificationOptions = $derived({
disabled: { label: m.disabled(), description: m.email_verification_disabled_description() },
optional: {
label: m.email_verification_optional(),
description: m.email_verification_optional_description()
},
required: {
label: m.email_verification_required(),
description: m.email_verification_required_description()
}
});
let isSendingTestEmail = $state(false);
const formSchema = z
@@ -63,7 +75,7 @@
smtpTls: z.enum(['none', 'starttls', 'tls']),
smtpSkipCertVerify: z.boolean(),
emailOneTimeAccessAsUnauthenticatedEnabled: z.boolean(),
emailVerificationEnabled: z.boolean(),
emailVerification: z.enum(['disabled', 'optional', 'required']),
emailOneTimeAccessAsAdminEnabled: z.boolean(),
emailLoginNotificationMode: z.enum([
'disabled',
@@ -82,7 +94,6 @@
const emailFields: (keyof z.infer<typeof formSchema>)[] = [
'emailOneTimeAccessAsUnauthenticatedEnabled',
'emailVerificationEnabled',
'emailOneTimeAccessAsAdminEnabled',
'emailApiKeyExpirationEnabled'
];
@@ -105,8 +116,19 @@
requireFieldsWhen(anyProvided, m.smtp_field_required_when_other_provided());
const emailEnabled =
data.emailLoginNotificationMode !== 'disabled' || emailFields.some((f) => data[f]);
data.emailLoginNotificationMode !== 'disabled' ||
data.emailVerification !== 'disabled' ||
emailFields.some((f) => data[f]);
requireFieldsWhen(emailEnabled, m.smtp_field_required_when_email_enabled());
// Requiring verification only protects accounts that have an email
if (data.emailVerification === 'required' && !data.requireUserEmail) {
ctx.addIssue({
code: 'custom',
path: ['emailVerification'],
message: m.email_verification_required_needs_user_email()
});
}
});
const formStore = createForm(formSchema, pickSchemaValues(formSchema, appConfig));
@@ -175,10 +197,53 @@
description={m.require_user_email_description()}
bind:checked={$inputs.requireUserEmail.value}
/>
<Field.Field>
<div>
<Field.Label for="email-verification">{m.email_verification()}</Field.Label>
<Field.Description>{m.email_verification_description()}</Field.Description>
</div>
<Select.Root
type="single"
disabled={$appConfigStore.uiConfigDisabled}
value={$inputs.emailVerification.value}
allowDeselect={false}
onValueChange={(value) => {
$inputs.emailVerification.value = value as AllAppConfig['emailVerification'];
// Emails verified by default would let every new account skip the verification
if (value === 'required') {
$inputs.emailsVerified.value = false;
}
}}
>
<Select.Trigger
id="email-verification"
class="w-full"
aria-invalid={!!$inputs.emailVerification.error}
>
{emailVerificationOptions[$inputs.emailVerification.value].label}
</Select.Trigger>
<Select.Content class="w-[calc(var(--bits-select-anchor-width)+--spacing(3))]">
<Select.Group>
{#each Object.entries(emailVerificationOptions) as [value, option] (value)}
<Select.Item {value} label={option.label}>
<div class="flex flex-col items-start gap-1 whitespace-normal">
<span class="font-medium">{option.label}</span>
<span class="text-muted-foreground text-xs">{option.description}</span>
</div>
</Select.Item>
{/each}
</Select.Group>
</Select.Content>
</Select.Root>
{#if $inputs.emailVerification.error}
<Field.Error>{$inputs.emailVerification.error}</Field.Error>
{/if}
</Field.Field>
<SwitchWithLabel
id="emails-verified-by-default"
label={m.emails_verified_by_default()}
description={m.emails_verified_by_default_description()}
disabled={$inputs.emailVerification.value === 'required'}
bind:checked={$inputs.emailsVerified.value}
/>
</div>
@@ -246,12 +311,6 @@
</Select.Content>
</Select.Root>
</Field.Field>
<SwitchWithLabel
id="email-verification"
label={m.email_verification()}
description={m.email_verification_description()}
bind:checked={$inputs.emailVerificationEnabled.value}
/>
<SwitchWithLabel
id="email-login-admin"
label={m.email_login_code_from_admin()}