Files
supabase/apps/studio/components/interfaces/Auth/BasicAuthSettingsForm.tsx
Gildas Garcia 0facd341a6 chore: remove UI form components _Shadcn_ suffix (#45212)
## Problem

We used to have a `_Shadcn_` suffix for all the shadcn form components
because we also had `formik` form components.
This is not needed anymore.

## Solution

- Remove the suffix
- Update all usages
2026-04-24 12:14:15 +02:00

351 lines
13 KiB
TypeScript

import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useEffect } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
Alert_Shadcn_,
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Button,
Card,
CardContent,
CardFooter,
Form,
FormControl,
FormField,
Switch,
WarningIcon,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import {
PageSection,
PageSectionContent,
PageSectionMeta,
PageSectionSummary,
PageSectionTitle,
} from 'ui-patterns/PageSection'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { NO_REQUIRED_CHARACTERS } from './Auth.constants'
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 { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { DOCS_URL } from '@/lib/constants'
const schema = z.object({
DISABLE_SIGNUP: z.boolean(),
EXTERNAL_ANONYMOUS_USERS_ENABLED: z.boolean(),
SECURITY_MANUAL_LINKING_ENABLED: z.boolean(),
MAILER_AUTOCONFIRM: z.boolean(),
SITE_URL: z.string().min(1, 'Must have a Site URL'),
})
export const BasicAuthSettingsForm = () => {
const { ref: projectRef } = useParams()
const showManualLinking = useIsFeatureEnabled('authentication:show_manual_linking')
const {
data: authConfig,
error: authConfigError,
isError,
isSuccess,
isPending: isLoading,
} = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig, isPending: isUpdatingConfig } = useAuthConfigUpdateMutation()
const { can: canReadConfig, isSuccess: isPermissionsLoaded } = useAsyncCheckPermissions(
PermissionAction.READ,
'custom_config_gotrue'
)
const { can: canUpdateConfig } = useAsyncCheckPermissions(
PermissionAction.UPDATE,
'custom_config_gotrue'
)
const form = useForm({
resolver: zodResolver(schema),
defaultValues: {
DISABLE_SIGNUP: true,
EXTERNAL_ANONYMOUS_USERS_ENABLED: false,
SECURITY_MANUAL_LINKING_ENABLED: false,
MAILER_AUTOCONFIRM: true,
SITE_URL: '',
},
})
const { isDirty } = form.formState
useEffect(() => {
if (authConfig) {
form.reset({
DISABLE_SIGNUP: !authConfig.DISABLE_SIGNUP,
EXTERNAL_ANONYMOUS_USERS_ENABLED: authConfig.EXTERNAL_ANONYMOUS_USERS_ENABLED,
SECURITY_MANUAL_LINKING_ENABLED: authConfig.SECURITY_MANUAL_LINKING_ENABLED,
// The backend uses false to represent that email confirmation is required
MAILER_AUTOCONFIRM: !authConfig.MAILER_AUTOCONFIRM,
SITE_URL: authConfig.SITE_URL,
})
}
}, [authConfig])
const onSubmit = (values: any) => {
const payload = { ...values }
payload.DISABLE_SIGNUP = !values.DISABLE_SIGNUP
// The backend uses empty string to represent no required characters in the password
if (payload.PASSWORD_REQUIRED_CHARACTERS === NO_REQUIRED_CHARACTERS) {
payload.PASSWORD_REQUIRED_CHARACTERS = ''
}
// The backend uses false to represent that email confirmation is required
payload.MAILER_AUTOCONFIRM = !values.MAILER_AUTOCONFIRM
updateAuthConfig(
{ projectRef: projectRef!, config: payload },
{
onError: (error) => {
toast.error(`Failed to update settings: ${error?.message}`)
},
onSuccess: () => {
toast.success('Successfully updated settings')
},
}
)
}
return (
<PageSection>
<PageSectionMeta>
<PageSectionSummary>
<PageSectionTitle>User Signups</PageSectionTitle>
</PageSectionSummary>
</PageSectionMeta>
<PageSectionContent>
{isError && (
<AlertError
error={authConfigError}
subject="Failed to retrieve auth configuration for hooks"
/>
)}
{isPermissionsLoaded && !canReadConfig && (
<div className="mt-8">
<NoPermission resourceText="view auth configuration settings" />
</div>
)}
{isLoading && (
<Card>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-6">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7">
<ShimmeringLoader />
</CardContent>
<CardContent className="py-7"></CardContent>
</Card>
)}
{isSuccess && (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<Card>
<CardContent>
<FormField
control={form.control}
name="DISABLE_SIGNUP"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow new users to sign up"
description="If this is disabled, new users will not be able to sign up to your application"
>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl>
</FormItemLayout>
)}
/>
</CardContent>
{showManualLinking && (
<CardContent>
<FormField
control={form.control}
name="SECURITY_MANUAL_LINKING_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow manual linking"
description={
<>
Enable{' '}
<InlineLink
className="text-foreground-light hover:text-foreground"
href={`${DOCS_URL}/guides/auth/auth-identity-linking#manual-linking-beta`}
>
manual linking APIs
</InlineLink>{' '}
for your project
</>
}
>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl>
</FormItemLayout>
)}
/>
</CardContent>
)}
<CardContent>
<FormField
control={form.control}
name="EXTERNAL_ANONYMOUS_USERS_ENABLED"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Allow anonymous sign-ins"
description={
<>
Enable{' '}
<InlineLink
className="text-foreground-light hover:text-foreground"
href={`${DOCS_URL}/guides/auth/auth-anonymous`}
>
anonymous sign-ins
</InlineLink>{' '}
for your project
</>
}
>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl>
</FormItemLayout>
)}
/>
{form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_
className="flex w-full items-center justify-between mt-4"
variant="warning"
>
<WarningIcon />
<div>
<AlertTitle_Shadcn_>
Anonymous users will use the{' '}
<code className="text-code-inline">authenticated</code> role when signing
in
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p>
As a result, anonymous users will be subjected to RLS policies that
apply to the <code className="text-code-inline">public</code> and{' '}
<code className="text-code-inline">authenticated</code> roles. We
strongly advise{' '}
<Link
href={`/project/${projectRef}/auth/policies`}
className="text-foreground underline"
>
reviewing your RLS policies
</Link>{' '}
to ensure that access to your data is restricted where required.
</p>
<Button asChild type="default" className="w-min" icon={<ExternalLink />}>
<Link href={`${DOCS_URL}/guides/auth/auth-anonymous#access-control`}>
View access control docs
</Link>
</Button>
</AlertDescription_Shadcn_>
</div>
</Alert_Shadcn_>
)}
{!authConfig?.SECURITY_CAPTCHA_ENABLED &&
form.watch('EXTERNAL_ANONYMOUS_USERS_ENABLED') && (
<Alert_Shadcn_ className="mt-4">
<WarningIcon />
<AlertTitle_Shadcn_>
We highly recommend{' '}
<InlineLink href={`/project/${projectRef}/auth/protection`}>
enabling captcha
</InlineLink>{' '}
for anonymous sign-ins
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This will prevent potential abuse on sign-ins which may bloat your
database and incur costs for monthly active users (MAU)
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
</CardContent>
<CardContent>
<FormField
control={form.control}
name="MAILER_AUTOCONFIRM"
render={({ field }) => (
<FormItemLayout
layout="flex-row-reverse"
label="Confirm email"
description="Users will need to confirm their email address before signing in for the first time"
>
<FormControl>
<Switch
checked={field.value}
onCheckedChange={field.onChange}
disabled={!canUpdateConfig}
/>
</FormControl>
</FormItemLayout>
)}
/>
</CardContent>
<CardFooter className="justify-end space-x-2">
{isDirty && (
<Button type="default" onClick={() => form.reset()}>
Cancel
</Button>
)}
<Button
type="primary"
htmlType="submit"
disabled={!canUpdateConfig || isUpdatingConfig || !isDirty}
loading={isUpdatingConfig}
>
Save changes
</Button>
</CardFooter>
</Card>
</form>
</Form>
)}
</PageSectionContent>
</PageSection>
)
}