diff --git a/apps/docs/content/guides/auth/auth-hooks.mdx b/apps/docs/content/guides/auth/auth-hooks.mdx
index 0286afb148fc0..04fef7cbb096b 100644
--- a/apps/docs/content/guides/auth/auth-hooks.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks.mdx
@@ -313,23 +313,37 @@ Hooks return status codes based on the nature of the response. These status code
| HTTP Status Code | Description | Example Usage |
| ---------------- | ------------------------------------------------------------- | ---------------------------------------------- |
| 200, 202, 204 | Valid response, proceed | Successful processing of the request |
-| 429, 503 | Retry-able errors with Retry-after header supported | Temporary server overload or maintenance |
| 403, 400 | Treated as Internal Server Errors and return a 500 Error Code | Malformed requests or insufficient permissions |
+| 429, 503 | Retry-able errors | Temporary server overload or maintenance |
-Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds.
+
+
+`204` Status is not supported by the following hooks which require a response body:
+
+- [Custom Access Token](/docs/guides/auth/auth-hooks/custom-access-token-hook)
+- [MFA Verification Attempt](/docs/guides/auth/auth-hooks/mfa-verification-hook)
+- [Password Verification Attempt](/docs/guides/auth/auth-hooks/password-verification-hook)
+
+
+
+Errors are responses which contain status codes 400 and above. On a retry-able error, such as an error with a `429` or `503` status code, HTTP Hooks will attempt up to three retries with a back-off of two seconds. We have a time budget of 5s for the entire webhook invocation, including retry requests.
Here's a sample HTTP retry schedule:
-| Time Since Start (HH:MM:SS) | Event | Notes |
-| --------------------------- | ----------------------- | ------------------------------------------------- |
-| 00:00:00 | Initial Attempt | Initial invocation begins. |
-| 00:00:05 | Initial Attempt Timeout | Initial invocation must complete. |
-| 00:00:07 | Retry Start #1 | After 2 sec delay, first retry begins. |
-| 00:00:12 | Retry Timeout #1 | First retry timeout. |
-| 00:00:14 | Retry Start #2 | After 2 sec delay, second retry begins. |
-| 00:00:19 | Retry Timeout #2 | Second retry timeout. Returns an error on failure |
+| Time Since Start (HH:MM:SS) | Event | Notes |
+| --------------------------- | --------------------- | -------------------------------------------------------------------------------- |
+| 00:00:00 | Initial Attempt | Initial invocation begins. |
+| 00:00:02 | Initial Attempt Fails | Initial invocation returns `429` or `503` with non-empty `retry-after` header. |
+| 00:00:04 | Retry Start #1 | After 2 sec delay, first retry begins. |
+| 00:00:05 | Retry Timeout #1 | First retry times out, exceeded 5 second budget and invocation returns an error. |
-Return a retry-able error by attaching a appropriate status code (`429`, `503` ) and a non-empty `retry-after` header
+Return a retry-able error by attaching a appropriate status code (`429`, `503`) and a non-empty `retry-after` header
+
+
+
+`Retry-After` Supabase Auth does not fully support the `Retry-After` header as described in RFC7231, we only check if it is a non-empty value such as `true` or `10`. Setting this to your preferred value is fine as a future update may address this.
+
+
```jsx
return new Response(
diff --git a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
index d18fd214ab2c1..7b765a018ca79 100644
--- a/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
+++ b/apps/docs/content/guides/auth/auth-hooks/send-email-hook.mdx
@@ -17,6 +17,33 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
| Disabled | Enabled | Email Signups Disabled |
| Disabled | Disabled | Email Signups Disabled |
+## Email change behavior and token hash mapping
+
+When `email_action_type` is `email_change`, the hook payload can include one or two OTPs and their hashes. This depends on your [Secure Email Change](/dashboard/project/_/auth/providers?provider=Email) setting.
+
+- Secure Email Change enabled: two OTPs are generated, one for the current email (`user.email`) and one for the new email (`user.email_new`). You must send two emails.
+- Secure Email Change disabled: only one OTP is generated for the new email. You send a single email.
+
+
+
+Important quirk (backward compatibility):
+
+- `email_data.token_hash_new` = Hash(`user.email`, `email_data.token`)
+- `email_data.token_hash` = Hash(`user.email_new`, `email_data.token_new`)
+
+This naming is historical and kept for backward compatibility. Do not assume that the `_new` suffix refers to the new email.
+
+
+
+### What to send
+
+If both `token_hash` and `token_hash_new` are present, send two messages:
+
+- To the current email (`user.email`): use `token` with `token_hash_new`.
+- To the new email (`user.email_new`): use `token_new` with `token_hash`.
+
+If only one token/hash pair is present, send a single email. In non-secure mode, this is typically the new email OTP. Use `token` with `token_hash` or `token_new` with `token_hash`, depending on which fields are present in the payload.
+
**Inputs**
| Field | Type | Description |
@@ -282,7 +309,15 @@ Email sending depends on two settings: Email Provider and Auth Hook status.
},
"email_action_type": {
"type": "string",
- "enum": ["signup", "invite", "magiclink", "recovery", "email_change", "email"]
+ "enum": [
+ "signup",
+ "invite",
+ "magiclink",
+ "recovery",
+ "email_change",
+ "email",
+ "reauthentication"
+ ]
},
"site_url": {
"type": "string",
@@ -578,6 +613,7 @@ import { readAll } from 'https://deno.land/std/io/read_all.ts'
const postmarkEndpoint = 'https://api.postmarkapp.com/email'
// Replace this with your email
const FROM_EMAIL = 'myemail@gmail.com'
+const PROJECT_REF = ''
// Email Subjects
const subjects = {
@@ -642,8 +678,14 @@ const templates = {
}
function generateConfirmationURL(email_data) {
- // TODO: replace the ref with your project ref
- return `https://.supabase.co/auth/v1/verify?token=${email_data.token_hash}&type=${email_data.email_action_type}&redirect_to=${email_data.redirect_to}`
+ const baseUrl = `https://${PROJECT_REF}.supabase.co/auth/v1/verify`
+ const params = new URLSearchParams({
+ token: email_data.token_hash,
+ type: email_data.email_action_type,
+ redirect_to: email_data.redirect_to,
+ })
+
+ return `${baseUrl}?${params.toString()}`
}
Deno.serve(async (req) => {
diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt
index f80575a22d5b9..e6b975879527e 100644
--- a/apps/docs/public/humans.txt
+++ b/apps/docs/public/humans.txt
@@ -35,6 +35,7 @@ Danny White
Darren Cunningham
Dave Wilson
David A. Ventimiglia
+David Weitzman
Deepthi Sigireddi
Deji I
Div Arora
@@ -68,6 +69,7 @@ Jordi Enric
José Luis Ledesma
Joshen Lim
Julien Goux
+Kanishk Dudeja
Kamil Ogórek
Kang Ming Tay
Karan S
diff --git a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx
index 047b511de692e..59382c015e36c 100644
--- a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx
+++ b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx
@@ -1,7 +1,6 @@
import type { PostgresPolicy } from '@supabase/postgres-meta'
import { isEmpty } from 'lodash'
-import { HelpCircle } from 'lucide-react'
-import { useRouter } from 'next/router'
+import Link from 'next/link'
import { useState } from 'react'
import { toast } from 'sonner'
@@ -11,32 +10,34 @@ import {
PolicyTableRowProps,
} from 'components/interfaces/Auth/Policies/PolicyTableRow'
import { ProtectedSchemaWarning } from 'components/interfaces/Database/ProtectedSchemaWarning'
-import NoSearchResults from 'components/to-be-cleaned/NoSearchResults'
-import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState'
-import InformationBox from 'components/ui/InformationBox'
+import { NoSearchResults } from 'components/ui/NoSearchResults'
import { useDatabasePolicyDeleteMutation } from 'data/database-policies/database-policy-delete-mutation'
import { useTableUpdateMutation } from 'data/tables/table-update-mutation'
import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
+import { Button, Card, CardContent } from 'ui'
import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog'
interface PoliciesProps {
+ search?: string
schema: string
tables: PolicyTableRowProps['table'][]
hasTables: boolean
isLocked: boolean
onSelectCreatePolicy: (table: string) => void
onSelectEditPolicy: (policy: PostgresPolicy) => void
+ onResetSearch?: () => void
}
-const Policies = ({
+export const Policies = ({
+ search,
schema,
tables,
hasTables,
isLocked,
onSelectCreatePolicy,
onSelectEditPolicy: onSelectEditPolicyAI,
+ onResetSearch,
}: PoliciesProps) => {
- const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
@@ -115,40 +116,21 @@ const Policies = ({
})
}
- if (tables.length === 0) {
+ if (!hasTables) {
return (
-
- router.push(`/project/${ref}/editor`)}
- >
-
- }
- description={
-
-
- Policies restrict, on a per-user basis, which rows can be returned by normal
- queries, or inserted, updated, or deleted by data modification commands.
-
-
- This is also known as Row-Level Security (RLS). Each policy is attached to a
- table, and the policy is executed each time its accessed.
-
-
- }
- />
-
- Create a table in this schema first before creating a policy.
-
-
-
-
+
+
+
No tables to create policies for
+
+
+ RLS Policies control per-user access to table rows. Create a table in this schema first
+ before creating a policy.
+
+
+
{!isLocked && (
@@ -127,9 +120,9 @@ const PolicyRow = ({
name: `Update policy ${policy.name}`,
open: true,
sqlSnippets: [sql],
- initialInput: `Update the policy with name "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table. It should...`,
+ initialInput: `Update the policy with name \"${policy.name}\" in the ${policy.schema} schema on the ${policy.table} table. It should...`,
suggestions: {
- title: `I can help you make a change to the policy "${policy.name}" in the ${policy.schema} schema on the ${policy.table} table, here are a few example prompts to get you started:`,
+ title: `I can help you make a change to the policy \"${policy.name}\" in the ${policy.schema} schema on the ${policy.table} table, here are a few example prompts to get you started:`,
prompts: [
{
label: 'Improve Policy',
@@ -169,8 +162,8 @@ const PolicyRow = ({
)}
-
-
+
+
)
}
diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx
index 6858191349ad2..dfa72075deb76 100644
--- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx
+++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx
@@ -1,13 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { noop } from 'lodash'
-import { Lock, Unlock } from 'lucide-react'
+import { Lock, Table } from 'lucide-react'
import { useParams } from 'common'
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { EditorTablePageLink } from 'data/prefetchers/project.$ref.editor.$id'
import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions'
import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
-import { AiIconAnimation, Badge } from 'ui'
+import { AiIconAnimation, Badge, CardTitle } from 'ui'
interface PolicyTableRowHeaderProps {
table: {
@@ -54,28 +54,19 @@ const PolicyTableRowHeader = ({
- {table.rls_enabled ? (
-
-
-
- {isPubliclyReadableWritable ? 'Warning' : 'Note'}:
- {' '}
-
+
{isPubliclyReadableWritable
- ? 'Row Level Security is disabled. Your table is publicly readable and writable.'
- : 'Row Level Security is enabled, but no policies exist. No data will be selectable via Supabase APIs.'}
-
- {isPubliclyReadableWritable && (
-
-
-
-
-
- Anyone with the project's anonymous key can modify or delete your data. Enable RLS
- and create access policies to keep your data secure.
-
-
- )}
-
+ ? "Anyone with your project's anonymous key can read, modify, or delete your data."
+ : 'No data will be selectable via Supabase APIs because RLS is enabled but no policies have been created yet.'}
+
+
)}
+
{isLoading && (
-