From a82bf1311d3c2a60aff856709fd15aeb765f74e0 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Fri, 7 Jun 2024 22:13:06 +0800 Subject: [PATCH 1/5] chore: branches tabs use url state (#27085) * chore: branches tabs use url state * break out tab type * Add extra type niceness. --------- Co-authored-by: Ivan Vasilov --- .../BranchManagement/BranchManagement.tsx | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx b/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx index 5e90f0db918..9ea6f8a0a47 100644 --- a/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx +++ b/apps/studio/components/interfaces/BranchManagement/BranchManagement.tsx @@ -12,7 +12,7 @@ import { useBranchDeleteMutation } from 'data/branches/branch-delete-mutation' import { useBranchesDisableMutation } from 'data/branches/branches-disable-mutation' import { Branch, useBranchesQuery } from 'data/branches/branches-query' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' -import { useSelectedOrganization, useSelectedProject } from 'hooks' +import { useSelectedOrganization, useSelectedProject, useUrlState } from 'hooks' import { Button, IconExternalLink, IconGitHub } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' @@ -25,6 +25,8 @@ import { } from './EmptyStates' import Overview from './Overview' +type Tab = 'overview' | 'prs' | 'branches' + const BranchManagement = () => { const router = useRouter() const { ref } = useParams() @@ -37,7 +39,10 @@ const BranchManagement = () => { const projectRef = project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined - const [view, setView] = useState<'overview' | 'prs' | 'branches'>('overview') + const [urlParams, setParams] = useUrlState<{ tab: Tab }>() + const tab = urlParams.tab ?? 'overview' + const setTab = (tab: Tab) => setParams({ tab }) + const [showCreateBranch, setShowCreateBranch] = useState(false) const [showDisableBranching, setShowDisableBranching] = useState(false) const [selectedBranchToDelete, setSelectedBranchToDelete] = useState() @@ -146,27 +151,27 @@ const BranchManagement = () => { @@ -237,26 +242,26 @@ const BranchManagement = () => { )} - {isErrorBranches && view === 'overview' && ( + {isErrorBranches && tab === 'overview' && ( )} {!isError && ( <> - {view === 'overview' && ( + {tab === 'overview' && ( setView('branches')} + onViewAllBranches={() => setTab('branches')} onSelectCreateBranch={() => setShowCreateBranch(true)} onSelectDeleteBranch={setSelectedBranchToDelete} generateCreatePullRequestURL={generateCreatePullRequestURL} /> )} - {view === 'prs' && ( + {tab === 'prs' && ( @@ -280,7 +285,7 @@ const BranchManagement = () => { )} )} - {view === 'branches' && ( + {tab === 'branches' && ( {isLoadingBranches && } {isErrorBranches && ( From 7940e50d93ceaeb62e230af44e0ba1d3511d39a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Og=C3=B3rek?= Date: Fri, 7 Jun 2024 16:49:47 +0200 Subject: [PATCH 2/5] Sentry docs updates (#27092) * docs: Update Sentry integration docs to work with SDK v8 * docs: Update Edge Functions monitoring with Sentry with note about scopes * docs: Reportdog stylistic updates * misc: Fix prettier errors --- .../functions/examples/sentry-monitoring.mdx | 9 ++ .../guides/platform/sentry-monitoring.mdx | 94 +++++++++++-------- 2 files changed, 63 insertions(+), 40 deletions(-) diff --git a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx index 3571c5108dd..ec522e0a892 100644 --- a/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx +++ b/apps/docs/content/guides/functions/examples/sentry-monitoring.mdx @@ -78,3 +78,12 @@ supabase functions deploy sentryfied --no-verify-jwt ### 4. Try it yourself Find the complete example on [GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/sentryfied/index.ts). + +## Working With Scopes + +Sentry Deno SDK currently do not support `Deno.serve` instrumentation, which means that there is no scope separation between requests. +Because of that, when the Edge Functions runtime is reused between multiple requests, all globally captured breadcrumbs and contextual data +will be shared, which is not the desired behavior. To work around this, all default integrations in the example code above are disabled, +and you should be relying on [`withScope`](https://docs.sentry.io/platforms/javascript/enriching-events/scopes/#using-withscope) to encapsulate +all Sentry SDK API calls, or [pass context directly](https://docs.sentry.io/platforms/javascript/enriching-events/context/#passing-context-directly) +to the `captureException` or `captureMessage` calls. diff --git a/apps/docs/content/guides/platform/sentry-monitoring.mdx b/apps/docs/content/guides/platform/sentry-monitoring.mdx index ba7adb00df8..b64cb0d9512 100644 --- a/apps/docs/content/guides/platform/sentry-monitoring.mdx +++ b/apps/docs/content/guides/platform/sentry-monitoring.mdx @@ -41,6 +41,12 @@ pnpm add @supabase/sentry-js-integration ## Use + + +If you are using Sentry JavaScript SDK v7, reference [`supabase-community/sentry-integration-js` repository](https://github.com/supabase-community/sentry-integration-js/blob/master/README-7v.md) instead. + + + To use the Supabase Sentry integration, add it to your `integrations` list when initializing your Sentry client. You can supply either the Supabase Client constructor or an already-initiated instance of a Supabase Client. @@ -52,12 +58,12 @@ You can supply either the Supabase Client constructor or an already-initiated in ```ts import * as Sentry from '@sentry/browser' import { SupabaseClient } from '@supabase/supabase-js' -import { SupabaseIntegration } from '@supabase/sentry-js-integration' +import { supabaseIntegration } from '@supabase/sentry-js-integration' Sentry.init({ dsn: SENTRY_DSN, integrations: [ - new SupabaseIntegration(SupabaseClient, { + supabaseIntegration(SupabaseClient, Sentry, { tracing: true, breadcrumbs: true, errors: true, @@ -73,14 +79,14 @@ Sentry.init({ ```ts import * as Sentry from '@sentry/browser' import { createClient } from '@supabase/supabase-js' -import { SupabaseIntegration } from '@supabase/sentry-js-integration' +import { supabaseIntegration } from '@supabase/sentry-js-integration' const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY) Sentry.init({ dsn: SENTRY_DSN, integrations: [ - new SupabaseIntegration(supabaseClient, { + supabaseIntegration(supabaseClient, Sentry, { tracing: true, breadcrumbs: true, errors: true, @@ -93,6 +99,8 @@ Sentry.init({ +All available configuration options are available in our [`supabase-community/sentry-integration-js` repository](https://github.com/supabase-community/sentry-integration-js/blob/master/README.md#options). + ## Deduplicating spans If you're already monitoring HTTP errors in Sentry, for example with the Http, Fetch, or Undici integrations, you will get duplicate spans for Supabase calls. You can deduplicate the spans by skipping them in your other integration: @@ -100,26 +108,26 @@ If you're already monitoring HTTP errors in Sentry, for example with the Http, F ```ts import * as Sentry from '@sentry/browser' import { SupabaseClient } from '@supabase/supabase-js' -import { SupabaseIntegration } from '@supabase/sentry-js-integration' +import { supabaseIntegration } from '@supabase/sentry-js-integration' Sentry.init({ dsn: SENTRY_DSN, integrations: [ - new SupabaseIntegration(SupabaseClient, { + supabaseIntegration(SupabaseClient, Sentry, { tracing: true, breadcrumbs: true, errors: true, }), // @sentry/browser - new Sentry.BrowserTracing({ + Sentry.browserTracingIntegration({ shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${SUPABASE_URL}/rest`) }, }), // or @sentry/node - new Sentry.Integrations.Http({ + Sentry.httpIntegration({ tracing: { shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${SUPABASE_URL}/rest`) @@ -128,14 +136,14 @@ Sentry.init({ }), // or @sentry/node with Fetch support - new Sentry.Integrations.Undici({ + Sentry.nativeNodeFetchIntegration({ shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${SUPABASE_URL}/rest`) }, }), // or @sentry/WinterCGFetch for Next.js Middleware & Edge Functions - new Sentry.Integrations.WinterCGFetch({ + Sentry.winterCGFetchIntegration({ breadcrumbs: true, shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${SUPABASE_URL}/rest`) @@ -156,40 +164,29 @@ See this example for a setup with Next.js to cover browser, server, and edge env ```ts sentry.client.config.ts import * as Sentry from '@sentry/nextjs' import { SupabaseClient } from '@supabase/supabase-js' -import { SupabaseIntegration } from '@supabase/sentry-js-integration' +import { supabaseIntegration } from '@supabase/sentry-js-integration' Sentry.init({ dsn: SENTRY_DSN, + integrations: [ + supabaseIntegration(SupabaseClient, Sentry, { + tracing: true, + breadcrumbs: true, + errors: true, + }), + Sentry.nativeNodeFetchIntegration({ + shouldCreateSpanForRequest: (url) => { + console.log('server', `${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`, url) + return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`) + }, + }), + ], + // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, // Setting this option to true will print useful information to the console while you're setting up Sentry. debug: true, - - replaysOnErrorSampleRate: 1.0, - - // This sets the sample rate to be 10%. You may want this to be 100% while - // in development and sample at a lower rate in production - replaysSessionSampleRate: 0.1, - - // You can remove this option if you're not planning to use the Sentry Session Replay feature: - integrations: [ - Sentry.replayIntegration({ - // Additional Replay configuration goes in here, for example: - maskAllText: true, - blockAllMedia: true, - }), - new SupabaseIntegration(SupabaseClient, { - tracing: true, - breadcrumbs: true, - errors: true, - }), - Sentry.browserTracingIntegration({ - shouldCreateSpanForRequest: (url) => { - return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`) - }, - }), - ], }) ``` @@ -200,23 +197,23 @@ Sentry.init({ ```ts sentry.server.config.ts import * as Sentry from '@sentry/nextjs' import { SupabaseClient } from '@supabase/supabase-js' -import { SupabaseIntegration } from '@supabase/sentry-js-integration' +import { supabaseIntegration } from '@supabase/sentry-js-integration' Sentry.init({ dsn: SENTRY_DSN, integrations: [ - new SupabaseIntegration(SupabaseClient, { + supabaseIntegration(SupabaseClient, Sentry, { tracing: true, breadcrumbs: true, errors: true, }), - new Sentry.Integrations.Undici({ + Sentry.winterCGFetchIntegration({ + breadcrumbs: true, shouldCreateSpanForRequest: (url) => { return !url.startsWith(`${process.env.NEXT_PUBLIC_SUPABASE_URL}/rest`) }, }), ], - // Adjust this value in production, or use tracesSampler for greater control tracesSampleRate: 1, @@ -259,6 +256,23 @@ Sentry.init({ + + +```js instrumentation.ts +// https://nextjs.org/docs/app/building-your-application/optimizing/instrumentation +export async function register() { + if (process.env.NEXT_RUNTIME === 'nodejs') { + await import('./sentry.server.config') + } + + if (process.env.NEXT_RUNTIME === 'edge') { + await import('./sentry.edge.config') + } +} +``` + + + Afterward build your application (`npm run build`) and start it locally (`npm run start`). You will now see the transactions being logged in the terminal when making supabase-js requests. From 5847ad54cfb56903274c88e62183e25f6c63d2f0 Mon Sep 17 00:00:00 2001 From: Ivan Vasilov Date: Fri, 7 Jun 2024 17:44:28 +0200 Subject: [PATCH 3/5] fix: Test the playwright tests in Github actions (#26799) * Add an API page for run-lints endpoint. * Fix the playwright test. * Make the check mandatory in the github action. * Fix a console.log. * Use a more stable way of waiting for buttons before clicking them. * Add helper method for dismissing toasts. * Wait for the other toast. * Try and fix the flakiness of the toasts. * Minor fix. * Upload artifacts at the end of the github action run. * Fix yaml stuff. * Use the newer action for uploading. Shorten the retention period to 7 days. * Add waitForResponse for the entity types API call. * snapshot. * Wait for the page to be loaded. * Make the timeout bigger when waiting for response. * Try to fix the timeouts. * Minor restructuring of the test. * Add another test and few minor todo tests. * Fix the table names. * Don't render hcaptcha in test mode. * Expand the test to include sorting and filtering. * Add a test for checking if the other schemas are can be listed. * Make the sort popover button more stable. * Fix a unused forward ref in the NavMenuItem component. * Don't render the Toaster container when in test mode. * Simplify starting the dev server when testing. * Fix all inputs to have proper test ids. * Fix the command for testing. --- .github/workflows/playwright.yml | 14 +- .../components/header/sort/SortPopover.tsx | 4 +- .../ColumnEditor/InputWithSuggestions.tsx | 3 + .../SidePanelEditor/RowEditor/InputField.tsx | 7 +- .../SidePanelEditor/TableEditor/Column.tsx | 2 + .../TableEditor/TableEditor.tsx | 8 +- .../TableEditorLayout/TableEditorMenu.tsx | 2 +- apps/studio/pages/_app.tsx | 4 +- .../pages/api/projects/[ref]/run-lints.ts | 24 +++ packages/ui/src/components/NavMenu/index.tsx | 15 +- .../ui/src/components/Popover/Popover.tsx | 3 + playwright-tests/playwright.config.ts | 12 +- .../tests/snapshot/spec/table-editor.spec.ts | 152 ++++++++++++++++-- 13 files changed, 205 insertions(+), 45 deletions(-) create mode 100644 apps/studio/pages/api/projects/[ref]/run-lints.ts diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 2ab350c96ee..2508ef13315 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -36,10 +36,10 @@ jobs: - name: Run Playwright tests run: npm run test:playwright # mark the action as succeeded even if the tests failed. This is temporarily until we make the tests more stable. - continue-on-error: true - # - uses: actions/upload-artifact@v3 - # if: always() - # with: - # name: playwright-report - # path: playwright-report/ - # retention-days: 30 + # continue-on-error: true + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-tests/playwright-report/ + retention-days: 7 diff --git a/apps/studio/components/grid/components/header/sort/SortPopover.tsx b/apps/studio/components/grid/components/header/sort/SortPopover.tsx index be08f894b07..c0ff288a5ab 100644 --- a/apps/studio/components/grid/components/header/sort/SortPopover.tsx +++ b/apps/studio/components/grid/components/header/sort/SortPopover.tsx @@ -143,11 +143,13 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, onApplySorts }: SortOverlayPr align="start" > ) : ( diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx index 2a787b8f7a6..80495d7e1c1 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx @@ -38,6 +38,7 @@ interface InputWithSuggestionsProps { suggestionsHeader?: string onChange: (event: any) => void onSelectSuggestion: (suggestion: Suggestion) => void + 'data-testid'?: string } const InputWithSuggestions = ({ @@ -54,6 +55,7 @@ const InputWithSuggestions = ({ suggestionsHeader, onChange = noop, onSelectSuggestion = noop, + 'data-testid': dataTestId, }: InputWithSuggestionsProps) => { const ref = useRef(null) const [filteredSuggestions, setFilteredSuggestions] = useState(suggestions) @@ -92,6 +94,7 @@ const InputWithSuggestions = ({ type="text" value={value} onChange={onInputChange} + data-testid={dataTestId} actions={ showSuggestions && ( diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx index dc36f696942..9303c52acc0 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/InputField.tsx @@ -10,11 +10,11 @@ import { Select, } from 'ui' +import { MAX_CHARACTERS } from 'data/table-rows/table-rows-query' import { Edit, Edit2, Link } from 'lucide-react' import { DATETIME_TYPES, JSON_TYPES, TEXT_TYPES } from '../SidePanelEditor.constants' import { DateTimeInput } from './DateTimeInput' import type { RowField } from './RowEditor.types' -import { MAX_CHARACTERS } from 'data/table-rows/table-rows-query' export interface InputFieldProps { field: RowField @@ -41,6 +41,7 @@ const InputField = ({ return (
Extra options
diff --git a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index c4aec7cb423..4bf450d4c53 100644 --- a/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/apps/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -255,6 +255,7 @@ const TableEditor = ({ > Recommended
} - // @ts-ignore - description={ - <> -

Restrict access to your table by enabling RLS and writing Postgres policies.

- - } + description="Restrict access to your table by enabling RLS and writing Postgres policies." checked={tableFields.isRLSEnabled} onChange={() => { // if isEnabled, show confirm modal to turn off diff --git a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx index 7576fc81c9b..061d1306273 100644 --- a/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx +++ b/apps/studio/components/layouts/TableEditorLayout/TableEditorMenu.tsx @@ -213,7 +213,7 @@ const TableEditorMenu = () => { /> )} {(entityTypes?.length ?? 0) > 0 && ( -
+
- - + {!isTestEnv && } + {!isTestEnv && } {!isTestEnv && } diff --git a/apps/studio/pages/api/projects/[ref]/run-lints.ts b/apps/studio/pages/api/projects/[ref]/run-lints.ts new file mode 100644 index 00000000000..91c1e8d3ec2 --- /dev/null +++ b/apps/studio/pages/api/projects/[ref]/run-lints.ts @@ -0,0 +1,24 @@ +import { NextApiRequest, NextApiResponse } from 'next' + +import apiWrapper from 'lib/api/apiWrapper' +import { extractResponse } from 'pages/api/constants' + +export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler) + +async function handler(req: NextApiRequest, res: NextApiResponse) { + const { method } = req + + switch (method) { + case 'GET': + return handleGet(req, res) + default: + res.setHeader('Allow', ['GET']) + res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } }) + } +} + +type ResponseData = extractResponse<'/platform/projects/{ref}/run-lints', 'get'> + +const handleGet = async (req: NextApiRequest, res: NextApiResponse) => { + return res.status(200).json([]) +} diff --git a/packages/ui/src/components/NavMenu/index.tsx b/packages/ui/src/components/NavMenu/index.tsx index fa2ba5e26be..f58afc18c4d 100644 --- a/packages/ui/src/components/NavMenu/index.tsx +++ b/packages/ui/src/components/NavMenu/index.tsx @@ -20,17 +20,16 @@ export const NavMenu = forwardRef( } ) -export const NavMenuItem = forwardRef( - ({ - children, - className, - active, - ...props - }: PropsWithChildren<{ +interface NavMenuItemProps + extends PropsWithChildren<{ className?: string active: boolean - }>) => ( + }> {} + +export const NavMenuItem = forwardRef( + ({ children, className, active, ...props }, ref) => (
  • {children} diff --git a/playwright-tests/playwright.config.ts b/playwright-tests/playwright.config.ts index 41f761f1a94..c75031d9658 100644 --- a/playwright-tests/playwright.config.ts +++ b/playwright-tests/playwright.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', // record a video for failed tests, but only for local testing. We can't store videos on CI atm. - video: process.env.CI ? 'off' : 'retain-on-failure', + video: 'retain-on-failure', }, /* Configure projects for major browsers */ @@ -72,8 +72,16 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ webServer: { - command: 'NODE_ENV=test npm --prefix ../apps/studio run dev', + // using npm run dev instead of turbo because turbo doesn't stop the server after a test (doesn't handle SIGTERM). + command: 'npm run dev', + cwd: '../apps/studio', url: 'http://localhost:8082', reuseExistingServer: !process.env.CI, + env: { + NODE_ENV: 'test', + }, + // Show the nextjs logs in the terminal. Useful for debugging. + // stdout: 'pipe', + // stderr: 'pipe', }, }) diff --git a/playwright-tests/tests/snapshot/spec/table-editor.spec.ts b/playwright-tests/tests/snapshot/spec/table-editor.spec.ts index 3a230f88a27..994784a3b0e 100644 --- a/playwright-tests/tests/snapshot/spec/table-editor.spec.ts +++ b/playwright-tests/tests/snapshot/spec/table-editor.spec.ts @@ -1,30 +1,148 @@ -import { expect, test } from '@playwright/test' +import { Page, expect, test } from '@playwright/test' +import { kebabCase } from 'lodash' + +const dismissToast = async (page: Page) => { + await page.locator('#toast').getByRole('button').waitFor({ state: 'visible' }) + await page.locator('#toast').getByRole('button').click() +} test.describe('Table Editor page', () => { - test('should create a column and insert a row', async ({ page }) => { - const name = 'TestTable' + Math.floor(Math.random() * 10) - + test.beforeEach(async ({ page }) => { + const tableResponsePromise = page.waitForResponse( + 'http://localhost:8082/api/pg-meta/default/query?key=public-entity-types', + { timeout: 0 } + ) await page.goto('/project/default/editor') + await tableResponsePromise + }) + + test('should create a new table, view its definition, add new rows, sort and filter', async ({ + page, + }, testInfo) => { + const tableName = `${kebabCase(testInfo.title).slice(0, 24)}-${testInfo.retry}-${Math.floor(Math.random() * 100)}` + + // The page has been loaded with the table data, we can now interact with the page await page.getByRole('button', { name: 'New table', exact: true }).click() - // need to wait for the panel animation - await page.waitForTimeout(1000) - await page.locator('.col-span-8 > div > .relative > .peer\\/input').first().click() - await page.locator('.col-span-8 > div > .relative > .peer\\/input').first().fill(name) + await page.getByTestId('table-name-input').waitFor({ state: 'visible' }) + await page.getByTestId('table-name-input').click() + await page.getByTestId('table-name-input').fill(tableName) + + // make the built-in created_at column nullable + await page.getByTestId('created_at-extra-options').click() + await page.getByText('Is Nullable').click() + // the force option is needed because the button is obscured by the popover but we just want to close the popover. + await page.getByTestId('created_at-extra-options').click({ force: true }) + + // add a new column and add default value await page.getByRole('button', { name: 'Add column' }).click() await page.getByRole('textbox', { name: 'column_name' }).click() - await page.getByRole('textbox', { name: 'column_name' }).fill('textColumn') + await page.getByRole('textbox', { name: 'column_name' }).fill('defaultValueColumn') await page.getByRole('button', { name: '---' }).click() - await page.getByText('textVariable-length character').click() - await page.waitForTimeout(1000) + await page.getByText('Signed two-byte integer').click() + await page.getByTestId('defaultValueColumn-default-value').click() + await page.getByTestId('defaultValueColumn-default-value').fill('2') + + await page.getByRole('button', { name: 'Save' }).waitFor({ state: 'visible' }) await page.getByRole('button', { name: 'Save' }).click() - await page.locator('#toast').getByRole('button').click() - await page.getByRole('link', { name }).click() + await dismissToast(page) + + // view its definition + await page.getByText('definition').click() + await expect(page.locator('div.view-lines')).toContainText( + `CREATE TABLE public.${tableName} ( id bigint GENERATED BY DEFAULT AS IDENTITY , created_at timestamp with time zone NULL DEFAULT now(), \"defaultValueColumn\" smallint NULL DEFAULT '2'::smallint, CONSTRAINT ${tableName}_pkey PRIMARY KEY (id)) TABLESPACE pg_default;` + ) + + // add a new row + await page.getByRole('button', { name: tableName }).click() await page.getByTestId('table-editor-insert-new-row').click() await page.getByText('Insert a new row into').click() - await page.getByPlaceholder('NULL').click() - await page.getByPlaceholder('NULL').fill('some text') + await page.getByTestId('defaultValueColumn-input').click() + await page.getByTestId('defaultValueColumn-input').fill('100') await page.getByTestId('action-bar-save-row').click() - await page.locator('#toast').getByRole('button').click() - await expect(page.getByRole('grid')).toContainText('some text') + await dismissToast(page) + + // add a second row + await page.getByRole('button', { name: tableName }).click() + await page.getByTestId('table-editor-insert-new-row').click() + await page.getByText('Insert a new row into').click() + // the default value should be '100' for defaultValueColumn + await page.getByTestId('action-bar-save-row').click() + await dismissToast(page) + + await expect(page.getByRole('grid')).toContainText('2') + await expect(page.getByRole('grid')).toContainText('100') + + // sort by the a column + await page.getByRole('button', { name: 'Sort' }).click() + await page.getByTestId('table-editor-pick-column-to-sort-button').click() + await page.getByLabel('Pick a column to sort by').getByText('defaultValueColumn').click() + await page.getByRole('button', { name: 'Apply sorting' }).click() + // click away to close the sorting dialog + await page + .locator('div') + .filter({ hasText: /^Table Editor$/ }) + .click() + // expect the row to be sorted by defaultValueColumn. They're inserted in the order 100, 2 + await expect(page.locator('div.rdg-row:nth-child(2)')).toContainText('2') + await expect(page.locator('div.rdg-row:nth-child(3)')).toContainText('100') + // remove the sorting + await page.getByRole('button', { name: 'Sorted by 1 rule' }).click() + await page.getByRole('dialog').getByRole('button').nth(1).click() + + // filter by a column + await page.getByRole('button', { name: 'Filter' }).click() + await page.getByRole('button', { name: 'Add filter' }).click() + await page.getByRole('button', { name: 'id' }).click() + await page.getByLabel('id').getByText('defaultValueColumn').click() + await page.getByPlaceholder('Enter a value').click() + await page.getByPlaceholder('Enter a value').fill('2') + await page.getByRole('button', { name: 'Apply filter' }).click() + // click away to close the filter dialog + await page + .locator('div') + .filter({ hasText: /^Table Editor$/ }) + .click() + await expect(page.getByRole('grid')).toContainText('2') + await expect(page.getByRole('grid')).not.toContainText('100') + }) + + test('should check the auth schema', async ({ page }) => { + const tableResponsePromise = page.waitForResponse( + 'http://localhost:8082/api/pg-meta/default/query?key=public-entity-types', + { timeout: 0 } + ) + + await page.getByRole('button', { name: 'schema: public' }).click() + await page.getByRole('option', { name: 'auth' }).click() + + // wait for the table data to load for the auth schema + await tableResponsePromise + + // extract the tables names from the sidebar + const tables = await page + .getByTestId('tables-list') + .innerText() + .then((text) => text.split('\n')) + + // expect the tables list to contain the following tables (additional tables may be present) + expect(tables).toEqual( + expect.arrayContaining([ + 'audit_log_entries', + 'flow_state', + 'identities', + 'instances', + 'mfa_amr_claims', + 'mfa_challenges', + 'mfa_factors', + 'refresh_tokens', + 'saml_providers', + 'saml_relay_states', + 'schema_migrations', + 'sessions', + 'sso_domains', + 'sso_providers', + 'users', + ]) + ) }) }) From 7547c52e4f5637e72a3cd9568f7c0efd6905dff3 Mon Sep 17 00:00:00 2001 From: Joel Lee Date: Fri, 7 Jun 2024 22:03:12 +0200 Subject: [PATCH 4/5] fix: revoke role on public for hooks (#27077) * fix: revoke role on public for hooks * fix: update SQL editor queries * fix: update examples * fix: update RBAC doc * fix: update description to describe why we revoke public * fix: correct use of we * fix: run prettier * fix: remove unintentionally added files --- apps/docs/content/guides/auth/auth-hooks.mdx | 24 +++++++++---------- ...ims-and-role-based-access-control-rbac.mdx | 8 +++---- .../interfaces/SQLEditor/SQLEditor.queries.ts | 12 +++++----- .../nextjs-slack-clone/full-schema.sql | 4 ++-- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/docs/content/guides/auth/auth-hooks.mdx b/apps/docs/content/guides/auth/auth-hooks.mdx index c6a833b30fc..336b9c950d9 100644 --- a/apps/docs/content/guides/auth/auth-hooks.mdx +++ b/apps/docs/content/guides/auth/auth-hooks.mdx @@ -77,12 +77,12 @@ You also need to grant usage to `supabase_auth_admin`: grant usage on schema public to supabase_auth_admin; ``` -Also revoke permissions from the `authenticated` and `anon` roles to ensure the function is not accessible by Supabase Serverless APIs. +Also revoke permissions from the `authenticated`, `public`, and `anon` roles to ensure the function is not accessible by Supabase Serverless APIs. The `public` role has access to functions created on `public.*` by default and `anon` and `authenticated` inherit permissions from the `public` role. Permission is revoked from the`public` role in order to prevent the `anon` and `authenticated` roles from inheriting permissions to invoke the Postgres Hook. ```sql revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; ``` For security, we recommend against the use the `security definer` tag. The `security definer` tag specifies that the function is to be executed with the privileges of the user that owns it. When a function is created via the Supabase dashboard with the tag, it will have the extensive permissions of the `postgres` role which make it easier for undesirable actions to occur. @@ -258,11 +258,11 @@ grant all revoke execute on function public.hook_mfa_verification_attempt - from authenticated, anon; + from authenticated, anon, public; revoke all on table public.mfa_failed_verification_attempts - from authenticated, anon; + from authenticated, anon, public; ``` @@ -389,11 +389,11 @@ grant all revoke execute on function public.hook_password_verification_attempt - from authenticated, anon; + from authenticated, anon, public; revoke all on table public.password_failed_verification_attempts - from authenticated, anon; + from authenticated, anon, public; ``` @@ -500,7 +500,7 @@ grant execute revoke execute on function public.hook_notify_user_on_failed_attempts - from authenticated, anon; + from authenticated, anon, public; grant all on table public.password_sign_in_attempts @@ -508,7 +508,7 @@ grant all revoke all on table public.password_sign_in_attempts - from authenticated, anon; + from authenticated, anon, public; ``` @@ -602,7 +602,7 @@ grant execute revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; grant all on table public.profiles @@ -610,7 +610,7 @@ grant all revoke all on table public.profiles - from authenticated, anon; + from authenticated, anon, public; ``` @@ -662,7 +662,7 @@ grant all revoke all on table public.profiles - from authenticated, anon; + from authenticated, anon, public; ``` @@ -706,7 +706,7 @@ grant execute revoke execute on function public.restrict_application_access - from authenticated, anon; + from authenticated, anon, public; ``` diff --git a/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx b/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx index 38d2ad99810..d429ad3baea 100644 --- a/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx +++ b/apps/docs/content/guides/database/postgres/custom-claims-and-role-based-access-control-rbac.mdx @@ -121,7 +121,7 @@ grant execute revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; grant all on table public.user_roles @@ -129,7 +129,7 @@ to supabase_auth_admin; revoke all on table public.user_roles - from authenticated, anon; + from authenticated, anon, public; create policy "Allow auth admin to read user roles" ON public.user_roles as permissive for select @@ -179,7 +179,7 @@ grant execute revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; grant all on table public.user_roles @@ -187,7 +187,7 @@ to supabase_auth_admin; revoke all on table public.user_roles - from authenticated, anon; + from authenticated, anon, public; create policy "Allow auth admin to read user roles" ON public.user_roles as permissive for select diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.queries.ts b/apps/studio/components/interfaces/SQLEditor/SQLEditor.queries.ts index ead4a507778..fd4a3b2c5a5 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.queries.ts +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.queries.ts @@ -1373,11 +1373,11 @@ grant all revoke execute on function public.hook_mfa_verification_attempt - from authenticated, anon; + from authenticated, anon, public; revoke all on table public.mfa_failed_verification_attempts - from authenticated, anon; + from authenticated, anon, public; grant usage on schema public to supabase_auth_admin;`.trim(), }, @@ -1445,11 +1445,11 @@ grant all revoke execute on function public.hook_password_verification_attempt - from authenticated, anon; + from authenticated, anon, public; revoke all on table public.password_failed_verification_attempts - from authenticated, anon; + from authenticated, anon, public; grant usage on schema public to supabase_auth_admin;`.trim(), }, @@ -1499,7 +1499,7 @@ grant execute revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; grant usage on schema public to supabase_auth_admin;`.trim(), }, @@ -1523,7 +1523,7 @@ end; $$; -- Permissions for the hook grant execute on function public.custom_access_token_hook to supabase_auth_admin; -revoke execute on function public.custom_access_token_hook from authenticated, anon; +revoke execute on function public.custom_access_token_hook from authenticated, anon, public; `, }, ] diff --git a/examples/slack-clone/nextjs-slack-clone/full-schema.sql b/examples/slack-clone/nextjs-slack-clone/full-schema.sql index 59486cae819..a3e294961cb 100644 --- a/examples/slack-clone/nextjs-slack-clone/full-schema.sql +++ b/examples/slack-clone/nextjs-slack-clone/full-schema.sql @@ -183,7 +183,7 @@ grant execute revoke execute on function public.custom_access_token_hook - from authenticated, anon; + from authenticated, anon, public; grant all on table public.user_roles @@ -191,7 +191,7 @@ to supabase_auth_admin; revoke all on table public.user_roles - from authenticated, anon; + from authenticated, anon, public; create policy "Allow auth admin to read user roles" ON public.user_roles as permissive for select From 5772fbf9119c56ade17fea2a5561ec4e27157aeb Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Sat, 8 Jun 2024 12:31:52 +0200 Subject: [PATCH 5/5] docs: add error codes auth (#22793) --- .../components/reference/Reference.types.ts | 3 + .../docs/docs/ref/shared/auth-error-codes.mdx | 92 +++++++++++++++++++ apps/docs/lib/mdx/generateRefMarkdown.tsx | 3 +- .../spec/common-client-libs-sections.json | 9 ++ apps/docs/spec/supabase_js_v2.yml | 1 + 5 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 apps/docs/docs/ref/shared/auth-error-codes.mdx diff --git a/apps/docs/components/reference/Reference.types.ts b/apps/docs/components/reference/Reference.types.ts index ce15320180e..e56647d9c18 100644 --- a/apps/docs/components/reference/Reference.types.ts +++ b/apps/docs/components/reference/Reference.types.ts @@ -53,6 +53,9 @@ export interface ICommonCategory extends ICommonBase { export interface ICommonMarkdown extends ICommonBaseSection { type: 'markdown' + meta?: { + shared?: boolean + } } export interface ICommonFunctionGroup extends ICommonBaseSection { diff --git a/apps/docs/docs/ref/shared/auth-error-codes.mdx b/apps/docs/docs/ref/shared/auth-error-codes.mdx new file mode 100644 index 00000000000..74f268d99d2 --- /dev/null +++ b/apps/docs/docs/ref/shared/auth-error-codes.mdx @@ -0,0 +1,92 @@ +Supabase Auth can throw or return various errors when using the API. All errors originating from the `supabase.auth` namespace of the JavaScript client library will be wrapped by the `AuthError` class. + +Error objects are split in a few classes: + +- `AuthApiError` -- errors which originate from the Supabase Auth API. + - Use `isAuthApiError` instead of `instanceof` checks to see if an error you caught is of this type. +- `CustomAuthError` -- errors which generally originate from state in the client library. + - Use the `name` property on the error to identify the class of error received. + +Errors originating from the server API classed as `AuthApiError` always have a `code` property that can be used to identify the error returned by the server. The `status` property is also present, encoding the HTTP status code received in the response. + +In general the HTTP status codes you will likely receive are: + +- [403 Forbidden](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403) is sent out in rare situations where a certain Auth feature is not available for the user, and you as the developer are not checking a precondition whether that API is available for the user. +- [422 Unprocessable Entity](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422) is sent out when the API request is accepted, but cannot be processed because the user or Auth server is in a state where it cannot satisfy the request. +- [429 Too Many Requests](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429) is sent out when rate-limits are breached for an API. You should handle this status code often, especially in functions that authenticate a user. +- [500 Internal Server Error](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500) often means that the Auth server's service is degraded. Most often it points to issues in your database setup such as a misbehaving trigger on a schema, function, view or other database object. +- [501 Not Implemented](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501) is sent out when a feature is not enabled on the Auth server, and you are trying to use an API which requires it. + +To supplement HTTP status codes, Supabase Auth returns a string error code which gives you more insight into what went wrong. These codes are stable and can be used to present an internationalized message to your users. + +| Code | Description | +| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `bad_code_verifier` | Returned from the PKCE flow where the provided code verifier does not match the expected one. Indicates a bug in the implementation of the client library. | +| `bad_json` | Usually used when the HTTP body of the request is not valid JSON. | +| `bad_jwt` | JWT sent in the `Authorization` header is not valid. | +| `bad_oauth_callback` | OAuth callback from provider to Auth does not have all the required attributes (state). Indicates an issue with the OAuth provider or client library implementation. | +| `bad_oauth_state` | OAuth state (data echoed back by the OAuth provider to Supabase Auth) is not in the correct format. Indicates an issue with the OAuth provider integration. | +| `captcha_failed` | Captcha challenge could not be verified with the captcha provider. Check your captcha integration. | +| `conflict` | General database conflict, such as concurrent requests on resources that should not be modified concurrently. Can often occur when you have too many session refresh requests firing off at the same time for a user. Check your app for concurrency issues, and if detected back off exponentially. | +| `email_conflict_identity_not_deletable` | Unlinking this identity causes the user's account to change to an email address which is already used by another user account. Indicates an issue where the user has two different accounts using different primary email addresses. You may need to migrate user data to one of their accounts in this case. | +| `email_exists` | Email address already exists in the system. | +| `email_not_confirmed` | Signing in is not allowed for this user as the email address is not confirmed. | +| `email_provider_disabled` | Signups are disabled for email and password. | +| `flow_state_expired` | PKCE flow state to which the API request relates has expired. Ask the user to sign in again. | +| `flow_state_not_found` | PKCE flow state to which the API request relates no longer exists. Flow states expire after a while and are progressively cleaned up, which can cause this error. Retried requests can cause this error, as the previous request likely destroyed the flow state. Ask the user to sign in again. | +| `identity_already_exists` | The identity to which the API relates is already linked to a user. | +| `identity_not_found` | Identity to which the API call relates does not exist, such as when an identity is unlinked or deleted. | +| `insufficient_aal` | To call this API, the user must have a higher [Authenticator Assurance Level](https://supabase.com/docs/guides/auth/auth-mfa). To resolve, ask the user to solve an MFA challenge. | +| `invite_not_found` | Invite is expired or already used. | +| `manual_linking_disabled` | Calling the `supabase.auth.linkUser()` and related APIs is not enabled on the Auth server. | +| `mfa_challenge_expired` | Responding to an MFA challenge should happen within a fixed time period. Request a new challenge when encountering this error. | +| `mfa_factor_name_conflict` | MFA factors for a single user should not have the same friendly name. | +| `mfa_factor_not_found` | MFA factor no longer exists. | +| `mfa_ip_address_mismatch` | The enrollment process for MFA factors must begin and end with the same IP address. | +| `mfa_verification_failed` | MFA challenge could not be verified -- wrong TOTP code. | +| `mfa_verification_rejected` | Further MFA verification is rejected. Only returned if the [MFA verification attempt hook](https://supabase.com/docs/guides/auth/auth-hooks?language=add-admin-role#hook-mfa-verification-attempt) returns a reject decision. | +| `no_authorization` | This HTTP request requires an `Authorization` header, which is not provided. | +| `not_admin` | User accessing the API is not admin, i.e. the JWT does not contain a `role` claim that identifies them as an admin of the Auth server. | +| `oauth_provider_not_supported` | Using an OAuth provider which is disabled on the Auth server. | +| `otp_disabled` | Sign in with OTPs (magic link, email OTP) is disabled. Check your sever's configuration. | +| `otp_expired` | OTP code for this sign-in has expired. Ask the user to sign in again. | +| `over_email_send_rate_limit` | Too many emails have been sent to this email address. Ask the user to wait a while before trying again. | +| `over_request_rate_limit` | Too many requests have been sent by this client (IP address). Ask the user to try again in a few minutes. Sometimes can indicate a bug in your application that mistakenly sends out too many requests (such as a badly written [`useEffect` React hook](https://react.dev/reference/react/useEffect)). | +| `over_sms_send_rate_limit` | Too many SMS messages have been sent to this phone number. Ask the user to wait a while before trying again. | +| `phone_exists` | Phone number already exists in the system. | +| `phone_not_confirmed` | Signing in is not allowed for this user as the phone number is not confirmed. | +| `phone_provider_disabled` | Signups are disabled for phone and password. | +| `provider_disabled` | OAuth provider is disabled for use. Check your server's configuration. | +| `provider_email_needs_verification` | Not all OAuth providers verify their user's email address. Supabase Auth requires emails to be verified, so this error is sent out when a verification email is sent after completing the OAuth flow. | +| `reauthentication_needed` | A user needs to reauthenticate to change their password. Ask the user to reauthenticate by calling the `supabase.auth.reauthenticate()` API. | +| `reauthentication_not_valid` | Verifying a reauthentication failed, the code is incorrect. Ask the user to enter a new code. | +| `same_password` | A user that is updating their password must use a different password than the one currently used. | +| `saml_assertion_no_email` | SAML assertion (user information) was received after sign in, but no email address was found in it which is required. Check the provider's attribute mapping and/or configuration. | +| `saml_assertion_no_user_id` | SAML assertion (user information) was received after sign in, but a user ID (called NameID) was not found in it which is required. Check the SAML identity provider's configuration. | +| `saml_entity_id_mismatch` | (Admin API.) Updating the SAML metadata for a SAML identity provider is not possible, as the entity ID in the update does not match the entity ID in the database. This is equivalent to creating a new identity provider, and you should do that instead. | +| `saml_idp_already_exists` | (Admin API.) Adding a SAML identity provider that is already added. | +| `saml_idp_not_found` | SAML identity provider not found. Most often returned after IdP-initiated sign-in with an unregistered SAML identity provider in Supabase Auth. | +| `saml_metadata_fetch_failed` | (Admin API.) Adding or updating a SAML provider failed as its metadata could not be fetched from the provided URL. | +| `saml_provider_disabled` | Using [Enterprise SSO with SAML 2.0](https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml) is not enabled on the Auth server. | +| `saml_relay_state_expired` | SAML relay state is an object that tracks the progress of a `supabase.auth.signInWithSSO()` request. The SAML identity provider should respond after a fixed amount of time, after which this error is shown. Ask the user to sign in again. | +| `saml_relay_state_not_found` | SAML relay states are progressively cleaned up after they expire, which can cause this error. Ask the user to sign in again. | +| `session_not_found` | Session to which the API request relates no longer exists. This can occur if the user has signed out, or the session entry in the database was deleted in some other way. | +| `signup_disabled` | Sign ups (new account creation) is disabled on the server. | +| `single_identity_not_deletable` | Every user must have at least one identity attached to it, so deleting (unlinking) an identity is not allowed if it's the only one for the user. | +| `sms_send_failed` | Sending an SMS message failed. Check your SMS provider configuration. | +| `sso_domain_already_exists` | (Admin API.) Only one SSO domain can be registered per SSO identity provider. | +| `sso_provider_not_found` | SSO provider not found. Check the arguments in `supabase.auth.signInWithSSO()`. | +| `too_many_enrolled_mfa_factors` | A user can only have a fixed number of enrolled MFA factors. | +| `unexpected_audience` | (Deprecated feature not available via Supabase JavaScript client.) The request's `X-JWT-AUD` claim does not match the JWT's audience. | +| `unexpected_failure` | Auth service is degraded or a bug is present, without a specific reason. | +| `user_already_exists` | User with this information (email address, phone number) cannot be created again as it already exists. | +| `user_banned` | User to which the API request relates has a `banned_until` property which is still active. No further API requests should be attempted until this field is cleared. | +| `user_not_found` | User to which the API request relates no longer exists. | +| `user_sso_managed` | When a user comes from SSO, certain fields of the user cannot be updated (like `email`). | +| `validation_failed` | Provided parameters are not in the expected format. | +| `weak_password` | User is signing up or changing their password without meeting the password strength criteria. Use the `AuthWeakPasswordError` class to access more information about what they need to do to make the assword pass. | + +### Tips for better error handling + +- Do not use string matching on error messages! Always use the `name` and `code` properties of error objects to identify the situation. +- Although HTTP status codes generally don't change, they can suddenly change due to bugs, so avoid relying on them unless absolutely necessary. diff --git a/apps/docs/lib/mdx/generateRefMarkdown.tsx b/apps/docs/lib/mdx/generateRefMarkdown.tsx index 77a550af7db..f30bc10f4fc 100644 --- a/apps/docs/lib/mdx/generateRefMarkdown.tsx +++ b/apps/docs/lib/mdx/generateRefMarkdown.tsx @@ -17,7 +17,8 @@ async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) { */ await Promise.all( sections.map(async (section) => { - const pathName = `docs/ref${slug}/${section.id}.mdx` + const isSharedSection = section.meta?.shared + const pathName = `docs/ref${isSharedSection ? '/shared' : slug}/${section.id}.mdx` function checkFileExists(x) { if (fs.existsSync(x)) { diff --git a/apps/docs/spec/common-client-libs-sections.json b/apps/docs/spec/common-client-libs-sections.json index b563968d1c1..77fc096242d 100644 --- a/apps/docs/spec/common-client-libs-sections.json +++ b/apps/docs/spec/common-client-libs-sections.json @@ -399,6 +399,15 @@ "slug": "auth-api", "type": "function" }, + { + "id": "auth-error-codes", + "title": "Error codes", + "slug": "auth-error-codes", + "type": "markdown", + "meta": { + "shared": true + } + }, { "id": "sign-up", "title": "Create a new user", diff --git a/apps/docs/spec/supabase_js_v2.yml b/apps/docs/spec/supabase_js_v2.yml index 41d0dcbfb49..bc7305718ff 100644 --- a/apps/docs/spec/supabase_js_v2.yml +++ b/apps/docs/spec/supabase_js_v2.yml @@ -186,6 +186,7 @@ functions: - You keep the `expo-secure-storage`, `aes-js` and `react-native-get-random-values` libraries up-to-date. - Choose the correct [`SecureStoreOptions`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestoreoptions) for your app's needs. E.g. [`SecureStore.WHEN_UNLOCKED`](https://docs.expo.dev/versions/latest/sdk/securestore/#securestorewhen_unlocked) regulates when the data can be accessed. - Carefully consider optimizations or other modifications to the above example, as those can lead to introducing subtle security vulnerabilities. + - id: auth-api title: 'Overview' notes: |