From 3aaee3ca28f2222a4182bfaeab429bcdcf464616 Mon Sep 17 00:00:00 2001 From: Konstantinos Kopanidis Date: Thu, 20 Aug 2026 16:06:15 +0300 Subject: [PATCH] feat(auth): add email restrictions settings --- .../authentication/strategies/settings.tsx | 115 +++++++++++++++++- src/lib/models/authentication/base.config.ts | 11 ++ 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/src/components/authentication/strategies/settings.tsx b/src/components/authentication/strategies/settings.tsx index 2ef92fa39..7b4a4cbec 100644 --- a/src/components/authentication/strategies/settings.tsx +++ b/src/components/authentication/strategies/settings.tsx @@ -9,6 +9,7 @@ import { useState } from 'react'; import { useSettingsSave } from '@/lib/hooks/use-settings-save'; import { SettingsFormActions } from '@/components/settings/SettingsFormActions'; import { AuthenticationConfig } from '@/lib/models/authentication'; +import { EmailRestrictions } from '@/lib/models/authentication/base.config'; import { patchAuthenticationSettings } from '@/lib/api/authentication'; import { InputField } from '@/components/ui/form-inputs/InputField'; import SwitchField from '@/components/ui/form-inputs/SwitchField'; @@ -58,6 +59,38 @@ const teamsSchema = z.object({ inviteUrl: z.string().default('https://mydomain.conduit/invite'), }), }); + +const domainTagSchema = z + .string() + .min(1) + .refine(value => !value.includes('@'), 'Enter a domain, not an email address') + .refine(value => !/^https?:\/\//i.test(value), 'Enter a hostname without a scheme') + .refine(value => value.includes('.'), 'Enter a domain with at least one dot'); + +const emailRestrictionsSchema = z.object({ + enabled: z.boolean().default(false), + blockDisposableEmails: z.boolean().default(true), + blockPlusAddressing: z.boolean().default(true), + blockedAddresses: z + .array(z.string().email('Please enter a valid email address')) + .default([]), + blockedDomains: z.array(domainTagSchema).default([]), + allowedAddresses: z + .array(z.string().email('Please enter a valid email address')) + .default([]), + allowedDomains: z.array(domainTagSchema).default([]), +}); + +const defaultEmailRestrictions: EmailRestrictions = { + enabled: false, + blockDisposableEmails: true, + blockPlusAddressing: true, + blockedAddresses: [], + blockedDomains: [], + allowedAddresses: [], + allowedDomains: [], +}; + const FormSchema = z.object({ captcha: z.object({ enabled: z.boolean().default(false), @@ -80,6 +113,7 @@ const FormSchema = z.object({ anonymousUsers: z.object({ enabled: z.boolean().default(false), }), + emailRestrictions: emailRestrictionsSchema, teams: teamsSchema, clients: clientsSchema, accessTokens: accessTokenSchema, @@ -96,7 +130,13 @@ export const AuthenticationSettings = ({ data }: Props) => { type formSchema = z.infer; const form = useForm({ resolver: rhfZodResolver(FormSchema), - defaultValues: data, + defaultValues: { + ...data, + emailRestrictions: { + ...defaultEmailRestrictions, + ...(data.emailRestrictions ?? {}), + }, + }, }); const { reset, control, handleSubmit, watch } = form; @@ -470,6 +510,79 @@ export const AuthenticationSettings = ({ data }: Props) => { )} + +
+
+ + + + Email restrictions + + +
+
+ + + +

+ Applies when an email is accepted (registration, email + change, invites). Existing users can still log in.{' '} + @anonymous.com is reserved for Conduit anonymous + users and cannot be registered. +

+ {form.watch('emailRestrictions.enabled') && ( +
+
+ + +
+ + + + +
+ )} +
+