import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useParams } from 'common' import { useEffect, useState } 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, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { PageSection, PageSectionContent, PageSectionMeta, PageSectionSummary, PageSectionTitle, } from 'ui-patterns/PageSection' import * as z from 'zod' import AlertError from '@/components/ui/AlertError' import NoPermission from '@/components/ui/NoPermission' import { UpgradeToPro } from '@/components/ui/UpgradeToPro' import { useAuthConfigQuery } from '@/data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from '@/data/auth/auth-config-update-mutation' import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' import { IS_PLATFORM } from '@/lib/constants' function HoursOrNeverText({ value }: { value: number }) { if (value === 0) { return 'never' } else if (value === 1) { return 'hour' } else { return 'hours' } } const RefreshTokenSchema = z.object({ REFRESH_TOKEN_ROTATION_ENABLED: z.boolean(), SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: z.coerce.number().min(0, 'Must be a value more than 0'), }) const UserSessionsSchema = z.object({ SESSIONS_TIMEBOX: z.coerce.number().min(0, 'Must be a positive number'), SESSIONS_INACTIVITY_TIMEOUT: z.coerce .number() .multipleOf(0.1) .min(0, 'Must be a positive number'), SESSIONS_SINGLE_PER_USER: z.boolean(), }) export const SessionsAuthSettingsForm = () => { const { ref: projectRef } = useParams() const { data: authConfig, error: authConfigError, isError, isPending: isLoading, } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig } = useAuthConfigUpdateMutation() // Separate loading states for each form const [isUpdatingRefreshTokens, setIsUpdatingRefreshTokens] = useState(false) const [isUpdatingUserSessions, setIsUpdatingUserSessions] = useState(false) const { can: canReadConfig } = useAsyncCheckPermissions( PermissionAction.READ, 'custom_config_gotrue' ) const { can: canUpdateConfig } = useAsyncCheckPermissions( PermissionAction.UPDATE, 'custom_config_gotrue' ) const { hasAccess: hasUserSessionsEntitlement, isLoading: isLoadingEntitlements } = useCheckEntitlements('auth.user_sessions') const promptProPlanUpgrade = IS_PLATFORM && !hasUserSessionsEntitlement const refreshTokenForm = useForm>({ resolver: zodResolver(RefreshTokenSchema as any), defaultValues: { REFRESH_TOKEN_ROTATION_ENABLED: false, SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: 0, }, }) const userSessionsForm = useForm({ resolver: zodResolver(UserSessionsSchema as any), defaultValues: { SESSIONS_TIMEBOX: 0, SESSIONS_INACTIVITY_TIMEOUT: 0, SESSIONS_SINGLE_PER_USER: false, }, }) useEffect(() => { if (authConfig) { // Only reset forms if they're not currently being updated if (!isUpdatingRefreshTokens) { refreshTokenForm.reset({ REFRESH_TOKEN_ROTATION_ENABLED: authConfig.REFRESH_TOKEN_ROTATION_ENABLED || false, SECURITY_REFRESH_TOKEN_REUSE_INTERVAL: authConfig.SECURITY_REFRESH_TOKEN_REUSE_INTERVAL, }) } if (!isUpdatingUserSessions) { userSessionsForm.reset({ SESSIONS_TIMEBOX: authConfig.SESSIONS_TIMEBOX || 0, SESSIONS_INACTIVITY_TIMEOUT: authConfig.SESSIONS_INACTIVITY_TIMEOUT || 0, SESSIONS_SINGLE_PER_USER: authConfig.SESSIONS_SINGLE_PER_USER || false, }) } } }, [authConfig, isUpdatingRefreshTokens, isUpdatingUserSessions]) const onSubmitRefreshTokens = (values: any) => { const payload = { ...values } setIsUpdatingRefreshTokens(true) updateAuthConfig( { projectRef: projectRef!, config: payload }, { onError: (error) => { toast.error(`Failed to update refresh token settings: ${error?.message}`) setIsUpdatingRefreshTokens(false) }, onSuccess: () => { toast.success('Successfully updated refresh token settings') setIsUpdatingRefreshTokens(false) }, } ) } const onSubmitUserSessions = (values: any) => { const payload = { ...values } setIsUpdatingUserSessions(true) updateAuthConfig( { projectRef: projectRef!, config: payload }, { onError: (error) => { toast.error(`Failed to update user session settings: ${error?.message}`) setIsUpdatingUserSessions(false) }, onSuccess: () => { toast.success('Successfully updated user session settings') setIsUpdatingUserSessions(false) }, } ) } if (isError) { return ( ) } if (!canReadConfig) { return ( ) } if (isLoading || isLoadingEntitlements) { return ( ) } return ( <> Refresh Tokens
( )} /> ( seconds )} /> {refreshTokenForm.formState.isDirty && ( )}
User Sessions
( )} /> ( )} /> ( )} /> {promptProPlanUpgrade && ( )} {userSessionsForm.formState.isDirty && ( )}
) }