From 8a4bca34a7d8b7751637453e7823f2b01fac88f5 Mon Sep 17 00:00:00 2001 From: Chris Stockton <180184+cstockton@users.noreply.github.com> Date: Wed, 2 Jul 2025 14:54:07 -0700 Subject: [PATCH] feat: add documentation and UI integration for Before User Created hook (#36567) * feat: add documentation and UI integration for Before User Created hook - Added new guide: `before-user-created-hook.mdx`, documenting usage, inputs, outputs, and multiple complete examples for HTTP and SQL. - Updated navigation menu and hook index table to include the new hook. - Hook is now listed as available on Free and Pro plans. Studio changes: - Registered `Before User Created` hook in `hooks.constants.ts` with proper title, slug, and configuration keys. - Enabled Docs button linking to hook guide in both `CreateHookSheet` and `HookCard` components. - Extended hook listing page to support docs access via `secondaryActions`. This hook allows developers to intercept and reject user creation across all flows (email/password, OAuth, SSO, SAML, anonymous) - enabling custom signup logic like domain allowlists, geofencing, or account gating. * fix: add subnet to mdx lint allowed list * fix: incorporate feedback and spelling changes * feat: add allow/deny domain and CIDR examples to before-user-created hook This commit enhances the documentation for the before-user-created auth hook: - Adds production-ready SQL examples for blocking or allowing signups by email domain (signup_email_domains) and IP address or CIDR (signup_networks) - Introduces enum-based classification (allow vs deny) for both domain and network restrictions - Adds full migration-style snippets for easy copy/paste into Supabase SQL Editor - Includes updated HTTP examples that delegate to Postgres functions via Supabase client rpc() - Adds environment setup guidance and edge function scaffolding * fix: pnpm format --------- Co-authored-by: Chris Stockton --- .../NavigationMenu.constants.ts | 4 + apps/docs/content/guides/auth/auth-hooks.mdx | 1 + .../auth-hooks/before-user-created-hook.mdx | 791 ++++++++++++++++++ .../interfaces/Auth/Hooks/CreateHookSheet.tsx | 2 + .../interfaces/Auth/Hooks/HookCard.tsx | 2 + .../interfaces/Auth/Hooks/hooks.constants.ts | 16 + .../studio/pages/project/[ref]/auth/hooks.tsx | 5 + supa-mdx-lint/Rule003Spelling.toml | 1 + 8 files changed, 822 insertions(+) create mode 100644 apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 0980af36740..0ed90e7079c 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -702,6 +702,10 @@ export const auth = { name: 'Password verification hook', url: '/guides/auth/auth-hooks/password-verification-hook', }, + { + name: 'Before User Created hook', + url: '/guides/auth/auth-hooks/before-user-created-hook', + }, ], }, { name: 'Custom SMTP', url: '/guides/auth/auth-smtp' }, diff --git a/apps/docs/content/guides/auth/auth-hooks.mdx b/apps/docs/content/guides/auth/auth-hooks.mdx index 2bf8d105f65..0286afb148f 100644 --- a/apps/docs/content/guides/auth/auth-hooks.mdx +++ b/apps/docs/content/guides/auth/auth-hooks.mdx @@ -20,6 +20,7 @@ The following hooks are available: | Hook | Available on Plan | | ---------------------------------------------------------------------------------------- | -------------------- | +| [Before User Created](/docs/guides/auth/auth-hooks/before-user-created-hook) | Free, Pro | | [Custom Access Token](/docs/guides/auth/auth-hooks/custom-access-token-hook) | Free, Pro | | [Send SMS](/docs/guides/auth/auth-hooks/send-sms-hook) | Free, Pro | | [Send Email](/docs/guides/auth/auth-hooks/send-email-hook) | Free, Pro | diff --git a/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx new file mode 100644 index 00000000000..5f2010fd435 --- /dev/null +++ b/apps/docs/content/guides/auth/auth-hooks/before-user-created-hook.mdx @@ -0,0 +1,791 @@ +--- +id: 'before-user-created-hook' +title: 'Before User Created Hook' +subtitle: 'Prevent unwanted signups by inspecting and rejecting user creation requests' +--- + +This hook runs before a new user is created. It allows developers to inspect the incoming user object and optionally reject the request. Use this to enforce custom signup policies that Supabase Auth does not handle natively - such as blocking disposable email domains, restricting access by region or IP, or requiring that users belong to a specific email domain. + +You can implement this hook using an HTTP endpoint or a Postgres function. If the hook returns an error object, the signup is denied and the user is not created. If the hook responds successfully (HTTP 200 or 204 with no error), the request proceeds as usual. This gives you full control over which users are allowed to register — and the flexibility to apply that logic server-side. + +## Inputs + +Supabase Auth will send a payload containing these fields to your hook: + +| Field | Type | Description | +| ---------- | -------- | ----------------------------------------------------------------------------------------- | +| `metadata` | `object` | Metadata about the request. Includes IP address, request ID, and hook type. | +| `user` | `object` | The user record that is about to be created. Matches the shape of the `auth.users` table. | + + + Because the hook is ran just before the insertion into the database, this user will not be found + in Postgres at the time the hook is called. + + + + + +```json +{ + "metadata": { + "uuid": "8b34dcdd-9df1-4c10-850a-b3277c653040", + "time": "2025-04-29T13:13:24.755552-07:00", + "name": "before-user-created", + "ip_address": "127.0.0.1" + }, + "user": { + "id": "ff7fc9ae-3b1b-4642-9241-64adb9848a03", + "aud": "authenticated", + "role": "", + "email": "valid.email@supabase.com", + "phone": "", + "app_metadata": { + "provider": "email", + "providers": ["email"] + }, + "user_metadata": {}, + "identities": [], + "created_at": "0001-01-01T00:00:00Z", + "updated_at": "0001-01-01T00:00:00Z", + "is_anonymous": false + } +} +``` + + + + +```json +{ + "type": "object", + "properties": { + "metadata": { + "type": "object", + "properties": { + "uuid": { + "type": "string", + "format": "uuid" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "ip_address": { + "type": "string", + "format": "ipv4" + }, + "name": { + "type": "string", + "enum": ["before-user-created"] + } + }, + "required": ["uuid", "time", "ip_address", "name"] + }, + "user": { + "type": "object", + "properties": { + "id": { "type": "string", "format": "uuid" }, + "aud": { "type": "string" }, + "role": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "phone": { "type": "string" }, + "app_metadata": { + "type": "object", + "properties": { + "provider": { "type": "string" }, + "providers": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["provider", "providers"] + }, + "user_metadata": { "type": "object" }, + "identities": { + "type": "array", + "items": { "type": "object" } + }, + "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { "type": "string", "format": "date-time" }, + "is_anonymous": { "type": "boolean" } + }, + "required": [ + "id", + "aud", + "role", + "email", + "phone", + "app_metadata", + "user_metadata", + "identities", + "created_at", + "updated_at", + "is_anonymous" + ] + } + }, + "required": ["metadata", "user"] +} +``` + + + + +## Outputs + +Your hook must return a response that either allows or blocks the signup request. + +| Field | Type | Description | +| ------- | -------- | ----------------------------------------------------------------------------------------------------- | +| `error` | `object` | (Optional) Return this to reject the signup. Includes a code, message, and optional HTTP status code. | + +Returning an empty object with a `200` or `204` status code allows the request to proceed. Returning a JSON response with an `error` object and a `4xx` status code blocks the request and propagates the error message to the client. See the [error handling documentation](/docs/guides/auth/auth-hooks#error-handling) for more details. + +### Allow the signup + +```json +{} +``` + +or with a `204 No Content` response: + +```http +HTTP/1.1 204 No Content +``` + +### Reject the signup with an error + +```json +{ + "error": { + "http_code": 400, + "message": "Only company emails are allowed to sign up." + } +} +``` + +This response will block the user creation and return the error message to the client that attempted signup. + +## Examples + +Each of the following examples shows how to use the `before-user-created` hook to control signup behavior. Each use case includes both a HTTP implementation (e.g. using an Edge Function) and a SQL implementation (Postgres function). + + + + + + + + +Allow signups only from specific domains like supabase.com or example.test. Reject all others. This is useful for private/internal apps, enterprise gating, or invite-only beta access. + +The `before-user-created` hook solves this by: + +- Detecting that a user is about to be created +- Providing the email address in the `user.email` field + +Run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `signup_email_domains` table with some sample data and a `hook_restrict_signup_by_email_domain` function to be called by the `before-user-created` auth hook. + +```sql +-- Create ENUM type for domain rule classification +do $$ begin + create type signup_email_domain_type as enum ('allow', 'deny'); +exception + when duplicate_object then null; +end $$; + +-- Create the signup_email_domains table +create table if not exists public.signup_email_domains ( + id serial primary key, + domain text not null, + type signup_email_domain_type not null, + reason text default null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +-- Create a trigger to maintain updated_at +create or replace function update_signup_email_domains_updated_at() +returns trigger as $$ +begin + new.updated_at = now(); + return new; +end; +$$ language plpgsql; + +drop trigger if exists trg_signup_email_domains_set_updated_at on public.signup_email_domains; + +create trigger trg_signup_email_domains_set_updated_at +before update on public.signup_email_domains +for each row +execute procedure update_signup_email_domains_updated_at(); + +-- Seed example data +insert into public.signup_email_domains (domain, type, reason) values + ('supabase.com', 'allow', 'Internal signups'), + ('gmail.com', 'deny', 'Public email provider'), + ('yahoo.com', 'deny', 'Public email provider'); + +-- Create the function +create or replace function public.hook_restrict_signup_by_email_domain(event jsonb) +returns jsonb +language plpgsql +as $$ +declare + email text; + domain text; + is_allowed int; + is_denied int; +begin + email := event->'user'->>'email'; + domain := split_part(email, '@', 2); + + -- Check for allow match + select count(*) into is_allowed + from public.signup_email_domains + where type = 'allow' and lower(domain) = lower($1); + + if is_allowed > 0 then + return '{}'::jsonb; + end if; + + -- Check for deny match + select count(*) into is_denied + from public.signup_email_domains + where type = 'deny' and lower(domain) = lower($1); + + if is_denied > 0 then + return jsonb_build_object( + 'error', jsonb_build_object( + 'message', 'Signups from this email domain are not allowed.', + 'http_code', 403 + ) + ); + end if; + + -- No match, allow by default + return '{}'::jsonb; +end; +$$; + +-- Permissions +grant execute + on function public.hook_restrict_signup_by_email_domain + to supabase_auth_admin; + +revoke execute + on function public.hook_restrict_signup_by_email_domain + from authenticated, anon, public; +``` + + + + +Some applications want to **allow sign-ins with a provider like Discord only for users who already exist**, while blocking new account creation via that provider. This prevents unwanted signups through OAuth flows and enables tighter control over who can join the app. + +The `before-user-created` hook solves this by: + +- Detecting that a user is about to be created +- Allowing you to inspect the `app_metadata.provider` +- Knowing the request came from an OAuth flow + +Run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `hook_reject_discord_signups` function to be called by the `before-user-created` auth hook. + +```sql +-- Create the function +create or replace function public.hook_reject_discord_signups(event jsonb) +returns jsonb +language plpgsql +as $$ +declare + provider text; +begin + provider := event->'user'->'app_metadata'->>'provider'; + + if provider = 'discord' then + return jsonb_build_object( + 'error', jsonb_build_object( + 'message', 'Signups with Discord are not allowed.', + 'http_code', 403 + ) + ); + end if; + + return '{}'::jsonb; +end; +$$; + +-- Permissions +grant execute + on function public.hook_reject_discord_signups + to supabase_auth_admin; + +revoke execute + on function public.hook_reject_discord_signups + from authenticated, anon, public; +``` + + + + +This example shows how you might restrict sign up from a single IP address or a range of them using [PostgreSQL’s built-in](https://www.postgresql.org/docs/current/datatype-net-types.html) `inet` and `<<` operators for [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) -- a method of representing IP address ranges. +For instance: `123.123.123.123/32` represents only a single IP address, while `123.123.123.0/24` means all IP addresses starting with `123.123.123.`. + +The `before-user-created` hook solves this by: + +- Detecting that a user is about to be created +- Providing the IP address in the `metadata.ip_address` field + +Run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `signup_networks` table with some sample data and a `hook_restrict_signup_by_network` function to be called by the `before-user-created` auth hook. + +```sql SQL_EDITOR +-- Create ENUM type for network rule classification +create type signup_network_type as enum ('allow', 'deny'); + +-- Create the signup_networks table for controlling sign-up access by CIDR +create table if not exists public.signup_networks ( + id serial primary key, + cidr cidr not null, + type public.signup_network_type not null, + reason text default null, + note text default null, + created_at timestamp with time zone not null default now(), + constraint signup_networks_cidr_key unique (cidr) +); + +-- Assign appropriate permissions +grant all + on table public.signup_networks + to supabase_auth_admin; + +revoke all + on table public.signup_networks + from authenticated, anon, public; + +-- Insert some sample data into the table +insert into public.signup_networks (cidr, type, reason, note) +values + ('192.0.2.0/24', 'allow', '', 'Corporate VPN'), + ('198.51.100.158/32', 'deny', + 'Your IP Address has been blocked for abuse.', + 'blocked by abuse: (Ticket: ABUSE-185)'), + ('203.0.113.0/24', 'deny', + 'Your network has been blocked for abuse.', + 'blocked by abuse: (Ticket: ABUSE-212)'); + +-- Create the hook function to be called by the auth server +create or replace function public.hook_restrict_signup_by_network(event jsonb) +returns jsonb +language plpgsql +as $$ +declare + ip inet; + allow_count int; + deny_count int; +begin + ip := event->'metadata'->>'ip_address'; + + -- Step 1: Check for explicit allow + select count(*) into allow_count + from public.signup_networks + where type = 'allow' and ip::inet << cidr; + + if allow_count > 0 then + -- If explicitly allowed, allow signup + return '{}'::jsonb; + end if; + + -- Step 2: Check for explicit deny + select count(*) into deny_count + from public.signup_networks + where type = 'deny' and ip::inet << cidr; + + if deny_count > 0 then + return jsonb_build_object( + 'error', jsonb_build_object( + 'message', 'Signups are not allowed from your network.', + 'http_code', 403 + ) + ); + end if; + + -- Step 3: No match: allow by default + return '{}'::jsonb; +end; +$$; + +-- Assign permissions +grant execute + on function public.hook_restrict_signup_by_network + to supabase_auth_admin; + +revoke execute + on function public.hook_restrict_signup_by_network + from authenticated, anon, public; +``` + + + + + + + + + + +Allow signups only from specific domains like supabase.com or example.test. Reject all others. This is useful for private/internal apps, enterprise gating, or invite-only beta access. + +The `before-user-created` hook solves this by: + +- Detecting that a user is about to be created +- Providing the email address in the `user.email` field + +Create a `.env` file with the following environment variables: + +```ini +BEFORE_USER_CREATED_HOOK_SECRET="v1,whsec_" +``` + + + +You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard. + + + +Set the secrets in your Supabase project: + +```bash +supabase secrets set --env-file .env +``` + +Create a new edge function: + +```bash +supabase functions new before-user-created-hook +``` + +Add the following code to your edge function: + +```ts +import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' + +const allowedDomains = ['supabase.com', 'example.test'] + +Deno.serve(async (req) => { + const payload = await req.text() + const secret = Deno.env.get('BEFORE_USER_CREATED_HOOK_SECRET')?.replace('v1,whsec_', '') + const headers = Object.fromEntries(req.headers) + const wh = new Webhook(secret) + + try { + const { user } = wh.verify(payload, headers) + const email = user.email || '' + const domain = email.split('@')[1] || '' + + if (!allowedDomains.includes(domain)) { + return new Response( + JSON.stringify({ + error: { + message: 'Please sign up with a company email address.', + http_code: 400, + }, + }), + { status: 400, headers: { 'Content-Type': 'application/json' } } + ) + } + + return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }) + } catch (error) { + return new Response(JSON.stringify({ error: { message: 'Invalid request format' } }), { + status: 400, + headers: { 'Content-Type': 'application/json' }, + }) + } +}) +``` + + + + +Some applications want to **allow sign-ins with a provider like Discord only for users who already exist**, while blocking new account creation via that provider. This prevents unwanted signups through OAuth flows and enables tighter control over who can join the app. + +The `before-user-created` hook solves this by: + +- Allowing you to inspect the `app_metadata.provider` +- Detecting that a user is about to be created +- Knowing the request came from an OAuth flow + +Create a `.env` file with the following environment variables: + +```ini +BEFORE_USER_CREATED_HOOK_SECRET="v1,whsec_" +``` + + + +You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard. + + + +Set the secrets in your Supabase project: + +```bash +supabase secrets set --env-file .env +``` + +Create a new edge function: + +```bash +supabase functions new before-user-created-hook +``` + +Add the following code to your edge function: + +```ts +import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' + +const blockedProviders = ['discord'] + +Deno.serve(async (req) => { + const payload = await req.text() + const secret = Deno.env.get('BEFORE_USER_CREATED_HOOK_SECRET')?.replace('v1,whsec_', '') + const headers = Object.fromEntries(req.headers) + const wh = new Webhook(secret) + + try { + const { user } = wh.verify(payload, headers) + const provider = user.app_metadata?.provider + + if (blockedProviders.includes(provider)) { + return new Response( + JSON.stringify({ + error: { + message: `Signups with ${provider} are not allowed.`, + http_code: 403, + }, + }), + { status: 403, headers: { 'Content-Type': 'application/json' } } + ) + } + + return new Response('{}', { status: 200, headers: { 'Content-Type': 'application/json' } }) + } catch { + return new Response('{}', { status: 400 }) + } +}) +``` + + + + +This example shows how you might restrict sign up from a single IP address or a range of them using [PostgreSQL’s built-in](https://www.postgresql.org/docs/current/datatype-net-types.html) `inet` and `<<` operators for [CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) -- a method of representing IP address ranges. +For instance: `123.123.123.123/32` represents only a single IP address, while `123.123.123.0/24` means all IP addresses starting with `123.123.123.`. + +The `before-user-created` hook solves this by: + +- Detecting that a user is about to be created +- Providing the IP address in the `metadata.ip_address` field + +Before creating the edge function run the following snippet in your project's [SQL Editor](https://supabase.com/dashboard/project/_/sql/new). This will create a `signup_networks` table with some sample data and a `hook_restrict_signup_by_network` function to be called by the `before-user-created` auth hook. + +```sql SQL_EDITOR +-- Create ENUM type for network rule classification +create type signup_network_type as enum ('allow', 'deny'); + +-- Create the signup_networks table for controlling sign-up access by CIDR +create table if not exists public.signup_networks ( + id serial primary key, + cidr cidr not null, + type public.signup_network_type not null, + reason text default null, + note text default null, + created_at timestamp with time zone not null default now(), + constraint signup_networks_cidr_key unique (cidr) +); + +-- Assign appropriate permissions +grant all + on table public.signup_networks + to supabase_auth_admin; + +revoke all + on table public.signup_networks + from authenticated, anon, public; + +-- Insert some sample data into the table +insert into public.signup_networks (cidr, type, reason, note) +values + ('192.0.2.0/24', 'allow', '', 'Corporate VPN'), + ('198.51.100.158/32', 'deny', + 'Your IP Address has been blocked for abuse.', + 'blocked by abuse: (Ticket: ABUSE-185)'), + ('203.0.113.0/24', 'deny', + 'Your network has been blocked for abuse.', + 'blocked by abuse: (Ticket: ABUSE-212)'); + +-- Create the hook function to be called by the auth server +create or replace function public.hook_restrict_signup_by_network(event jsonb) +returns jsonb +language plpgsql +as $$ +declare + ip inet; + allow_count int; + deny_count int; +begin + ip := event->'metadata'->>'ip_address'; + + -- Step 1: Check for explicit allow + select count(*) into allow_count + from public.signup_networks + where type = 'allow' and ip::inet << cidr; + + if allow_count > 0 then + -- If explicitly allowed, allow signup + return '{}'::jsonb; + end if; + + -- Step 2: Check for explicit deny + select count(*) into deny_count + from public.signup_networks + where type = 'deny' and ip::inet << cidr; + + if deny_count > 0 then + return jsonb_build_object( + 'error', jsonb_build_object( + 'message', 'Signups are not allowed from your network.', + 'http_code', 403 + ) + ); + end if; + + -- Step 3: No match: allow by default + return '{}'::jsonb; +end; +$$; + +-- Assign permissions +grant execute + on function public.hook_restrict_signup_by_network + to supabase_auth_admin; + +revoke execute + on function public.hook_restrict_signup_by_network + from authenticated, anon, public; +``` + +Create a `.env` file with the following environment variables: + +```ini +BEFORE_USER_CREATED_HOOK_SECRET="v1,whsec_" +``` + + + +You can generate the secret in the [Auth Hooks](/dashboard/project/_/auth/hooks) section of the Supabase dashboard. + + + +Set the secrets in your Supabase project: + +```bash +supabase secrets set --env-file .env +``` + +Create a new edge function: + +```bash +supabase functions new before-user-created-hook +``` + +Add the following code to your edge function: + +```ts +import { Webhook } from 'https://esm.sh/standardwebhooks@1.0.0' +import { createClient } from 'https://esm.sh/@supabase/supabase-js' + +const whSecret = Deno.env.get('BEFORE_USER_CREATED_HOOK_SECRET')?.replace('v1,whsec_', '') +const supabaseUrl = Deno.env.get('SUPABASE_URL') +const supabaseKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') + +const wh = new Webhook(whSecret) +const supabase = createClient(supabaseUrl, supabaseKey) + +Deno.serve(async (req) => { + const payload = await req.text() + const headers = Object.fromEntries(req.headers) + try { + const event = wh.verify(payload, headers) + + // Call the same Postgres function as in the SQL example. + const { data, error } = await supabase.rpc('hook_restrict_signup_by_network', { + event: JSON.parse(payload), + }) + if (error) { + console.error('RPC call failed:', error) + return new Response( + JSON.stringify({ + error: { + message: 'Internal error processing signup restriction', + http_code: 500, + }, + }), + { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + } + ) + } + return new Response(JSON.stringify(data ?? {}), { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + }) + } catch (err) { + console.error('Webhook verification failed:', err) + return new Response( + JSON.stringify({ + error: { + message: 'Invalid request format or signature', + }, + }), + { + status: 400, + headers: { + 'Content-Type': 'application/json', + }, + } + ) + } +}) +``` + + + + + + + diff --git a/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx b/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx index 78ac5b91c6d..9534e48e2de 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/CreateHookSheet.tsx @@ -38,6 +38,7 @@ import { import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout' import { HOOKS_DEFINITIONS, HOOK_DEFINITION_TITLE, Hook } from './hooks.constants' import { extractMethod, getRevokePermissionStatements, isValidHook } from './hooks.utils' +import { DocsButton } from 'components/ui/DocsButton' interface CreateHookSheetProps { visible: boolean @@ -289,6 +290,7 @@ export const CreateHookSheet = ({ {isCreating ? `Add ${title}` : `Update ${title}`} + diff --git a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx index 38bb4226a16..d626ef3efee 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx +++ b/apps/studio/components/interfaces/Auth/Hooks/HookCard.tsx @@ -5,6 +5,7 @@ import { Badge, Input, copyToClipboard } from 'ui' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { Hook } from './hooks.constants' +import { DocsButton } from 'components/ui/DocsButton' interface HookCardProps { hook: Hook @@ -107,6 +108,7 @@ export const HookCard = ({ hook, onSelect }: HookCardProps) => { > Configure hook +
diff --git a/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts b/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts index a120817513b..7002b60ab4e 100644 --- a/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts +++ b/apps/studio/components/interfaces/Auth/Hooks/hooks.constants.ts @@ -6,6 +6,7 @@ export const HOOKS_DEFINITIONS = [ uriKey: 'HOOK_SEND_SMS_URI', secretsKey: 'HOOK_SEND_SMS_SECRETS', enterprise: false, + docSlug: 'send-sms-hook', }, { title: 'Send Email hook', @@ -14,6 +15,7 @@ export const HOOKS_DEFINITIONS = [ uriKey: 'HOOK_SEND_EMAIL_URI', secretsKey: 'HOOK_SEND_EMAIL_SECRETS', enterprise: false, + docSlug: 'send-email-hook', }, { title: 'Customize Access Token (JWT) Claims hook', @@ -23,6 +25,7 @@ export const HOOKS_DEFINITIONS = [ uriKey: 'HOOK_CUSTOM_ACCESS_TOKEN_URI', secretsKey: 'HOOK_CUSTOM_ACCESS_TOKEN_SECRETS', enterprise: false, + docSlug: 'custom-access-token-hook', }, { title: 'MFA Verification Attempt hook', @@ -32,6 +35,7 @@ export const HOOKS_DEFINITIONS = [ uriKey: 'HOOK_MFA_VERIFICATION_ATTEMPT_URI', secretsKey: 'HOOK_MFA_VERIFICATION_ATTEMPT_SECRETS', enterprise: true, + docSlug: 'mfa-verification-hook', }, { title: 'Password Verification Attempt hook', @@ -41,6 +45,17 @@ export const HOOKS_DEFINITIONS = [ uriKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI', secretsKey: 'HOOK_PASSWORD_VERIFICATION_ATTEMPT_SECRETS', enterprise: true, + docSlug: 'password-verification-hook', + }, + { + title: 'Before User Created hook', + subtitle: + 'Will be called by Supabase Auth before creating a new user. Returning an error will prevent the user from being created.', + enabledKey: 'HOOK_BEFORE_USER_CREATED_ENABLED', + uriKey: 'HOOK_BEFORE_USER_CREATED_URI', + secretsKey: 'HOOK_BEFORE_USER_CREATED_SECRETS', + enterprise: false, + docSlug: 'before-user-created-hook', }, ] as const @@ -53,6 +68,7 @@ export interface Hook { enabledKey: string uriKey: string secretsKey: string + docSlug: string method: | { type: 'postgres'; schema: string; functionName: string } | { type: 'https'; url: string; secret: string } diff --git a/apps/studio/pages/project/[ref]/auth/hooks.tsx b/apps/studio/pages/project/[ref]/auth/hooks.tsx index 09215e8aa33..b9e6e60e5f1 100644 --- a/apps/studio/pages/project/[ref]/auth/hooks.tsx +++ b/apps/studio/pages/project/[ref]/auth/hooks.tsx @@ -8,6 +8,7 @@ import { ScaffoldContainer } from 'components/layouts/Scaffold' import NoPermission from 'components/ui/NoPermission' import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions' import type { NextPageWithLayout } from 'types' +import { DocsButton } from 'components/ui/DocsButton' const Hooks: NextPageWithLayout = () => { const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue') @@ -23,6 +24,9 @@ const Hooks: NextPageWithLayout = () => { ) } +const secondaryActions = [ + , +] Hooks.getLayout = (page) => ( @@ -30,6 +34,7 @@ Hooks.getLayout = (page) => ( {page} diff --git a/supa-mdx-lint/Rule003Spelling.toml b/supa-mdx-lint/Rule003Spelling.toml index cda1c7fc545..db481b95d52 100644 --- a/supa-mdx-lint/Rule003Spelling.toml +++ b/supa-mdx-lint/Rule003Spelling.toml @@ -326,6 +326,7 @@ allow_list = [ "sessionStorage", "stdin", "stdout", + "[Ss]ubnet(s)?", "[Ss]ubpage", "supabase-auth-ui", "supabase-csharp",