From e1d1de7a8c4f53c1bd18e04e02cfcdcd9da3c02b Mon Sep 17 00:00:00 2001 From: Oliver Rice Date: Mon, 7 Nov 2022 08:30:54 -0600 Subject: [PATCH 01/34] remove (outdated) references to graphql.rebuild_schema --- apps/docs/docs/guides/api.mdx | 7 ------- apps/docs/docs/guides/migrations/heroku.mdx | 8 -------- apps/reference/docs/guides/api.mdx | 7 ------- apps/reference/docs/guides/migrations/heroku.mdx | 8 -------- apps/www/_blog/2022-03-29-graphql-now-available.mdx | 3 --- 5 files changed, 33 deletions(-) diff --git a/apps/docs/docs/guides/api.mdx b/apps/docs/docs/guides/api.mdx index 68974f7c1da..4b4686d1d7e 100644 --- a/apps/docs/docs/guides/api.mdx +++ b/apps/docs/docs/guides/api.mdx @@ -199,13 +199,6 @@ JS Reference: [select()](../reference/javascript/select), ### GraphQL API - - -To rebuild your GraphQL schema from the SQL schema, call `select graphql.rebuild_schema();`. -Be sure to rebuild the GraphQL schema after altering the SQL schema. - - - You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/). Date: Wed, 9 Nov 2022 15:58:25 +0000 Subject: [PATCH 02/34] placeholder text changed --- .../interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx b/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx index 14b467abf00..64398eb56af 100644 --- a/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx +++ b/studio/components/interfaces/Auth/Policies/PolicyEditor/PolicyRoles.tsx @@ -32,7 +32,7 @@ const PolicyRoles: FC = ({ roles, selectedRoles, onUpdateSelectedRoles }) From 42abed370d9346e772152c8cc9338573e569cd2b Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Thu, 10 Nov 2022 01:01:36 +0000 Subject: [PATCH 03/34] Update docs to match latest SvelteKit auth helpers --- .../guides/auth/auth-helpers/sveltekit.mdx | 815 ++++++++++++------ 1 file changed, 551 insertions(+), 264 deletions(-) diff --git a/apps/docs/docs/guides/auth/auth-helpers/sveltekit.mdx b/apps/docs/docs/guides/auth/auth-helpers/sveltekit.mdx index f8267ea371c..62ed1502e16 100644 --- a/apps/docs/docs/guides/auth/auth-helpers/sveltekit.mdx +++ b/apps/docs/docs/guides/auth/auth-helpers/sveltekit.mdx @@ -53,11 +53,361 @@ PUBLIC_SUPABASE_ANON_KEY=your-anon-key Start off by creating a `db.ts` file inside of the `src/lib` directory and instantiate the `supabaseClient`. ```ts title=src/lib/db.ts +import { createClient } from '@supabase/auth-helpers-sveltekit'; +import { env } from '$env/dynamic/public'; +// or use the static env +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; + +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_ANON_KEY +); +``` + +To make sure the client is initialized on the server and the client we include this file in `src/hooks.server.js` and `src/hooks.client.js`: + +```ts +import '$lib/db'; +``` + +### Synchronizing the page store + +Edit your `+layout.svelte` file and set up the client side. + +```html + + + + +``` + +Every `PageLoad` or `LayoutLoad` using `getSupabase()` will update when `invalidate('supabase:auth')` is called. + +If some data is not updated on signin/signout you can fall back to `invalidateAll()`. + +### Send session to client + +In order to make the session available to the UI (pages, layouts) we need to pass the session in the root layout server load function: + +```ts +// src/routes/+layout.server.ts +import type { LayoutServerLoad } from './$types'; +import { getServerSession } from '@supabase/auth-helpers-sveltekit'; + +export const load: LayoutServerLoad = async (event) => { + return { + session: await getServerSession(event) + }; +}; +``` + +In addition you can create a layout load function if you are using `invalidate('supabase:auth')`: + +```ts +// src/routes/+layout.ts +import type { LayoutLoad } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; + +export const load: LayoutLoad = async (event) => { + const { session } = await getSupabase(event); + return { session }; +}; +``` + +This results in less server calls as the client manages the session on it's own. + +### Typings + +In order to get the most out of TypeScript and it´s intellisense, you should import our types into the `app.d.ts` type definition file that comes with your SvelteKit project. + +```ts +// src/app.d.ts + +/// + +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +// and what to do when importing types +declare namespace App { + interface Supabase { + Database: import('./DatabaseDefinitions').Database; + SchemaName: 'public'; + } + + // interface Locals {} + interface PageData { + session: import('@supabase/supabase-js').Session | null; + } + // interface Error {} + // interface Platform {} +} +``` + +### Basic Setup + +You can now determine if a user is authenticated on the client-side by checking that the `session` object in `$page.data` is defined. + +```html + + + +{#if !$page.data.session} +

