import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import Link from 'next/link' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import * as z from 'zod' import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import { FormActions } from 'components/ui/Forms/FormActions' import { FormHeader } from 'components/ui/Forms/FormHeader' import { FormPanel } from 'components/ui/Forms/FormPanel' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, FormControl_Shadcn_, FormDescription_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, Form_Shadcn_, Input_Shadcn_, WarningIcon, } from 'ui' import { isSmtpEnabled } from '../SmtpForm/SmtpForm.utils' const RateLimits = () => { const formId = 'auth-rate-limits-form' const { ref: projectRef } = useParams() const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const { data: authConfig, error, isLoading, isError, isSuccess, } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation({ onSuccess: () => { toast.success('Rate limits successfully updated') }, onError: (error) => { toast.error(`Failed to update rate limits: ${error.message}`) }, }) const canUpdateEmailLimit = authConfig?.EXTERNAL_EMAIL_ENABLED && isSmtpEnabled(authConfig) const canUpdateSMSRateLimit = authConfig?.EXTERNAL_PHONE_ENABLED const canUpdateAnonymousUsersRateLimit = authConfig?.EXTERNAL_ANONYMOUS_USERS_ENABLED const FormSchema = z.object({ RATE_LIMIT_TOKEN_REFRESH: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an 5 minutes'), RATE_LIMIT_VERIFY: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an 5 minutes'), RATE_LIMIT_EMAIL_SENT: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an hour'), RATE_LIMIT_SMS_SENT: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an hour'), RATE_LIMIT_ANONYMOUS_USERS: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an hour'), RATE_LIMIT_OTP: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an hour'), }) const form = useForm>({ resolver: zodResolver(FormSchema), defaultValues: { RATE_LIMIT_TOKEN_REFRESH: 0, RATE_LIMIT_VERIFY: 0, RATE_LIMIT_EMAIL_SENT: 0, RATE_LIMIT_SMS_SENT: 0, RATE_LIMIT_OTP: 0, }, }) const onSubmit = (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') const payload: Partial> = {} const params = [ 'RATE_LIMIT_TOKEN_REFRESH', 'RATE_LIMIT_VERIFY', 'RATE_LIMIT_EMAIL_SENT', 'RATE_LIMIT_SMS_SENT', 'RATE_LIMIT_ANONYMOUS_USERS', 'RATE_LIMIT_OTP', ] as (keyof typeof payload)[] params.forEach((param) => { if (data[param] !== authConfig?.[param]) payload[param] = data[param] }) updateAuthConfig({ projectRef, config: payload }, { onSuccess: () => form.reset(data) }) } useEffect(() => { if (isSuccess) { form.reset({ RATE_LIMIT_TOKEN_REFRESH: authConfig.RATE_LIMIT_TOKEN_REFRESH, RATE_LIMIT_VERIFY: authConfig.RATE_LIMIT_VERIFY, RATE_LIMIT_EMAIL_SENT: authConfig.RATE_LIMIT_EMAIL_SENT, RATE_LIMIT_SMS_SENT: authConfig.RATE_LIMIT_SMS_SENT, RATE_LIMIT_ANONYMOUS_USERS: authConfig.RATE_LIMIT_ANONYMOUS_USERS, RATE_LIMIT_OTP: authConfig.RATE_LIMIT_OTP, }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccess]) return (
{isError && } {isLoading && } {isSuccess && (
form.reset()} disabled={!canUpdateConfig} helper={ !canUpdateConfig ? 'You need additional permissions to update authentication settings' : undefined } />
} > Number of emails that can be sent per hour from your project

} > Rate limit for sending emails } > ( {!authConfig.EXTERNAL_EMAIL_ENABLED ? ( Email-based logins are not enabled for your project

Enable email-based logins to update this rate limit

) : !isSmtpEnabled(authConfig) ? ( Custom SMTP provider is required to update this configuration

The built-in email service has a fixed rate limit. You will need to set up your own custom SMTP provider to update your email rate limit

) : null}
)} />
Number of SMS messages that can be sent per hour from your project

} > Rate limit for sending SMS messages } > ( {!canUpdateSMSRateLimit && ( Phone-based logins are not enabled for your project

Enable phone-based logins to update this rate limit

)}
)} />
Number of sessions that can be refreshed in a 5 minute interval per IP address.

} > Rate limit for token refreshes } > ( {field.value > 0 && ( <>

This is equivalent to {field.value * 12} requests per hour

)}
)} />
Number of OTP/Magic link verifications that can be made in a 5 minute interval per IP address.

} > Rate limit for token verifications } > ( {field.value > 0 && (

This is equivalent to {field.value * 12} requests per hour

)}
)} />
Number of anonymous sign-ins that can be made per hour per IP address.

} > Rate limit for anonymous users } > ( {!canUpdateAnonymousUsersRateLimit && ( Anonymous logins are not enabled for your project

Enable anonymous logins to update this rate limit

)}
)} />
Number of sign up and sign-in requests that can be made in a 5 minute interval per IP address (excludes anonymous users).

} > Rate limit for sign ups and sign ins } > ( {field.value > 0 && ( This is equivalent to {field.value * 12} requests per hour )} )} />
)} ) } export default RateLimits