import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import Link from 'next/link' import { useEffect } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { Button, Card, CardContent, CardFooter, Form, FormControl, FormField, FormInputGroupInput, InputGroup, InputGroupAddon, InputGroupText, Switch, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageSection, PageSectionContent, PageSectionDescription, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import * as z from 'zod' import { isSmtpEnabled } from '../SmtpForm/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' import { DOCS_URL } from '@/lib/constants' export const RateLimits = () => { const { ref: projectRef } = useParams() const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) const { data: authConfig, error, isPending: isLoading, isError, isSuccess, } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isPending: 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 canUpdateWeb3RateLimit = authConfig?.EXTERNAL_WEB3_SOLANA_ENABLED const IPForwardingFormSchema = z.object({ SECURITY_SB_FORWARDED_FOR_ENABLED: z.coerce.boolean(), }) const ipForwardingForm = useForm>({ resolver: zodResolver(IPForwardingFormSchema as any), defaultValues: { SECURITY_SB_FORWARDED_FOR_ENABLED: false, }, }) const onSubmitIPForwardingForm = (data: z.infer) => { if (!projectRef) return console.error('Project ref is required') const payload: Partial> = {} if (data.SECURITY_SB_FORWARDED_FOR_ENABLED !== authConfig?.SECURITY_SB_FORWARDED_FOR_ENABLED) { payload.SECURITY_SB_FORWARDED_FOR_ENABLED = data.SECURITY_SB_FORWARDED_FOR_ENABLED } updateAuthConfig( { projectRef, config: payload }, { onSuccess: () => ipForwardingForm.reset(data) } ) } const RateLimitFormSchema = 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'), RATE_LIMIT_WEB3: z.coerce .number() .min(0, 'Must be not be lower than 0') .max(32767, 'Must not be more than 32,767 an hour'), }) const rateLimitForm = useForm>({ resolver: zodResolver(RateLimitFormSchema as any), defaultValues: { RATE_LIMIT_TOKEN_REFRESH: 0, RATE_LIMIT_VERIFY: 0, RATE_LIMIT_EMAIL_SENT: 0, RATE_LIMIT_SMS_SENT: 0, RATE_LIMIT_ANONYMOUS_USERS: 0, RATE_LIMIT_OTP: 0, RATE_LIMIT_WEB3: 0, }, }) const onSubmitRateLimitForm = (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', 'RATE_LIMIT_WEB3', ] as (keyof typeof payload)[] params.forEach((param) => { if (data[param] !== authConfig?.[param]) payload[param] = data[param] }) updateAuthConfig( { projectRef, config: payload }, { onSuccess: () => rateLimitForm.reset(data) } ) } useEffect(() => { if (isSuccess) { rateLimitForm.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, RATE_LIMIT_WEB3: authConfig.RATE_LIMIT_WEB3 ?? 0, }) ipForwardingForm.reset({ SECURITY_SB_FORWARDED_FOR_ENABLED: authConfig.SECURITY_SB_FORWARDED_FOR_ENABLED, }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isSuccess]) if (isError) { return ( ) } if (!canReadConfig) { return ( ) } if (isLoading) { return ( ) } return ( <>
( emails/h {!canUpdateConfig || !canUpdateEmailLimit ? ( {!authConfig.EXTERNAL_EMAIL_ENABLED ? ( <>

Email-based logins are not enabled for your project

Enable email-based logins to update this rate limit

) : ( <>

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}
)} />
( sms/h {!canUpdateConfig || !canUpdateSMSRateLimit ? (

Phone-based logins are not enabled for your project

Enable phone-based logins to update this rate limit

) : null}
)} />
( requests/5 min {!canUpdateConfig && (

You don't have permission to update this setting

You need additional permissions to update auth configuration settings

)}
{rateLimitForm.watch('RATE_LIMIT_TOKEN_REFRESH') > 0 && (

{rateLimitForm.watch('RATE_LIMIT_TOKEN_REFRESH') * 12} requests per hour

)}
)} />
( requests/5 min {!canUpdateConfig && (

You don't have permission to update this setting

You need additional permissions to update auth configuration settings

)}
{rateLimitForm.watch('RATE_LIMIT_VERIFY') > 0 && (

{rateLimitForm.watch('RATE_LIMIT_VERIFY') * 12} requests per hour

)}
)} />
( requests/h {!canUpdateConfig || !canUpdateAnonymousUsersRateLimit ? (

Anonymous sign-ins are not enabled for your project. Enable them to control this rate limit.

) : null}
)} />
( requests/5 min {!canUpdateConfig && (

You don't have permission to update this setting

You need additional permissions to update auth configuration settings

)}
{rateLimitForm.watch('RATE_LIMIT_OTP') > 0 && (

{rateLimitForm.watch('RATE_LIMIT_OTP') * 12} requests per hour

)}
)} />
( requests/5 min {!canUpdateConfig || !canUpdateWeb3RateLimit ? (

Web3 auth provider is not enabled for this project. Enable it to control this rate limit.

) : null}
)} />
{rateLimitForm.formState.isDirty && ( )}
IP Address Forwarding Control how Auth determines source IP address for rate limiting.
( Clients can forward end-user IP addresses to Auth for rate limiting when using secret API keys.{' '} Learn more > field.onChange(value)} disabled={!canUpdateConfig} /> )} /> {ipForwardingForm.formState.isDirty && ( )}
) }