From fb0b6b134204d9a357bc301eed1b9578398b2313 Mon Sep 17 00:00:00 2001 From: thorwebdev Date: Wed, 2 Nov 2022 13:58:12 +0800 Subject: [PATCH 01/20] chore: fix remix auth helper docs --- .../docs/guides/auth/auth-helpers/nextjs.mdx | 1 + .../docs/guides/auth/auth-helpers/remix.mdx | 1236 +++++++++-------- 2 files changed, 620 insertions(+), 617 deletions(-) diff --git a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx index 4e8ffdd87dc..b509792f914 100644 --- a/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx +++ b/apps/reference/docs/guides/auth/auth-helpers/nextjs.mdx @@ -70,6 +70,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY ## Basic Setup +```jsx title="app/root.jsx" +export const loader = async ({ request }) => { + // Environment variables may be stored somewhere other than `process.env` in runtimes other than node. + // We need to pipe these Supabase environment variables to the browser. + const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env + + // We can retrieve the session on the server and hand it to the client. + // This is used to make sure the session is available immediately upon rendering. + const response = new Response() + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_ANON_KEY!, + { request, response } + ) + const { + data: { session: initialSession }, + } = await supabase.auth.getSession() + + // In order for the set-cookie header to be set, headers must be returned as part of the loader response. + return json( + { + initialSession, + env: { + SUPABASE_URL, + SUPABASE_ANON_KEY, + }, + }, + { + headers: response.headers, + } + ) +} +``` + + + + +```tsx title="app/root.tsx" +export const loader: LoaderFunction = async ({ request }) => { + // Environment variables may be stored somewhere other than `process.env` in runtimes other than node. + // We need to pipe these Supabase environment variables to the browser. + const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env + + // We can retrieve the session on the server and hand it to the client. + // This is used to make sure the session is available immediately upon rendering. + const response = new Response() + const supabase = createServerClient( + process.env.SUPABASE_URL!, + process.env.SUPABASE_ANON_KEY!, + { request, response } + ) + const { + data: { session: initialSession }, + } = await supabase.auth.getSession() + + // In order for the set-cookie header to be set, headers must be returned as part of the loader response. + return json( + { + initialSession, + env: { + SUPABASE_URL, + SUPABASE_ANON_KEY, + }, + }, + { + headers: response.headers, + } + ) +} +``` + + + + +Next, upon the initial client-side rendering of your application, set up a browser client and listen to session changes and expose both throughout your routes via the outlet context: + + + + +```jsx title="app/root.jsx" +import { useEffect, useState } from 'react' +import { json } from '@remix-run/node' +import { Outlet, useLoaderData } from '@remix-run/react' +import { + createServerClient, + createBrowserClient, +} from '@supabase/auth-helpers-remix' + +export const loader: LoaderFunction = async ({ request }) => { + // ... see loader function above. +} + +export default function App() { + const { env, initialSession } = useLoaderData() + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(initialSession) + + const context = { supabase, session } + + useEffect(() => { + if (!supabase) { + const supabase = createBrowserClient( + env.SUPABASE_URL, + env.SUPABASE_ANON_KEY + ) + setSupabase(supabase) + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_, session) => setSession(session)) + return () => { + subscription.unsubscribe() + } + } + }, []) + + return ( + + + + + + ) +} +``` + + + + +```tsx title="app/root.tsx" +import { useEffect, useState } from 'react' +import { json, LoaderFunction } from '@remix-run/node' +import { Outlet, useLoaderData } from '@remix-run/react' +import { + createServerClient, + createBrowserClient, + supabase, + Session, +} from '@supabase/auth-helpers-remix' +import { Database } from '../db_types' + +export type ContextType = { + supabase: supabase | null + session: Session | null +} + +type LoaderData = { + env: { SUPABASE_URL: string; SUPABASE_ANON_KEY: string } + initialSession: Session | null +} + +export const loader: LoaderFunction = async ({ request }) => { + // ... see loader function above. +} + +export default function App() { + const { env, initialSession } = useLoaderData() + const [supabase, setSupabase] = useState(null) + const [session, setSession] = useState(initialSession) + + const context: ContextType = { supabase, session } + + useEffect(() => { + if (!supabase) { + const supabase = createBrowserClient( + env.SUPABASE_URL, + env.SUPABASE_ANON_KEY + ) + setSupabase(supabase) + const { + data: { subscription }, + } = supabase.auth.onAuthStateChange((_, session) => setSession(session)) + return () => { + subscription.unsubscribe() + } + } + }, []) + + return ( + + + + + + ) +} +``` + + + + +Now the supabase client and session are available throughout the application via the `useOutletContext`: + + + + +```jsx title="app/routes/index.jsx" +import { useOutletContext } from '@remix-run/react' + +export default function Index() { + const { supabase, session } = useOutletContext() + + return
{JSON.stringify({ session }, null, 2)}
+} +``` + +
+ + +```tsx title="app/routes/index.tsx" +import { useOutletContext } from '@remix-run/react' +import type { ContextType } from '../root' + +export default function Index() { + const { supabase, session } = useOutletContext() + + return
{JSON.stringify({ session }, null, 2)}
+} +``` + +
+
+ +You can find a full working example in the [source code on GitHub](https://github.com/supabase/auth-helpers/tree/main/examples/remix). + +## Usage with TypeScript + +You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the `createServerClient` or `createBrowserClient` functions to get enhanced type safety and auto completion: + +### Server-side + +```tsx +import { createServerClient } from '@supabase/auth-helpers-remix' +import { Database } from '../../db_types' + +export const loader = async ({ request }) => { + const response = new Response() + + const supabase = createServerClient( + process.env.SUPABASE_URL, + process.env.SUPABASE_ANON_KEY, + { request, response } + ) +} +``` + +### Client-side + +```tsx +import { createBrowserClient } from '@supabase/auth-helpers-remix' +import { Database } from '../../db_types' + +const supabase = createBrowserClient(SUPABASE_URL, SUPABASE_ANON_KEY) +``` + +## Loader + Loader functions run on the server immediately before the component is rendered. They respond to all GET requests on a route. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. + + + ```jsx import { json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' @@ -74,13 +354,13 @@ export const loader = async ({ request }) => { // an empty response is required for the auth helpers // to set cookies to manage auth - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') // in order for the set-cookie header to be set, // headers must be returned as part of the loader response @@ -93,30 +373,22 @@ export const loader = async ({ request }) => { } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. - -Loader functions run on the server immediately before the component is rendered. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - -```jsx +```tsx import { LoaderFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' -export const loader: LoaderFunction = async ({ - request, -}: { - request: Request, -}) => { +export const loader: LoaderFunction = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -127,14 +399,17 @@ export const loader: LoaderFunction = async ({ } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. - +> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Loader` function. + ## Action +Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. + -Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - ```jsx import { json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' @@ -151,13 +424,13 @@ import { createServerClient } from '@supabase/auth-helpers-remix' export const action = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -168,31 +441,23 @@ export const action = async ({ request }) => { } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. - -Action functions run on the server and respond to HTTP requests to a route, other than GET - POST, PUT, PATCH, DELETE etc. You can create an authenticated Supabase client by calling the `createServerClient` function and passing it your `SUPABASE_URL`, `SUPABASE_ANON_KEY`, and a `Request` and `Response`. - -```jsx +```tsx import { ActionFunction, json } from '@remix-run/node' // change this import to whatever runtime you are using import { createServerClient } from '@supabase/auth-helpers-remix' -export const action: ActionFunction = async ({ - request, -}: { - request: Request, -}) => { +export const action: ActionFunction = async ({ request }) => { const response = new Response() - const supabaseClient = createServerClient( + const supabase = createServerClient( process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY, { request, response } ) - const { data } = await supabaseClient.from('test').select('*') + const { data } = await supabase.from('test').select('*') return json( { data }, @@ -203,11 +468,11 @@ export const action: ActionFunction = async ({ } ``` -> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. - +> Supabase will set cookie headers to manage the user's auth session, therefore, the `response.headers` must be returned from the `Action` function. + ## Session and User You can determine if a user is authenticated by checking their session using the `getSession` function. @@ -215,7 +480,7 @@ You can determine if a user is authenticated by checking their session using the ```jsx const { data: { session }, -} = await supabaseClient.auth.getSession() +} = await supabase.auth.getSession() ``` The session contains a user property. @@ -224,681 +489,418 @@ The session contains a user property. const user = session?.user ``` -> This is the recommended way for accessing the logged in user. There is also a `getUser()` function but this does not refresh the session if it has expired. +## Authentication UI -## Client-side +### Pre-built Auth UI + +We recommend using our [pre-built Auth UI](/docs/guides/auth/auth-helpers/auth-ui) for authenticating users. It supports custom themes and extensible styles to match your brand and aesthetic. + +### Custom Auth UI + +Should you prefer to build your own Auth UI, you can use our [JavaScript Library](/docs/reference/javascript/) to interact with Supabase Auth from the client-side. + ]}> -In order to use the Supabase client in the browser - fetching data in `useEffect` or subscribing to realtime events - we need to do a little more plumbing. Remix does not include a way to make environment variables available to the browser, so we need to pipe them through from a `loader` function in our `root.jsx` route and attach them to the `window`. +```jsx title="app/routes/signin.jsx" +import { useOutletContext } from '@remix-run/react' -```jsx title="app/root.jsx" -export const loader = () => { - const { SUPABASE_URL, SUPABASE_ANON_KEY } = process.env - return json({ - env: { - SUPABASE_URL, - SUPABASE_ANON_KEY, - }, - }) -} -``` +export default function Auth() { + const { supabase, session } = useOutletContext() -> These may not be stored in `process.env` for environments other than Node. + const handleSubmit = async (event) => { + event.preventDefault() + if (!supabase) return + const formData = new FormData(event.currentTarget) + const email = formData.get('loginEmail') + const password = formData.get('loginPassword') + // Sign in + const { error } = await supabase.auth.signInWithPassword({ + email, + password, + }) + if (error) alert(error.message) + } -Next, we call the `useLoaderData` hook in our component to get the `env` object. - -```jsx title="app/root.jsx" -const { env } = useLoaderData() -``` - -And then, add a ` -
+ From 658d169e3b12df2a3b18a2f64f24826b1594d619 Mon Sep 17 00:00:00 2001 From: dng Date: Wed, 2 Nov 2022 10:36:02 -0700 Subject: [PATCH 17/20] Update apps/reference/docs/guides/with-sveltekit.mdx --- apps/reference/docs/guides/with-sveltekit.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/reference/docs/guides/with-sveltekit.mdx b/apps/reference/docs/guides/with-sveltekit.mdx index cd5d8e4fcf6..622c7671331 100644 --- a/apps/reference/docs/guides/with-sveltekit.mdx +++ b/apps/reference/docs/guides/with-sveltekit.mdx @@ -442,7 +442,7 @@ And then we can add the widget to the Account component: // Import the new component import Avatar from './Avatar.svelte' - // ..rest of script... + // rest of script From d0589552c92a78cae38b202234bc764a85e43125 Mon Sep 17 00:00:00 2001 From: Copple <10214025+kiwicopple@users.noreply.github.com> Date: Wed, 2 Nov 2022 18:39:27 +0100 Subject: [PATCH 18/20] updates spelling (#10022) --- apps/reference/docs/guides/realtime/quickstart.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/reference/docs/guides/realtime/quickstart.mdx b/apps/reference/docs/guides/realtime/quickstart.mdx index 47a77610d94..babacf9f45e 100644 --- a/apps/reference/docs/guides/realtime/quickstart.mdx +++ b/apps/reference/docs/guides/realtime/quickstart.mdx @@ -28,7 +28,7 @@ npm install @supabase/supabase-js In [multiplayer.dev](https://multiplayer.dev), client's cursor positions are sent to other clients in the room. However, cursor positions will be randomly generated for this example. -You need to get the public `anon` access token from your project's [API settings](https://app.supabase.com/project/_/settings/api). Then you can set up the Suapbase client and start sending a client's cursor positions to other clients in channel `room1`: +You need to get the public `anon` access token from your project's [API settings](https://app.supabase.com/project/_/settings/api). Then you can set up the Supabase client and start sending a client's cursor positions to other clients in channel `room1`: ```js const { createClient } = require('@supabase/supabase-js') From 1fee7dae3d9a32c8eede71c59e20e8b3a8103a5f Mon Sep 17 00:00:00 2001 From: dng Date: Wed, 2 Nov 2022 11:52:21 -0700 Subject: [PATCH 19/20] Update spec/supabase_dart_v1.yml --- spec/supabase_dart_v1.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/supabase_dart_v1.yml b/spec/supabase_dart_v1.yml index ebc9cf7918b..382c76bae08 100644 --- a/spec/supabase_dart_v1.yml +++ b/spec/supabase_dart_v1.yml @@ -1100,7 +1100,7 @@ pages: ``` Using Modifiers: description: | - Filters work on the row level—they allows you to return rows that + Filters work on the row level—they allow you to return rows that only match certain conditions without changing the shape of the rows. Modifiers are everything that don't fit that definition—allowing you to change the format of the response (e.g., returning a CSV string). From 5ee89f678602c03abfb47d87433cc35ca7b4b20c Mon Sep 17 00:00:00 2001 From: TzeYiing Date: Tue, 1 Nov 2022 23:02:39 +0800 Subject: [PATCH 20/20] fix: Logs pages should use logs layout --- studio/pages/project/[ref]/logs/pgbouncer-logs.tsx | 4 ++-- studio/pages/project/[ref]/logs/postgrest-logs.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx b/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx index 0a4ddf0b520..6d257379b45 100644 --- a/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx +++ b/studio/pages/project/[ref]/logs/pgbouncer-logs.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { DatabaseLayout } from 'components/layouts' +import { LogsLayout } from 'components/layouts' import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import { NextPageWithLayout } from 'types' import { LogsTableName } from 'components/interfaces/Settings/Logs' @@ -20,6 +20,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default observer(LogPage) diff --git a/studio/pages/project/[ref]/logs/postgrest-logs.tsx b/studio/pages/project/[ref]/logs/postgrest-logs.tsx index 78efcdc97f6..cd31e9f66c9 100644 --- a/studio/pages/project/[ref]/logs/postgrest-logs.tsx +++ b/studio/pages/project/[ref]/logs/postgrest-logs.tsx @@ -1,7 +1,7 @@ import React from 'react' import { useRouter } from 'next/router' import { observer } from 'mobx-react-lite' -import { DatabaseLayout } from 'components/layouts' +import { LogsLayout } from 'components/layouts' import LogsPreviewer from 'components/interfaces/Settings/Logs/LogsPreviewer' import { NextPageWithLayout } from 'types' import { LogsTableName } from 'components/interfaces/Settings/Logs' @@ -20,6 +20,6 @@ export const LogPage: NextPageWithLayout = () => { ) } -LogPage.getLayout = (page) => {page} +LogPage.getLayout = (page) => {page} export default observer(LogPage)