diff --git a/README.md b/README.md index 70298a09e54..491a08b1009 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ You can also [self-host](https://supabase.com/docs/guides/hosting/overview) and - [pg_graphql](http://github.com/supabase/pg_graphql/) a PostgreSQL extension that exposes a GraphQL API - [Storage](https://github.com/supabase/storage-api) provides a RESTful interface for managing Files stored in S3, using Postgres to manage permissions. - [postgres-meta](https://github.com/supabase/postgres-meta) is a RESTful API for managing your Postgres, allowing you to fetch tables, add roles, and run queries, etc. -- [GoTrue](https://github.com/supabase/gotrue) is an JWT based API for managing users and issuing JWT tokens. +- [GoTrue](https://github.com/supabase/gotrue) is a JWT based API for managing users and issuing JWT tokens. - [Kong](https://github.com/Kong/kong) is a cloud-native API gateway. #### Client libraries diff --git a/apps/docs/components/MDX/storage_management.mdx b/apps/docs/components/MDX/storage_management.mdx index ce28a1684c8..94aaa57a999 100644 --- a/apps/docs/components/MDX/storage_management.mdx +++ b/apps/docs/components/MDX/storage_management.mdx @@ -61,13 +61,16 @@ as $$ declare status int; content text; + avatar_name text; begin if coalesce(old.avatar_url, '') <> '' and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then + -- extract avatar name + avatar_name := substring(old.avatar_url from '/([^\/]+)\?.*$'); select into status, content result.status, result.content - from public.delete_avatar(old.avatar_url) as result; + from public.delete_avatar(avatar_name) as result; if status <> 200 then raise warning 'Could not delete avatar: % %', status, content; end if; diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index 488226925ee..81f0c44369e 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -495,22 +495,36 @@ export const auth = { { name: 'Managing User Data', url: '/guides/auth/managing-user-data' }, { name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' }, { name: 'Row Level Security', url: '/guides/auth/row-level-security' }, - { name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering' }, ], }, { - name: 'Auth Helpers', + name: 'Server-side Auth', url: undefined, items: [ { name: 'Overview', url: '/guides/auth/auth-helpers' }, - { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' }, - { name: 'Flutter Auth UI', url: '/guides/auth/auth-helpers/flutter-auth-ui' }, { name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', }, { name: 'Remix', url: '/guides/auth/auth-helpers/remix' }, { name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit' }, + { name: 'Server-side Rendering', url: '/guides/auth/server-side-rendering' }, + { + name: 'Email Auth with PKCE flow for SSR', + url: '/guides/auth/server-side/email-based-auth-with-pkce-flow-for-ssr', + }, + { + name: 'OAuth with PKCE flow for SSR', + url: '/guides/auth/server-side/oauth-with-pkce-flow-for-ssr', + }, + ], + }, + { + name: 'Auth UI', + url: undefined, + items: [ + { name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui' }, + { name: 'Flutter Auth UI', url: '/guides/auth/auth-helpers/flutter-auth-ui' }, ], }, { diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 94cd0ae0631..1b0f0a75df1 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -244,7 +244,7 @@ const Container = memo(function Container(props) { className={[ // 'overflow-x-auto', 'w-full h-screen transition-all ease-out', - 'absolute lg:relative', + // 'absolute lg:relative', mobileMenuOpen ? '!w-auto ml-[75%] sm:ml-[50%] md:ml-[33%] overflow-hidden' : 'overflow-auto', diff --git a/apps/docs/pages/guides/auth/auth-helpers.mdx b/apps/docs/pages/guides/auth/auth-helpers.mdx index 26b798b820b..041df7fa54b 100644 --- a/apps/docs/pages/guides/auth/auth-helpers.mdx +++ b/apps/docs/pages/guides/auth/auth-helpers.mdx @@ -2,31 +2,15 @@ import Layout from '~/layouts/DefaultGuideLayout' export const meta = { id: 'index', - title: 'Auth Helpers Overview', - description: 'A collection of framework-specific Auth utilities for working with Supabase.', + title: 'Server-Side Auth Overview', + description: 'Server-Side Auth guides and utilities for working with Supabase.', sidebar_label: 'Overview', } -A collection of framework-specific Auth utilities for working with Supabase. +Working with server-side frameworks is slightly different to client-side frameworks. In this section we cover the various ways of handling server-side authentication and demonstrate how to use the Supabase helper-libraries to make the process more seamless.
- {/* Auth UI */} -
- -
- {/* Flutter Auth UI */} -
- -
{/* Next.js */}
+ + + +```bash +npm install @supabase/auth-helpers-nextjs @supabase/supabase-js +``` + + + + + +```bash +npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js +``` + + + + + +### Set environment variables + +Create an `.env.local` file in your project root directory. You can get your `SITE_URL` and `ANON_KEY` from inside of the [dashboard](https://supabase.com/dashboard/project/_/settings/api). + + + + + +```bash .env.local +NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +``` + + + + +```bash .env.local +PUBLIC_SUPABASE_URL=your_supabase_project_url +PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +``` + + + + +### Setting up the Auth Helpers + +When using the Supabase client on the server, you must perform extra steps to ensure the user's auth session remains active. Since the user's session is tracked in a cookie, we need to read this cookie and update it if necessary. + + + +Next.js Server Components allow you to read a cookie but not write back to it. Middleware on the other hand allow you to both read and write to cookies. + +Next.js [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) runs immediately before each route is rendered. We'll use Middleware to refresh the user's session before loading Server Component routes. + +Create a new `middleware.js` file in the root of your project and populate with the following: + +```js middleware.js +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' + +export async function middleware(req) { + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req, res }) + await supabase.auth.getSession() + return res +} +``` + + + +Create a new `hooks.server.js` file in the root of your project and populate with the following: + +```ts src/hooks.server.js +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' +import type { Handle } from '@sveltejs/kit' + +export const handle: Handle = async ({ event, resolve }) => { + event.locals.supabase = createSupabaseServerClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event, + }) + + event.locals.getSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + return session + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' + }, + }) +} +``` + + + + +### Create API endpoint for handling `token_hash` + +In order to use the updated email links we will need to setup a endpoint for verifying the `token_hash` along with the `type` to exchange `token_hash` for the user's `session`, which is set as a cookie for future requests made to Supabase. + + + +Create a new file at `app/auth/confirm/route.js` and populate with the following: + +```js app/auth/confirm/route.js +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export async function GET(req) { + const { searchParams } = new URL(req.url) + const token_hash = searchParams.get('token_hash') + const type = searchParams.get('type') + const next = searchParams.get('next') ?? '/' + + if (token_hash && type) { + const supabase = createRouteHandlerClient({ cookies }) + const { error } = await supabase.auth.verifyOtp({ type, token_hash }) + if (!error) { + return NextResponse.redirect(new URL(`/${next.slice(1)}`, req.url)) + } + } + + // return the user to an error page with some instructions + return NextResponse.redirect(new URL('/auth/auth-code-error', req.url)) +} +``` + + + +Create a new file at `src/routes/auth/confirm/+server.js` and populate with the following: + +```js src/routes/auth/confirm/+server.js +import { redirect } from '@sveltejs/kit'; + +export const GET = async (event) => { + const { + url, + locals: { supabase } + } = event; + const token_hash = url.searchParams.get('token') as string; + const type = url.searchParams.get('type') as string; + const next = url.searchParams.get('next') ?? '/'; + + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ token_hash, type }); + if (!error) { + throw redirect(303, `/${next.slice(1)}`); + } + } + + // return the user to an error page with some instructions + throw redirect(303, '/auth/auth-code-error'); +}; +``` + + + + +### Update email templates with URL for API endpoint + +Let's update the URL in our email templates to point to our new confirmation endpoint for the user to get confirmed. + +**Confirm signup template** + +```html +

Confirm your signup

+ +

Follow this link to confirm your user:

+

+ Confirm your email +

+``` + +**Invite user template** + +```html +

You have been invited

+ +

+ You have been invited to create a user on {{ .SiteURL }}. Follow this link to accept the invite: +

+ +

+ Accept the invite +

+``` + +**Magic Link template** + +```html +

Magic Link

+ +

Follow this link to login:

+

Log In

+``` + +**Change Email Address template** + +```html +

Confirm Change of Email

+ +

Follow this link to confirm the update of your email from {{ .Email }} to {{ .NewEmail }}:

+

Change Email

+``` + +**Reset Password template** + +```html +

Reset Password

+ +

Follow this link to reset the password for your user:

+

+ Reset Password +

+``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx b/apps/docs/pages/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx new file mode 100644 index 00000000000..60f6fb53a1c --- /dev/null +++ b/apps/docs/pages/guides/auth/server-side/oauth-with-pkce-flow-for-ssr.mdx @@ -0,0 +1,207 @@ +import Layout from '~/layouts/DefaultGuideLayout' + +export const meta = { + title: 'OAuth with PKCE flow for SSR', + description: + 'Learn how to configure OAuth authentication in your server-side rendering (SSR) application to work with the PKCE flow.', + subtitle: + 'Learn how to configure OAuth authentication in your server-side rendering (SSR) application to work with the PKCE flow.', +} + +### Install Supabase Auth Helpers + +The Auth Helpers assist with user authentication within server-side rendering (SSR) frameworks. + + + + +```bash +npm install @supabase/auth-helpers-nextjs @supabase/supabase-js +``` + + + + +```bash +npm install @supabase/auth-helpers-sveltekit @supabase/supabase-js +``` + + + + +### Set environment variables + +Create an `.env.local` file in your project root directory. You can get your `SITE_URL` and `ANON_KEY` from inside of the [dashboard](https://supabase.com/dashboard/project/_/settings/api). + + + + +```bash .env.local +NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +``` + + + + +```bash .env.local +PUBLIC_SUPABASE_URL=your_supabase_project_url +PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key +``` + + + + +### Setting up the Auth Helpers + +For SSR, the Supabase client requires extra steps to ensure the user's auth session remains active. Since the user's session is tracked in a cookie, we need to read this cookie and update it if necessary. + + + +Next.js Server Components allow you to read a cookie but not write back to it. Middleware on the other hand allow you to both read and write to cookies. + +Next.js [Middleware](https://nextjs.org/docs/app/building-your-application/routing/middleware) runs immediately before each route is rendered. We'll use Middleware to refresh the user's session before loading Server Component routes. + +Create a new `middleware.js` file in the root of your project and populate with the following: + +```js middleware.js +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' + +export async function middleware(req) { + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req, res }) + await supabase.auth.getSession() + return res +} +``` + + + +Create a new `hooks.server.js` file in the root of your project and populate with the following: + +```ts src/hooks.server.js +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { createSupabaseServerClient } from '@supabase/auth-helpers-sveltekit' +import type { Handle } from '@sveltejs/kit' + +export const handle: Handle = async ({ event, resolve }) => { + event.locals.supabase = createSupabaseServerClient({ + supabaseUrl: PUBLIC_SUPABASE_URL, + supabaseKey: PUBLIC_SUPABASE_ANON_KEY, + event, + }) + + event.locals.getSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + return session + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' + }, + }) +} +``` + + + + +### Create API endpoint for handling the `code` exchange + +In order to use OAuth we will need to setup a endpoint for the `code` exchange, to exchange an auth `code` for the user's `session`, which is set as a cookie for future requests made to Supabase. + + + +Create a new file at `app/auth/callback/route.js` and populate with the following: + +```js app/auth/callback/route.js +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +export async function GET(req) { + const { searchParams } = new URL(req.url) + const code = searchParams.get('code') + const next = searchParams.get('next') ?? '/' + + if (code) { + const supabase = createRouteHandlerClient({ cookies: () => cookies() }) + const { error } = await supabase.auth.exchangeCodeForSession(code) + if (!error) { + return NextResponse.redirect(new URL(`/${next.slice(1)}`, req.url)) + } + } + + // return the user to an error page with instructions + return NextResponse.redirect(new URL('/auth/auth-code-error', req.url)) +} +``` + + + +Create a new file at `src/routes/auth/callback/+server.js` and populate with the following: + +```js src/routes/auth/callback/+server.js +import { redirect } from '@sveltejs/kit'; + +export const GET = async (event) => { + const { + url, + locals: { supabase } + } = event; + const code = url.searchParams.get('code') as string; + const next = url.searchParams.get('next') ?? '/'; + + if (code) { + const { error } = await supabase.auth.exchangeCodeForSession(code) + if (!error) { + throw redirect(303, `/${next.slice(1)}`); + } + } + + // return the user to an error page with instructions + throw redirect(303, '/auth/auth-code-error'); +}; +``` + + + + +Let's point our `.signInWithOAuth` method's redirect to the callback route we create above: + +```js +await supabase.auth.signInWithOAuth({ + email, + options: { + redirectTo: `http://example.com/auth/callback`, + }, +}) +``` + +export const Page = ({ children }) => + +export default Page diff --git a/apps/docs/pages/guides/platform/compute-add-ons.mdx b/apps/docs/pages/guides/platform/compute-add-ons.mdx index d3bd4ad528a..e809e93476a 100644 --- a/apps/docs/pages/guides/platform/compute-add-ons.mdx +++ b/apps/docs/pages/guides/platform/compute-add-ons.mdx @@ -8,21 +8,23 @@ export const meta = { Every project on the Supabase Platform comes with its own dedicated Postgres instance running inside a virtual machine (VM). The following table describes the base instance with additional compute add-ons available if you need extra performance when scaling up Supabase. -| Plan | Pricing | CPU | Memory | Connections: Direct | Connections: Pooler | -| --------------- | ------- | ----------------------- | ------ | ------------------- | ------------------- | -| Free (Included) | $0 | 2-core ARM (shared) | 1 GB | 60 | 200 | -| Small | $5 | 2-core ARM (shared) | 2 GB | 90 | 200 | -| Medium | $50 | 2-core ARM (shared) | 4 GB | 120 | 200 | -| Large | $100 | 2-core ARM (dedicated) | 8 GB | 160 | 300 | -| XL | $200 | 4-core ARM (dedicated) | 16 GB | 240 | 700 | -| 2XL | $400 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 | -| 4XL | $950 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 | -| 8XL | $1,860 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 | -| 12XL | $2,790 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 | -| 16XL | $3,720 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | +| Plan | Hourly Price USD | Monthly Price USD | CPU | Memory | Connections: Direct | Connections: Pooler | +| ------- | ---------------- | ----------------- | ----------------------- | ------ | ------------------- | ------------------- | +| Starter | $0.01344 | ~$10 | 2-core ARM (shared) | 1 GB | 60 | 200 | +| Small | $0.0206 | ~$15 | 2-core ARM (shared) | 2 GB | 90 | 200 | +| Medium | $0.0822 | ~$60 | 2-core ARM (shared) | 4 GB | 120 | 200 | +| Large | $0.1517 | ~$110 | 2-core ARM (dedicated) | 8 GB | 160 | 300 | +| XL | $0.2877 | ~$210 | 4-core ARM (dedicated) | 16 GB | 240 | 700 | +| 2XL | $0.562 | ~$410 | 8-core ARM (dedicated) | 32 GB | 380 | 1500 | +| 4XL | $1.32 | ~$960 | 16-core ARM (dedicated) | 64 GB | 480 | 3000 | +| 8XL | $2.562 | ~$1,870 | 32-core ARM (dedicated) | 128 GB | 490 | 6000 | +| 12XL | $3.836 | ~$2,800 | 48-core ARM (dedicated) | 192 GB | 500 | 9000 | +| 16XL | $5.12 | ~$3,730 | 64-core ARM (dedicated) | 256 GB | 500 | 12,000 | Number of connections above are recommended values. +We charge hourly for additional compute based on your usage. Read more about [usage-based billing for compute](/docs/guides/platform/org-based-billing#usage-based-billing-for-compute). + [Contact us](https://supabase.com/contact/enterprise) if you require a custom plan. ## Dedicated vs. shared CPU @@ -37,18 +39,18 @@ When considering compute upgrades, assess whether your bottlenecks are hardware- SSD Disks are attached to your servers and the disk performance depends on the compute add-on of your instance. Disk IO refers to two metrics: throughput (Megabits per Second) and IOPS (Input/Output Operations per Second). -| Plan | Pricing | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS | -| --------------- | ------- | ------------------- | ------------------------ | ----------- | ------------- | -| Free (Included) | $0 | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS | -| Small | $5 | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS | -| Medium | $50 | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS | -| Large | $100 | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS | -| XL | $200 | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS | -| 2XL | $400 | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS | -| 4XL | $950 | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS | -| 8XL | $1,860 | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS | -| 12XL | $2,790 | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS | -| 16XL | $3,720 | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS | +| Plan | Max Disk Throughput | Baseline Disk Throughput | Max IOPS | Baseline IOPS | +| ------- | ------------------- | ------------------------ | ----------- | ------------- | +| Starter | 2,085 Mbps | 87 Mbps | 11,800 IOPS | 500 IOPS | +| Small | 2,085 Mbps | 174 Mbps | 11,800 IOPS | 1,000 IOPS | +| Medium | 2,085 Mbps | 347 Mbps | 11,800 IOPS | 2,000 IOPS | +| Large | 4,750 Mbps | 630 Mbps | 20,000 IOPS | 3,600 IOPS | +| XL | 4,750 Mbps | 1,188 Mbps | 20,000 IOPS | 6,000 IOPS | +| 2XL | 4,750 Mbps | 2,375 Mbps | 20,000 IOPS | 12,000 IOPS | +| 4XL | 4,750 Mbps | 4,750 Mbps | 20,000 IOPS | 20,000 IOPS | +| 8XL | 9,500 Mbps | 9,500 Mbps | 40,000 IOPS | 40,000 IOPS | +| 12XL | 14,250 Mbps | 14,250 Mbps | 50,000 IOPS | 50,000 IOPS | +| 16XL | 19,000 Mbps | 19,000 Mbps | 80,000 IOPS | 80,000 IOPS | [Contact us](https://supabase.com/contact/enterprise) if you require a custom plan. diff --git a/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx b/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx index e60afb4f74a..fd9e8d98675 100644 --- a/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx +++ b/apps/docs/pages/guides/realtime/realtime-with-nextjs.mdx @@ -7,8 +7,8 @@ export const meta = { sidebar_label: 'Videos', } -In this guide we explore the best ways to receive realtime Postgres changes with your Next.js application. -We'll show both client and serverside updates, and explore the which option is best. +In this guide, we explore the best ways to receive real-time Postgres changes with your Next.js application. +We'll show both client and server side updates, and explore which option is best.