diff --git a/apps/docs/content/guides/auth/server-side/nextjs.mdx b/apps/docs/content/guides/auth/server-side/nextjs.mdx index 2cae8e90d4609..0443da172579b 100644 --- a/apps/docs/content/guides/auth/server-side/nextjs.mdx +++ b/apps/docs/content/guides/auth/server-side/nextjs.mdx @@ -264,7 +264,8 @@ export async function updateSession(request: NextRequest) { if ( !user && !request.nextUrl.pathname.startsWith('/login') && - !request.nextUrl.pathname.startsWith('/auth') + !request.nextUrl.pathname.startsWith('/auth') && + !request.nextUrl.pathname.startsWith('/error') ) { // no user, potentially respond by redirecting the user to the login page const url = request.nextUrl.clone() diff --git a/apps/docs/content/guides/deployment/branching/dashboard.mdx b/apps/docs/content/guides/deployment/branching/dashboard.mdx index 2d938165736cd..a7577014d5a8d 100644 --- a/apps/docs/content/guides/deployment/branching/dashboard.mdx +++ b/apps/docs/content/guides/deployment/branching/dashboard.mdx @@ -58,8 +58,6 @@ When reviewing a merge request you may see a notice at the top of the page askin There are a few limitations you should be aware of before deciding to use branching without git. - Custom roles created through the dashboard are not captured on branch creation -- Only public schema changes are supported right now -- Extensions are not included in the diff process - Branches can only be merged to main; merging between preview branches is not supported - If your branch is out of date, you can pull in latest changes from main, but keep in mind that all functions will be overwritten - Deleting functions must be done manually on main branch diff --git a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx index 1b114f50a60be..43def489bf8d5 100644 --- a/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx +++ b/apps/docs/content/guides/getting-started/tutorials/with-sveltekit.mdx @@ -17,14 +17,14 @@ If you get stuck while working through this guide, refer to the [full example on ## Building the app -Let's start building the Svelte app from scratch. +Start building the Svelte app from scratch. ### Initialize a Svelte app -We can use the [SvelteKit Skeleton Project](https://kit.svelte.dev/docs) to initialize an app called `supabase-sveltekit` (for this tutorial we will be using TypeScript): +Use the [SvelteKit Skeleton Project](https://svelte.dev/docs/kit) to initialize an app called `supabase-sveltekit` (for this tutorial, select "SvelteKit minimal" and use TypeScript): ```bash -npm create svelte@latest supabase-sveltekit +npx sv create supabase-sveltekit cd supabase-sveltekit npm install ``` @@ -35,8 +35,8 @@ Then install the Supabase client library: [supabase-js](https://github.com/supab npm install @supabase/supabase-js ``` -And finally we want to save the environment variables in a `.env`. -All we need are the `SUPABASE_URL` and the `SUPABASE_KEY` key that you copied [earlier](#get-the-api-keys). +And finally, save the environment variables in a `.env` file. +All you need are the `PUBLIC_SUPABASE_URL` and the `PUBLIC_SUPABASE_ANON_KEY` key that you copied [earlier](#get-the-api-keys). <$CodeTabs> @@ -47,108 +47,46 @@ PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_KEY" -Optionally, add `src/styles.css` with the [CSS from the example](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/sveltekit-user-management/src/styles.css). +### App styling (optional) + +An optional step is to update the CSS file `src/styles.css` to make the app look nice. +You can find the full contents of this file [in the example repository](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/sveltekit-user-management/src/styles.css). ### Creating a Supabase client for SSR -The `ssr` package configures Supabase to use Cookies, which is required for server-side languages and frameworks. +The `ssr` package configures Supabase to use Cookies, which are required for server-side languages and frameworks. -Install the Supabase packages: +Install the SSR package: ```bash -npm install @supabase/ssr @supabase/supabase-js +npm install @supabase/ssr ``` -Creating a Supabase client with the `ssr` package automatically configures it to use Cookies. This means your user's session is available throughout the entire SvelteKit stack - page, layout, server, hooks. +Creating a Supabase client with the `ssr` package automatically configures it to use Cookies. This means the user's session is available throughout the entire SvelteKit stack - page, layout, server, and hooks. -Add the code below to your `src/hooks.server.ts` to initialize the client on the server: +Add the code below to a `src/hooks.server.ts` file to initialize the client on the server: <$CodeTabs> -```ts name=src/hooks.server.ts -// src/hooks.server.ts -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' -import { createServerClient } from '@supabase/ssr' -import type { Handle } from '@sveltejs/kit' - -export const handle: Handle = async ({ event, resolve }) => { - event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - cookies: { - getAll: () => event.cookies.getAll(), - /** - * SvelteKit's cookies API requires `path` to be explicitly set in - * the cookie options. Setting `path` to `/` replicates previous/ - * standard behavior. - */ - setAll: (cookiesToSet) => { - cookiesToSet.forEach(({ name, value, options }) => { - event.cookies.set(name, value, { ...options, path: '/' }) - }) - }, - }, - }) - - /** - * Unlike `supabase.auth.getSession()`, which returns the session _without_ - * validating the JWT, this function also calls `getUser()` to validate the - * JWT before returning the session. - */ - event.locals.safeGetSession = async () => { - const { - data: { session }, - } = await event.locals.supabase.auth.getSession() - if (!session) { - return { session: null, user: null } - } - - const { - data: { user }, - error, - } = await event.locals.supabase.auth.getUser() - if (error) { - // JWT validation has failed - return { session: null, user: null } - } - - return { session, user } - } - - return resolve(event, { - filterSerializedResponseHeaders(name) { - return name === 'content-range' || name === 'x-supabase-api-version' - }, - }) -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/hooks.server.ts" +lines={[[1, -1]]} +meta="name=src/hooks.server.ts" +/> <$Partial path="get_session_warning.mdx" /> - -If you are using TypeScript the compiler might complain about `event.locals.supabase` and `event.locals.safeGetSession`, this can be fixed by updating your `src/app.d.ts` with the content below: +{/* TODO: Change when adding JS autoconversion */} +As this tutorial uses TypeScript the compiler complains about `event.locals.supabase` and `event.locals.safeGetSession`, you can fix this by updating the `src/app.d.ts` with the content below: <$CodeTabs> -```ts name=src/app.d.ts -// src/app.d.ts - -import { SupabaseClient, Session } from '@supabase/supabase-js' - -declare global { - namespace App { - interface Locals { - supabase: SupabaseClient - safeGetSession(): Promise<{ session: Session | null; user: User | null }> - } - interface PageData { - session: Session | null - user: User | null - } - // interface Error {} - // interface Platform {} - } -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/app.d.ts" +lines={[[1, -1]]} +meta="name=src/app.d.ts" +/> @@ -156,26 +94,17 @@ Create a new `src/routes/+layout.server.ts` file to handle the session on the se <$CodeTabs> -```ts name=src/routes/+layout.server.ts -// src/routes/+layout.server.ts -import type { LayoutServerLoad } from './$types' - -export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => { - const { session, user } = await safeGetSession() - - return { - session, - user, - cookies: cookies.getAll(), - } -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+layout.server.ts" +lines={[[1, -1]]} +meta="name=src/routes/+layout.server.ts" +/> -Start your dev server (`npm run dev`) in order to generate the `./$types` files we are referencing in our project. +Start the dev server (`npm run dev`) to generate the `./$types` files we are referencing in our project. @@ -183,202 +112,49 @@ Create a new `src/routes/+layout.ts` file to handle the session and the `supabas <$CodeTabs> -```ts name=src/routes/+layout.ts -// src/routes/+layout.ts -import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' -import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' -import type { LayoutLoad } from './$types' - -export const load: LayoutLoad = async ({ fetch, data, depends }) => { - depends('supabase:auth') - - const supabase = isBrowser() - ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - global: { - fetch, - }, - }) - : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { - global: { - fetch, - }, - cookies: { - getAll() { - return data.cookies - }, - }, - }) - - /** - * It's fine to use `getSession` here, because on the client, `getSession` is - * safe, and on the server, it reads `session` from the `LayoutData`, which - * safely checked the session using `safeGetSession`. - */ - const { - data: { session }, - } = await supabase.auth.getSession() - - return { supabase, session } -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+layout.ts" +lines={[[1, -1]]} +meta="name=src/routes/+layout.ts" +/> -Update your `src/routes/+layout.svelte`: +Create `src/routes/+layout.svelte`: <$CodeTabs> -```svelte name=src/routes/+layout.svelte - - - - - User Management - - -
- -
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+layout.svelte" +lines={[[1, -1]]} +meta="name=src/routes/+layout.svelte" +/> ## Set up a login page -Create a magic link login/signup page for your application: +Create a magic link login/signup page for your application by updating the `routes/+page.svelte` file: <$CodeTabs> -```svelte name=src/routes/+page.svelte - - - - - User Management - - -
-
-

Supabase + SvelteKit

-

Sign in via magic link with your email below

- {#if form?.message !== undefined} -
- {form?.message} -
- {/if} -
- - -
- {#if form?.errors?.email} - - {form?.errors?.email} - - {/if} -
- -
-
-
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.svelte" +lines={[[1, -1]]} +meta="name=src/routes/+page.svelte" +/> -Create a `src/routes/+page.server.ts` file that will handle our magic link form when submitted. +Create a `src/routes/+page.server.ts` file that handles the magic link form when submitted. <$CodeTabs> -```ts name=src/routes/+page.server.ts -// src/routes/+page.server.ts -import { fail, redirect } from '@sveltejs/kit' -import type { Actions, PageServerLoad } from './$types' - -export const load: PageServerLoad = async ({ url, locals: { safeGetSession } }) => { - const { session } = await safeGetSession() - - // if the user is already logged in return them to the account page - if (session) { - redirect(303, '/account') - } - - return { url: url.origin } -} - -export const actions: Actions = { - default: async (event) => { - const { - url, - request, - locals: { supabase }, - } = event - const formData = await request.formData() - const email = formData.get('email') as string - const validEmail = /^[\w-\.+]+@([\w-]+\.)+[\w-]{2,8}$/.test(email) - - if (!validEmail) { - return fail(400, { errors: { email: 'Please enter a valid email address' }, email }) - } - - const { error } = await supabase.auth.signInWithOtp({ email }) - - if (error) { - return fail(400, { - success: false, - email, - message: `There was an issue, Please contact support.`, - }) - } - - return { - success: true, - message: 'Please check your email for a magic link to log into the website.', - } - }, -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.server.ts" +lines={[[1, -1]]} +meta="name=src/routes/+page.server.ts" +/> @@ -388,239 +164,77 @@ Change the email template to support a server-side authentication flow. Before we proceed, let's change the email template to support sending a token hash: -- Go to the [Auth templates](/dashboard/project/_/auth/templates) page in your dashboard. -- Select `Confirm signup` template. +- Go to the [**Auth** > **Emails**](/dashboard/project/_/auth/templates) page in the project dashboard. +- Select the **Confirm signup** template. - Change `{{ .ConfirmationURL }}` to `{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email`. -- Repeat the previous step for `Magic link` template. +- Repeat the previous step for **Magic link** template. -Did you know? You could also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates). +**Did you know?** You can also customize emails sent out to new users, including the email's looks, content, and query parameters. Check out the [settings of your project](/dashboard/project/_/auth/templates). ### Confirmation endpoint -As we are working in a server-side rendering (SSR) environment, it is necessary to create a server endpoint responsible for exchanging the `token_hash` for a session. +As this is a server-side rendering (SSR) environment, you need to create a server endpoint responsible for exchanging the `token_hash` for a session. -In the following code snippet, we perform the following steps: +The following code snippet performs the following steps: -- Retrieve the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter. -- Exchange this `token_hash` for a session, which we store in storage (in this case, cookies). -- Finally, the user is redirected to the `account` page or the `error` page. +- Retrieves the `token_hash` sent back from the Supabase Auth server using the `token_hash` query parameter. +- Exchanges this `token_hash` for a session, which you store in storage (in this case, cookies). +- Finally, redirect the user to the `account` page or the `error` page. <$CodeTabs> -```ts name=src/routes/auth/confirm/+server.ts -// src/routes/auth/confirm/+server.ts -import type { EmailOtpType } from '@supabase/supabase-js' -import { redirect } from '@sveltejs/kit' - -import type { RequestHandler } from './$types' - -export const GET: RequestHandler = async ({ url, locals: { supabase } }) => { - const token_hash = url.searchParams.get('token_hash') - const type = url.searchParams.get('type') as EmailOtpType | null - const next = url.searchParams.get('next') ?? '/account' - - /** - * Clean up the redirect URL by deleting the Auth flow parameters. - * - * `next` is preserved for now, because it's needed in the error case. - */ - const redirectTo = new URL(url) - redirectTo.pathname = next - redirectTo.searchParams.delete('token_hash') - redirectTo.searchParams.delete('type') - - if (token_hash && type) { - const { error } = await supabase.auth.verifyOtp({ type, token_hash }) - if (!error) { - redirectTo.searchParams.delete('next') - redirect(303, redirectTo) - } - } - - redirectTo.pathname = '/auth/error' - redirect(303, redirectTo) -} -``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/auth/confirm/+server.ts" +lines={[[1, -1]]} +meta="name=src/routes/auth/confirm/+server.ts" +/> ### Authentication error page -If there is an error with confirming the token you will be redirect to this error page. +If there is an error with confirming the token, redirect the user to an error page. <$CodeTabs> - -```svelte name=src/routes/auth/error/+page.svelte -

Login error

-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/auth/error/+page.svelte" +lines={[[1, -1]]} +meta="name=src/routes/auth/error/+page.svelte" +/> ### Account page -After a user is signed in, they need to be able to edit their profile details and manage their account. +After a user signs in, they need to be able to edit their profile details page. Create a new `src/routes/account/+page.svelte` file with the content below. <$CodeTabs> -```svelte name=src/routes/account/+page.svelte - - - -
-
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
-
- -
-
- -
-
-
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte" +lines={[[1, 3],[6,12],[15,38],[47,-1]]} +meta="name=src/routes/account/+page.svelte" +/> -Now create the associated `src/routes/account/+page.server.ts` file that will handle loading our data from the server through the `load` function -and handle all our form actions through the `actions` object. - -```ts name=src/routes/account/+page.server.ts -import { fail, redirect } from '@sveltejs/kit' -import type { Actions, PageServerLoad } from './$types' - -export const load: PageServerLoad = async ({ locals: { supabase, safeGetSession } }) => { - const { session } = await safeGetSession() - - if (!session) { - redirect(303, '/') - } - - const { data: profile } = await supabase - .from('profiles') - .select(`username, full_name, website, avatar_url`) - .eq('id', session.user.id) - .single() - - return { session, profile } -} - -export const actions: Actions = { - update: async ({ request, locals: { supabase, safeGetSession } }) => { - const formData = await request.formData() - const fullName = formData.get('fullName') as string - const username = formData.get('username') as string - const website = formData.get('website') as string - const avatarUrl = formData.get('avatarUrl') as string - - const { session } = await safeGetSession() - - const { error } = await supabase.from('profiles').upsert({ - id: session?.user.id, - full_name: fullName, - username, - website, - avatar_url: avatarUrl, - updated_at: new Date(), - }) - - if (error) { - return fail(500, { - fullName, - username, - website, - avatarUrl, - }) - } - - return { - fullName, - username, - website, - avatarUrl, - } - }, - signout: async ({ locals: { supabase, safeGetSession } }) => { - const { session } = await safeGetSession() - if (session) { - await supabase.auth.signOut() - redirect(303, '/') - } - }, -} -``` +Now, create the associated `src/routes/account/+page.server.ts` file that handles loading data from the server through the `load` function +and handle all form actions through the `actions` object. + +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/+page.server.ts" +lines={[[1, -1]]} +meta="name=src/routes/+page.server.ts" +/> ### Launch! -Now that we have all the pages in place, run this in a terminal window: +With all the pages in place, run this command in a terminal: ```bash npm run dev @@ -636,144 +250,29 @@ Every Supabase project is configured with [Storage](/docs/guides/storage) for ma ### Create an upload widget -Let's create an avatar for the user so that they can upload a profile photo. We can start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory: +Create an avatar for the user so that they can upload a profile photo. Start by creating a new component called `Avatar.svelte` in the `src/routes/account` directory: <$CodeTabs> -```svelte name=src/routes/account/Avatar.svelte - - - -
- {#if avatarUrl} - {avatarUrl - {:else} -
- {/if} - - -
- - -
-
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/Avatar.svelte" +lines={[[1, -1]]} +meta="name=src/routes/account/Avatar.svelte" +/> ### Add the new widget -And then we can add the widget to the Account page: +Add the widget to the Account page: <$CodeTabs> -```svelte name=src/routes/account/+page.svelte - - - -
-
- - { - profileForm.requestSubmit(); - }} - /> - - - -
-``` +<$CodeSample +path="/user-management/sveltekit-user-management/src/routes/account/+page.svelte" +lines={[[1, 1], [4,4], [31,31],[39,46],[82,82]]} +meta="name=src/routes/account/+page.svelte" +/> diff --git a/apps/studio/components/interfaces/App/FeaturePreview/Branching2Preview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/Branching2Preview.tsx index 6aba6698b34a2..9bf5f6d30c1d5 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/Branching2Preview.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/Branching2Preview.tsx @@ -25,10 +25,6 @@ export const Branching2Preview = () => {

Limitations: