// @ts-nocheck import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useEffect, useState } from 'react' import { SubmitHandler, useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, cn, Form, FormControl, FormField, FormInputGroupInput, Input, InputGroup, InputGroupAddon, InputGroupText, Switch, } from 'ui' import { Admonition, PageSection, PageSectionContent } from 'ui-patterns' import { Input as PasswordInput } from 'ui-patterns/DataInputs/Input' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import * as z from 'zod' import { urlRegex } from '../Auth.constants' import { defaultDisabledSmtpFormValues } from './SmtpForm.constants' import { generateFormValues, isSmtpEnabled } from './SmtpForm.utils' import AlertError from '@/components/ui/AlertError' import { InlineLink } from '@/components/ui/InlineLink' import NoPermission from '@/components/ui/NoPermission' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' const smtpEnabledSchema = z.object({ ENABLE_SMTP: z.literal(true), SMTP_ADMIN_EMAIL: z .string() .trim() .min(1, 'Sender email address is required') .email('Must be a valid email'), SMTP_SENDER_NAME: z.string().trim().min(1, 'Sender name is required'), SMTP_HOST: z .string() .trim() .min(1, 'Host URL is required') .regex(urlRegex({ excludeSimpleDomains: false }), 'Must be a valid URL or IP address'), SMTP_PORT: z.preprocess( (val) => (val === '' || val == null ? undefined : val), z.coerce .number({ required_error: 'Port number is required', invalid_type_error: 'Port number is required', }) .min(1, 'Must be a valid port number more than 0') .max(65535, 'Must be a valid port number no more than 65535') ), SMTP_MAX_FREQUENCY: z.preprocess( (val) => (val === '' || val == null ? undefined : val), z.coerce .number({ required_error: 'Rate limit is required', invalid_type_error: 'Rate limit is required', }) .min(1, 'Must be more than 0') .max(32767, 'Must not be more than 32,767 an hour') ), SMTP_USER: z.string().trim().min(1, 'SMTP Username is required'), SMTP_PASS: z.string().trim().optional(), }) const smtpDisabledSchema = z.object({ ENABLE_SMTP: z.literal(false), SMTP_ADMIN_EMAIL: z.string().optional(), SMTP_SENDER_NAME: z.string().optional(), SMTP_HOST: z.string().optional(), SMTP_PORT: z.preprocess( (val) => (val === '' || val == null ? undefined : val), z.coerce.number().optional() ), SMTP_MAX_FREQUENCY: z.preprocess( (val) => (val === '' || val == null ? undefined : val), z.coerce.number().optional() ), SMTP_USER: z.string().optional(), SMTP_PASS: z.string().optional(), }) const smtpSchema = z.discriminatedUnion('ENABLE_SMTP', [smtpEnabledSchema, smtpDisabledSchema]) type SmtpFormValues = z.infer export const SmtpForm = () => { const { ref: projectRef } = useParams() const { data: authConfig, error: authConfigError, isError } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation() const [enableSmtp, setEnableSmtp] = useState(false) const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) const form = useForm({ resolver: zodResolver( smtpSchema.superRefine((data, ctx: any) => { const isEnablingSmtp = data.ENABLE_SMTP && !isSmtpEnabled(authConfig) if (isEnablingSmtp && !data.SMTP_PASS) { ctx.addIssue({ code: 'custom', message: 'SMTP Password is required', path: ['SMTP_PASS'], }) } }) ), defaultValues: { SMTP_ADMIN_EMAIL: '', SMTP_SENDER_NAME: '', SMTP_HOST: '', SMTP_PORT: undefined, SMTP_MAX_FREQUENCY: undefined, SMTP_USER: '', SMTP_PASS: '', ENABLE_SMTP: false, }, }) const { isDirty } = form.formState // Update form values when auth config is loaded useEffect(() => { if (authConfig) { const formValues = generateFormValues(authConfig) form.reset({ ...formValues, ENABLE_SMTP: isSmtpEnabled(authConfig), } as SmtpFormValues) setEnableSmtp(isSmtpEnabled(authConfig)) } }, [authConfig, form]) // Update enableSmtp state when the form field changes useEffect(() => { const subscription = form.watch((value, { name }) => { if (name === 'ENABLE_SMTP') { setEnableSmtp(value.ENABLE_SMTP as boolean) } }) return () => subscription.unsubscribe() }, [form]) const onSubmit: SubmitHandler = (values) => { const { ENABLE_SMTP, ...rest } = values const basePayload = ENABLE_SMTP ? rest : defaultDisabledSmtpFormValues // When enabling SMTP, set RATE_LIMIT_EMAIL_SENT to 30 // When disabling, backend will handle resetting to default const isEnablingSmtp = ENABLE_SMTP && !isSmtpEnabled(authConfig) const payload = { ...basePayload, ...(isEnablingSmtp && { RATE_LIMIT_EMAIL_SENT: 30 }), } // Format payload: Convert port to string if (payload.SMTP_PORT) { payload.SMTP_PORT = payload.SMTP_PORT.toString() as any } // the SMTP_PASS is write-only, it's never shown. If we don't delete it from the payload, it will replace the // previously saved value with an empty one if (payload.SMTP_PASS === '') { delete payload.SMTP_PASS } updateAuthConfig( { projectRef: projectRef!, config: payload as any }, { onError: (error) => { toast.error(`Failed to update settings: ${error.message}`) }, onSuccess: () => { toast.success('Successfully updated settings') }, } ) } if (isError) { return ( ) } if (!canReadConfig) { return ( ) } const showFooterMessage = form.formState.isDirty && ((enableSmtp && !isSmtpEnabled(authConfig)) || !enableSmtp) return (
( Emails will be sent using your custom SMTP provider. Email rate limits can be adjusted{' '} here .

} >
)} /> {enableSmtp && !isSmtpEnabled(form.getValues() as any) && ( )}
{enableSmtp && ( <>

Sender details

Configure the sender information for your emails.

( )} /> ( )} />

SMTP provider settings

Your SMTP credentials will always be encrypted in our database.

( )} /> {form.watch('SMTP_HOST')?.endsWith('.gmail.com') && ( )} ( Port used by your SMTP server. Common ports include 465 and 587. Avoid using port 25 as it is often blocked by providers to curb spam. } > field.onChange(e.target.value)} placeholder="587" disabled={!canUpdateConfig} /> )} /> ( field.onChange(e.target.value)} disabled={!canUpdateConfig} /> seconds )} /> ( )} /> ( )} />
)} {showFooterMessage && (enableSmtp ? (

Rate limit for sending emails will be increased to 30 and{' '} can be adjusted {' '} after enabling custom SMTP

) : (

Rate limit for sending emails will be reduced to 2 after disabling custom SMTP

))}
{isDirty && ( )}
) }