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. +

+ +
+
) } @@ -170,7 +152,7 @@ const Policies = ({ )) ) : hasTables ? ( - + ) : null} @@ -202,5 +184,3 @@ const Policies = ({ ) } - -export default Policies diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx index c58cc46fa172b..82a9085861427 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyRow.tsx @@ -4,7 +4,6 @@ import { noop } from 'lodash' import { Edit, MoreVertical, Trash } from 'lucide-react' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' -import Panel from 'components/ui/Panel' import { useAuthConfigQuery } from 'data/auth/auth-config-query' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' @@ -13,6 +12,8 @@ import { Badge, Button, cn, + TableRow, + TableCell, DropdownMenu, DropdownMenuContent, DropdownMenuItem, @@ -57,58 +58,50 @@ const PolicyRow = ({ (policy.roles.includes('authenticated') || policy.roles.includes('public')) return ( - -
-
-

- {policy.command} -

- -
- -
-
- Applied to:{' '} - {policy.roles.slice(0, 3).map((role, i) => ( - - {role} - {i < Math.min(policy.roles.length, 3) - 1 ? ', ' : ' '} - - ))} - {policy.roles.length > 1 ? 'roles' : 'role'} -
- {policy.roles.length > 3 && ( - - - - + {policy.roles.length - 3} more roles - - - - {policy.roles.slice(3).join(', ')} - - - )} -
-
- + + +
+ {appliesToAnonymousUsers ? ( Applies to anonymous users ) : null}
-
-
+ + + {policy.command} + + +
+
+ {policy.roles.slice(0, 3).map((role, i) => ( + + {role} + {i < Math.min(policy.roles.length, 3) - 1 ? ', ' : ' '} + + ))} + {policy.roles.length > 1 ? 'roles' : 'role'} +
+ {policy.roles.length > 3 && ( + + + + + {policy.roles.length - 3} more roles + + + + {policy.roles.slice(3).join(', ')} + + + )} +
+
+ {!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 ? ( -
- -
- ) : ( -
- -
- )} -

{table.name}

+ + {table.name} + {!table.rls_enabled && RLS Disabled} -
- {isTableLocked && ( - - - Locked - - - )} -
+ {isTableLocked && ( + + + Locked + + + )} {!isTableLocked && (
diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx index 8732566cc2329..e6199df9fa616 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/index.tsx @@ -1,14 +1,24 @@ import type { PostgresPolicy } from '@supabase/postgres-meta' import { noop } from 'lodash' -import { Info } from 'lucide-react' import AlertError from 'components/ui/AlertError' -import Panel from 'components/ui/Panel' import { useProjectPostgrestConfigQuery } from 'data/config/project-postgrest-config-query' import { useDatabasePoliciesQuery } from 'data/database-policies/database-policies-query' import { useTableRolesAccessQuery } from 'data/tables/table-roles-access-query' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' -import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' +import { + Alert_Shadcn_, + AlertDescription_Shadcn_, + Card, + CardContent, + CardHeader, + cn, + Table, + TableBody, + TableHead, + TableHeader, + TableRow, +} from 'ui' import ShimmeringLoader from 'ui-patterns/ShimmeringLoader' import PolicyRow from './PolicyRow' import PolicyTableRowHeader from './PolicyTableRowHeader' @@ -73,85 +83,86 @@ export const PolicyTableRow = ({ .filter((policy) => policy.schema === table.schema && policy.table === table.name) .sort((a, b) => a.name.localeCompare(b.name)) const rlsEnabledNoPolicies = isRLSEnabled && policies.length === 0 + const isRealtimeSchema = table.schema === 'realtime' + const isRealtimeMessagesTable = isRealtimeSchema && table.name === 'messages' + const isTableLocked = isRealtimeSchema ? !isRealtimeMessagesTable : isLocked return ( - + - } - > + + {(isPubliclyReadableWritable || rlsEnabledNoPolicies) && ( -
-
- - {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 && ( -
+ -
+ )} + {isError && ( - + + + )} + {isSuccess && ( - <> - {policies.length === 0 && ( -
-

No policies created yet

-
+ + {policies.length === 0 ? ( +

No policies created yet

+ ) : ( +
+ + + Name + Command + Applied to + + Actions + + + + + {policies.map((policy) => ( + + ))} + +
)} - {policies?.map((policy) => ( - - ))} - + )} - + ) } diff --git a/apps/studio/components/interfaces/Realtime/Policies.tsx b/apps/studio/components/interfaces/Realtime/Policies.tsx index cffdf03c2351b..8c96c07a41a6e 100644 --- a/apps/studio/components/interfaces/Realtime/Policies.tsx +++ b/apps/studio/components/interfaces/Realtime/Policies.tsx @@ -1,7 +1,7 @@ import { PostgresPolicy } from '@supabase/postgres-meta' import { useState } from 'react' -import Policies from 'components/interfaces/Auth/Policies/Policies' +import { Policies } from 'components/interfaces/Auth/Policies/Policies' import { PolicyEditorPanel } from 'components/interfaces/Auth/Policies/PolicyEditorPanel' import AlertError from 'components/ui/AlertError' import { FormHeader } from 'components/ui/Forms/FormHeader' diff --git a/apps/studio/components/to-be-cleaned/NoSearchResults.tsx b/apps/studio/components/to-be-cleaned/NoSearchResults.tsx index 5cf4e9d5d3a2c..4214b8a92f8b5 100644 --- a/apps/studio/components/to-be-cleaned/NoSearchResults.tsx +++ b/apps/studio/components/to-be-cleaned/NoSearchResults.tsx @@ -1,8 +1,9 @@ -import SVG from 'react-inlinesvg' import { BASE_PATH } from 'lib/constants' +import SVG from 'react-inlinesvg' -// [Joshen] To be deprecated in favor of NoSearchResults in components/ui - +/** + * To be deprecated in favor of NoSearchResults in components/ui + */ export const NoSearchResults = () => { return (
diff --git a/apps/studio/components/ui/NoSearchResults.tsx b/apps/studio/components/ui/NoSearchResults.tsx index 71eba41ab86f4..d307c8535d5fa 100644 --- a/apps/studio/components/ui/NoSearchResults.tsx +++ b/apps/studio/components/ui/NoSearchResults.tsx @@ -6,7 +6,11 @@ export interface NoSearchResultsProps { className?: string } -const NoSearchResults = ({ searchString, onResetFilter, className }: NoSearchResultsProps) => { +export const NoSearchResults = ({ + searchString, + onResetFilter, + className, +}: NoSearchResultsProps) => { return (
void onSelectCreateSchema?: () => void portal?: boolean + align?: 'start' | 'end' } const SchemaSelector = ({ @@ -48,6 +49,7 @@ const SchemaSelector = ({ onSelectSchema, onSelectCreateSchema, portal = true, + align = 'start', }: SchemaSelectorProps) => { const [open, setOpen] = useState(false) const { can: canCreateSchemas } = useAsyncCheckProjectPermissions( @@ -130,7 +132,7 @@ const SchemaSelector = ({ diff --git a/apps/studio/pages/project/[ref]/auth/policies.tsx b/apps/studio/pages/project/[ref]/auth/policies.tsx index f379c6d457a9c..8a602e108d8a1 100644 --- a/apps/studio/pages/project/[ref]/auth/policies.tsx +++ b/apps/studio/pages/project/[ref]/auth/policies.tsx @@ -3,14 +3,15 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { Search } from 'lucide-react' import { useState } from 'react' -import { useParams } from 'common' import { useIsInlineEditorEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' -import Policies from 'components/interfaces/Auth/Policies/Policies' +import { Policies } from 'components/interfaces/Auth/Policies/Policies' import { getGeneralPolicyTemplates } from 'components/interfaces/Auth/Policies/PolicyEditorModal/PolicyEditorModal.constants' import { PolicyEditorPanel } from 'components/interfaces/Auth/Policies/PolicyEditorPanel' import { generatePolicyUpdateSQL } from 'components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRow.utils' import AuthLayout from 'components/layouts/AuthLayout/AuthLayout' import DefaultLayout from 'components/layouts/DefaultLayout' +import { PageLayout } from 'components/layouts/PageLayout/PageLayout' +import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import AlertError from 'components/ui/AlertError' import { DocsButton } from 'components/ui/DocsButton' import { EditorPanel } from 'components/ui/EditorPanel/EditorPanel' @@ -24,7 +25,7 @@ import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { useUrlState } from 'hooks/ui/useUrlState' import { useIsProtectedSchema } from 'hooks/useProtectedSchemas' import type { NextPageWithLayout } from 'types' -import { Input } from 'ui' +import { Input } from 'ui-patterns/DataInputs/Input' /** * Filter tables by table name and policy name @@ -62,7 +63,6 @@ const onFilterTables = ( } const AuthPoliciesPage: NextPageWithLayout = () => { - const { ref } = useParams() const [params, setParams] = useUrlState<{ schema?: string search?: string @@ -106,140 +106,144 @@ const AuthPoliciesPage: NextPageWithLayout = () => { } return ( -
-
-
+ + +
+ { + const str = e.target.value + setParams({ ...params, search: str === '' ? undefined : str }) + }} + icon={} + /> { setParams({ ...params, search: undefined, schema }) }} /> -
- { - const str = e.target.value - setParams({ ...params, search: str === '' ? undefined : str }) - }} - icon={} - /> - -
-
- {isLoading && } - - {isError && } + {isLoading && } + + {isError && } + + {isSuccess && ( + 0} + isLocked={isSchemaLocked} + onSelectCreatePolicy={(table: string) => { + setSelectedTable(table) + setSelectedPolicyToEdit(undefined) + if (isInlineEditorEnabled) { + setEditorPanelOpen(true) + } else { + setShowPolicyAiEditor(true) + } + }} + onSelectEditPolicy={(policy) => { + setSelectedPolicyToEdit(policy) + setSelectedTable(undefined) + if (isInlineEditorEnabled) { + setEditorPanelOpen(true) + } else { + setShowPolicyAiEditor(true) + } + }} + onResetSearch={() => setParams({ ...params, search: undefined })} + /> + )} - {isSuccess && ( - 0} - isLocked={isSchemaLocked} - onSelectCreatePolicy={(table: string) => { - setSelectedTable(table) + searchString={searchString} + selectedTable={selectedTable} + selectedPolicy={selectedPolicyToEdit} + onSelectCancel={() => { + setSelectedTable(undefined) + setShowPolicyAiEditor(false) setSelectedPolicyToEdit(undefined) - if (isInlineEditorEnabled) { - setEditorPanelOpen(true) - } else { - setShowPolicyAiEditor(true) - } }} - onSelectEditPolicy={(policy) => { - setSelectedPolicyToEdit(policy) + authContext="database" + /> + + { + setEditorPanelOpen(false) + setSelectedPolicyToEdit(undefined) setSelectedTable(undefined) - if (isInlineEditorEnabled) { - setEditorPanelOpen(true) - } else { - setShowPolicyAiEditor(true) - } }} + onRunSuccess={() => { + setEditorPanelOpen(false) + setSelectedPolicyToEdit(undefined) + setSelectedTable(undefined) + }} + initialValue={ + selectedPolicyToEdit + ? generatePolicyUpdateSQL(selectedPolicyToEdit) + : selectedTable + ? `create policy "replace_with_policy_name"\n on ${schema}.${selectedTable}\n for select\n to authenticated\n using (\n true -- Write your policy condition here\n);` + : '' + } + label={ + selectedPolicyToEdit + ? 'RLS policies are just SQL statements that you can alter' + : selectedTable + ? `Create new RLS policy on "${selectedTable}"` + : '' + } + initialPrompt={ + selectedPolicyToEdit + ? `Update the policy with name "${selectedPolicyToEdit.name}" in the ${selectedPolicyToEdit.schema} schema on the ${selectedPolicyToEdit.table} table. It should...` + : selectedTable + ? `Create and name a entirely new RLS policy for the "${selectedTable}" table in the ${schema} schema. The policy should...` + : '' + } + templates={ + selectedPolicyToEdit + ? getGeneralPolicyTemplates( + selectedPolicyToEdit.schema, + selectedPolicyToEdit.table + ).map((template) => ({ + name: template.templateName, + description: template.description, + content: template.statement, + })) + : [] + } /> - )} - - { - setSelectedTable(undefined) - setShowPolicyAiEditor(false) - setSelectedPolicyToEdit(undefined) - }} - authContext="database" - /> - - { - setEditorPanelOpen(false) - setSelectedPolicyToEdit(undefined) - setSelectedTable(undefined) - }} - onRunSuccess={() => { - setEditorPanelOpen(false) - setSelectedPolicyToEdit(undefined) - setSelectedTable(undefined) - }} - initialValue={ - selectedPolicyToEdit - ? generatePolicyUpdateSQL(selectedPolicyToEdit) - : selectedTable - ? `create policy "replace_with_policy_name" - on ${schema}.${selectedTable} - for select - to authenticated - using ( - true -- Write your policy condition here -);` - : '' - } - label={ - selectedPolicyToEdit - ? 'RLS policies are just SQL statements that you can alter' - : selectedTable - ? `Create new RLS policy on "${selectedTable}"` - : '' - } - initialPrompt={ - selectedPolicyToEdit - ? `Update the policy with name "${selectedPolicyToEdit.name}" in the ${selectedPolicyToEdit.schema} schema on the ${selectedPolicyToEdit.table} table. It should...` - : selectedTable - ? `Create and name a entirely new RLS policy for the "${selectedTable}" table in the ${schema} schema. The policy should...` - : '' - } - templates={ - selectedPolicyToEdit - ? getGeneralPolicyTemplates( - selectedPolicyToEdit.schema, - selectedPolicyToEdit.table - ).map((template) => ({ - name: template.templateName, - description: template.description, - content: template.statement, - })) - : [] - } - /> -
+ + ) } AuthPoliciesPage.getLayout = (page) => ( -
{page}
+ + } + size="large" + > + {page} +
) diff --git a/apps/www/public/images/blog/multigres/sharding-scale.png b/apps/www/public/images/blog/multigres/sharding-scale.png index d925167d23d7c..deddfb4484997 100644 Binary files a/apps/www/public/images/blog/multigres/sharding-scale.png and b/apps/www/public/images/blog/multigres/sharding-scale.png differ