import { PermissionAction } from '@supabase/shared-types/out/constants' import { JwtSecretUpdateError, JwtSecretUpdateProgress, JwtSecretUpdateStatus, } from '@supabase/shared-types/out/events' import { AlertCircle, ChevronDown, Eye, EyeOff, Key, Loader2, PenTool, RefreshCw, } from 'lucide-react' import { Dispatch, SetStateAction, useState } from 'react' import { toast } from 'sonner' import { number, object } from 'yup' import { useParams } from 'common' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import Panel from 'components/ui/Panel' import { useJwtSecretUpdateMutation } from 'data/config/jwt-secret-update-mutation' import { useJwtSecretUpdatingStatusQuery } from 'data/config/jwt-secret-updating-status-query' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { uuidv4 } from 'lib/helpers' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, Input, InputNumber, Modal, WarningIcon, Form, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { JWT_SECRET_UPDATE_ERROR_MESSAGES, JWT_SECRET_UPDATE_PROGRESS_MESSAGES, } from './API.constants' import { Admonition } from 'ui-patterns' import { FormActions } from 'components/ui/Forms/FormActions' import { FormPanel } from 'components/ui/Forms/FormPanel' import { FormSection, FormSectionContent, FormSectionLabel } from 'components/ui/Forms/FormSection' const schema = object({ JWT_EXP: number() .max(604800, 'Must be less than 604800') .required('Must have a JWT expiry value'), }) const JWTSettings = () => { const { ref: projectRef } = useParams() const [customToken, setCustomToken] = useState('') const [showCustomTokenInput, setShowCustomTokenInput] = useState(false) const [isCreatingKey, setIsCreatingKey] = useState(false) const [isRegeneratingKey, setIsGeneratingKey] = useState(false) const canReadJWTSecret = useCheckPermissions(PermissionAction.READ, 'field.jwt_secret') const canGenerateNewJWTSecret = useCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_job.projects.update_jwt' ) const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue') const { data } = useJwtSecretUpdatingStatusQuery({ projectRef }) const { data: config, isError } = useProjectPostgrestConfigQuery({ projectRef }) const { mutateAsync: updateJwt, isLoading: isSubmittingJwtSecretUpdateRequest } = useJwtSecretUpdateMutation() const { data: authConfig, error: authConfigError, isLoading: isLoadingAuthConfig, isSuccess: isSuccessAuthConfig, } = useAuthConfigQuery({ projectRef }) const { mutate: updateAuthConfig, isLoading: isUpdatingAuthConfig } = useAuthConfigUpdateMutation() const { Failed, Updated, Updating } = JwtSecretUpdateStatus const isJwtSecretUpdateFailed = data?.jwtSecretUpdateStatus === Failed const isNotUpdatingJwtSecret = data?.jwtSecretUpdateStatus === undefined || data?.jwtSecretUpdateStatus === Updated const isUpdatingJwtSecret = data?.jwtSecretUpdateStatus === Updating const jwtSecretUpdateErrorMessage = JWT_SECRET_UPDATE_ERROR_MESSAGES[data?.jwtSecretUpdateError as JwtSecretUpdateError] const jwtSecretUpdateProgressMessage = JWT_SECRET_UPDATE_PROGRESS_MESSAGES[data?.jwtSecretUpdateProgress as JwtSecretUpdateProgress] const INITIAL_VALUES = { JWT_EXP: authConfig?.JWT_EXP ?? 3600, } const onUpdateJwtExp = async (values: any, { resetForm }: any) => { if (!projectRef) return console.error('Project ref is required') updateAuthConfig( { projectRef, config: values }, { onError: (error) => { toast.error(`Failed to update JWT expiry: ${error?.message}`) }, onSuccess: () => { toast.success('Successfully updated JWT expiry') resetForm({ values, initialValues: values }) }, } ) } async function handleJwtSecretUpdate( jwt_secret: string, setModalVisibility: Dispatch> ) { if (!projectRef) return console.error('Project ref is required') const trackingId = uuidv4() try { await updateJwt({ projectRef, jwtSecret: jwt_secret, changeTrackingId: trackingId }) setModalVisibility(false) toast( 'Successfully submitted JWT secret update request. Please wait while your project is updated.' ) } catch (error) {} } return ( <>
{({ handleReset, values, initialValues }: any) => { const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) return ( JWT Settings} footer={
} > {isError ? (

Failed to retrieve JWT settings

) : ( <> seconds} disabled={!canUpdateConfig || isLoadingAuthConfig} />
{isUpdatingJwtSecret ? (

Updating JWT secret: {jwtSecretUpdateProgressMessage}

) : (

Generate a new JWT secret

A random secret will be created, or you can create your own.

{isUpdatingJwtSecret ? ( ) : !canGenerateNewJWTSecret ? ( } tooltip={{ content: { side: 'bottom', text: 'You need additional permissions to generate a new JWT secret', }, }} > Generate a new secret ) : ( setIsGeneratingKey(true)} >

Generate a random secret

setIsCreatingKey(true)} >

Create my own secret

)}
)}
{isJwtSecretUpdateFailed ? ( Please try again. If the failures persist, please contact Supabase support with the following details:
Change tracking ID: {data?.changeTrackingId}
Error message: {jwtSecretUpdateErrorMessage}
) : null}
)}
) }}
setIsGeneratingKey(false)} onConfirm={() => handleJwtSecretUpdate('ROLL', setIsGeneratingKey)} alert={{ title: 'This will invalidate all existing API keys', description: ( <> Generating a new JWT secret will invalidate all of your API keys, including your service_role and{' '} anon keys. Your project will also be restarted during this process, which will terminate any existing connections. You may receive API errors for up to 2 minutes while the new secret is deployed. ), }} >

This action cannot be undone and the old JWT secret will be lost. All existing API keys will be invalidated, and any open connections will be terminated.

} >

Create a custom JWT secret. Make sure it is a strong combination of characters that cannot be guessed easily.

This will invalidate all existing API keys Generating a new JWT secret will invalidate all of your API keys, including your service_role and{' '} anon keys. Your project will also be restarted during this process, which will terminate any existing connections. setCustomToken(e.target.value)} value={customToken} icon={} type={showCustomTokenInput ? 'text' : 'password'} className="w-full text-left" label="Custom JWT secret" descriptionText="Minimally 32 characters long, '@' and '$' are not allowed." actions={
} />
) } export default JWTSettings