I am not logged in

+{:else} +

Welcome {$page.data.session.user.email}

+

I am logged in!

+{/if} +``` + +## Client-side data fetching with RLS + +For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `$lib/db` and only run your query once the session is defined client-side in `$page.data`: + +```html + + +{#if $page.data.session} +

client-side data fetching with RLS

+
{JSON.stringify(loadedData, null, 2)}
+{/if} +``` + +## Server-side data fetching with RLS + +```html + + + +
Protected content for {user.email}
+
{JSON.stringify(tableData, null, 2)}
+
{JSON.stringify(user, null, 2)}
+``` + +For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to use the `getSupabase` helper to check if the user is authenticated. The helper requires the `event` and returns `session` and `supabaseClient`: + +```ts +// src/routes/profile/+page.ts +import type { PageLoad } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { redirect } from '@sveltejs/kit'; + +export const load: PageLoad = async (event) => { + const { session, supabaseClient } = await getSupabase(event); + if (!session) { + throw redirect(303, '/'); + } + const { data: tableData } = await supabaseClient.from('test').select('*'); + + return { + user: session.user, + tableData + }; +}; +``` + +## Protecting API routes + +Wrap an API Route to check that the user has a valid session. If they're not logged in the session is `null`. + +```ts +// src/routes/api/protected-route/+server.ts +import type { RequestHandler } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { json, redirect } from '@sveltejs/kit'; + +export const GET: RequestHandler = async (event) => { + const { session, supabaseClient } = await getSupabase(event); + if (!session) { + throw redirect(303, '/'); + } + const { data } = await supabaseClient.from('test').select('*'); + + return json({ data }); +}; +``` + +If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response. + +## Protecting Actions + +Wrap an Action to check that the user has a valid session. If they're not logged in the session is `null`. + +```ts +// src/routes/posts/+page.server.ts +import type { Actions } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { error, invalid } from '@sveltejs/kit'; + +export const actions: Actions = { + createPost: async (event) => { + const { request } = event; + const { session, supabaseClient } = await getSupabase(event); + if (!session) { + // the user is not signed in + throw error(403, { message: 'Unauthorized' }); + } + // we are save, let the user create the post + const formData = await request.formData(); + const content = formData.get('content'); + + const { error: createPostError, data: newPost } = await supabaseClient + .from('posts') + .insert({ content }); + + if (createPostError) { + return invalid(500, { + supabaseErrorMessage: createPostError.message + }); + } + return { + newPost + }; + } +}; +``` + +If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 403 error response. + +## Saving and deleting the session + +```ts +import type { Actions } from './$types'; +import { invalid, redirect } from '@sveltejs/kit'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; + +export const actions: Actions = { + signin: async (event) => { + const { request, cookies, url } = event; + const { session, supabaseClient } = await getSupabase(event); + const formData = await request.formData(); + + const email = formData.get('email') as string; + const password = formData.get('password') as string; + + const { error } = await supabaseClient.auth.signInWithPassword({ + email, + password + }); + + if (error) { + if (error instanceof AuthApiError && error.status === 400) { + return invalid(400, { + error: 'Invalid credentials.', + values: { + email + } + }); + } + return invalid(500, { + error: 'Server error. Try again later.', + values: { + email + } + }); + } + + throw redirect(303, '/dashboard'); + }, + + signout: async (event) => { + const { supabaseClient } = await getSupabase(event); + await supabaseClient.auth.signOut(); + throw redirect(303, '/'); + } +}; +``` + +## Protecting multiple routes + +To avoid writing the same auth logic in every single route you can use the handle hook to +protect multiple routes at once. + +```ts +// src/hooks.server.ts +import type { RequestHandler } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { redirect, error } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + // protect requests to all routes that start with /protected-routes + if (event.url.pathname.startsWith('/protected-routes')) { + const { session, supabaseClient } = await getSupabase(event); + + if (!session) { + throw redirect(303, '/'); + } + } + + // protect POST requests to all routes that start with /protected-posts + if ( + event.url.pathname.startsWith('/protected-posts') && + event.request.method === 'POST' + ) { + const { session, supabaseClient } = await getSupabase(event); + + if (!session) { + throw error(303, '/'); + } + } + + return resolve(event); +}; +``` + +## Migrate from 0.7.x to 0.8 {#migration} + +### Set up the Supabase client {#migration-set-up-supabase-client} + + + + +```js title=src/lib/db.ts import { createClient } from '@supabase/supabase-js' import { setupSupabaseHelpers } from '@supabase/auth-helpers-sveltekit' import { dev } from '$app/environment' import { env } from '$env/dynamic/public' // or use the static env + // import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; export const supabaseClient = createClient(env.PUBLIC_SUPABASE_URL, env.PUBLIC_SUPABASE_ANON_KEY, { @@ -73,9 +423,34 @@ setupSupabaseHelpers({ }) ``` -### Initialize the client + + -Edit your `+layout.svelte` file and set up the client-side. +```js title=src/lib/db.ts +import { createClient } from '@supabase/auth-helpers-sveltekit' +import { env } from '$env/dynamic/public' +// or use the static env + +// import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; + +export const supabaseClient = createClient( + env.PUBLIC_SUPABASE_URL, + env.PUBLIC_SUPABASE_ANON_KEY +); +``` + + + + +### Initialize the client {#migration-initialize-client} + + + ```html title=src/routes/+layout.svelte + + +``` + + + + +### Set up hooks {#migration-set-up-hooks} + + + ```ts title=src/hooks.server.ts // make sure the supabase instance is initialized on the server @@ -106,39 +515,45 @@ import { dev } from '$app/environment' import { auth } from '@supabase/auth-helpers-sveltekit/server' export const handle = auth() +``` -// use the sequence helper if you have additional Handle methods +**Optional** _if using additional handle methods_ + +```ts title=src/hooks.server.ts +// make sure the supabase instance is initialized on the server +import '$lib/db' +import { dev } from '$app/environment' +import { auth } from '@supabase/auth-helpers-sveltekit/server' import { sequence } from '@sveltejs/kit/hooks' export const handle = sequence(auth(), yourHandler) ``` -There are three handle methods available: + + -- `callback()`: - This creates a handler for `/api/auth/callback`. The `client` forwards the session details here every time `onAuthStateChange` fires on the client-side. This is needed to set up the cookies for your application so that SSR works seamlessly. -- `session()`: - This parses the session from the cookie and populate it in locals -- `auth()`: - a shorthand for `sequence(callback(), session())` that uses both handlers - -### Send session to client - -In order to make the session available to the UI (e.g., pages and layouts), pass the session in the root layout load function: - -```ts title=src/routes/+layout.server.ts -import type { LayoutServerLoad } from './$types' - -export const load: LayoutServerLoad = async ({ locals }) => { - return { - session: locals.session, - } -} +```ts title=src/hooks.server.ts +// make sure the supabase instance is initialized on the server +import '$lib/db' ``` -### Typings +```ts title=src/hooks.client.ts +// make sure the supabase instance is initialized on the client +import '$lib/db' +``` -In order to get the most out of TypeScript and IntelliSense, import the types into the `app.d.ts` type definition file that comes with your SvelteKit project. + + + +### Typings {#migration-typings} + + + ```ts title=src/app.d.ts /// @@ -150,95 +565,91 @@ declare namespace App { interface Locals { session: import('@supabase/auth-helpers-sveltekit').SupabaseSession } + interface PageData { session: import('@supabase/auth-helpers-sveltekit').SupabaseSession } + + // interface Error {} + // interface Platform {} +} +``` + + + + +```ts title=src/app.d.ts +/// + +// See https://kit.svelte.dev/docs/types#app +// for information about these interfaces +// and what to do when importing types +declare namespace App { + interface Supabase { + Database: import('./DatabaseDefinitions').Database; + SchemaName: 'public'; + } + + // interface Locals {} + interface PageData { + session: import('@supabase/auth-helpers-sveltekit').SupabaseSession; + } // interface Error {} // interface Platform {} } ``` -### Signing out + + -This library has provided a method to delete the session cookie. +### withPageAuth {#migration-with-page-auth} -Create a server only page with an action to delete the cookie + + -```ts title=src/routes/logout/+page.server.ts -import { deleteSession } from '@supabase/auth-helpers-sveltekit/server' -import { redirect } from '@sveltejs/kit' -import type { Actions } from './$types' - -export const actions: Actions = { - async default({ cookies }) { - deleteSession(cookies) - throw redirect(303, '/') - }, -} -``` - -Then make a **POST** request with a form to the server only page above. - -```html +```html title=src/routes/protected-route/+page.svelte -
- -
+
Protected content for {user.email}
+

server-side fetched data with RLS:

+
{JSON.stringify(tableData, null, 2)}
+

user:

+
{JSON.stringify(user, null, 2)}
``` -### Basic setup +```ts title=src/routes/protected-route/+page.ts +import { withAuth } from '@supabase/auth-helpers-sveltekit' +import { redirect } from '@sveltejs/kit' +import type { PageLoad } from './$types' -You can now determine if a user is authenticated on the client-side by checking that the `user` object in `$page.data.session` is defined. - -```html title=src/routes/+page.svelte - - -{#if !$page.data.session.user} -

I am not logged in

-{:else} -

Welcome {$page.data.session.user.email}

-

I am logged in!

-{/if} -``` - -## Client-side data fetching with RLS - -For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `$lib/db` and only run your query once the user is defined client-side in `$page.data.session`: - -```html - - -{#if $page.data.session.user} -

client-side data fetching with RLS

-
{JSON.stringify(loadedData, null, 2)}
-{/if} + const { data: tableData } = await getSupabaseClient().from('test').select('*') + return { tableData, user: session.user } +}) ``` -## Server-side data fetching with RLS +
+ -```html title=src/routes/profile/+page.svelte +```html title=src/routes/protected-route/+page.svelte
Protected content for {user.email}
@@ -246,52 +657,38 @@ For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to
{JSON.stringify(user, null, 2)}
``` -For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work in a server environment, you need to use the `withAuth` helper to check if the user is authenticated. The helper extends the event with `session` and `getSupabaseClient()`: - -```ts title=src/routes/profile/+page.ts +```ts title=src/routes/protected-route/+page.ts +// src/routes/profile/+page.ts import type { PageLoad } from './$types'; -import { withAuth } from '@supabase/auth-helpers-sveltekit'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; import { redirect } from '@sveltejs/kit'; -interface TestTable { - id: string; - created_at: string; -} - -export const load: PageLoad = withAuth(async ({ getSupabaseClient, session }) => { - if (!session.user) { +export const load: PageLoad = async (event) => { + const { session, supabaseClient } = await getSupabase(event); + if (!session) { throw redirect(303, '/'); } - const { data: tableData } = await getSupabaseClient() - .from('test') - .select('*'); + const { data: tableData } = await supabaseClient.from('test').select('*'); return { user: session.user, tableData }; -); +}; ``` -**Caution:** +
+
-Always use the instance returned by `getSupabaseClient()` directly! +### withApiAuth {#migration-with-api-auth} -```ts -// Bad -const supabaseClient = getSupabaseClient() - -await supabaseClient.from('table1').select() -await supabaseClient.from('table2').select() - -// Good -await getSupabaseClient().from('table1').select() -await getSupabaseClient().from('table2').select() -``` - -## Protecting API routes - -Wrap an API Route to check that the user has a valid session. If they're not logged in the session is `null`. + + ```ts title=src/routes/api/protected-route/+server.ts import type { RequestHandler } from './$types'; @@ -307,148 +704,38 @@ export const GET: RequestHandler = withAuth(async ({ session, getSupabaseClient if (!session.user) { throw redirect(303, '/'); } + const { data } = await getSupabaseClient() .from('test') .select('*'); return json({ data }); -); +}); ``` -If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response. + + -## Protecting Actions +```ts title=src/routes/api/protected-route/+server.ts +import type { RequestHandler } from './$types'; +import { getSupabase } from '@supabase/auth-helpers-sveltekit'; +import { json, redirect } from '@sveltejs/kit'; -Wrap an Action to check that the user has a valid session. If they're not logged in, the session is `null`. - -```ts title=src/routes/posts/+page.server.ts -import type { Actions } from './$types' -import { withAuth } from '@supabase/auth-helpers-sveltekit' -import { error, invalid } from '@sveltejs/kit' - -export const actions: Actions = { - createPost: withAuth(async ({ session, getSupabaseClient, request }) => { - if (!session.user) { - // the user is not signed in - throw error(403, { message: 'Unauthorized' }) - } - // we are save, let the user create the post - const formData = await request.formData() - const content = formData.get('content') - - const { error: createPostError, data: newPost } = await getSupabaseClient() - .from('posts') - .insert({ content }) - - if (createPostError) { - return invalid(500, { - supabaseErrorMessage: createPostError.message, - }) - } - return { - newPost, - } - }), -} -``` - -If you try to submit a form with the action `?/createPost` without a valid session cookie, you will get a 403 error response. - -## Saving and deleting the session - -Use `saveSession` to save the session cookies: - -```ts -import type { Actions } from './$types' -import { supabaseClient } from '$lib/db' -import { invalid, redirect } from '@sveltejs/kit' -import { saveSession } from '@supabase/auth-helpers-sveltekit/server' - -export const actions: Actions = { - async signin({ request, cookies, url }) { - const formData = await request.formData() - - const email = formData.get('email') as string - const password = formData.get('password') as string - - const { data, error } = await supabaseClient.auth.api.signInWithEmail(email, password, { - redirectTo: `${url.origin}/logging-in`, - }) - - if (error || !data) { - if (error?.status === 400) { - return invalid(400, { - error: 'Invalid credentials', - values: { - email, - }, - }) - } - return invalid(500, { - error: 'Server error. Try again later.', - values: { - email, - }, - }) - } - - saveSession(cookies, data) - throw redirect(303, '/dashboard') - }, -} -``` - -Use `deleteSession` to delete the session cookies: - -```ts -import type { Actions } from './$types' -import { deleteSession } from '@supabase/auth-helpers-sveltekit/server' -import { redirect } from '@sveltejs/kit' - -export const actions: Actions = { - async logout({ cookies }) { - deleteSession(cookies) - throw redirect(303, '/') - }, -} -``` - -## Custom session namespace - -If you want to use something other than `locals.session` and `$page.data.session`, update the types and create three helper functions: - -```ts title=src/app.d.ts -declare namespace App { - interface Locals { - mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession +export const GET: RequestHandler = async (event) => { + const { session, supabaseClient } = await getSupabase(event); + if (!session) { + throw redirect(303, '/'); } - interface PageData { - mySupabaseSession: import('@supabase/auth-helpers-sveltekit').SupabaseSession - } -} + const { data } = await supabaseClient.from('test').select('*'); + + return json({ data }); +}; ``` -```ts title=src/hooks.server.ts -setupSupabaseServer({ - supabaseClient, - cookieOptions: { - secure: !dev, - }, - // --- change location within locals --- - getSessionFromLocals: (locals) => locals.mySupabaseSession, - setSessionToLocals: (locals, session) => (locals.mySupabaseSession = session), -}) -``` + + -```ts title=src/lib/db.ts -setupSupabaseClient({ - supabaseClient, - // --- change location within pageData --- - getSessionFromPageData: (data) => data.mySupabaseSession, -}) -``` - -## Migrate from 0.6.11 and below to 0.7.0 {#migration} +## Migrate from 0.6.11 and below to 0.7.0 {#migration-0-7} There are numerous breaking changes in the latest 0.7.0 version of this library. @@ -456,7 +743,7 @@ There are numerous breaking changes in the latest 0.7.0 version of this library. The environment variable prefix is now `PUBLIC_` instead of `VITE_` (e.g., `VITE_SUPABASE_URL` is now `PUBLIC_SUPABASE_URL`). -### Set up the Supabase client {#migration-set-up-supabase-client} +### Set up the Supabase client {#migration-set-up-supabase-client-0-7} - + ```js title=src/lib/db.ts import { createClient } from '@supabase/supabase-js' @@ -505,7 +792,7 @@ setupSupabaseHelpers({ -### Initialize the client {#migration-initialize-client} +### Initialize the client {#migration-initialize-client-0-7} - + The `@supabase/auth-helpers-svelte` library is no longer required as the `@supabase/auth-helpers-sveltekit` library handles all the client-side code. @@ -553,7 +840,7 @@ The `@supabase/auth-helpers-svelte` library is no longer required as the `@supab -### Set up hooks {#migration-set-up-hooks} +### Set up hooks {#migration-set-up-hooks-0-7} { ``` - + ```ts title=src/hooks.server.ts // make sure the supabase instance is initialized on the server @@ -607,7 +894,7 @@ export const handle = sequence(auth(), yourHandler) -### Typings {#migration-typings} +### Typings {#migration-typings-0-7} - + ```ts title=src/app.d.ts /// @@ -688,7 +975,7 @@ declare namespace App { ``` - + ```html title=src/routes/+page.svelte @@ -178,7 +174,7 @@ You can now determine if a user is authenticated on the client-side by checking ## Client-side data fetching with RLS -For [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `$lib/db` and only run your query once the session is defined client-side in `$page.data`: +For [row level security](/docs/learn/auth-deep-dive/auth-row-level-security) to work properly when fetching data client-side, you need to make sure to import the `{ supabaseClient }` from `$lib/db` and only run your query once the session is defined client-side in `$page.data`: ```html