Home / Function/ CreateHookSheet() — supabase Function Reference

CreateHookSheet() — supabase Function Reference

Architecture documentation for the CreateHookSheet() function in CreateHookSheet.tsx from the supabase codebase.

Entity Profile

Dependency Diagram

graph TD
  ed51c473_5272_57db_6316_2f767f81b646["CreateHookSheet()"]
  44a1d22a_5d71_8dd5_1cd2_b6af6a48cf0f["extractMethod()"]
  ed51c473_5272_57db_6316_2f767f81b646 -->|calls| 44a1d22a_5d71_8dd5_1cd2_b6af6a48cf0f
  2b26d2a1_635d_8e95_7c0f_a3382ff8636f["isValidHook()"]
  ed51c473_5272_57db_6316_2f767f81b646 -->|calls| 2b26d2a1_635d_8e95_7c0f_a3382ff8636f
  53875c04_b5ee_2f68_7ad7_2d91dfb75f9f["getRevokePermissionStatements()"]
  ed51c473_5272_57db_6316_2f767f81b646 -->|calls| 53875c04_b5ee_2f68_7ad7_2d91dfb75f9f
  8dea59e5_8aa4_2ef7_1dfc_44399c459807["generateAuthHookSecret()"]
  ed51c473_5272_57db_6316_2f767f81b646 -->|calls| 8dea59e5_8aa4_2ef7_1dfc_44399c459807
  style ed51c473_5272_57db_6316_2f767f81b646 fill:#6366f1,stroke:#818cf8,color:#fff

Relationship Graph

Source Code

apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx lines 108–512

export const CreateHookSheet = ({
  visible,
  title,
  authConfig,
  onClose,
  onDelete,
}: CreateHookSheetProps) => {
  const { ref: projectRef } = useParams()
  const { data: project } = useSelectedProjectQuery()

  const definition = useMemo(
    () => HOOKS_DEFINITIONS.find((d) => d.title === title) || HOOKS_DEFINITIONS[0],
    [title]
  )

  const supportedReturnTypes =
    definition.enabledKey === 'HOOK_SEND_EMAIL_ENABLED'
      ? ['json', 'jsonb', 'void']
      : ['json', 'jsonb']

  const hook: Hook = useMemo(() => {
    return {
      ...definition,
      enabled: authConfig?.[definition.enabledKey] || false,
      method: extractMethod(
        authConfig?.[definition.uriKey] || '',
        authConfig?.[definition.secretsKey] || ''
      ),
    }
  }, [definition, authConfig])

  // if the hook has all parameters, then it is not being created.
  const isCreating = !isValidHook(hook)

  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      hookType: title || '',
      enabled: true,
      selectedType: 'postgres',
      httpsValues: {
        url: '',
        secret: '',
      },
      postgresValues: {
        schema: 'public',
        functionName: '',
      },
    },
  })

  const values = form.watch()

  const statements = useMemo(() => {
    let permissionChanges: string[] = []
    if (hook.method.type === 'postgres') {
      if (
        hook.method.schema !== '' &&
        hook.method.functionName !== '' &&
        hook.method.functionName !== values.postgresValues.functionName
      ) {
        permissionChanges = getRevokePermissionStatements(
          hook.method.schema,
          hook.method.functionName
        )
      }
    }

    if (values.postgresValues.functionName !== '') {
      permissionChanges = [
        ...permissionChanges,
        `-- Grant access to function to supabase_auth_admin\ngrant execute on function ${values.postgresValues.schema}.${values.postgresValues.functionName} to supabase_auth_admin;`,
        `-- Grant access to schema to supabase_auth_admin\ngrant usage on schema ${values.postgresValues.schema} to supabase_auth_admin;`,
        `-- Revoke function permissions from authenticated, anon and public\nrevoke execute on function ${values.postgresValues.schema}.${values.postgresValues.functionName} from authenticated, anon, public;`,
      ]
    }
    return permissionChanges
  }, [hook, values.postgresValues.schema, values.postgresValues.functionName])

  const { mutate: updateAuthHooks, isPending: isUpdatingAuthHooks } = useAuthHooksUpdateMutation({
    onSuccess: () => {
      toast.success(`Successfully created ${values.hookType}.`)
      if (statements.length > 0) {
        executeSql({
          projectRef,
          connectionString: project!.connectionString,
          sql: statements.join('\n'),
        })
      }
      onClose()
    },
    onError: (error) => {
      toast.error(`Failed to create hook: ${error.message}`)
    },
  })

  const onSubmit: SubmitHandler<z.infer<typeof FormSchema>> = async (values) => {
    if (!project) return console.error('Project is required')
    const definition = HOOKS_DEFINITIONS.find((d) => values.hookType === d.title)

    if (!definition) {
      return
    }

    const enabledLabel = definition.enabledKey
    const uriLabel = definition.uriKey
    const secretsLabel = definition.secretsKey

    let url = ''
    if (values.selectedType === 'postgres') {
      url = `pg-functions://postgres/${values.postgresValues.schema}/${values.postgresValues.functionName}`
    } else {
      url = values.httpsValues.url
    }

    const payload = {
      [enabledLabel]: values.enabled,
      [uriLabel]: url,
      [secretsLabel]: values.selectedType === 'https' ? values.httpsValues.secret : null,
    }

    updateAuthHooks({ projectRef: projectRef!, config: payload })
  }

  useEffect(() => {
    if (visible) {
      if (definition) {
        const values = extractMethod(
          authConfig?.[definition.uriKey] || '',
          authConfig?.[definition.secretsKey] || ''
        )

        form.reset({
          hookType: definition.title,
          enabled: authConfig?.[definition.enabledKey] || true,
          selectedType: values.type,
          httpsValues: {
            url: (values.type === 'https' && values.url) || '',
            secret: (values.type === 'https' && values.secret) || '',
          },
          postgresValues: {
            schema: (values.type === 'postgres' && values.schema) || 'public',
            functionName: (values.type === 'postgres' && values.functionName) || '',
          },
        })
      } else {
        form.reset({
          hookType: title || '',
          enabled: true,
          selectedType: 'postgres',
          httpsValues: {
            url: '',
            secret: '',
          },
          postgresValues: {
            schema: 'public',
            functionName: '',
          },
        })
      }
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [authConfig, title, visible, definition])

  return (
    <Sheet open={visible} onOpenChange={() => onClose()}>
      <SheetContent
        aria-describedby={undefined}
        size="lg"
        showClose={false}
        className="flex flex-col gap-0"
      >
        <SheetHeader className="py-3 flex flex-row justify-between items-center border-b-0">
          <SheetTitle className="truncate">
            {isCreating ? `Add ${title}` : `Update ${title}`}
          </SheetTitle>
          <DocsButton href={`${DOCS_URL}/guides/auth/auth-hooks/${hook.docSlug}`} />
        </SheetHeader>
        <Separator />
        <SheetSection className="overflow-auto flex-grow px-0">
          <Form_Shadcn_ {...form}>
            <form
              id={FORM_ID}
              className="space-y-6 w-full py-5 flex-1"
              onSubmit={form.handleSubmit(onSubmit)}
            >
              <FormField_Shadcn_
                key="enabled"
                name="enabled"
                control={form.control}
                render={({ field }) => (
                  <FormItemLayout
                    layout="flex"
                    className="px-5"
                    label={`Enable ${values.hookType}`}
                    description={
                      values.hookType === 'Send SMS hook'
                        ? 'SMS Provider settings will be disabled in favor of SMS hooks'
                        : undefined
                    }
                  >
                    <FormControl_Shadcn_>
                      <Switch
                        checked={field.value}
                        onCheckedChange={field.onChange}
                        disabled={field.disabled}
                      />
                    </FormControl_Shadcn_>
                  </FormItemLayout>
                )}
              />
              <Separator />
              <FormField_Shadcn_
                control={form.control}
                name="selectedType"
                render={({ field }) => (
                  <FormItemLayout label="Hook type" className="px-5">
                    <FormControl_Shadcn_>
                      <RadioGroupStacked
                        value={field.value}
                        onValueChange={(value) => field.onChange(value)}
                      >
                        <RadioGroupStackedItem
                          value="postgres"
                          id="postgres"
                          key="postgres"
                          label="Postgres"
                          description="Used to call a Postgres function."
                        />
                        <RadioGroupStackedItem
                          value="https"
                          id="https"
                          key="https"
                          label="HTTPS"
                          description="Used to call any HTTPS endpoint."
                        />
                      </RadioGroupStacked>
                    </FormControl_Shadcn_>
                  </FormItemLayout>
                )}
              />
              {values.selectedType === 'postgres' ? (
                <>
                  <div className="grid grid-cols-2 gap-8 px-5">
                    <FormField_Shadcn_
                      key="postgresValues.schema"
                      control={form.control}
                      name="postgresValues.schema"
                      render={({ field }) => (
                        <FormItemLayout
                          label="Postgres Schema"
                          description="Postgres schema where the function is defined"
                        >
                          <FormControl_Shadcn_>
                            <SchemaSelector
                              size="small"
                              showError={false}
                              selectedSchemaName={field.value}
                              onSelectSchema={(name) => field.onChange(name)}
                              disabled={field.disabled}
                            />
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                    <FormField_Shadcn_
                      key="postgresValues.functionName"
                      control={form.control}
                      name="postgresValues.functionName"
                      render={({ field }) => (
                        <FormItemLayout
                          label="Postgres function"
                          description="This function will be called by Supabase Auth each time the hook is triggered"
                        >
                          <FormControl_Shadcn_>
                            <FunctionSelector
                              size="small"
                              schema={values.postgresValues.schema}
                              value={field.value}
                              onChange={field.onChange}
                              disabled={field.disabled}
                              filterFunction={(func) => {
                                if (supportedReturnTypes.includes(func.return_type)) {
                                  const { value } = convertArgumentTypes(func.argument_types)
                                  if (value.length !== 1) return false
                                  return value[0].type === 'json' || value[0].type === 'jsonb'
                                }
                                return false
                              }}
                              noResultsLabel={
                                <span>
                                  No function with a single JSON/B argument
                                  <br />
                                  and JSON/B
                                  {definition.enabledKey === 'HOOK_SEND_EMAIL_ENABLED'
                                    ? ' or void'
                                    : ''}{' '}
                                  return type found in this schema.
                                </span>
                              }
                            />
                          </FormControl_Shadcn_>
                        </FormItemLayout>
                      )}
                    />
                  </div>
                  <div className="h-72 w-full gap-3 flex flex-col">
                    <p className="text-sm text-foreground-light px-5">
                      The following statements will be executed on the selected function:
                    </p>
                    <CodeEditor
                      id="postgres-hook-editor"
                      isReadOnly={true}
                      language="pgsql"
                      value={statements.join('\n\n')}
                    />
                  </div>
                </>
              ) : (
                <div className="flex flex-col gap-4 px-5">
                  <FormField_Shadcn_
                    key="httpsValues.url"
                    control={form.control}
                    name="httpsValues.url"
                    render={({ field }) => (
                      <FormItemLayout
                        label="URL"
                        description="Supabase Auth will send a HTTPS POST request to this URL each time the hook is triggered."
                      >
                        <FormControl_Shadcn_>
                          <Input_Shadcn_ {...field} />
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
                  <FormField_Shadcn_
                    key="httpsValues.secret"
                    control={form.control}
                    name="httpsValues.secret"
                    render={({ field }) => (
                      <FormItemLayout
                        label="Secret"
                        description={
                          <div className="flex items-center gap-x-2">
                            <p>
                              Should be a base64 encoded hook secret with a prefix{' '}
                              <code className="text-code-inline">v1,whsec_</code>.
                            </p>
                            <InfoTooltip side="bottom" className="w-60 text-center">
                              <code className="text-code-inline">v1</code> denotes the signature
                              version and <code className="text-code-inline">whsec_</code> signifies
                              a symmetric secret.
                            </InfoTooltip>
                          </div>
                        }
                      >
                        <FormControl_Shadcn_>
                          <div className="flex flex-row">
                            <Input_Shadcn_ {...field} className="rounded-r-none border-r-0" />
                            <Button
                              type="default"
                              size="small"
                              className="rounded-l-none text-xs"
                              onClick={() => {
                                const authHookSecret = generateAuthHookSecret()
                                form.setValue('httpsValues.secret', authHookSecret)
                              }}
                            >
                              Generate secret
                            </Button>
                          </div>
                        </FormControl_Shadcn_>
                      </FormItemLayout>
                    )}
                  />
                </div>
              )}
            </form>
          </Form_Shadcn_>
        </SheetSection>
        <SheetFooter>
          {!isCreating && (
            <div className="flex-1">
              <Button type="danger" onClick={() => onDelete()}>
                Delete hook
              </Button>
            </div>
          )}

          <Button disabled={isUpdatingAuthHooks} type="default" onClick={() => onClose()}>
            Cancel
          </Button>
          <Button
            form={FORM_ID}
            htmlType="submit"
            disabled={isUpdatingAuthHooks}
            loading={isUpdatingAuthHooks}
          >
            {isCreating ? 'Create hook' : 'Update hook'}
          </Button>
        </SheetFooter>
      </SheetContent>
    </Sheet>
  )
}

Subdomains

Frequently Asked Questions

What does CreateHookSheet() do?
CreateHookSheet() is a function in the supabase codebase.
What does CreateHookSheet() call?
CreateHookSheet() calls 4 function(s): extractMethod, generateAuthHookSecret, getRevokePermissionStatements, isValidHook.

Analyze Your Own Codebase

Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.

Try Supermodel Free