mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
109 files changed
+3167
-2216
No files matched your search
@@ -1,15 +1,9 @@
|
||||
# This is a basic workflow to help you get started with Actions
|
||||
name: Tests
|
||||
|
||||
# Controls when the workflow will run
|
||||
on:
|
||||
# Triggers the workflow on push or pull request events but only for the main branch
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
schedule:
|
||||
- cron: '0 4/6 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
@@ -41,17 +35,23 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run infrastructure
|
||||
run: |
|
||||
cp ../docker/.env.example ../docker/.env
|
||||
npm run docker:up
|
||||
- uses: supabase/setup-cli@v1
|
||||
- run: supabase start
|
||||
|
||||
- name: Run Test
|
||||
run: npm run test
|
||||
run: npm run test:local
|
||||
env:
|
||||
SUPABASE_DB_PORT: 54322
|
||||
SUPABASE_DB_PASS: postgres
|
||||
SUPABASE_DB_HOST: localhost
|
||||
SUPABASE_GOTRUE: http://localhost:54321
|
||||
SUPABASE_URL: http://localhost:54321
|
||||
SUPABASE_KEY_ANON: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs
|
||||
SUPABASE_KEY_ADMIN: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU
|
||||
|
||||
- name: Stop infrastructure
|
||||
if: always()
|
||||
run: npm run docker:down
|
||||
run: supabase stop
|
||||
|
||||
- name: Get Allure history
|
||||
uses: actions/checkout@v2
|
||||
@@ -73,7 +73,7 @@ jobs:
|
||||
keep_reports: 50
|
||||
|
||||
- name: Deploy report to Github Pages
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: peaceiris/actions-gh-pages@v2
|
||||
env:
|
||||
EXTERNAL_REPOSITORY: supabase/test-reports
|
||||
@@ -82,7 +82,7 @@ jobs:
|
||||
PUBLISH_DIR: allure-history
|
||||
|
||||
- name: Post the link to the report
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
if: always()
|
||||
uses: Sibz/github-status-action@v1
|
||||
with:
|
||||
authToken: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+443
-93
File diff suppressed because it is too large.
Load diff
@@ -1,17 +0,0 @@
|
||||
---
|
||||
slug: /
|
||||
sidebar_label: Auth Helpers
|
||||
---
|
||||
|
||||
# Auth Helpers
|
||||
|
||||
A collection of framework specific Auth utilities for working with Supabase.
|
||||
|
||||
## Status
|
||||
|
||||
The Auth Helpers are in `beta`. They are usable in their current state, but it's likely that there will be breaking changes.
|
||||
|
||||
## Additional Links
|
||||
|
||||
- [Source code](https://github.com/supabase/auth-helpers)
|
||||
- [Known bugs and issues](https://github.com/supabase/auth-helpers/issues)
|
||||
@@ -1,321 +0,0 @@
|
||||
---
|
||||
id: next-js
|
||||
slug: next-js
|
||||
sidebar_label: With Next.js
|
||||
---
|
||||
|
||||
# Supabase Auth with Next.js
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
|
||||
|
||||
## Installation
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
|
||||
- Next.js: `>=10`
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Configuration
|
||||
|
||||
Set up the following env vars. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
|
||||
- Create an `auth` directory under the `/pages/api/` directory.
|
||||
|
||||
- Create a `[...supabase].js` file under the newly created `auth` directory.
|
||||
|
||||
The path to your dynamic API route file would be `/pages/api/auth/[...supabase].js`. Populate that file as follows:
|
||||
|
||||
```js
|
||||
import { handleAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default handleAuth({ logout: { returnTo: '/' } })
|
||||
```
|
||||
|
||||
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
|
||||
|
||||
- `/api/auth/callback`: The `UserProvider` 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.
|
||||
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
|
||||
- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`
|
||||
|
||||
Wrap your `pages/_app.js` component with the `UserProvider` component:
|
||||
|
||||
```jsx
|
||||
// pages/_app.js
|
||||
import React from 'react'
|
||||
import { UserProvider } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<UserProvider supabaseClient={supabaseClient}>
|
||||
<Component {...pageProps} />
|
||||
</UserProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
|
||||
|
||||
## 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 `# @supabase/auth-helpers-nextjs` and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
|
||||
```js
|
||||
import { Auth } from '@supabase/ui'
|
||||
import { useUser } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LoginPage = () => {
|
||||
const { user, error } = useUser()
|
||||
const [data, setData] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*')
|
||||
setData(data)
|
||||
}
|
||||
// Only run query once user is logged in.
|
||||
if (user) loadData()
|
||||
}, [user])
|
||||
|
||||
if (!user)
|
||||
return (
|
||||
<>
|
||||
{error && <p>{error.message}</p>}
|
||||
<Auth
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => supabaseClient.auth.signOut()}>Sign out</button>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginPage
|
||||
```
|
||||
|
||||
### Server-side rendering (SSR) - withPageAuth
|
||||
|
||||
If you wrap your `getServerSideProps` with `withPageAuth` your props object will be augmented with the user object.
|
||||
|
||||
```js
|
||||
// pages/profile.js
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }) {
|
||||
return <div>Hello {user.name}</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({ redirectTo: '/login' })
|
||||
```
|
||||
|
||||
If there is no authenticated user, they will be redirect to your home page, unless you specify the `redirectTo` option.
|
||||
|
||||
You can pass in your own `getServerSideProps` method, the props returned from this will be merged with the
|
||||
user props. You can also access the user session data by calling `getUser` inside of this method, eg:
|
||||
|
||||
```js
|
||||
// pages/protected-page.js
|
||||
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, customProp }) {
|
||||
return <div>Protected content</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/foo',
|
||||
async getServerSideProps(ctx) {
|
||||
// Access the user object
|
||||
const { user, accessToken } = await getUser(ctx)
|
||||
return { props: { email: user?.email } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
|
||||
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 inject the request context into the supabase client:
|
||||
|
||||
```js
|
||||
import {
|
||||
User,
|
||||
withPageAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
data,
|
||||
}: {
|
||||
user: User,
|
||||
data: any,
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(ctx).from('test').select('*')
|
||||
return { props: { data } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Server-side data fetching to OAuth APIs using `provider_token`
|
||||
|
||||
When using third-party auth providers, sessions are initiated with an additional `provider_token` field which is persisted as an HTTPOnly cookie upon logging in to enabled usage on the server side. The `provider_token` can be used to make API requests to the OAuth provider's API endpoints on behalf of the logged-in user. In the following example, we fetch the user's full profile from the third-party API during SSR using their id and auth token:
|
||||
|
||||
```js
|
||||
import { User, withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
interface Profile {
|
||||
/* ... */
|
||||
}
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
data,
|
||||
}: {
|
||||
user: User,
|
||||
profile: Profile,
|
||||
}) {
|
||||
return <div>Protected content</div>
|
||||
}
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
// Retrieve provider_token from cookies
|
||||
const provider_token = ctx.req.cookies['sb-provider-token']
|
||||
// Get logged in user's third-party id from metadata
|
||||
const { user } = await getUser(ctx)
|
||||
const userId = user?.user_metadata.provider_id
|
||||
const profile: Profile = await (
|
||||
await fetch(`https://api.example.com/users/${userId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Authorization: `Bearer ${provider_token}`,
|
||||
},
|
||||
})
|
||||
).json()
|
||||
return { props: { profile } }
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
401 Unauthorized.
|
||||
|
||||
```js
|
||||
// pages/api/protected-route.js
|
||||
import {
|
||||
withApiAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default withApiAuth(async function ProtectedRoute(req, res) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient({ req, res })
|
||||
.from('test')
|
||||
.select('*')
|
||||
res.json(data)
|
||||
})
|
||||
```
|
||||
|
||||
If you visit `/api/protected-route` without a valid session cookie, you will get a 401 response.
|
||||
|
||||
## Protecting routes with [Nextjs Middleware](https://nextjs.org/docs/middleware)
|
||||
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `middleware` file to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
|
||||
```ts title="/middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({ redirectTo: '/' })
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to add finer granularity based on the user logged in. I.e. you can specify a promise to determine if a specific user has permission or not.
|
||||
|
||||
```ts title="/middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware'
|
||||
|
||||
export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/login',
|
||||
authGuard: {
|
||||
isPermitted: async (user) => user.email?.endsWith('@example.com') ?? false,
|
||||
redirectTo: '/insufficient-permissions',
|
||||
},
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
|
||||
|
||||
This is a step by step guide on migrating away from the `@supabase/supabase-auth-helpers` to the newly released `@supabase/auth-helpers`.
|
||||
|
||||
1. Install `@supabase/supabase-js`, `@supabase/auth-helpers-nextjs` and `@supabase/auth-helpers-react` libraries from npm.
|
||||
2. Replace all imports of `@supabase/supabase-auth-helpers/nextjs` in your project with `@supabase/auth-helpers-nextjs`.
|
||||
3. Replace all imports of `@supabase/supabase-auth-helpers/react` in your project with `@supabase/auth-helpers-react`.
|
||||
4. Replace all instances of `withAuthRequired` in any of your NextJS pages with `withPageAuth`.
|
||||
5. Replace all instances of `withAuthRequired` in any of your NextJS API endpoints with `withApiAuth`.
|
||||
6. Uninstall `@supabase/supabase-auth-helpers`.
|
||||
@@ -1,303 +0,0 @@
|
||||
---
|
||||
id: sveltekit
|
||||
slug: sveltekit
|
||||
sidebar_label: With SvelteKit
|
||||
---
|
||||
|
||||
# Supabase Auth with SvelteKit
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in [SvelteKit](https://kit.svelte.dev/) applications.
|
||||
|
||||
## Installation
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-sveltekit
|
||||
|
||||
# Main component for Svelte based frameworks (optional but recommended)
|
||||
npm install @supabase/auth-helpers-svelte
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-sveltekit
|
||||
|
||||
# Main component for Svelte based frameworks (optional but recommended)
|
||||
yarn add @supabase/auth-helpers-svelte
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^16.15.0`
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Configuration
|
||||
|
||||
Set up the fillowing env vars. For local development you can set them in a `.env` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/sveltekit/.env.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
VITE_SUPABASE_URL=https://your-project.supabase.co
|
||||
VITE_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### SupabaseClient and SupaAuthHelper component setup
|
||||
|
||||
We will start off by creating a `db.ts` file inside of our `src/lib` directory. Now lets instantiate our `supabaseClient` by using our `createSupabaseClient` function from the `@supabase/auth-helpers-sveltekit` library.
|
||||
|
||||
```ts
|
||||
// src/lib/db.ts
|
||||
import { createSupabaseClient } from '@supabase/auth-helpers-sveltekit'
|
||||
|
||||
const { supabaseClient } = createSupabaseClient(
|
||||
import.meta.env.VITE_SUPABASE_URL as string,
|
||||
import.meta.env.VITE_SUPABASE_ANON_KEY as string
|
||||
)
|
||||
|
||||
export { supabaseClient }
|
||||
```
|
||||
|
||||
Edit your `__layout.svelte` file and add import the `SupaAuthHelper` component, the `supabaseClient` we just instantiated and the `session` store.
|
||||
|
||||
```html
|
||||
// src/routes/__layout.svelte
|
||||
<script>
|
||||
import { session } from '$app/stores'
|
||||
import { supabaseClient } from '$lib/db'
|
||||
import { SupaAuthHelper } from '@supabase/auth-helpers-svelte'
|
||||
</script>
|
||||
|
||||
<SupaAuthHelper {supabaseClient} {session}>
|
||||
<slot />
|
||||
</SupaAuthHelper>
|
||||
```
|
||||
|
||||
### Hooks setup
|
||||
|
||||
Our `hooks.ts` file is where the heavy lifting of this library happens, we need to import our function to handle the sign in, signing out and cookie creation phase. we can import all the hooks using `handleAuth` function and destructure its returned data.
|
||||
|
||||
```ts
|
||||
// src/hooks.ts
|
||||
import { handleAuth } from '@supabase/auth-helpers-sveltekit'
|
||||
import type { GetSession, Handle } from '@sveltejs/kit'
|
||||
import { sequence } from '@sveltejs/kit/hooks'
|
||||
|
||||
export const handle: Handle = sequence(...handleAuth())
|
||||
|
||||
export const getSession: GetSession = async (event) => {
|
||||
const { user, accessToken, error } = event.locals
|
||||
return {
|
||||
user,
|
||||
accessToken,
|
||||
error,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
These will create the handlers under the hood that perform different parts of the authentication flow:
|
||||
|
||||
- `/api/auth/callback`: The `UserHelper` 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.
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
- `/api/auth/logout`: You can logout the user.
|
||||
|
||||
### Typings
|
||||
|
||||
In order to get the most out of TypeScript and its 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
|
||||
/// <reference types="@sveltejs/kit" />
|
||||
// See https://kit.svelte.dev/docs/types#app
|
||||
// for information about these interfaces
|
||||
declare namespace App {
|
||||
interface UserSession {
|
||||
user: import('@supabase/supabase-js').User
|
||||
accessToken?: string
|
||||
}
|
||||
interface Locals extends UserSession {
|
||||
error: import('@supabase/supabase-js').ApiError
|
||||
}
|
||||
|
||||
interface Session extends UserSession {} // interface Platform {} // interface Stuff {}
|
||||
}
|
||||
```
|
||||
|
||||
### Signing out
|
||||
|
||||
This library has provided a dedicated endpoint for you to use to sign a user out. This endpoint will sign the user out of the Gotrue server, clear the cookies that were set when the user logged in and redirect the user to a configurable path.
|
||||
|
||||
The logout handler endpoint is `/api/auth/logout`, this will take a `GET` request which means it can be used as the href for a normal `a` tag in your html.
|
||||
|
||||
```html
|
||||
<a href="/api/auth/logout">Sign out</a>
|
||||
```
|
||||
|
||||
### Logout handler configuration
|
||||
|
||||
In your `src/hooks.ts` file the logout handler is already setup and you can configure the redirect path from here.
|
||||
|
||||
> By default the redirect path after logging out will be `/`.
|
||||
|
||||
```ts
|
||||
export const handle = sequence(
|
||||
...handleAuth({
|
||||
logout: { returnTo: '/auth/signin' },
|
||||
})
|
||||
)
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
|
||||
You can now determine if a user is authenticated on the client-side by checking that the `user` object returned by the `$session` store is defined.
|
||||
|
||||
```html
|
||||
// example
|
||||
<script>
|
||||
import { session } from '$app/stores'
|
||||
</script>
|
||||
|
||||
{#if !$session.user}
|
||||
<h1>I am not logged in</h1>
|
||||
{:else}
|
||||
<h1>Welcome {$session.user.email}</h1>
|
||||
<p>I am logged in!</p>
|
||||
{/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 `@supabase/auth-helpers-sveltekit` and only run your query once the user is defined client-side in the `$session`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
import Auth from 'supabase-ui-svelte';
|
||||
import { error, isLoading } from '@supabase/auth-helpers-svelte';
|
||||
import { supabaseClient } from '$lib/db';
|
||||
import { session } from '$app/stores';
|
||||
|
||||
let loadedData = [];
|
||||
async function loadData() {
|
||||
const { data } = await supabaseClient.from('test').select('*').single();
|
||||
loadedData = data
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($session.user && $session.user.id) {
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if !$session.user}
|
||||
{#if $error}
|
||||
<p>{$error.message}</p>
|
||||
{/if}
|
||||
<h1>{$isLoading ? `Loading...` : `Loaded!`}</h1>
|
||||
<Auth
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
/>
|
||||
{:else}
|
||||
<a href=="/api/auth/logout">Sign out</a>
|
||||
<p>user:</p>
|
||||
<pre>{JSON.stringify($session.user, null, 2)}</pre>
|
||||
<p>client-side data fetching with RLS</p>
|
||||
<pre>{JSON.stringify(loadedData, null, 2)}</pre>
|
||||
{/if}
|
||||
```
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
|
||||
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 inject the request context into the supabase client:
|
||||
|
||||
```html
|
||||
<!-- src/routes/profile.svelte -->
|
||||
<script>
|
||||
export let user
|
||||
export let data
|
||||
</script>
|
||||
|
||||
<div>Protected content for {user.email}</div>
|
||||
<pre>{JSON.stringify(data, null, 2)}</pre>
|
||||
<pre>{JSON.stringify(user, null, 2)}</pre>
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/routes/profile.ts
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withApiAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { RequestHandler } from './__types/profile'
|
||||
|
||||
interface TestTable {
|
||||
id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface GetOutput {
|
||||
user: User
|
||||
data: TestTable[]
|
||||
}
|
||||
|
||||
export const GET: RequestHandler<GetOutput> = async ({ locals }) =>
|
||||
withApiAuth(
|
||||
{
|
||||
redirectTo: '/',
|
||||
user: locals.user,
|
||||
},
|
||||
async () => {
|
||||
const { data } = await supabaseServerClient(session.accessToken)
|
||||
.from<TestTable>('test')
|
||||
.select('*')
|
||||
|
||||
return {
|
||||
body: {
|
||||
user: locals.user,
|
||||
data,
|
||||
},
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
## Protecting API routes
|
||||
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
303 and redirect header.
|
||||
|
||||
```ts
|
||||
// src/routes/api/protected-route.ts
|
||||
import {
|
||||
supabaseServerClient,
|
||||
withApiAuth,
|
||||
} from '@supabase/auth-helpers-sveltekit'
|
||||
import type { RequestHandler } from './__types/protected-route'
|
||||
|
||||
interface TestTable {
|
||||
id: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
interface GetOutput {
|
||||
data: TestTable[]
|
||||
}
|
||||
|
||||
export const GET: RequestHandler<GetOutput> = async ({ locals, request }) =>
|
||||
withApiAuth({ user: locals.user }, async () => {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(request)
|
||||
.from('test')
|
||||
.select('*')
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
body: { data },
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
If you visit `/api/protected-route` without a valid session cookie, you will get a 303 response.
|
||||
@@ -11,21 +11,25 @@ A `config.toml` file is generated after running `supabase init`.
|
||||
|
||||
This file is located in the `supabase` folder under `supabase/config.toml`.
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY -->
|
||||
|
||||
## General {#general}
|
||||
|
||||
|
||||
|
||||
### `project_id` {#project_id}
|
||||
|
||||
A string used to distinguish different Supabase projects on the same host. Defaults to the working directory name when running `supabase init`.
|
||||
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Required: <code>true</code>
|
||||
</li>
|
||||
<li>
|
||||
Default: <code>None</code>
|
||||
</li>
|
||||
<li>Required: <code>true</code></li>
|
||||
<li>Default: <code>None</code></li>
|
||||
</ul>
|
||||
|
||||
<br />
|
||||
|
||||
|
||||
@@ -5,11 +5,14 @@ title: Usage
|
||||
toc_max_heading_level: 3
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
Documentation of the gotrue API.
|
||||
|
||||
<!-- AUTOGENERATED: DO NOT EDIT DIRECTLY IF THIS IS VERSION "next" -->
|
||||
|
||||
|
||||
@@ -7,14 +7,6 @@ hide_table_of_contents: true
|
||||
|
||||
# Supabase JavaScript Library
|
||||
|
||||
:::note
|
||||
|
||||
You're viewing the Supabase docs for the next version of our library which is not yet released.
|
||||
|
||||
Refer to the `v1` docs for a stable release.
|
||||
|
||||
:::
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
@@ -28,6 +20,6 @@ You can use the `supabase-js` library to:
|
||||
## Additional Links
|
||||
|
||||
- Source Code: [github.com/supabase/supabase-js](https://github.com/supabase/supabase-js)
|
||||
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js)
|
||||
- TypeDoc: [supabase.github.io/supabase-js](https://supabase.github.io/supabase-js/v2/)
|
||||
- NPM: [npmjs.com/package/@supabase/supabase-js](https://www.npmjs.com/package/@supabase/supabase-js)
|
||||
- [Known bugs and issues](https://github.com/supabase/supabase-js/issues)
|
||||
@@ -6,9 +6,9 @@ id: release-notes
|
||||
|
||||
Supabase.js v2 release notes.
|
||||
|
||||
## 2.0.0 Release Candidate
|
||||
## 2.0.0
|
||||
|
||||
Install the latest with `npm install @supabase/supabase-js@rc`.
|
||||
Install the latest with `npm install @supabase/supabase-js`.
|
||||
|
||||
### Explicit constructor options
|
||||
|
||||
|
||||
@@ -5,6 +5,10 @@ slug: /
|
||||
sidebar_label: Supabase JavaScript Library
|
||||
---
|
||||
|
||||
:::caution
|
||||
You're viewing the docs for an older version of the `supabase-js` library.
|
||||
:::
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript library, `supabase-js`.
|
||||
|
||||
You can use the `supabase-js` library to:
|
||||
|
||||
@@ -35,5 +35,5 @@ Most notably, we use Postgres rather than a NoSQL store. This choice was deliber
|
||||
| Auth | SvelteKit Auth Helpers | Alpha | [Link](/docs/guides/auth/auth-helpers/sveltekit) |
|
||||
| Public API | | Beta | [Link](/docs/reference/api) |
|
||||
| CLI | | Beta | [Link](/docs/guides/cli) |
|
||||
| Client Library: JavaScript | | GA | [Link](/docs/reference/javascript/next/) |
|
||||
| Client Library: JavaScript | | GA | [Link](/docs/reference/javascript) |
|
||||
| Client Library: Dart | | Beta | [Link](/docs/reference/dart) |
|
||||
@@ -177,7 +177,14 @@ Supabase provides a special function in Postgres, `auth.uid()`, which extracts t
|
||||
|
||||
## User Management
|
||||
|
||||
Supabase makes it simple to manage your users.
|
||||
Supabase provides multiple endpoints to authenticate and manage your users:
|
||||
- [Sign up](/docs/reference/javascript/auth-signup)
|
||||
- [Sign in with password](/docs/reference/javascript/auth-signinwithpassword)
|
||||
- [Sign in with passwordless / one-time password (OTP)](/docs/reference/javascript/auth-signinwithotp)
|
||||
- [Sign in with OAuth](/docs/reference/javascript/auth-signinwithoauth)
|
||||
- [Sign out](/docs/reference/javascript/auth-signout)
|
||||
|
||||
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
|
||||
|
||||
<video width="99%" muted playsInline controls="true">
|
||||
<source
|
||||
@@ -187,12 +194,3 @@ Supabase makes it simple to manage your users.
|
||||
playsInline
|
||||
/>
|
||||
</video>
|
||||
|
||||
When users sign up, Supabase assigns them a unique ID. You can reference this ID anywhere in your database. For example, you might create a `profiles` table referencing `id` in the `auth.users` table using a `user_id` field.
|
||||
|
||||
Supabase provides the routes to [sign up](/docs/reference/javascript/auth-signup), [log in](/docs/reference/javascript/auth-signin),
|
||||
[log out](/docs/reference/javascript/auth-signout), and manage users in your apps and websites.
|
||||
|
||||
## Next Steps
|
||||
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
@@ -20,16 +20,14 @@ Apple OAuth consists of six broad steps:
|
||||
- Add your `client id` and `client secret` keys to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Apple Developer account
|
||||
## Access your Apple Developer account
|
||||
|
||||
- Go to [developer.apple.com](https://developer.apple.com).
|
||||
- Click on `Account` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Obtain an App ID
|
||||
## Obtain an App ID
|
||||
|
||||
- Go to `Certificates, Identifiers & Profiles`.
|
||||
- Click on `Identifiers` at the left.
|
||||
@@ -43,7 +41,7 @@ Apple OAuth consists of six broad steps:
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Register` at the top right.
|
||||
|
||||
### Obtain a Services ID
|
||||
## Obtain a Services ID
|
||||
|
||||
This will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -58,7 +56,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Register` at the top right.
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -79,7 +77,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Configure your Services ID
|
||||
## Configure your Services ID
|
||||
|
||||
- Under `Identifiers`, click on your newly-created Services ID.
|
||||
- Check the box next to `Sign In With Apple` to enable it.
|
||||
@@ -91,7 +89,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click `Continue` at the top right.
|
||||
- Click `Save` at the top right.
|
||||
|
||||
### Download your secret key
|
||||
## Download your secret key
|
||||
|
||||
Now you'll need to download a `secret key` file from Apple that will be used to generate your `client_secret`.
|
||||
|
||||
@@ -109,7 +107,7 @@ Now you'll need to download a `secret key` file from Apple that will be used to
|
||||
- Save the downloaded file -- this contains your "secret key" that will be used to generate your `client_secret`.
|
||||
- Click `Done` at the top right.
|
||||
|
||||
### Generate a `client_secret`
|
||||
## Generate a `client_secret`
|
||||
|
||||
The `secret key` you downloaded is used to create the `client_secret` string you'll need to authenticate your users.
|
||||
|
||||
@@ -173,7 +171,7 @@ puts token
|
||||
2. From the command line, run: `ruby secret_gen.rb > client_secret.txt`.
|
||||
3. Your `client_secret` is now stored in this `client_secret.txt` file.
|
||||
|
||||
### Add your OAuth credentials to Supabase
|
||||
## Add your OAuth credentials to Supabase
|
||||
|
||||
- Go to your [Supabase Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -183,27 +181,19 @@ puts token
|
||||
- Enter your `client_id` and `client_secret` saved in the previous steps.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'apple',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `apple` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithApple() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'apple',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -18,14 +18,12 @@ Azure OAuth consists of four broad steps:
|
||||
- Create a `Secret ID` with “Sign In with Azure” capabilities. The value of the secret will be used as the `client secret`.
|
||||
- Add the callback url of your application to the allowlist.
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Azure Developer account
|
||||
## Access your Azure Developer account
|
||||
|
||||
- Go to [portal.azure.com](https://portal.azure.com/#home).
|
||||
- Login and select "Azure Active Directory" under the list of Azure Services.
|
||||
|
||||
### Register an application
|
||||
## Register an application
|
||||
|
||||
- Under Azure Active Directory, select "App registrations" in the side panel.
|
||||
- Select "New registration".
|
||||
@@ -36,7 +34,7 @@ Azure OAuth consists of four broad steps:
|
||||
|
||||

|
||||
|
||||
### Obtain a Client ID
|
||||
## Obtain a Client ID
|
||||
|
||||
This will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -44,7 +42,7 @@ This will serve as the `client_id` when you make API calls to authenticate the u
|
||||
|
||||

|
||||
|
||||
### Obtain a Secret ID
|
||||
## Obtain a Secret ID
|
||||
|
||||
This will serve as the `client_secret` when you make API calls to authenticate the user.
|
||||
|
||||
@@ -65,21 +63,13 @@ This will allow your users to use your custom Azure login page when logging in.
|
||||
|
||||

|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'azure',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `azure` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithAzure() {
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'azure',
|
||||
},
|
||||
@@ -90,7 +80,7 @@ async function signInWithAzure() {
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
@@ -100,5 +90,5 @@ async function signout() {
|
||||
|
||||
## Resources
|
||||
|
||||
- [Azure Developer Account](https://portal.azure.com).
|
||||
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573).
|
||||
- [Azure Developer Account](https://portal.azure.com)
|
||||
- [GitHub Discussion](https://github.com/supabase/gotrue/pull/54#issuecomment-757043573)
|
||||
@@ -17,16 +17,14 @@ Setting up Bitbucket logins for your application consists of 3 parts:
|
||||
- Add your Bitbucket OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Bitbucket account
|
||||
## Access your Bitbucket account
|
||||
|
||||
- Go to [bitbucket.org](https://bitbucket.org/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Bitbucket OAuth app
|
||||
## Create a Bitbucket OAuth app
|
||||
|
||||
- Click on your profile icon at the bottom left
|
||||
- Click on `All Workspaces`
|
||||
@@ -62,7 +60,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click on your app name (the name of your new OAuth Consumer)
|
||||
- Copy your `Key` (`client_key`) and `Secret` (`client_secret`) codes
|
||||
|
||||
### Add your Bitbucket credentials into your Supabase Project
|
||||
## Add your Bitbucket credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -72,27 +70,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'bitbucket',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `bitbucket` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithBitbucket() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'bitbucket',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
- Add your Discord OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Discord account
|
||||
## Access your Discord account
|
||||
|
||||
- Go to [discord.com](https://discord.com/).
|
||||
- Click on `Login` at the top right to log in.
|
||||
@@ -30,7 +28,7 @@ Setting up Discord logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
In the next step you require a callback URL, which looks like this:
|
||||
|
||||
@@ -51,7 +49,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Discord Application
|
||||
## Create a Discord Application
|
||||
|
||||
- Click on `New Application` at the top right.
|
||||
- Enter the name of your application and click `Create`.
|
||||
@@ -61,7 +59,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Click `Save Changes` at the bottom.
|
||||
- Copy your `Client ID` and `Client Secret` under `Client information`.
|
||||
|
||||
### Add your Discord credentials into your Supabase Project
|
||||
## Add your Discord credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -71,33 +69,21 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'discord',
|
||||
})
|
||||
```
|
||||
|
||||
:::note
|
||||
|
||||
If you call `signIn()` when already logged in, Discord will prompt the user again for authorization.
|
||||
|
||||
:::
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `discord` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithDiscord() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'discord',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
If your user is already signed in, Discord prompts the user again for authorization.
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -30,12 +30,7 @@ See the [self-hosting docs](/docs/guides/hosting/overview#configuration) for det
|
||||
|
||||
:::
|
||||
|
||||
### Add login code to your client app
|
||||
|
||||
Add logins using our client libraries:
|
||||
|
||||
- [JavaScript](/docs/reference/javascript/auth-signin#sign-in-with-email)
|
||||
- [Dart](/docs/reference/dart/auth-signin#sign-in-with-email)
|
||||
## Add login code to your client app
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
@@ -45,44 +40,11 @@ Add logins using our client libraries:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
```dart
|
||||
final res = await supabase.auth.signIn(
|
||||
email: 'example@email.com',
|
||||
password: 'example-password'
|
||||
);
|
||||
|
||||
final user = res.data?.user;
|
||||
final error = res.error;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
|
||||
>
|
||||
|
||||
<TabItem value="js">
|
||||
When your user signs in, call [signInWithPassword()](/docs/reference/javascript/auth-signinwithpassword) with their email address and password:
|
||||
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithPassword({
|
||||
email: 'example@email.com',
|
||||
password: 'example-password',
|
||||
})
|
||||
@@ -93,6 +55,8 @@ async function signInWithEmail() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs in, call [signIn()](/docs/reference/dart/auth-signin) with their email address and password:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
await supabase.auth.signIn(
|
||||
@@ -106,8 +70,6 @@ Future<void> signInWithEmail() async {
|
||||
|
||||
</Tabs>
|
||||
|
||||
To log out:
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
@@ -116,6 +78,8 @@ To log out:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signOut() {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
@@ -126,6 +90,8 @@ async function signOut() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```dart
|
||||
Future<void> signOut() async {
|
||||
await supabase.auth.signOut();
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Add your Facebook keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Facebook Developer account
|
||||
## Access your Facebook Developer account
|
||||
|
||||
- Go to [developers.facebook.com](https://developers.facebook.com).
|
||||
- Click on `Log In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a Facebook App
|
||||
## Create a Facebook App
|
||||
|
||||
- Click on `My Apps` at the top right.
|
||||
- Click `Create App` near the top right.
|
||||
@@ -34,7 +32,7 @@ Setting up Facebook logins for your application consists of 3 parts:
|
||||
- Fill in your app information, then click `Create App`.
|
||||
- This should bring you to the screen: `Add Products to Your App`. (Alternatively you can click on `Add Product` in the left sidebar to get to this screen.)
|
||||
|
||||
### Find your callback URI
|
||||
## Find your callback URI
|
||||
|
||||
The next step requires a callback URI, which looks like this:
|
||||
|
||||
@@ -55,7 +53,7 @@ The next step requires a callback URI, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Set up FaceBook Login for your Facebook App
|
||||
## Set up FaceBook Login for your Facebook App
|
||||
|
||||
From the `Add Products to your App` screen:
|
||||
|
||||
@@ -72,14 +70,14 @@ From the `App Review -> Permissions and Features` screen:
|
||||
|
||||
You can read more about access levels [here](https://developers.facebook.com/docs/graph-api/overview/access-levels/)
|
||||
|
||||
### Copy your Facebook App ID and Secret
|
||||
## Copy your Facebook App ID and Secret
|
||||
|
||||
- Click `Settings / Basic` in the left sidebar
|
||||
- Copy your App ID from the top of the `Basic Settings` page
|
||||
- Under `App Secret` click `Show` then copy your secret
|
||||
- Make sure all required fields are completed on this screen.
|
||||
|
||||
### Enter your Facebook App ID and Secret into your Supabase Project
|
||||
## Enter your Facebook App ID and Secret into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -89,27 +87,19 @@ You can read more about access levels [here](https://developers.facebook.com/doc
|
||||
- Enter your `Facebook client ID` and `Facebook secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'facebook',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `facebook` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithFacebook() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'facebook',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up GitHub logins for your application consists of 3 parts:
|
||||
- Add your GitHub OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your GitHub account
|
||||
## Access your GitHub account
|
||||
|
||||
- Go to [github.com](https://github.com).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a GitHub Oauth App
|
||||
## Create a GitHub Oauth App
|
||||
|
||||
Go to the [GitHub Developer Settings](https://github.com/settings/developers) page:
|
||||
|
||||
@@ -35,7 +33,7 @@ Go to the [GitHub Developer Settings](https://github.com/settings/developers) pa
|
||||
- In the left sidebar, click `Developer settings` (near the bottom)
|
||||
- In the left sidebar, click `OAuth Apps`
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -56,7 +54,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Register a new OAuth application
|
||||
## Register a new OAuth application
|
||||
|
||||
- Click `Register a new application`. If you've created an app before, click `New OAuth App` here.
|
||||
- In `Application name`, type the name of your app.
|
||||
@@ -72,7 +70,7 @@ Copy your new OAuth credentials
|
||||
- Click `Generate a new client secret`.
|
||||
- Copy and save your `Client secret`.
|
||||
|
||||
### Enter your GitHub credentials into your Supabase Project
|
||||
## Enter your GitHub credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -82,27 +80,19 @@ Copy your new OAuth credentials
|
||||
- Enter your `GitHub Client ID` and `GitHub Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `github` as the `provider`:
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'github',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
```js
|
||||
async function signInWithGithub() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
async function signInWithGitHub() {
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up GitLab logins for your application consists of 3 parts:
|
||||
- Add your GitLab Application keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your GitLab account
|
||||
## Access your GitLab account
|
||||
|
||||
- Go to [gitlab.com](https://gitlab.com).
|
||||
- Click on `Login` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create your GitLab Application
|
||||
## Create your GitLab Application
|
||||
|
||||
- Click on your `profile logo` (avatar) in the top-right corner.
|
||||
- Select `Edit profile`.
|
||||
@@ -59,7 +57,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Click `Save Application` at the bottom.
|
||||
- Copy and save your `Application ID` (`client_id`) and `Secret` (`client_secret`) which you'll need later.
|
||||
|
||||
### Add your GitLab credentials into your Supabase Project
|
||||
## Add your GitLab credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -69,27 +67,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'gitlab',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `gitlab` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGitLab() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'gitlab',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- Add your Google OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Google Cloud Platform account
|
||||
## Access your Google Cloud Platform account
|
||||
|
||||
- Go to [cloud.google.com](https://cloud.google.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Create a Google Cloud Platform Project
|
||||
## Create a Google Cloud Platform Project
|
||||
|
||||
- Click on `Select a Project` at the top left.
|
||||
- (Or, if a project is currently selected, click on the current project name at the top left.)
|
||||
@@ -35,7 +33,7 @@ Setting up Google logins for your application consists of 3 parts:
|
||||
- (This can take a few minutes.)
|
||||
- This should bring you to the dashboard for your new project.
|
||||
|
||||
### Create the OAuth Keys for your project
|
||||
## Create the OAuth Keys for your project
|
||||
|
||||
From your project's dashboard screen:
|
||||
|
||||
@@ -44,12 +42,12 @@ From your project's dashboard screen:
|
||||
- On the `OAuth consent screen` page select `External`.
|
||||
- Click `Create`.
|
||||
|
||||
### Edit your app information
|
||||
## Edit your app information
|
||||
|
||||
- On the `Edit app registration` page fill out your app information.
|
||||
- Click `Save and continue` at the bottom.
|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -70,7 +68,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create your Google credentials
|
||||
## Create your Google credentials
|
||||
|
||||
- Click `Credentials` at the left to go to the `Credentials` page on the Google Cloud Platform console.
|
||||
- Click `Create Credentials` near the top then select `OAuth client ID`
|
||||
@@ -87,7 +85,7 @@ Copy your new OAuth credentials
|
||||
- A box will appear called `OAuth client created`.
|
||||
- Copy and save the values under `Your Client ID` and `Your Client Secret`.
|
||||
|
||||
### Enter your Google credentials into your Supabase Project
|
||||
## Enter your Google credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -97,27 +95,19 @@ Copy your new OAuth credentials
|
||||
- Enter your `Google Client ID` and `Google Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'google',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `google` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithGoogle() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -18,10 +18,10 @@ It supports custom themes and extensible styles to match your brand and aestheti
|
||||
|
||||
## Set up Auth UI
|
||||
|
||||
Install the latest version of [supabase-js](/docs/reference/javascript/next/) and the Auth UI package:
|
||||
Install the latest version of [supabase-js](/docs/reference/javascript) and the Auth UI package:
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc @supabase/auth-ui-react
|
||||
npm install @supabase/supabase-js @supabase/auth-ui-react
|
||||
```
|
||||
|
||||
### Import the Auth component
|
||||
|
||||
@@ -5,99 +5,136 @@ description: Authentication helpers for Next.js API routes, middleware, and SSR.
|
||||
sidebar_label: 'Next.js'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
This submodule provides convenience helpers for implementing user authentication in Next.js applications.
|
||||
|
||||
## Installation
|
||||
## Install the Next.js helper library
|
||||
|
||||
Using [npm](https://npmjs.org):
|
||||
<Tabs
|
||||
groupId="install"
|
||||
defaultValue="npm"
|
||||
values={[
|
||||
{label: 'npm', value: 'npm'},
|
||||
{label: 'Yarn', value: 'yarn'},
|
||||
]}>
|
||||
|
||||
<TabItem value="npm">
|
||||
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
Using [yarn](https://yarnpkg.com/):
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
|
||||
# Main components and hooks for React based frameworks (optional)
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
|
||||
- Next.js: `>=10`
|
||||
|
||||
## Getting Started
|
||||
Optionally, install **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
```sh
|
||||
npm install @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
### Configuration
|
||||
</TabItem>
|
||||
<TabItem value="yarn">
|
||||
|
||||
Set up the following env vars. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
This library supports the following tooling versions:
|
||||
|
||||
- Node.js: `^10.13.0 || >=12.0.0`
|
||||
- Next.js: `>=10`
|
||||
|
||||
Optionally, install **React Auth Helpers** for components and hooks that can be used across all React-based frameworks.
|
||||
|
||||
```sh
|
||||
yarn add @supabase/auth-helpers-react
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Set up environment variables
|
||||
|
||||
Retrieve your project URL and anon key in your project's [API settings](https://app.supabase.com/project/_/settings/api) in the Dashboard to set up the following environment variables. For local development you can set them in a `.env.local` file. See an [example](https://github.com/supabase/auth-helpers/blob/main/examples/nextjs/.env.local.example).
|
||||
|
||||
```bash
|
||||
# Find these in your Supabase project settings > API
|
||||
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
||||
```
|
||||
|
||||
### Basic Setup
|
||||
## Basic Setup
|
||||
|
||||
- Create an `auth` directory under the `/pages/api/` directory.
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
- Create a `[...supabase].js` file under the newly created `auth` directory.
|
||||
|
||||
The path to your dynamic API route file would be `/pages/api/auth/[...supabase].js`. Populate that file as follows:
|
||||
|
||||
```js
|
||||
import { handleAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default handleAuth({ logout: { returnTo: '/' } })
|
||||
```
|
||||
|
||||
Executing `handleAuth()` creates the following route handlers under the hood that perform different parts of the authentication flow:
|
||||
|
||||
- `/api/auth/callback`: The `UserProvider` 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.
|
||||
|
||||
- `/api/auth/user`: You can fetch user profile information in JSON format.
|
||||
|
||||
- `/api/auth/logout`: Your Next.js application logs out the user. You can optionally pass a `returnTo` parameter to return to a custom relative URL after logout, eg `/api/auth/logout?returnTo=/login`. This will overwrite the logout `returnTo` option specified `handleAuth()`
|
||||
|
||||
Wrap your `pages/_app.js` component with the `UserProvider` component:
|
||||
|
||||
```jsx
|
||||
// pages/_app.js
|
||||
```jsx title="pages/_app.js"
|
||||
import React from 'react'
|
||||
import { UserProvider } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { useRouter } from 'next/router'
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
const router = useRouter()
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
export default function App({ Component, pageProps }) {
|
||||
return (
|
||||
<UserProvider supabaseClient={supabaseClient}>
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabaseClient.auth.signOut()
|
||||
router.push('/')
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
|
||||
<Component {...pageProps} />
|
||||
</UserProvider>
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You can now determine if a user is authenticated by checking that the `user` object returned by the `useUser()` hook is defined.
|
||||
|
||||
## Client-side data fetching with RLS
|
||||
## Usage with TypeScript
|
||||
|
||||
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 `# @supabase/auth-helpers-nextjs` and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
|
||||
```js
|
||||
import { Auth } from '@supabase/ui'
|
||||
import { useUser } from '@supabase/auth-helpers-react'
|
||||
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
// Creating a new supabase client object:
|
||||
import { Database } from '../db_types';
|
||||
|
||||
const [supabaseClient] = useState(() =>
|
||||
createBrowserSupabaseClient<Database>()
|
||||
);
|
||||
```
|
||||
|
||||
```js
|
||||
// Retrieving a supabase client object from the SessionContext:
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react';
|
||||
import { Database } from '../db_types';
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>();
|
||||
```
|
||||
|
||||
## 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 use the `supabaseClient` from the `useSessionContext` hook and only run your query once the user is defined client-side in the `useUser()` hook:
|
||||
|
||||
```js
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useUser, useSessionContext } from '@supabase/auth-helpers-react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
const LoginPage = () => {
|
||||
const { user, error } = useUser()
|
||||
const { isLoading, session, error, supabaseClient } = useSessionContext()
|
||||
const user = useUser()
|
||||
const [data, setData] = useState()
|
||||
|
||||
useEffect(() => {
|
||||
@@ -114,10 +151,11 @@ const LoginPage = () => {
|
||||
<>
|
||||
{error && <p>{error.message}</p>}
|
||||
<Auth
|
||||
redirectTo="http://localhost:3000/"
|
||||
appearance={{ theme: ThemeSupa }}
|
||||
supabaseClient={supabaseClient}
|
||||
providers={['google', 'github']}
|
||||
socialLayout="horizontal"
|
||||
socialButtonSize="xlarge"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
@@ -140,8 +178,7 @@ export default LoginPage
|
||||
|
||||
If you wrap your `getServerSideProps` with `withPageAuth` your props object will be augmented with the user object.
|
||||
|
||||
```js
|
||||
// pages/profile.js
|
||||
```js title="pages/profile.js"
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function Profile({ user }) {
|
||||
@@ -156,9 +193,8 @@ If there is no authenticated user, they will be redirect to your home page, unle
|
||||
You can pass in your own `getServerSideProps` method, the props returned from this will be merged with the
|
||||
user props. You can also access the user session data by calling `getUser` inside of this method, eg:
|
||||
|
||||
```js
|
||||
// pages/protected-page.js
|
||||
import { withPageAuth, getUser } from '@supabase/auth-helpers-nextjs'
|
||||
```js title="pages/protected-page.js"
|
||||
import { withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({ user, customProp }) {
|
||||
return <div>Protected content</div>
|
||||
@@ -166,9 +202,11 @@ export default function ProtectedPage({ user, customProp }) {
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/foo',
|
||||
async getServerSideProps(ctx) {
|
||||
async getServerSideProps(ctx, supabase) {
|
||||
// Access the user object
|
||||
const { user, accessToken } = await getUser(ctx)
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser()
|
||||
return { props: { email: user?.email } }
|
||||
},
|
||||
})
|
||||
@@ -176,14 +214,10 @@ export const getServerSideProps = withPageAuth({
|
||||
|
||||
### Server-side data fetching with RLS
|
||||
|
||||
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 inject the request context into the supabase client:
|
||||
Both `withApiAuth` and `withPageAuth` return a supabase client that you can use to run [row level security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) authenticated queries server-side:
|
||||
|
||||
```js
|
||||
import {
|
||||
User,
|
||||
withPageAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
import { User, withPageAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default function ProtectedPage({
|
||||
user,
|
||||
@@ -203,7 +237,7 @@ export default function ProtectedPage({
|
||||
|
||||
export const getServerSideProps = withPageAuth({
|
||||
redirectTo: '/',
|
||||
async getServerSideProps(ctx) {
|
||||
async getServerSideProps(ctx, supabaseServerClient) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient(ctx).from('test').select('*')
|
||||
return { props: { data } }
|
||||
@@ -258,18 +292,16 @@ export const getServerSideProps = withPageAuth({
|
||||
Wrap an API Route to check that the user has a valid session. If they're not logged in the handler will return a
|
||||
401 Unauthorized.
|
||||
|
||||
```js
|
||||
// pages/api/protected-route.js
|
||||
import {
|
||||
withApiAuth,
|
||||
supabaseServerClient,
|
||||
} from '@supabase/auth-helpers-nextjs'
|
||||
```js title="pages/api/protected-route.js"
|
||||
import { withApiAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default withApiAuth(async function ProtectedRoute(req, res) {
|
||||
export default withApiAuth(async function ProtectedRoute(
|
||||
req,
|
||||
res,
|
||||
supabaseServerClient
|
||||
) {
|
||||
// Run queries with RLS on the server
|
||||
const { data } = await supabaseServerClient({ req, res })
|
||||
.from('test')
|
||||
.select('*')
|
||||
const { data } = await supabaseServerClient.from('test').select('*')
|
||||
res.json(data)
|
||||
})
|
||||
```
|
||||
@@ -278,20 +310,22 @@ If you visit `/api/protected-route` without a valid session cookie, you will get
|
||||
|
||||
## Protecting routes with [Nextjs Middleware](https://nextjs.org/docs/middleware)
|
||||
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `_middleware` file to protect an entire directory. In the following example, all requests to `/protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
As an alternative to protecting individual pages using `getServerSideProps` with `withPageAuth`, `withMiddlewareAuth` can be used from inside a `middleware` file to protect the entire directory or those that match the config object. In the following example, all requests to `/middleware-protected/*` will check whether a user is signed in, if successful the request will be forwarded to the destination route, otherwise the user will be redirected to `/login` (defaults to: `/`) with a 307 Temporary Redirect response status:
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/middleware'
|
||||
```ts title="middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({ redirectTo: '/login' })
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
It is also possible to add finer granularity based on the user logged in. I.e. you can specify a promise to determine if a specific user has permission or not.
|
||||
|
||||
```ts
|
||||
// pages/protected/_middleware.ts
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs/dist/middleware'
|
||||
```ts title="middleware.ts"
|
||||
import { withMiddlewareAuth } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/login',
|
||||
@@ -300,20 +334,61 @@ export const middleware = withMiddlewareAuth({
|
||||
redirectTo: '/insufficient-permissions',
|
||||
},
|
||||
})
|
||||
|
||||
export const config = {
|
||||
matcher: ['/middleware-protected/:path*'],
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating from @supabase/supabase-auth-helpers to @supabase/auth-helpers
|
||||
## Migrating to version `0.4.X` {#migration}
|
||||
|
||||
This is a step by step guide on migrating away from the `@supabase/supabase-auth-helpers` to the newly released `@supabase/auth-helpers`.
|
||||
- With `supabase-js` v2, the `auth` API routes are no longer required and you can delete the `auth` directory under the `/pages/api/` directory.
|
||||
- The `/api/auth/logout` API route has been removed—use the `signout` method instead.
|
||||
```js
|
||||
<button
|
||||
onClick={async () => {
|
||||
await supabaseClient.auth.signOut()
|
||||
router.push('/')
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
</button>
|
||||
```
|
||||
- The `supabaseClient` and `supabaseServerClient` have been removed in favor of the `createBrowserSupabaseClient` and `createServerSupabaseClient` methods. This allows you to provide the CLI-generated types to the client:
|
||||
```js
|
||||
// client-side
|
||||
import type { Database } from 'types_db';
|
||||
const [supabaseClient] = useState(() =>
|
||||
createBrowserSupabaseClient<Database>()
|
||||
);
|
||||
|
||||
1. Install `@supabase/supabase-js`, `@supabase/auth-helpers-nextjs` and `@supabase/auth-helpers-react` libraries from npm.
|
||||
2. Replace all imports of `@supabase/supabase-auth-helpers/nextjs` in your project with `@supabase/auth-helpers-nextjs`.
|
||||
3. Replace all imports of `@supabase/supabase-auth-helpers/react` in your project with `@supabase/auth-helpers-react`.
|
||||
4. Replace all instances of `withAuthRequired` in any of your NextJS pages with `withPageAuth`.
|
||||
5. Replace all instances of `withAuthRequired` in any of your NextJS API endpoints with `withApiAuth`.
|
||||
6. Uninstall `@supabase/supabase-auth-helpers`.
|
||||
// server-side API route
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { Database } from 'types_db';
|
||||
|
||||
## Additional Links
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const supabaseServerClient = createServerSupabaseClient<Database>({ req, res })
|
||||
const { data:{ user } } = await supabaseServerClient.auth.getUser()
|
||||
|
||||
- [Auth Helpers Source code](https://github.com/supabase/auth-helpers)
|
||||
- [Next.js example](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs)
|
||||
res.status(200).json({ name: user?.name ?? '' })
|
||||
}
|
||||
```
|
||||
- The `UserProvider` has been replaced by the `SessionContextProvider`. Make sure to wrap your `pages/_app.js` componenent with the `SessionContextProvider`. Then, throughout your application you can use the `useSessionContext` hook to get the `session` and the `useSupabaseClient` hook to get an authenticated `supabaseClient`.
|
||||
- The `useUser` hook now returns the `user` object or `null`.
|
||||
- Usage with TypeScript: You can pass types that were [generated with the Supabase CLI](/docs/reference/javascript/typescript-support#generating-types) to the Supabase Client to get enhanced type safety and auto completion:
|
||||
```js
|
||||
// Creating a new supabase client object:
|
||||
import { Database } from '../db_types';
|
||||
|
||||
const [supabaseClient] = useState(() =>
|
||||
createBrowserSupabaseClient<Database>()
|
||||
);
|
||||
```
|
||||
|
||||
```js
|
||||
// Retrieving a supabase client object from the SessionContext:
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react';
|
||||
import { Database } from '../db_types';
|
||||
|
||||
const supabaseClient = useSupabaseClient<Database>();
|
||||
```
|
||||
@@ -24,13 +24,11 @@ Keycloak OAuth consists of five broad steps:
|
||||
- Obtain the `Secret` from the credentials tab which will be used as the `client secret`.
|
||||
- Add the callback url of your application to your allowlist.
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Keycloak Admin console
|
||||
## Access your Keycloak Admin console
|
||||
|
||||
- Login by visiting [`http://localhost:8080`](http://localhost:8080) and clicking on "Administration Console".
|
||||
|
||||
### Create a Keycloak Realm
|
||||
## Create a Keycloak Realm
|
||||
|
||||
- Once you've logged in to the Keycloak console, you can add a realm from the side panel. The default realm should be named "Master".
|
||||
- After you've added a new realm, you can retrieve the `issuer` from the "OpenID Endpoint Configuration" endpoint. The `issuer` will be used as the `Keycloak URL`.
|
||||
@@ -38,13 +36,13 @@ Keycloak OAuth consists of five broad steps:
|
||||
|
||||

|
||||
|
||||
### Create a Keycloak Client
|
||||
## Create a Keycloak Client
|
||||
|
||||
The "Client ID" of the created client will serve as the `client_id` when you make API calls to authenticate the user.
|
||||
|
||||

|
||||
|
||||
### Client Settings
|
||||
## Client Settings
|
||||
|
||||
After you've created the client successfully, ensure that you set the following settings:
|
||||
|
||||
@@ -55,34 +53,26 @@ After you've created the client successfully, ensure that you set the following
|
||||

|
||||

|
||||
|
||||
### Obtain the Client Secret
|
||||
## Obtain the Client Secret
|
||||
|
||||
This will serve as the `client_secret` when you make API calls to authenticate the user.
|
||||
Under the "Credentials" tab, the `Secret` value will be used as the `client secret`.
|
||||
|
||||

|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'keycloak',
|
||||
})
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `keycloak` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithKeycloak() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'keycloak',
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up LinkedIn logins for your application consists of 3 parts:
|
||||
- Add your LinkedIn `client_id` and `client_secret` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your LinkedIn Developer account
|
||||
## Access your LinkedIn Developer account
|
||||
|
||||
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Log in (if necessary.)
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a LinkedIn OAuth app
|
||||
## Create a LinkedIn OAuth app
|
||||
|
||||
- Go to [LinkedIn Developer Dashboard](https://www.linkedin.com/developers/apps).
|
||||
- Click on `Create App` at the top right
|
||||
@@ -58,7 +56,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Copy and save your newly-generated `Client ID`
|
||||
- Copy and save your newly-generated `Client Secret`
|
||||
|
||||
### Enter your LinkedIn credentials into your Supabase Project
|
||||
## Enter your LinkedIn credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -68,27 +66,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'LinkedIn',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `linkedin` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithLinkedIn() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'LinkedIn',
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'linkedin',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
id: auth-magic-link
|
||||
title: 'Login With Magic Link'
|
||||
description: Use Supabase to Authenticate and Authorize your users using Magic Link.
|
||||
description: Use Supabase to authenticate and authorize your users using magic links.
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
By default, if no password is provided, the user will be sent a "magic link" to their email address, which they can click to open your application with a valid session. By default, a given user can only request a Magic Link once every 60 seconds.
|
||||
Magic links are a form of passwordless logins where users click on a link sent to their email address to log in to their accounts.
|
||||
Magic links only work with email addresses. By default, a user can only request a magic link once every 60 seconds.
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -25,12 +26,7 @@ Setting up Magic Link logins for your Supabase application.
|
||||
- Under `Email Auth` turn `Enable Email Signup` to ON
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
|
||||
Add logins using our client libraries:
|
||||
|
||||
- [JavaScript](/docs/reference/javascript/auth-signin#sign-in-with-magic-link)
|
||||
- [Dart](/docs/reference/dart/auth-signin#sign-in-with-magic-link)
|
||||
## Add login code to your client app
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
@@ -40,39 +36,11 @@ Add logins using our client libraries:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
```js
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
email: 'example@email.com',
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
```dart
|
||||
final res = await supabase.auth.signIn(email: 'example@email.com');
|
||||
|
||||
final error = res.error;
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
|
||||
</Tabs>
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
values={[{ label: 'JavaScript', value: 'js' }, { label: 'Dart', value: 'dart' }]}
|
||||
>
|
||||
|
||||
<TabItem value="js">
|
||||
When your user signs in, call [signInWithOtp()](/docs/reference/javascript/auth-signinwithotp) with their email address:
|
||||
|
||||
```js
|
||||
async function signInWithEmail() {
|
||||
const { user, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOtp({
|
||||
email: 'example@email.com',
|
||||
})
|
||||
}
|
||||
@@ -82,6 +50,8 @@ async function signInWithEmail() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs in, call [signIn()](/docs/reference/dart/auth-signin) with their email address:
|
||||
|
||||
```dart
|
||||
Future<void> signInWithEmail() async {
|
||||
await supabase.auth.signIn(email: 'example@email.com');
|
||||
@@ -92,8 +62,6 @@ Future<void> signInWithEmail() async {
|
||||
|
||||
</Tabs>
|
||||
|
||||
To log out:
|
||||
|
||||
<Tabs
|
||||
defaultValue="js"
|
||||
groupId="guides/auth"
|
||||
@@ -102,6 +70,8 @@ To log out:
|
||||
|
||||
<TabItem value="js">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signOut() {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
@@ -112,6 +82,8 @@ async function signOut() {
|
||||
|
||||
<TabItem value="dart">
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/dart/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```dart
|
||||
Future<void> signOut() async {
|
||||
await supabase.auth.signOut();
|
||||
|
||||
@@ -172,7 +172,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '+13334445555',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -209,7 +209,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '+13334445555',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
- Retrieve your OAuth client ID and OAuth client secret and add them to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Create your notion integration
|
||||
## Create your notion integration
|
||||
|
||||
- Go to [developers.notion.com](https://developers.notion.com/).
|
||||
- Click "View my integrations" and login.
|
||||
@@ -33,7 +31,7 @@ Setting up Notion logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Add the redirect uri
|
||||
## Add the redirect URI
|
||||
|
||||
- After selecting "Public integration", you should see an option to add "Redirect URIs".
|
||||
|
||||
@@ -58,7 +56,7 @@ Your redirect uri should look like the following: `https://<project-ref>.supabas
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Add your Notion credentials into your Supabase Project
|
||||
## Add your Notion credentials into your Supabase Project
|
||||
|
||||
- Once you've created your notion integration, you should be able to retrieve the "OAuth client ID" and "OAuth client secret" from the "OAuth Domain and URIs" tab.
|
||||
|
||||
@@ -71,27 +69,19 @@ Your redirect uri should look like the following: `https://<project-ref>.supabas
|
||||
- Enter the "OAuth client ID" and "OAuth client secret" obtained in the `client id` and `client secret` fields.
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'notion',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `notion` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithNotion() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'notion',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Slack logins for your application consists of 3 parts:
|
||||
- Add your Slack `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Slack Developer account
|
||||
## Access your Slack Developer account
|
||||
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Your Apps` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Slack OAuth app
|
||||
## Create a Slack OAuth app
|
||||
|
||||
- Go to [api.slack.com](https://api.slack.com/apps).
|
||||
- Click on `Create an App`
|
||||
@@ -71,7 +69,7 @@ Under `Redirect URLs`:
|
||||
- Paste your `Callback URL` then click `Add`
|
||||
- Click `Save URLs`
|
||||
|
||||
### Enter your Slack credentials into your Supabase Project
|
||||
## Enter your Slack credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -81,27 +79,19 @@ Under `Redirect URLs`:
|
||||
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'slack',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `slack` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSlack() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'slack',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Spotify logins for your application consists of 3 parts:
|
||||
- Add your Spotify `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Spotify Developer account
|
||||
## Access your Spotify Developer account
|
||||
|
||||
- Log into [Spotify](https://spotify.com)
|
||||
- Access the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Spotify OAuth app
|
||||
## Create a Spotify OAuth app
|
||||
|
||||
- Log into [Spotify](https://spotify.com).
|
||||
- Go to the [Spotify Developer Dashboard](https://developer.spotify.com/dashboard)
|
||||
@@ -66,7 +64,7 @@ Under `Redirect URIs`:
|
||||
- Click `Add`
|
||||
- Click `Save` at the bottom
|
||||
|
||||
### Enter your Spotify credentials into your Supabase Project
|
||||
## Enter your Spotify credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -76,27 +74,19 @@ Under `Redirect URIs`:
|
||||
- Enter your `Client ID` (`client_id`) and `Client Secret` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'spotify',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `spotify` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithSpotify() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'spotify',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -189,7 +189,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '+13334445555',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -226,7 +226,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '+13334445555',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -17,9 +17,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
- Add your Twitch OAuth Consumer keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Twitch Developer account
|
||||
## Access your Twitch Developer account
|
||||
|
||||
- Go to [dev.twitch.tv](https://dev.twitch.tv).
|
||||
- Click on `Log in with Twitch` at the top right to log in.
|
||||
@@ -31,7 +29,7 @@ Setting up Twitch logins for your application consists of 3 parts:
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
In the next step you require a callback URL, which looks like this:
|
||||
|
||||
@@ -52,7 +50,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Twitch Application
|
||||
## Create a Twitch Application
|
||||
|
||||

|
||||
|
||||
@@ -65,7 +63,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Select a category for your app.
|
||||
- Check the Captcha box and click `Create`.
|
||||
|
||||
### Retrieve your Twitch OAuth Client ID and Client Secret
|
||||
## Retrieve your Twitch OAuth Client ID and Client Secret
|
||||
|
||||
- Click `Manage` at the right of your application entry in the list.
|
||||
|
||||
@@ -77,7 +75,7 @@ In the next step you require a callback URL, which looks like this:
|
||||
|
||||

|
||||
|
||||
### Add your Twitch credentials into your Supabase Project
|
||||
## Add your Twitch credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -87,27 +85,19 @@ In the next step you require a callback URL, which looks like this:
|
||||
- Enter your `client_id` and `client_secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'twitch',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitch` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitch() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'twitch',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Twitter logins for your application consists of 3 parts:
|
||||
- Add your Twitter `API Key` and `API Secret Key` to your [Supabase Project](https://app.supabase.com).
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js).
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Twitter Developer account
|
||||
## Access your Twitter Developer account
|
||||
|
||||
- Go to [developer.twitter.com](https://developer.twitter.com).
|
||||
- Click on `Sign in` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Twitter OAuth app
|
||||
## Create a Twitter OAuth app
|
||||
|
||||
- Click `+ Create Project`.
|
||||
- Enter your project name, click `Next`.
|
||||
@@ -67,7 +65,7 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `Privacy policy URL`.
|
||||
- Click `Save`.
|
||||
|
||||
### Enter your Twitter credentials into your Supabase Project
|
||||
## Enter your Twitter credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com).
|
||||
- In the left sidebar, click the `Authentication` icon (near the top).
|
||||
@@ -77,27 +75,19 @@ The next step requires a callback URL, which looks like this:
|
||||
- Enter your `API Key` (`client_id`) and `API Secret Key` (`client_secret`) saved in the previous step.
|
||||
- Click `Save`.
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers).
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'twitter',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `twitter` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithTwitter() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'twitter',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -163,7 +163,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithPassword({
|
||||
phone: '491512223334444',
|
||||
password: 'some-password',
|
||||
})
|
||||
@@ -200,7 +200,7 @@ values={[
|
||||
<TabItem value="JS">
|
||||
|
||||
```js
|
||||
let { user, error } = await supabase.auth.signIn({
|
||||
let { user, error } = await supabase.auth.signInWithOtp({
|
||||
phone: '491512223334444',
|
||||
})
|
||||
```
|
||||
|
||||
@@ -21,27 +21,25 @@ The procedure consists of five broad steps:
|
||||
- Connect a WorkOS Supported Identity Provider
|
||||
- Add your WorkOS credentials into your Supabase project
|
||||
|
||||
## Steps
|
||||
|
||||
### Create a WorkOS Organization
|
||||
## Create a WorkOS Organization
|
||||
|
||||
Log in to the dashboard and hop over to the Organizations tab to create and organization
|
||||

|
||||
|
||||
### Obtain the Client ID and configure Redirect URI
|
||||
## Obtain the Client ID and configure Redirect URI
|
||||
|
||||
Head over to the Configuration tab and configure the redirect URI.The redirect URI should look like `https://<project-ref>.supabase.co/auth/v1/callback`
|
||||
Note that this is distinct from the redirect URI referred to in the Supabase dashboard
|
||||
|
||||

|
||||
|
||||
### Obtain the WorkOS Secret
|
||||
## Obtain the WorkOS Secret
|
||||
|
||||
Head over to the API Keys page and obtain the secret key.
|
||||
|
||||

|
||||
|
||||
### Connect a WorkOS Supported Identity Provider
|
||||
## Connect a WorkOS Supported Identity Provider
|
||||
|
||||
Set up the identity provider by visiting the setup link.
|
||||
|
||||
@@ -49,7 +47,7 @@ Set up the identity provider by visiting the setup link.
|
||||
|
||||
You can pick between any one of the many identity providers that WorkOS supports.
|
||||
|
||||
### Add your WorkOS credentials into your Supabase Project
|
||||
## Add your WorkOS credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -58,29 +56,13 @@ You can pick between any one of the many identity providers that WorkOS supports
|
||||
- Enter the `Client ID`, `Secret`, and `WorkOS URL` saved in the previous steps. The ``WorkOS URL` setting should be set to https://api.workos.com/
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented in the [Supabase OAuth Reference](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers). Note that you only need to include one of the three parameters: `connection`, `organization`, and `provider`.
|
||||
You can refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
{
|
||||
provider: 'workos',
|
||||
},
|
||||
{
|
||||
connection: '<your_connection>',
|
||||
organization: '<your_organization',
|
||||
provider: '<your_provider>',
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Add a function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `workos` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithWorkOS() {
|
||||
const { user, session, error } = await supabase.auth.signIn(
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'workos',
|
||||
},
|
||||
@@ -93,7 +75,9 @@ async function signInWithWorkOS() {
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/authorize/) to learn more about the different methods.
|
||||
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -17,16 +17,14 @@ Setting up Zoom logins for your application consists of 3 parts:
|
||||
- Add your Zoom OAuth keys to your [Supabase Project](https://app.supabase.com)
|
||||
- Add the login code to your [Supabase JS Client App](https://github.com/supabase/supabase-js)
|
||||
|
||||
## Steps
|
||||
|
||||
### Access your Zoom Developer account
|
||||
## Access your Zoom Developer account
|
||||
|
||||
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
|
||||

|
||||
|
||||
### Find your callback URL
|
||||
## Find your callback URL
|
||||
|
||||
The next step requires a callback URL, which looks like this:
|
||||
|
||||
@@ -47,7 +45,7 @@ The next step requires a callback URL, which looks like this:
|
||||
/>
|
||||
</video>
|
||||
|
||||
### Create a Zoom Oauth App
|
||||
## Create a Zoom Oauth App
|
||||
|
||||
- Go to [marketplace.zoom.us](https://marketplace.zoom.us/).
|
||||
- Click on `Sign In` at the top right to log in.
|
||||
@@ -67,7 +65,7 @@ Under `Redirect URL for OAuth`
|
||||
- Paste your `Callback URL`
|
||||
- Click `Continue`
|
||||
|
||||
### Enter your Zoom credentials into your Supabase Project
|
||||
## Enter your Zoom credentials into your Supabase Project
|
||||
|
||||
- Go to your [Supabase Project Dashboard](https://app.supabase.com)
|
||||
- In the left sidebar, click the `Authentication` icon (near the top)
|
||||
@@ -77,27 +75,19 @@ Under `Redirect URL for OAuth`
|
||||
- Enter your `Zoom Client ID` and `Zoom Client Secret` saved in the previous step
|
||||
- Click `Save`
|
||||
|
||||
### Add login code to your client app
|
||||
## Add login code to your client app
|
||||
|
||||
The JavaScript client code is documented here: [Supabase OAuth Client Code](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers)
|
||||
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
provider: 'zoom',
|
||||
})
|
||||
```
|
||||
|
||||
Add this function which you can call from a button, link, or UI element.
|
||||
When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/auth-signinwithoauth) with `zoom` as the `provider`:
|
||||
|
||||
```js
|
||||
async function signInWithZoom() {
|
||||
const { user, session, error } = await supabase.auth.signIn({
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'zoom',
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
To log out:
|
||||
When your user signs out, call [signOut()](/docs/reference/javascript/auth-signout) to remove them from the browser session and any objects from localStorage:
|
||||
|
||||
```js
|
||||
async function signout() {
|
||||
|
||||
@@ -7,29 +7,6 @@ sidebar_label: Overview
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
import ButtonCard from '@site/src/components/ButtonCard'
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'With CORS headers',
|
||||
description: 'Send CORS headers for invoking from the browser.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'React Native with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Flutter with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Flutter.',
|
||||
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
]
|
||||
|
||||
Edge Functions are server-side TypeScript functions, distributed globally at the edge—close to your users. They can be used for listening to webhooks or integrating your Supabase project with third-parties [like Stripe](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/stripe-webhooks).
|
||||
|
||||
@@ -235,26 +212,6 @@ To see all the secrets which you have set remotely, use [`supabase secrets list`
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{examples.map((x) => (
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={x.href}
|
||||
title={x.name}
|
||||
description={x.description}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Suggestions
|
||||
|
||||
### Database Functions vs Edge Functions
|
||||
@@ -313,5 +270,5 @@ serve(async (req) => {
|
||||
- Cannot write to File System
|
||||
- Edge Functions
|
||||
- Local development - only one function at a time
|
||||
- Supabase Functions only supports `GET` and `POST` requests.
|
||||
- Supabase Functions do not support HTML responses.
|
||||
- Supabase Functions only supports `POST`, `OPTIONS`, and `GET` requests.
|
||||
- Serving of HTML content is not supported (`GET` requests that return `text/html` will be rewritten to `text/plain`).
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: auth
|
||||
title: Auth
|
||||
description: Supabase Edge Functions and Auth.
|
||||
---
|
||||
|
||||
Edge Functions work seamlessly with [Supabase Auth](/docs/guides/auth), allowing you to identify which user called your function.
|
||||
|
||||
When [invoking](/docs/reference/javascript/invoke) a function with one of the [client libraries](/docs/reference), the logged in user's JWT is automatically attached to the function call and becomes accessible within your function.
|
||||
|
||||
This is important, for example, to identify which customer's credit card should be charged. You can see this concept end-to-end in our [Stripe example app](https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions).
|
||||
|
||||
## Auth Context & RLS
|
||||
|
||||
By creating a supabase client with the auth context from the function, you can do two things:
|
||||
|
||||
1. Get the user object.
|
||||
2. Run queries in the context of the user with [Row Level Security (RLS)](/docs/guides/auth/row-level-security) policies enforced.
|
||||
|
||||
```js title="supabase/functions/select-from-table-with-auth-rls/index.ts"
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.0.0-rc.12'
|
||||
|
||||
serve(async (req: Request) => {
|
||||
try {
|
||||
// Create a Supabase client with the Auth context of the logged in user.
|
||||
const supabaseClient = createClient(
|
||||
// Supabase API URL - env var exported by default.
|
||||
Deno.env.get('SUPABASE_URL') ?? '',
|
||||
// Supabase API ANON KEY - env var exported by default.
|
||||
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
||||
// Create client with Auth context of the user that called the function.
|
||||
// This way your row-level-security (RLS) policies are applied.
|
||||
// highlight-next-line
|
||||
{ global: { headers: { Authorization: req.headers.get('Authorization')! } } }
|
||||
)
|
||||
// Now we can get the session or user object
|
||||
// highlight-start
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabaseClient.auth.getUser()
|
||||
// highlight-end
|
||||
|
||||
// And we can run queries in the context of our authenticated user
|
||||
// highlight-start
|
||||
const { data, error } = await supabaseClient.from('users').select('*')
|
||||
if (error) throw error
|
||||
// highlight-end
|
||||
|
||||
return new Response(JSON.stringify({ user, data }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
})
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts).
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: examples
|
||||
title: Examples
|
||||
description: Useful Supabase Edge Functions Examples.
|
||||
---
|
||||
|
||||
import ButtonCard from '@site/src/components/ButtonCard'
|
||||
const examples = [
|
||||
{
|
||||
name: 'With supabase-js',
|
||||
description: 'Use the Supabase client inside your Edge Function.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/select-from-table-with-auth-rls/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'With CORS headers',
|
||||
description: 'Send CORS headers for invoking from the browser.',
|
||||
href: 'https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts',
|
||||
},
|
||||
{
|
||||
name: 'React Native with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Expo.',
|
||||
href: 'https://github.com/supabase-community/expo-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
{
|
||||
name: 'Flutter with Stripe',
|
||||
description: 'Full example for using Supabase and Stripe, with Flutter.',
|
||||
href: 'https://github.com/supabase-community/flutter-stripe-payments-with-supabase-functions',
|
||||
},
|
||||
]
|
||||
|
||||
You can find a list of useful [Edge Function Examples](https://github.com/supabase/supabase/tree/master/examples/edge-functions) in our GitHub repository.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{examples.map((x) => (
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={x.href}
|
||||
title={x.name}
|
||||
description={x.description}
|
||||
style={{ height: '100%' }}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,7 +34,32 @@ To update the screenshots, ensure that at least one log line is selected to disp
|
||||
</TabItem>
|
||||
<TabItem value="postgres" label="Postgres">
|
||||
|
||||
[Postgres logs](https://app.supabase.com/project/_/database/postgres-logs) show all queries and activity for your [database](../../guides/database).
|
||||
[Postgres logs](https://app.supabase.com/project/_/database/postgres-logs) show queries and activity for your [database](../../guides/database).
|
||||
|
||||
By default, query logs are disabled for all new Supabase projects, as they can reveal metadata about the contents of your database (e.g. table and column names). If you'd like to enable query logs, you can enable and configure the [pgAudit extension](https://www.pgaudit.org/):
|
||||
|
||||
- [Enable the pgAudit extension for your project](https://app.supabase.com/project/_/database/extensions)
|
||||
- [Reboot the DB](https://app.supabase.com/project/_/settings/general); a "Fast database reboot" is sufficient, and faster than a full project restart
|
||||
- Configure `pgaudit.log` to the [appropriate value](https://github.com/pgaudit/pgaudit/blob/master/README.md#pgauditlog)
|
||||
- You should be able to view query logs in your [log explorer](https://app.supabase.com/project/_/database/postgres-logs) now
|
||||
|
||||
As an example, if you wanted to enable logs for writes and DDL statements for a single session, you could execute within the session:
|
||||
|
||||
```sql
|
||||
set pgaudit.log = 'write, ddl';
|
||||
```
|
||||
|
||||
To _permanently_ enable logs for all statements that relate to roles and privileges, you could execute (followed by a db reboot):
|
||||
|
||||
```sql
|
||||
alter system set pgaudit.log to 'role';
|
||||
```
|
||||
|
||||
To remove a system-wide setting, you can use (followed by a db reboot):
|
||||
|
||||
```sql
|
||||
alter system reset pgaudit.log
|
||||
```
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -10,8 +10,14 @@ Anyone with access to a valid JWT signed with the project's JWT secret is able t
|
||||
|
||||
Clients can choose to receive `INSERT`, `UPDATE`, `DELETE`, or `*` (all) changes for all changes in a schema, a table in a schema, or a column's value in a table. Your clients should only listen to tables in the `public` schema and you must first enable the tables you want your clients to listen to.
|
||||
|
||||
Postgres CDC works out of the box for tables in the `public` schema. You can listen to tables in your private schemas by granting table `SELECT` permissions to the database role found in your access token. You can run a query similar to the following:
|
||||
|
||||
```sql
|
||||
GRANT SELECT ON "private_schema"."table" TO authenticated;
|
||||
```
|
||||
|
||||
:::caution
|
||||
Postgres CDC works out of the box for tables in the `public` schema. We do not yet recommend attempting to listen to tables in private schemas due to security concerns and additional permission requirements.
|
||||
We strongly encourage you to enable RLS and create policies for tables in private schemas. Otherwise, any role you grant access to will have unfettered read access to the table.
|
||||
:::
|
||||
|
||||
You can do this in the [Replication](https://app.supabase.com/project/_/database/replication) section in the Dashboard or with the [SQL editor](https://app.supabase.com/project/_/sql):
|
||||
|
||||
@@ -235,7 +235,7 @@ const roomId = 'room1'
|
||||
const userId = 'user1'
|
||||
|
||||
channel.on(
|
||||
'postgres-changes',
|
||||
'postgres_changes',
|
||||
{
|
||||
event: 'INSERT',
|
||||
schema: 'public',
|
||||
|
||||
@@ -19,7 +19,7 @@ Aside from performance, CDNs also help with security and availability by mitigat
|
||||
|
||||
By default, assets are cached both in the CDN and in the user’s browser for 1 hour. After this, the CDN nodes ping the storage server to see if an object has been updated.
|
||||
|
||||
You can modify this cache time when you are [uploading](https://supabase.com/docs/reference/javascript/next/storage-from-upload) or [updating](https://supabase.com/docs/reference/javascript/next/storage-from-update) an object by modifying the `cacheControl` parameter.
|
||||
You can modify this cache time when you are [uploading](https://supabase.com/docs/reference/javascript/storage-from-upload) or [updating](https://supabase.com/docs/reference/javascript/storage-from-update) an object by modifying the `cacheControl` parameter.
|
||||
|
||||
If you expect the object to not change at a given URL, setting a longer cache duration is preferable.
|
||||
|
||||
|
||||
@@ -272,7 +272,7 @@ You can access the storage API directly with the same endpoint. See the <a href=
|
||||
|
||||
Supabase Storage is integrated with your [Postgres Database](/docs/guides/database).
|
||||
This means that you can use the same [Row Level Security Policies](/docs/guides/auth#policies)
|
||||
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/next/storage-createbucket).
|
||||
for managing access to your files. Supabase Storage stores metadata in the `objects` and `buckets` table in the storage schema. To allow read access to files, the RLS policy must allow users to `SELECT` the `objects` table and for uploading a new object, the RLS policy must grant users access to `INSERT` into the `objects` table and so on. The mapping between the different API calls and the database permissions required is documented in the [Reference docs](/docs/reference/javascript/storage-createbucket).
|
||||
|
||||
:::note
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: Expo
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and React Native. It includes:
|
||||
@@ -155,7 +149,7 @@ cd expo-user-management
|
||||
Then let's install the additional dependencies: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
npm install react-native-elements @react-native-async-storage/async-storage react-native-url-polyfill
|
||||
```
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: 'Next.js'
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Next.js. It includes:
|
||||
@@ -177,7 +171,7 @@ cd supabase-nextjs
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env.local`.
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: React
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and React. It includes:
|
||||
@@ -174,10 +168,10 @@ npx create-react-app supabase-react
|
||||
cd supabase-react
|
||||
```
|
||||
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js). We're going to install the "rc" (release candidate) version until this is released for general use.
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js).
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: SolidJS
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Solid JS. It includes:
|
||||
@@ -176,7 +170,7 @@ cd supabase-solid
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
|
||||
@@ -8,12 +8,6 @@ sidebar_label: Svelte
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
:::note
|
||||
|
||||
This guide uses supabase-js v2.0.0 Release Candidate (RC).
|
||||
|
||||
:::
|
||||
|
||||
## Intro
|
||||
|
||||
This example provides the steps to build a simple user management app (from scratch!) using Supabase and Svelte. It includes:
|
||||
@@ -160,7 +154,7 @@ npm install
|
||||
Then let's install the only additional dependency: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js@rc
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env`.
|
||||
|
||||
@@ -82,13 +82,13 @@ For the curious, try heading to the SQL editor and querying:
|
||||
select * from auth.users;
|
||||
```
|
||||
|
||||
If supabase-js is loaded on your site (in this case http://localhost:3000) then it will automatically pluck the access_token out of the URL and initiate a session. You can check the [session()](../../reference/javascript/auth-session) method to see if there is a valid session:
|
||||
If supabase-js is loaded on your site (in this case http://localhost:3000) it automatically plucks the `access_token` out of the URL and initiates a session. You can retrieve the [session](/docs/reference/javascript/auth-getsession) to see if there is a valid session:
|
||||
|
||||
```jsx
|
||||
console.log(supabase.auth.session())
|
||||
console.log(supabase.auth.getSession())
|
||||
```
|
||||
|
||||
Now that we can use methods like `supabase.auth.signIn({ email, password})` to issue JWTs to users we want to start fetching resources specific to that user. So let's make some. Go to the SQL editor and run:
|
||||
Now that we can use methods to issue JWTs to users, we want to start fetching resources specific to that user. So let's make some. Go to the SQL editor and run:
|
||||
|
||||
```sql
|
||||
create table my_scores (
|
||||
|
||||
@@ -41,26 +41,6 @@ Reference documentation for the official Supabase client libraries, APIs, and to
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Community
|
||||
|
||||
Reference documentation for community-contributed utilities.
|
||||
|
||||
<div class="container" style={{ padding: 0 }}>
|
||||
<div class="row is-multiline">
|
||||
{/* Auth */}
|
||||
<div class="col col--6">
|
||||
<ButtonCard
|
||||
class="card"
|
||||
to={useBaseUrl('/reference/auth-helpers')}
|
||||
style={{ height: '100%' }}
|
||||
title="Auth Helpers"
|
||||
description="Collection of framework-specific auth utilities for working with Supabase."
|
||||
/>
|
||||
</div>
|
||||
{/* */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
## Self-hosting
|
||||
|
||||
Reference documentation for self-hosting Supabase features.
|
||||
|
||||
@@ -12,10 +12,10 @@ For help and questions about best practices, we have a discussion forum set up o
|
||||
|
||||
## Business Support
|
||||
|
||||
We offer email based support for business users. You can email us at support@supabase.io.
|
||||
We offer email based support for business users. You can email us at support@supabase.com.
|
||||
|
||||
If you need SLAs, guaranteed response times, or other enterprise level services, please contact us at this email address.
|
||||
|
||||
## Slack Support
|
||||
|
||||
If you are building with Supabase and you would like to set up a shared Slack channel (via [Slack Connect](https://slack.com/connect)), please reach out to us at support@supabase.io.
|
||||
If you are building with Supabase and you would like to set up a shared Slack channel (via [Slack Connect](https://slack.com/connect)), please reach out to us at support@supabase.com.
|
||||
@@ -112,11 +112,10 @@ const config = {
|
||||
breadcrumbs: false,
|
||||
editUrl:
|
||||
'https://github.com/supabase/supabase/edit/master/apps/reference/',
|
||||
lastVersion: 'v1',
|
||||
lastVersion: 'current',
|
||||
versions: {
|
||||
current: {
|
||||
label: 'v2 RC',
|
||||
path: '/next',
|
||||
label: 'v2',
|
||||
},
|
||||
v1: {
|
||||
label: 'v1',
|
||||
@@ -124,18 +123,6 @@ const config = {
|
||||
},
|
||||
},
|
||||
],
|
||||
[
|
||||
'@docusaurus/plugin-content-docs',
|
||||
{
|
||||
id: '_auth_helpers',
|
||||
path: '_auth_helpers',
|
||||
routeBasePath: '/reference/auth-helpers',
|
||||
sidebarPath: require.resolve('./nav/auth_helpers_sidebars.js'),
|
||||
breadcrumbs: false,
|
||||
editUrl:
|
||||
'https://github.com/supabase/supabase/edit/master/apps/reference/',
|
||||
},
|
||||
],
|
||||
],
|
||||
|
||||
presets: [
|
||||
|
||||
@@ -156,7 +156,12 @@ const sidebars = {
|
||||
type: 'category',
|
||||
label: 'Edge Functions',
|
||||
collapsed: true,
|
||||
items: ['guides/functions', 'guides/functions/cicd-workflow'],
|
||||
items: [
|
||||
'guides/functions',
|
||||
'guides/functions/auth',
|
||||
'guides/functions/examples',
|
||||
'guides/functions/cicd-workflow',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
@@ -251,19 +256,20 @@ const sidebars = {
|
||||
{ type: 'link', label: 'Supabase API', href: '/reference/api' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Community',
|
||||
// link: { type: 'doc', id: 'about' },
|
||||
collapsed: false,
|
||||
items: [
|
||||
{
|
||||
type: 'link',
|
||||
label: 'Supabase Auth Helpers',
|
||||
href: '/reference/auth-helpers',
|
||||
},
|
||||
],
|
||||
},
|
||||
// TODO: add back in with typedocs
|
||||
// {
|
||||
// type: 'category',
|
||||
// label: 'Community',
|
||||
// // link: { type: 'doc', id: 'about' },
|
||||
// collapsed: false,
|
||||
// items: [
|
||||
// {
|
||||
// type: 'link',
|
||||
// label: 'Supabase Auth Helpers',
|
||||
// href: '/reference/auth-helpers',
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
type: 'category',
|
||||
label: 'Self-hosting',
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
/**
|
||||
* Creating a sidebar enables you to:
|
||||
- create an ordered group of docs
|
||||
- render a sidebar for each doc of that group
|
||||
- provide next/previous navigation
|
||||
|
||||
The sidebars can be generated from the filesystem, or explicitly defined here.
|
||||
|
||||
Create as many sidebars as you want.
|
||||
*/
|
||||
|
||||
// @ts-check
|
||||
|
||||
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
|
||||
const sidebars = {
|
||||
// By default, Docusaurus generates a sidebar from the docs folder structure
|
||||
// tutorialSidebar: [{ type: 'autogenerated', dirName: '.' }],
|
||||
|
||||
sidebar: [
|
||||
'intro',
|
||||
'next-js',
|
||||
'sveltekit',
|
||||
// 'usage',
|
||||
// 'config',
|
||||
// 'release-notes',
|
||||
// {
|
||||
// type: "category",
|
||||
// label: "Release Notes",
|
||||
// items: ["release-notes"],
|
||||
// },
|
||||
],
|
||||
}
|
||||
|
||||
module.exports = sidebars
|
||||
@@ -44,7 +44,7 @@ Writing SQL just got 10x easier. We added autocomplete to the SQL editor, includ
|
||||
|
||||
### Auth Redirects
|
||||
|
||||
Redirect your users to specific route within your site on [`signIn()`](/docs/reference/javascript/auth-signin#sign-in-with-redirect) and [`signUp()`](/docs/reference/javascript/auth-signup#sign-up-with-redirect).
|
||||
Redirect your users to specific route within your site on [`signIn()`](/docs/reference/javascript/v1/auth-signin#sign-in-with-redirect) and [`signUp()`](/docs/reference/javascript/auth-signup#sign-up-with-redirect).
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -302,7 +302,7 @@ Update today by running:
|
||||
npm i @supabase/supabase-js@rc
|
||||
```
|
||||
|
||||
You can find a full list of updates, breaking changes, and a migration guide in our [Release Notes](https://supabase.com/docs/reference/javascript/next/release-notes).
|
||||
You can find a full list of updates, breaking changes, and a migration guide in our [Release Notes](https://supabase.com/docs/reference/javascript/release-notes).
|
||||
|
||||
We'll continuing merging security fixes to v1, with maintenance patches for the next three months.
|
||||
|
||||
@@ -321,8 +321,8 @@ We'll continuing merging security fixes to v1, with maintenance patches for the
|
||||
|
||||
## Links
|
||||
|
||||
- [v2 Release Candidate Documentation](https://supabase.com/docs/reference/javascript/next)
|
||||
- [Release Notes & Migration Guide](https://supabase.com/docs/reference/javascript/next/release-notes)
|
||||
- [v2 Release Candidate Documentation](https://supabase.com/docs/reference/javascript)
|
||||
- [Release Notes & Migration Guide](https://supabase.com/docs/reference/javascript/release-notes)
|
||||
|
||||
## More Launch Week 5
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
title: 'Supabase Vault'
|
||||
description: "Today we're announcing Vault, a Postgres extension for managing secrets and encryption inside your database."
|
||||
author: michel
|
||||
image: lw5-vault/lw5-vault.jpg
|
||||
thumb: lw5-vault/lw5-vault.jpg
|
||||
image: lw5-vault/supabase-vault.jpg
|
||||
thumb: lw5-vault/supabase-vault.jpg
|
||||
tags:
|
||||
- launch-week
|
||||
date: '2022-08-19'
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
title: Choosing a Postgres Primary Key
|
||||
description: Turns out the question of which identifier to use as a Primary Key is complicated -- we're going to dive into some of the complexity and inherent trade-offs, and figure things out
|
||||
author: victor
|
||||
image: primary-keys/choosing-a-postgres-primary-key-og.jpg
|
||||
thumb: primary-keys/choosing-a-postgres-primary-key-og.jpg
|
||||
image: primary-keys/postgres-primary-key.jpg
|
||||
thumb: primary-keys/postgres-primary-key.jpg
|
||||
tags:
|
||||
- postgres
|
||||
date: '2022-09-08'
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
"products": [
|
||||
{
|
||||
"title": "Supabase.js v2 Docs",
|
||||
"url": "/docs/reference/javascript/next/installing",
|
||||
"url": "/docs/reference/javascript/installing",
|
||||
"description": "Updated documentation for supabase-js"
|
||||
},
|
||||
{
|
||||
"title": "Typescript support",
|
||||
"url": "/docs/reference/javascript/next/typescript-support",
|
||||
"url": "/docs/reference/javascript/typescript-support",
|
||||
"description": "Using types generated directly from your database."
|
||||
}
|
||||
]
|
||||
|
||||
@@ -255,7 +255,7 @@ module.exports = [
|
||||
{
|
||||
permanent: false,
|
||||
source: '/docs/client/auth-signin',
|
||||
destination: '/docs/reference/javascript/auth-signin',
|
||||
destination: '/docs/reference/javascript/v1/auth-signin',
|
||||
},
|
||||
{
|
||||
permanent: false,
|
||||
@@ -1331,7 +1331,7 @@ module.exports = [
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/javascript/next/migration-guide',
|
||||
destination: '/docs/reference/javascript/next/release-notes',
|
||||
destination: '/docs/reference/javascript/release-notes',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
@@ -1343,6 +1343,21 @@ module.exports = [
|
||||
source: '/docs/guides/client-libraries',
|
||||
destination: '/docs/reference',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/auth-helpers',
|
||||
destination: '/docs/guides/auth/auth-helpers/',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/auth-helpers/next-js',
|
||||
destination: '/docs/guides/auth/auth-helpers/nextjs',
|
||||
},
|
||||
{
|
||||
permanent: true,
|
||||
source: '/docs/reference/auth-helpers/sveltekit',
|
||||
destination: '/docs/guides/auth/auth-helpers/sveltekit',
|
||||
},
|
||||
|
||||
// V2 redirects
|
||||
// {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -2,10 +2,10 @@
|
||||
|
||||
To celebrate the support of GET requests in Supabase Edge Functions we're giving away some limited edition Functions Shirts!
|
||||
|
||||
To win a Supabase Edge Functions T-Shirt, make a GET request to https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition with your email address, your Twitter handle (optional), and the correct answer, e.g.
|
||||
To win a Supabase Edge Functions T-Shirt, make a GET request to https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition with your email address, your Twitter handle (optional), your t-shirt size (S-2XL) and the correct answer, e.g.
|
||||
|
||||
```text
|
||||
https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition?email=testr@test.de&twitter=thorwebdev&answer=20
|
||||
https://obuldanrptloktxcffvn.functions.supabase.co/get-tshirt-competition?email=testr@test.de&twitter=thorwebdev&size=2XL&answer=20
|
||||
```
|
||||
|
||||
You can read the functions source code to figure out the answer ;)
|
||||
|
||||
@@ -27,11 +27,17 @@ serve(async (req) => {
|
||||
const url = new URL(req.url)
|
||||
const email = url.searchParams.get('email')
|
||||
const twitter = url.searchParams.get('twitter')
|
||||
const size = url.searchParams.get('size')
|
||||
const answer = url.searchParams.get('answer')
|
||||
|
||||
if (!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email ?? '') || !answer)
|
||||
if (
|
||||
!/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(email ?? '') ||
|
||||
!answer ||
|
||||
!twitter ||
|
||||
!size
|
||||
)
|
||||
throw new Error(
|
||||
`You're missing either a valid 'email' and/or 'answer' param(s). HINT: https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/get-tshirt-competition/index.ts`
|
||||
`Please provide valid 'email', 'twitter', 'size', and 'answer' params. HINT: https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/get-tshirt-competition/index.ts`
|
||||
)
|
||||
|
||||
if (Number(answer) !== positionInAlphabet(email!))
|
||||
@@ -50,6 +56,7 @@ serve(async (req) => {
|
||||
{
|
||||
email,
|
||||
twitter,
|
||||
size,
|
||||
},
|
||||
{ onConflict: 'email' }
|
||||
)
|
||||
@@ -71,4 +78,4 @@ serve(async (req) => {
|
||||
})
|
||||
|
||||
// To invoke:
|
||||
// curl -i --location --request GET 'http://localhost:54321/functions/v1/get-tshirt-competition?email=testr@test.de&answer=20'
|
||||
// curl -i --location --request GET 'http://localhost:54321/functions/v1/get-tshirt-competition?email=testr@test.de&twitter=thorwebdev&size=2XL&answer=20'
|
||||
@@ -102,6 +102,9 @@ module.exports = ui({
|
||||
border: '1px solid ' + theme('borderColor.DEFAULT'),
|
||||
borderRadius: theme('borderRadius.lg'),
|
||||
},
|
||||
td: {
|
||||
borderBottom: '1px solid ' + theme('colors.scale[400]'),
|
||||
},
|
||||
code: {
|
||||
fontWeight: '400',
|
||||
padding: '0.2rem 0.4rem',
|
||||
|
||||
@@ -63702,7 +63702,7 @@
|
||||
"kindString": "Type parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/next/typescript-support)\n"
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/typescript-support)\n"
|
||||
},
|
||||
"default": {
|
||||
"type": "intrinsic",
|
||||
|
||||
@@ -1717,7 +1717,7 @@
|
||||
"kindString": "Type parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/next/typescript-support)\n"
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/typescript-support)\n"
|
||||
},
|
||||
"default": {
|
||||
"type": "intrinsic",
|
||||
|
||||
@@ -1792,7 +1792,7 @@
|
||||
"kindString": "Type parameter",
|
||||
"flags": {},
|
||||
"comment": {
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/next/typescript-support)\n"
|
||||
"shortText": "Types for the schema from the [type\ngenerator](https://supabase.com/docs/reference/javascript/typescript-support)\n"
|
||||
},
|
||||
"default": {
|
||||
"type": "intrinsic",
|
||||
|
||||
@@ -58,10 +58,10 @@ pages:
|
||||
- name: Sign up with third-party providers.
|
||||
hideCodeBlock: true
|
||||
description: |
|
||||
You can sign up with OAuth providers using the [`signIn()`](/docs/reference/javascript/auth-signin#sign-in-using-third-party-providers) method.
|
||||
You can sign up with OAuth providers using the [`signIn()`](/docs/reference/javascript/v1/auth-signin#sign-in-using-third-party-providers) method.
|
||||
- name: Sign up with Phone.
|
||||
description: |
|
||||
Supabase supports Phone Auth. After a user has verified their number, they can use the [`signIn()`](/docs/reference/javascript/auth-signin#sign-in-using-phone) method.
|
||||
Supabase supports Phone Auth. After a user has verified their number, they can use the [`signIn()`](/docs/reference/javascript/v1/auth-signin#sign-in-using-phone) method.
|
||||
js: |
|
||||
```js
|
||||
const { user, session, error } = await supabase.auth.signUp({
|
||||
|
||||
@@ -93,7 +93,7 @@ pages:
|
||||
- If signUp() is called for an existing confirmed user:
|
||||
- If **Confirm email** is enabled in [your project](https://app.supabase.com/project/_/auth/settings), an obfuscated/fake user object is returned.
|
||||
- If **Confirm email** is disabled, the error message, `User already registered` is returned.
|
||||
- To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/next/auth-getuser).
|
||||
- To fetch the currently logged-in user, refer to [`getUser()`](/docs/reference/javascript/auth-getuser).
|
||||
examples:
|
||||
- name: Sign up.
|
||||
isSpotlight: true
|
||||
@@ -641,8 +641,8 @@ pages:
|
||||
$ref: '@supabase/postgrest-js.PostgrestQueryBuilder.select'
|
||||
notes: |
|
||||
- By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](https://app.supabase.com/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data.
|
||||
- `select()` can be combined with [Filters](/docs/reference/javascript/next/using-filters)
|
||||
- `select()` can be combined with [Modifiers](/docs/reference/javascript/next/using-modifiers)
|
||||
- `select()` can be combined with [Filters](/docs/reference/javascript/using-filters)
|
||||
- `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers)
|
||||
- `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/hosting/platform) and should be avoided as a column name](https://github.com/supabase/supabase/issues/5465).
|
||||
examples:
|
||||
- name: Getting your data
|
||||
@@ -1088,7 +1088,7 @@ pages:
|
||||
- name: Querying with count option
|
||||
description: |
|
||||
You can get the number of rows by using the
|
||||
[count](/docs/reference/javascript/next/select#parameters) option. For
|
||||
[count](/docs/reference/javascript/select#parameters) option. For
|
||||
example, to get the table count without returning all rows:
|
||||
|
||||
<Tabs>
|
||||
@@ -1367,7 +1367,7 @@ pages:
|
||||
title: 'Modify data: update()'
|
||||
$ref: '@supabase/postgrest-js.PostgrestQueryBuilder.update'
|
||||
notes: |
|
||||
- `update()` should always be combined with [Filters](/docs/reference/javascript/next/using-filters) to target the item(s) you wish to update.
|
||||
- `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update.
|
||||
examples:
|
||||
- name: Updating your data
|
||||
description: |
|
||||
@@ -1758,7 +1758,7 @@ pages:
|
||||
title: 'Delete data: delete()'
|
||||
$ref: '@supabase/postgrest-js.PostgrestQueryBuilder.delete'
|
||||
notes: |
|
||||
- `delete()` should always be combined with [filters](/docs/reference/javascript/next/using-filters) to target the item(s) you wish to delete.
|
||||
- `delete()` should always be combined with [filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to delete.
|
||||
- If you use `delete()` with filters and you have
|
||||
[RLS](/docs/learn/auth-deep-dive/auth-row-level-security) enabled, only
|
||||
rows visible through `SELECT` policies are deleted. Note that by default
|
||||
@@ -1958,8 +1958,8 @@ pages:
|
||||
- name: Call a Postgres function with filters
|
||||
description: |
|
||||
Postgres functions that return tables can also be combined with
|
||||
[Filters](/docs/reference/javascript/next/using-filters) and
|
||||
[Modifiers](/docs/reference/javascript/next/using-modifiers).
|
||||
[Filters](/docs/reference/javascript/using-filters) and
|
||||
[Modifiers](/docs/reference/javascript/using-modifiers).
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="schema" label="Schema">
|
||||
@@ -2019,7 +2019,7 @@ pages:
|
||||
|
||||
Filters must be applied after any of `select()`, `update()`, `upsert()`,
|
||||
`delete()`, and `rpc()` and before
|
||||
[modifiers](/docs/reference/javascript/next/using-modifiers).
|
||||
[modifiers](/docs/reference/javascript/using-modifiers).
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase
|
||||
|
||||
@@ -112,12 +112,13 @@ const RowHeader: FC<RowHeaderProps> = ({ sorts, filters }) => {
|
||||
}
|
||||
|
||||
const onRowsDelete = () => {
|
||||
const numRows = allRowsSelected ? totalRows : selectedRows.size
|
||||
confirmAlert({
|
||||
title: 'Confirm to delete',
|
||||
message: `Are you sure you want to delete the selected ${selectedRows.size} row${
|
||||
selectedRows.size > 1 ? 's' : ''
|
||||
message: `Are you sure you want to delete the selected ${numRows} row${
|
||||
numRows > 1 ? 's' : ''
|
||||
}? This action cannot be undone.`,
|
||||
confirmText: `Delete ${selectedRows.size} rows`,
|
||||
confirmText: `Delete ${numRows} rows`,
|
||||
onAsyncConfirm: async () => {
|
||||
if (allRowsSelected) {
|
||||
const { error } =
|
||||
|
||||
@@ -27,10 +27,10 @@ const PolicyRow: FC<Props> = ({
|
||||
'w-full space-x-4 border-b py-4 lg:items-center',
|
||||
].join(' ')}
|
||||
>
|
||||
<div className="flex grow flex-col space-y-1 truncate">
|
||||
<div className="flex grow flex-col space-y-1">
|
||||
<div className="flex items-center space-x-4">
|
||||
<p className="font-mono text-xs text-scale-1000">{policy.command}</p>
|
||||
<p className="max-w-xs truncate text-sm text-scale-1200">{policy.name}</p>
|
||||
<p className="text-sm text-scale-1200">{policy.name}</p>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<p className="text-scale-1000 text-sm">Applied to:</p>
|
||||
|
||||
@@ -1,23 +1,37 @@
|
||||
import { FC } from 'react'
|
||||
import { IconAlertCircle } from 'ui'
|
||||
import InformationBox from 'components/ui/InformationBox'
|
||||
import { MemberWithFreeProjectLimit } from 'hooks'
|
||||
|
||||
interface Props {
|
||||
limit: number
|
||||
membersExceededLimit: MemberWithFreeProjectLimit[]
|
||||
}
|
||||
|
||||
const FreeProjectLimitWarning: FC<Props> = ({ limit }) => {
|
||||
const FreeProjectLimitWarning: FC<Props> = ({ membersExceededLimit }) => {
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<InformationBox
|
||||
icon={<IconAlertCircle className="text-scale-1200" size="large" strokeWidth={1.5} />}
|
||||
defaultVisibility={true}
|
||||
hideCollapse
|
||||
title="Your account has reached its free project limit"
|
||||
title="The organization has members who have exceeded their free project limits"
|
||||
description={
|
||||
<div className="space-y-3">
|
||||
<p className="text-sm leading-normal">
|
||||
{`Your account can only have up to ${limit} free projects - to create another free project, you'll need to delete or pause an existing free project first. Otherwise, you may create a project on the Pro tier instead.`}
|
||||
The following members have reached their maximum limits for the number of active free
|
||||
tier projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="list-disc pl-5">
|
||||
{membersExceededLimit.map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit} free
|
||||
projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm leading-normal">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to create a free project within this organization.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,9 +13,10 @@ interface Props {
|
||||
const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
|
||||
const defaultHelper = getDefaultHelper(helpers)
|
||||
const [helperValue, setHelperValue] = useState<string>(to || from ? '' : defaultHelper.text)
|
||||
|
||||
const handleHelperChange = (newValue: string) => {
|
||||
setHelperValue(newValue)
|
||||
const selectedHelper = helpers.find((h) => h.text === newValue)
|
||||
|
||||
if (onChange && selectedHelper) {
|
||||
onChange({ to: selectedHelper.calcTo(), from: selectedHelper.calcFrom() })
|
||||
}
|
||||
@@ -28,6 +29,7 @@ const DatePickers: React.FC<Props> = ({ to, from, onChange, helpers }) => {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedHelper && helperValue !== selectedHelper.text) {
|
||||
setHelperValue(selectedHelper.text)
|
||||
|
||||
@@ -21,7 +21,7 @@ export const TEMPLATES: LogTemplate[] = [
|
||||
description: 'Count of commits made by users on the database',
|
||||
mode: 'custom',
|
||||
searchString: `select
|
||||
p.user_name,
|
||||
p.user_name,
|
||||
count(*) as count
|
||||
from postgres_logs
|
||||
left join unnest(metadata) as m on true
|
||||
@@ -38,7 +38,7 @@ group by
|
||||
description: 'List all IP addresses that used the Supabase API',
|
||||
mode: 'custom',
|
||||
searchString: `select
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
h.x_real_ip
|
||||
from edge_logs
|
||||
left join unnest(metadata) as m on true
|
||||
@@ -52,8 +52,8 @@ where h.x_real_ip is not null
|
||||
label: 'Requests by Country',
|
||||
description: 'List all ISO 3166-1 alpha-2 country codes that used the Supabase API',
|
||||
mode: 'custom',
|
||||
searchString: `select
|
||||
cf.country,
|
||||
searchString: `select
|
||||
cf.country,
|
||||
count(*) as count
|
||||
from edge_logs
|
||||
left join unnest(metadata) as m on true
|
||||
@@ -71,11 +71,11 @@ order by
|
||||
mode: 'custom',
|
||||
description: 'List all Supabase API requests that are slow',
|
||||
searchString: `select
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
event_message,
|
||||
r.origin_time
|
||||
from edge_logs
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.response) as r
|
||||
where
|
||||
r.origin_time > 1000
|
||||
@@ -90,11 +90,11 @@ limit 100
|
||||
description: 'List all Supabase API requests that responded witha 5XX status code',
|
||||
mode: 'custom',
|
||||
searchString: `select
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
event_message,
|
||||
r.status_code
|
||||
from edge_logs
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.response) as r
|
||||
where
|
||||
r.status_code >= 500
|
||||
@@ -113,11 +113,11 @@ limit 100
|
||||
r.search as params,
|
||||
count(timestamp) as c
|
||||
from edge_logs
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.request) as r
|
||||
group by
|
||||
group by
|
||||
path,
|
||||
params
|
||||
params
|
||||
order by
|
||||
c desc
|
||||
limit 100
|
||||
@@ -132,7 +132,7 @@ limit 100
|
||||
cast(timestamp as datetime) as timestamp,
|
||||
event_message
|
||||
from edge_logs
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.request) as r
|
||||
where
|
||||
path like '%rest/v1%'
|
||||
@@ -453,14 +453,14 @@ export const PREVIEWER_DATEPICKER_HELPERS: DatetimeHelper[] = [
|
||||
calcTo: () => '',
|
||||
},
|
||||
{
|
||||
text: 'Last day',
|
||||
text: 'Last 24 hours',
|
||||
calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(),
|
||||
calcTo: () => '',
|
||||
},
|
||||
]
|
||||
export const EXPLORER_DATEPICKER_HELPERS: DatetimeHelper[] = [
|
||||
{
|
||||
text: 'Last day',
|
||||
text: 'Last 24 hours',
|
||||
calcFrom: () => dayjs().subtract(1, 'day').startOf('day').toISOString(),
|
||||
calcTo: () => '',
|
||||
default: true,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Filters, LogData, LogsEndpointParams, LogsTableName, SQL_FILTER_TEMPLATES } from '.'
|
||||
import dayjs, { Dayjs } from 'dayjs'
|
||||
import { get } from 'lodash'
|
||||
import { StripeSubscription } from 'components/interfaces/Billing'
|
||||
|
||||
/**
|
||||
* Convert a micro timestamp from number/string to iso timestamp
|
||||
@@ -130,10 +131,10 @@ export const genDefaultQuery = (table: LogsTableName, filters: Filters) => {
|
||||
`
|
||||
|
||||
case 'postgres_logs':
|
||||
return `select postgres_logs.timestamp, id, event_message, parsed.error_severity from ${table}
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.parsed) as parsed
|
||||
${where}
|
||||
return `select postgres_logs.timestamp, id, event_message, parsed.error_severity from ${table}
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.parsed) as parsed
|
||||
${where}
|
||||
limit 100
|
||||
`
|
||||
|
||||
@@ -145,7 +146,7 @@ export const genDefaultQuery = (table: LogsTableName, filters: Filters) => {
|
||||
`
|
||||
|
||||
case 'function_edge_logs':
|
||||
return `select id, ${table}.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from ${table}
|
||||
return `select id, ${table}.timestamp, event_message, response.status_code, request.method, m.function_id, m.execution_time_ms, m.deployment_id, m.version from ${table}
|
||||
cross join unnest(metadata) as m
|
||||
cross join unnest(m.response) as response
|
||||
cross join unnest(m.request) as request
|
||||
@@ -156,7 +157,7 @@ export const genDefaultQuery = (table: LogsTableName, filters: Filters) => {
|
||||
default:
|
||||
return `select id, ${table}.timestamp, event_message from ${table}
|
||||
${where}
|
||||
limit 100
|
||||
limit 100
|
||||
`
|
||||
}
|
||||
}
|
||||
@@ -167,6 +168,18 @@ export const genDefaultQuery = (table: LogsTableName, filters: Filters) => {
|
||||
export const genSingleLogQuery = (table: LogsTableName, id: string) =>
|
||||
`select id, timestamp, event_message, metadata from ${table} where id = '${id}' limit 1`
|
||||
|
||||
/**
|
||||
* Determine if we should show the user an upgrade prompt while browsing logs
|
||||
*/
|
||||
export const maybeShowUpgradePrompt = (
|
||||
from: string | null | undefined,
|
||||
tierKey?: StripeSubscription['tier']["key"]
|
||||
) => {
|
||||
const day = Math.abs(dayjs().diff(dayjs(from), 'day'))
|
||||
|
||||
return (day > 1 && tierKey === 'FREE') || (day > 7 && tierKey === 'PRO') || day > 90 && tierKey === 'ENTERPRISE'
|
||||
}
|
||||
|
||||
export const genCountQuery = (table: string): string => `SELECT count(*) as count FROM ${table}`
|
||||
|
||||
/** calculates how much the chart start datetime should be offset given the current datetime filter params */
|
||||
@@ -209,11 +222,10 @@ SELECT
|
||||
FROM
|
||||
${table} t
|
||||
cross join unnest(t.metadata) as metadata
|
||||
${
|
||||
where
|
||||
${where
|
||||
? where + ` and t.timestamp > '${startOffset.toISOString()}'`
|
||||
: `where t.timestamp > '${startOffset.toISOString()}'`
|
||||
}
|
||||
}
|
||||
GROUP BY
|
||||
timestamp
|
||||
ORDER BY
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { IconAlertCircle, IconRewind, Button, Card, Input } from 'ui'
|
||||
import { IconRewind, Button } from 'ui'
|
||||
|
||||
import {
|
||||
LogTable,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
LogEventChart,
|
||||
Filters,
|
||||
ensureNoTimestampConflict,
|
||||
maybeShowUpgradePrompt,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import useLogsPreview from 'hooks/analytics/useLogsPreview'
|
||||
import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel'
|
||||
@@ -19,6 +20,9 @@ import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilte
|
||||
import { LOGS_TABLES } from './Logs.constants'
|
||||
import ShimmerLine from 'components/ui/ShimmerLine'
|
||||
import LoadingOpacity from 'components/ui/LoadingOpacity'
|
||||
import { useProjectSubscription } from 'hooks'
|
||||
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
|
||||
import { StripeProduct } from 'components/interfaces/Billing'
|
||||
import UpgradePrompt from './UpgradePrompt'
|
||||
|
||||
/**
|
||||
@@ -46,8 +50,10 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
tableName,
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const { s, ite, its } = router.query
|
||||
const { s, ite, its, ref } = router.query
|
||||
const [showChart, setShowChart] = useState(true)
|
||||
const { subscription } = useProjectSubscription(ref as string)
|
||||
const tier = subscription?.tier
|
||||
|
||||
const table = !tableName ? LOGS_TABLES[queryType] : tableName
|
||||
|
||||
@@ -56,6 +62,10 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
{ loadOlder, setFilters, refresh, setParams },
|
||||
] = useLogsPreview(projectRef as string, table, filterOverride)
|
||||
|
||||
const { showUpgradePrompt, setShowUpgradePrompt } = useUpgradePrompt(
|
||||
params.iso_timestamp_start as string
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setFilters((prev) => ({ ...prev, search_query: s as string }))
|
||||
if (ite || its) {
|
||||
@@ -67,6 +77,16 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
}
|
||||
}, [s, ite, its])
|
||||
|
||||
// Show the prompt on page load based on query params
|
||||
useEffect(() => {
|
||||
if (its) {
|
||||
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, tier?.key)
|
||||
if (shouldShowUpgradePrompt) {
|
||||
setShowUpgradePrompt(!showUpgradePrompt)
|
||||
}
|
||||
}
|
||||
}, [its, tier])
|
||||
|
||||
const onSelectTemplate = (template: LogTemplate) => {
|
||||
setFilters((prev: any) => ({ ...prev, search_query: template.searchString }))
|
||||
}
|
||||
@@ -109,19 +129,25 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
},
|
||||
})
|
||||
} else if (event === 'datepicker-change') {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
iso_timestamp_start: from || '',
|
||||
iso_timestamp_end: to || '',
|
||||
}))
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
its: from || '',
|
||||
ite: to || '',
|
||||
},
|
||||
})
|
||||
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, tier?.key)
|
||||
|
||||
if (shouldShowUpgradePrompt) {
|
||||
setShowUpgradePrompt(!showUpgradePrompt)
|
||||
} else {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
iso_timestamp_start: from || '',
|
||||
iso_timestamp_end: to || '',
|
||||
}))
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: {
|
||||
...router.query,
|
||||
its: from || '',
|
||||
ite: to || '',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +224,9 @@ export const LogsPreviewer: React.FC<Props> = ({
|
||||
<Button onClick={loadOlder} icon={<IconRewind />} type="default">
|
||||
Load older
|
||||
</Button>
|
||||
<UpgradePrompt projectRef={projectRef} from={params.iso_timestamp_start || ''} />
|
||||
<div className="mt-2 flex flex-row justify-end">
|
||||
<UpgradePrompt show={showUpgradePrompt} setShowUpgradePrompt={setShowUpgradePrompt} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -51,11 +51,7 @@ const LogsQueryPanel: React.FC<Props> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className="
|
||||
rounded
|
||||
rounded-bl-none rounded-br-none border border-panel-border-light bg-panel-header-light
|
||||
dark:border-panel-border-dark dark:bg-panel-header-dark
|
||||
|
||||
className=" rounded rounded-bl-none rounded-br-none border border-panel-border-light bg-panel-header-light dark:border-panel-border-dark dark:bg-panel-header-dark
|
||||
"
|
||||
>
|
||||
<div className="flex w-full items-center justify-between px-5 py-2">
|
||||
|
||||
@@ -1,101 +1,70 @@
|
||||
import Link from 'next/link'
|
||||
import dayjs from 'dayjs'
|
||||
import { useState } from 'react'
|
||||
import { Button, IconHelpCircle, IconLoader, Modal } from 'ui'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
import { TIER_QUERY_LIMITS } from '.'
|
||||
import { useProjectSubscription } from 'hooks'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
interface Props {
|
||||
projectRef: string
|
||||
from: string
|
||||
show: boolean
|
||||
setShowUpgradePrompt: (value: boolean) => void
|
||||
}
|
||||
|
||||
const UpgradePrompt: React.FC<Props> = ({ projectRef, from }) => {
|
||||
const [showHelperModal, setShowHelperModal] = useState(false)
|
||||
const { subscription, isLoading, isError } = useProjectSubscription(projectRef)
|
||||
|
||||
if (isLoading) return <IconLoader size={16} className="animate-spin" />
|
||||
if (isError) console.error('Error fetching project subscription')
|
||||
|
||||
const tier = subscription?.tier
|
||||
const queryLimit = TIER_QUERY_LIMITS[(tier?.key || 'FREE') as keyof typeof TIER_QUERY_LIMITS]
|
||||
|
||||
const fromValue = from ? dayjs(from) : dayjs()
|
||||
const fromMax = dayjs().startOf('day').subtract(queryLimit.value, queryLimit.unit)
|
||||
const isExceedingLimit = fromValue.isBefore(fromMax)
|
||||
const UpgradePrompt: React.FC<Props> = ({ show, setShowUpgradePrompt }) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`flex flex-row items-center gap-3 px-2 py-1 text-xs transition-all ${
|
||||
isExceedingLimit
|
||||
? 'rounded border border-yellow-700 bg-yellow-200 font-semibold text-yellow-1100'
|
||||
: ''
|
||||
}`}
|
||||
>
|
||||
<span className="text-scale-1100">{`${queryLimit.text} retention`}</span>
|
||||
<IconHelpCircle
|
||||
size={16}
|
||||
strokeWidth={1.5}
|
||||
className="cursor-pointer text-scale-1100 transition hover:text-scale-1200"
|
||||
onClick={() => setShowHelperModal(true)}
|
||||
/>
|
||||
{queryLimit.promptUpgrade && (
|
||||
<Link href={`/project/${projectRef}/settings/billing`}>
|
||||
<Button size="tiny">Upgrade</Button>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
hideFooter
|
||||
visible={showHelperModal}
|
||||
size="medium"
|
||||
header="Log retention"
|
||||
onCancel={() => setShowHelperModal(false)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Logs can be retained up to a duration of 3 months depending on the plan that your
|
||||
project is on. The table below shows an overview of the duration for which your logs
|
||||
will be retained for based on each plan.
|
||||
</p>
|
||||
<div className="rounded border border-scale-600 bg-scale-500">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="w-[40%] text-sm text-scale-1100">Plan</p>
|
||||
<p className="w-[60%] text-sm text-scale-1100">Retention duration</p>
|
||||
<Modal
|
||||
hideFooter
|
||||
visible={show}
|
||||
closable
|
||||
size="medium"
|
||||
header="Log retention"
|
||||
onCancel={() => setShowUpgradePrompt(false)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-4">
|
||||
<p className="text-sm">
|
||||
Logs can be retained up to a duration of 3 months depending on the plan that your
|
||||
project is on.
|
||||
</p>
|
||||
<div className="border-scale-600 bg-scale-500 rounded border">
|
||||
<div className="flex items-center px-4 pt-2 pb-1">
|
||||
<p className="text-scale-1100 w-[40%] text-sm">Plan</p>
|
||||
<p className="text-scale-1100 w-[60%] text-sm">Retention duration</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Free</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.FREE.text}</p>
|
||||
</div>
|
||||
<div className="py-1">
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Free</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.FREE.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Pro</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.PRO.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Enterprise</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.ENTERPRISE.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Pro</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.PRO.text}</p>
|
||||
</div>
|
||||
<div className="flex items-center px-4 py-1">
|
||||
<p className="w-[40%] text-sm">Enterprise</p>
|
||||
<p className="w-[60%] text-sm">{TIER_QUERY_LIMITS.ENTERPRISE.text}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Seperator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button block type="primary" onClick={() => setShowHelperModal(false)}>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Seperator />
|
||||
<Modal.Content>
|
||||
<div className="flex justify-between">
|
||||
<Button type="default" onClick={() => setShowUpgradePrompt(false)}>
|
||||
Close
|
||||
</Button>
|
||||
|
||||
<Link href={`/project/${ref}/settings/billing`}>
|
||||
<Button size="tiny">Upgrade</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
-4
@@ -127,10 +127,6 @@ const parseDescription = (description: string | null) => {
|
||||
}
|
||||
|
||||
const convertPostgresDatetimeToInputDatetime = (format: string, value: string) => {
|
||||
if (value) {
|
||||
value = value.includes('-') ? value.replaceAll('-', '/') : value
|
||||
}
|
||||
|
||||
if (!value || value.length == 0) return ''
|
||||
if (TIMESTAMP_TYPES.includes(format)) {
|
||||
return dayjs(value).format('YYYY-MM-DDTHH:mm:ss')
|
||||
|
||||
@@ -27,6 +27,7 @@ const AccountLayout: FC<Props> = ({ children, title, breadcrumbs }) => {
|
||||
|
||||
const onClickLogout = async () => {
|
||||
await auth.signOut()
|
||||
localStorage.clear();
|
||||
router.reload()
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@ import { Route } from 'components/ui/ui.types'
|
||||
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
|
||||
|
||||
export const generateProductRoutes = (ref?: string, project?: ProjectBase): Route[] => {
|
||||
const isProjectBuilding = project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY
|
||||
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
|
||||
const isProjectPaused = project?.status === PROJECT_STATUS.INACTIVE
|
||||
|
||||
const homeUrl = `/project/${ref}`
|
||||
const buildingUrl = `/project/${ref}/building`
|
||||
|
||||
return [
|
||||
@@ -29,13 +32,21 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
|
||||
preProcessor={(code) => code.replace(/svg/, 'svg class="m-auto text-color-inherit"')}
|
||||
/>
|
||||
),
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/editor`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused ? homeUrl : isProjectBuilding ? buildingUrl : `/project/${ref}/editor`),
|
||||
},
|
||||
{
|
||||
key: 'auth',
|
||||
label: 'Authentication',
|
||||
icon: <IconUsers size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/auth/users`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/auth/users`),
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
@@ -43,7 +54,13 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
|
||||
key: 'storage',
|
||||
label: 'Storage',
|
||||
icon: <IconArchive size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/storage/buckets`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/storage/buckets`),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -57,13 +74,21 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
|
||||
preProcessor={(code) => code.replace(/svg/, 'svg class="m-auto text-color-inherit"')}
|
||||
/>
|
||||
),
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/sql`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused ? homeUrl : isProjectBuilding ? buildingUrl : `/project/${ref}/sql`),
|
||||
},
|
||||
{
|
||||
key: 'database',
|
||||
label: 'Database',
|
||||
icon: <IconDatabase size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/database/tables`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/database/tables`),
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
@@ -71,7 +96,13 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
|
||||
key: 'functions',
|
||||
label: 'Edge Functions',
|
||||
icon: <IconCode size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/functions`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/functions`),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -79,7 +110,10 @@ export const generateProductRoutes = (ref?: string, project?: ProjectBase): Rout
|
||||
}
|
||||
|
||||
export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[] => {
|
||||
const isProjectBuilding = project?.status !== PROJECT_STATUS.ACTIVE_HEALTHY
|
||||
const isProjectBuilding = project?.status === PROJECT_STATUS.COMING_UP
|
||||
const isProjectPaused = project?.status === PROJECT_STATUS.INACTIVE
|
||||
|
||||
const homeUrl = `/project/${ref}`
|
||||
const buildingUrl = `/project/${ref}/building`
|
||||
|
||||
return [
|
||||
@@ -89,7 +123,13 @@ export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[
|
||||
key: 'logs-explorer',
|
||||
label: 'Logs Explorer',
|
||||
icon: <IconList size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/logs-explorer`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/logs-explorer`),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -99,7 +139,13 @@ export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[
|
||||
key: 'reports',
|
||||
label: 'Reports',
|
||||
icon: <IconBarChart size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/reports`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused
|
||||
? homeUrl
|
||||
: isProjectBuilding
|
||||
? buildingUrl
|
||||
: `/project/${ref}/reports`),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@@ -107,7 +153,9 @@ export const generateOtherRoutes = (ref?: string, project?: ProjectBase): Route[
|
||||
key: 'api',
|
||||
label: 'API',
|
||||
icon: <IconFileText size={18} strokeWidth={2} />,
|
||||
link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/api`),
|
||||
link:
|
||||
ref &&
|
||||
(isProjectPaused ? homeUrl : isProjectBuilding ? buildingUrl : `/project/${ref}/api`),
|
||||
},
|
||||
...(IS_PLATFORM
|
||||
? [
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { FC, useState } from 'react'
|
||||
import * as Tooltip from '@radix-ui/react-tooltip'
|
||||
import { Alert, Button, IconPauseCircle } from 'ui'
|
||||
import { Modal, Button, IconPauseCircle } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { Project } from 'types'
|
||||
import { checkPermissions, useStore, useSubscriptionStats, useFlag } from 'hooks'
|
||||
import { checkPermissions, useStore, useFlag, useFreeProjectLimitCheck } from 'hooks'
|
||||
import { post } from 'lib/common/fetch'
|
||||
import { API_URL, PROJECT_STATUS, DEFAULT_FREE_PROJECTS_LIMIT } from 'lib/constants'
|
||||
import { API_URL, PROJECT_STATUS } from 'lib/constants'
|
||||
import { DeleteProjectButton } from 'components/interfaces/Settings/General'
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
|
||||
@@ -16,16 +16,17 @@ interface Props {
|
||||
|
||||
const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
const { ui, app } = useStore()
|
||||
const subscriptionStats = useSubscriptionStats()
|
||||
const { total_active_free_projects: totalActiveFreeProjects } = subscriptionStats
|
||||
const freeProjectsLimit = ui.profile?.free_project_limit ?? DEFAULT_FREE_PROJECTS_LIMIT
|
||||
|
||||
const enablePermissions = useFlag('enablePermissions')
|
||||
const kpsEnabled = useFlag('initWithKps')
|
||||
const isOwner = ui.selectedOrganization?.is_owner
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
|
||||
const kpsEnabled = useFlag('initWithKps')
|
||||
const enablePermissions = useFlag('enablePermissions')
|
||||
|
||||
const [showConfirmRestore, setShowConfirmRestore] = useState(false)
|
||||
const hasExceedActiveFreeProjectsLimit = totalActiveFreeProjects >= freeProjectsLimit
|
||||
const [showFreeProjectLimitWarning, setShowFreeProjectLimitWarning] = useState(false)
|
||||
|
||||
const { membersExceededLimit } = useFreeProjectLimitCheck(orgSlug)
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
|
||||
const canResumeProject = enablePermissions
|
||||
? checkPermissions(PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.initialize_or_resume')
|
||||
@@ -33,12 +34,12 @@ const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
|
||||
const onSelectRestore = () => {
|
||||
if (!canResumeProject) {
|
||||
return ui.setNotification({
|
||||
ui.setNotification({
|
||||
category: 'error',
|
||||
message: 'You do not have the required permissions to restore this project',
|
||||
})
|
||||
}
|
||||
setShowConfirmRestore(true)
|
||||
} else if (hasMembersExceedingFreeTierLimit) setShowFreeProjectLimitWarning(true)
|
||||
else setShowConfirmRestore(true)
|
||||
}
|
||||
|
||||
const onConfirmRestore = async () => {
|
||||
@@ -50,17 +51,6 @@ const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="space-y-4">
|
||||
{hasExceedActiveFreeProjectsLimit && (
|
||||
<div className="mx-6">
|
||||
<Alert
|
||||
withIcon
|
||||
variant="warning"
|
||||
title={`Your account can only have ${freeProjectsLimit} active free projects`}
|
||||
>
|
||||
To restore this project you'll need to pause or delete an existing free project.
|
||||
</Alert>
|
||||
</div>
|
||||
)}
|
||||
<div className="mx-auto mb-16 w-full max-w-7xl">
|
||||
<div className="mx-6 flex h-[500px] items-center justify-center rounded border border-scale-400 bg-scale-300 p-8">
|
||||
<div className="grid w-[420px] gap-4">
|
||||
@@ -76,7 +66,7 @@ const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
<Button
|
||||
size="tiny"
|
||||
type="primary"
|
||||
disabled={hasExceedActiveFreeProjectsLimit || !canResumeProject}
|
||||
disabled={!canResumeProject}
|
||||
onClick={onSelectRestore}
|
||||
>
|
||||
Restore project
|
||||
@@ -101,11 +91,9 @@ const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
<DeleteProjectButton type="default" />
|
||||
</div>
|
||||
|
||||
{!hasExceedActiveFreeProjectsLimit && (
|
||||
<p className="mt-4 text-sm text-scale-1000">
|
||||
Restore this project and get back to building the next big thing!
|
||||
</p>
|
||||
)}
|
||||
<p className="mt-4 text-sm text-scale-1000">
|
||||
Restore this project and get back to building the next big thing!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,6 +107,49 @@ const ProjectPausedState: FC<Props> = ({ project }) => {
|
||||
onSelectCancel={() => setShowConfirmRestore(false)}
|
||||
onSelectConfirm={onConfirmRestore}
|
||||
/>
|
||||
<Modal
|
||||
hideFooter
|
||||
visible={showFreeProjectLimitWarning}
|
||||
size="medium"
|
||||
header="Your organization has members who have exceeded their free project limits"
|
||||
onCancel={() => setShowFreeProjectLimitWarning(false)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-scale-1100">
|
||||
The following members have reached their maximum limits for the number of active
|
||||
free tier projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="list-disc pl-5 text-sm text-scale-1100">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit}{' '}
|
||||
free projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-scale-1100">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to unpause this project.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Seperator />
|
||||
<Modal.Content>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
htmlType="button"
|
||||
type="default"
|
||||
onClick={() => setShowFreeProjectLimitWarning(false)}
|
||||
block
|
||||
>
|
||||
Understood
|
||||
</Button>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useProjectSubscription } from 'hooks'
|
||||
import { useRouter } from 'next/router'
|
||||
import { maybeShowUpgradePrompt } from 'components/interfaces/Settings/Logs'
|
||||
import { StripeProduct } from 'components/interfaces/Billing'
|
||||
|
||||
export const useUpgradePrompt = (from: string) => {
|
||||
const router = useRouter()
|
||||
const { ref } = router.query
|
||||
const { subscription } = useProjectSubscription(ref as string)
|
||||
const tier = subscription?.tier
|
||||
const [showUpgradePrompt, setShowUpgradePrompt] = useState(false)
|
||||
|
||||
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, tier?.key)
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldShowUpgradePrompt) {
|
||||
setShowUpgradePrompt(true)
|
||||
}
|
||||
}, [from])
|
||||
|
||||
return { showUpgradePrompt, setShowUpgradePrompt, shouldShowUpgradePrompt }
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './useJwtSecretUpdateStatus'
|
||||
export * from './useOrganizationDetail'
|
||||
export * from './useOrganizationRoles'
|
||||
export * from './useFreeProjectLimitCheck'
|
||||
export * from './usePermissions'
|
||||
export * from './useProfile'
|
||||
export * from './useProjectAuthConfig'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import useSWR from 'swr'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import { API_URL } from 'lib/constants'
|
||||
|
||||
export interface MemberWithFreeProjectLimit {
|
||||
free_project_limit: number
|
||||
primary_email: string
|
||||
username: string
|
||||
}
|
||||
|
||||
export function useFreeProjectLimitCheck(slug?: string) {
|
||||
const url = `${API_URL}/organizations/${slug}/members/reached-free-project-limit`
|
||||
const { data, error } = useSWR<any>(slug ? url : null, get)
|
||||
const anyError = data?.error || error
|
||||
|
||||
return {
|
||||
membersExceededLimit: anyError ? undefined : (data as MemberWithFreeProjectLimit[]),
|
||||
error: anyError,
|
||||
isLoading: !anyError && !data,
|
||||
isError: !!anyError,
|
||||
}
|
||||
}
|
||||
+38
-23
@@ -4,7 +4,7 @@ import { debounce, isUndefined, values } from 'lodash'
|
||||
import { toJS } from 'mobx'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import generator from 'generate-password'
|
||||
import { Button, Listbox, IconUsers, Input, IconLoader, Alert } from 'ui'
|
||||
import { Button, Listbox, IconUsers, Input, Alert } from 'ui'
|
||||
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
||||
|
||||
import { NextPageWithLayout } from 'types'
|
||||
@@ -19,10 +19,16 @@ import {
|
||||
PRICING_TIER_LABELS,
|
||||
PRICING_TIER_DEFAULT_KEY,
|
||||
PRICING_TIER_FREE_KEY,
|
||||
DEFAULT_FREE_PROJECTS_LIMIT,
|
||||
PRICING_TIER_PRODUCT_IDS,
|
||||
} from 'lib/constants'
|
||||
import { useStore, useFlag, withAuth, useSubscriptionStats, checkPermissions } from 'hooks'
|
||||
import {
|
||||
useStore,
|
||||
useFlag,
|
||||
withAuth,
|
||||
useSubscriptionStats,
|
||||
checkPermissions,
|
||||
useFreeProjectLimitCheck,
|
||||
} from 'hooks'
|
||||
|
||||
import { WizardLayoutWithoutAuth } from 'components/layouts'
|
||||
import Panel from 'components/ui/Panel'
|
||||
@@ -43,6 +49,8 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
|
||||
const kpsEnabled = useFlag('initWithKps')
|
||||
const subscriptionStats = useSubscriptionStats()
|
||||
const { membersExceededLimit, isLoading: isLoadingFreeProjectLimitCheck } =
|
||||
useFreeProjectLimitCheck(slug as string)
|
||||
|
||||
const [projectName, setProjectName] = useState('')
|
||||
const [dbPass, setDbPass] = useState('')
|
||||
@@ -59,31 +67,29 @@ const Wizard: NextPageWithLayout = () => {
|
||||
const stripeCustomerId = currentOrg?.stripe_customer_id
|
||||
|
||||
const isOrganizationOwner = ui.selectedOrganization?.is_owner
|
||||
const totalFreeProjects = subscriptionStats.total_active_free_projects
|
||||
const freeProjectsLimit = ui.profile?.free_project_limit ?? DEFAULT_FREE_PROJECTS_LIMIT
|
||||
const availableRegions = getAvailableRegions()
|
||||
|
||||
const isAdmin = enablePermissions
|
||||
? checkPermissions(PermissionAction.CREATE, 'projects')
|
||||
: isOrganizationOwner
|
||||
|
||||
const isInvalidSlug = isUndefined(currentOrg)
|
||||
const isEmptyOrganizations = organizations.length <= 0 && app.organizations.isInitialized
|
||||
const isEmptyPaymentMethod = paymentMethods ? !paymentMethods.length : false
|
||||
const isOverFreeProjectLimit = totalFreeProjects >= freeProjectsLimit
|
||||
const isInvalidSlug = isUndefined(currentOrg)
|
||||
const isSelectFreeTier = dbPricingTierKey === PRICING_TIER_FREE_KEY
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
|
||||
const canCreateProject =
|
||||
isAdmin &&
|
||||
!subscriptionStats.isError &&
|
||||
!subscriptionStats.isLoading &&
|
||||
(!isSelectFreeTier || (isSelectFreeTier && !isOverFreeProjectLimit))
|
||||
(!isSelectFreeTier || (isSelectFreeTier && !hasMembersExceedingFreeTierLimit))
|
||||
|
||||
const canSubmit =
|
||||
projectName != '' &&
|
||||
projectName !== '' &&
|
||||
passwordStrengthScore >= DEFAULT_MINIMUM_PASSWORD_STRENGTH &&
|
||||
dbRegion != '' &&
|
||||
dbPricingTierKey != '' &&
|
||||
dbRegion !== '' &&
|
||||
dbPricingTierKey !== '' &&
|
||||
(isSelectFreeTier || (!isSelectFreeTier && !isEmptyPaymentMethod))
|
||||
|
||||
const delayedCheckPasswordStrength = useRef(
|
||||
@@ -202,7 +208,11 @@ const Wizard: NextPageWithLayout = () => {
|
||||
return (
|
||||
<Panel
|
||||
hideHeaderStyling
|
||||
loading={!app.organizations.isInitialized}
|
||||
loading={
|
||||
!app.organizations.isInitialized ||
|
||||
subscriptionStats.isLoading ||
|
||||
isLoadingFreeProjectLimitCheck
|
||||
}
|
||||
title={
|
||||
<div key="panel-title">
|
||||
<h3>Create a new project</h3>
|
||||
@@ -243,7 +253,12 @@ const Wizard: NextPageWithLayout = () => {
|
||||
</Panel.Content>
|
||||
) : (
|
||||
<>
|
||||
<Panel.Content className="Form section-block--body has-inputs-centered space-y-4 border-t border-b border-panel-border-interior-light dark:border-panel-border-interior-dark">
|
||||
<Panel.Content
|
||||
className={[
|
||||
'Form section-block--body has-inputs-centered space-y-4 border-t border-b',
|
||||
'border-panel-border-interior-light dark:border-panel-border-interior-dark',
|
||||
].join(' ')}
|
||||
>
|
||||
{organizations.length > 0 && (
|
||||
<Listbox
|
||||
label="Organization"
|
||||
@@ -266,9 +281,15 @@ const Wizard: NextPageWithLayout = () => {
|
||||
|
||||
{!isAdmin && <NotOrganizationOwnerWarning />}
|
||||
</Panel.Content>
|
||||
|
||||
{canCreateProject && (
|
||||
<>
|
||||
<Panel.Content className="Form section-block--body has-inputs-centered border-t border-b border-panel-border-interior-light dark:border-panel-border-interior-dark">
|
||||
<Panel.Content
|
||||
className={[
|
||||
'Form section-block--body has-inputs-centered border-t border-b',
|
||||
'border-panel-border-interior-light dark:border-panel-border-interior-dark',
|
||||
].join(' ')}
|
||||
>
|
||||
<Input
|
||||
id="project-name"
|
||||
layout="horizontal"
|
||||
@@ -335,6 +356,7 @@ const Wizard: NextPageWithLayout = () => {
|
||||
</Panel.Content>
|
||||
</>
|
||||
)}
|
||||
|
||||
{isAdmin && (
|
||||
<Panel.Content className="Form section-block--body has-inputs-centered ">
|
||||
<Listbox
|
||||
@@ -376,8 +398,8 @@ const Wizard: NextPageWithLayout = () => {
|
||||
})}
|
||||
</Listbox>
|
||||
|
||||
{isSelectFreeTier && isOverFreeProjectLimit && (
|
||||
<FreeProjectLimitWarning limit={freeProjectsLimit} />
|
||||
{isSelectFreeTier && hasMembersExceedingFreeTierLimit && (
|
||||
<FreeProjectLimitWarning membersExceededLimit={membersExceededLimit || []} />
|
||||
)}
|
||||
|
||||
{!isSelectFreeTier && isEmptyPaymentMethod && (
|
||||
@@ -385,13 +407,6 @@ const Wizard: NextPageWithLayout = () => {
|
||||
)}
|
||||
</Panel.Content>
|
||||
)}
|
||||
{subscriptionStats.isLoading && (
|
||||
<Panel.Content>
|
||||
<div className="flex items-center justify-center py-10">
|
||||
<IconLoader size={16} className="animate-spin" />
|
||||
</div>
|
||||
</Panel.Content>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Input, Modal, Form, Button } from 'ui'
|
||||
|
||||
import { useStore } from 'hooks'
|
||||
import { useProjectSubscription, useStore } from 'hooks'
|
||||
import useLogsQuery from 'hooks/analytics/useLogsQuery'
|
||||
import { NextPageWithLayout, UserContent } from 'types'
|
||||
import { uuidv4 } from 'lib/helpers'
|
||||
@@ -21,8 +21,11 @@ import {
|
||||
LOGS_LARGE_DATE_RANGE_DAYS_THRESHOLD,
|
||||
LogTable,
|
||||
LogTemplate,
|
||||
maybeShowUpgradePrompt,
|
||||
TEMPLATES,
|
||||
} from 'components/interfaces/Settings/Logs'
|
||||
import { useUpgradePrompt } from 'hooks/misc/useUpgradePrompt'
|
||||
import { StripeProduct } from 'components/interfaces/Billing'
|
||||
import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt'
|
||||
|
||||
export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
@@ -33,6 +36,8 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
const [saveModalOpen, setSaveModalOpen] = useState<boolean>(false)
|
||||
const [warnings, setWarnings] = useState<LogsWarning[]>([])
|
||||
const { content } = useStore()
|
||||
const { subscription } = useProjectSubscription(ref as string)
|
||||
const tier = subscription?.tier
|
||||
|
||||
const [{ params, logData, error, isLoading }, { changeQuery, runQuery, setParams }] =
|
||||
useLogsQuery(ref as string, {
|
||||
@@ -40,6 +45,10 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
iso_timestamp_end: ite ? (ite as string) : undefined,
|
||||
})
|
||||
|
||||
const { showUpgradePrompt, setShowUpgradePrompt } = useUpgradePrompt(
|
||||
params.iso_timestamp_start as string
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
// on mount, set initial values
|
||||
if (q) {
|
||||
@@ -65,6 +74,16 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
setWarnings(newWarnings)
|
||||
}, [editorValue, params.iso_timestamp_start, params.iso_timestamp_end])
|
||||
|
||||
// Show the prompt on page load based on query params
|
||||
useEffect(() => {
|
||||
if (its) {
|
||||
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(its as string, tier?.key)
|
||||
if (shouldShowUpgradePrompt) {
|
||||
setShowUpgradePrompt(!showUpgradePrompt)
|
||||
}
|
||||
}
|
||||
}, [its, tier])
|
||||
|
||||
const onSelectTemplate = (template: LogTemplate) => {
|
||||
setEditorValue(template.searchString)
|
||||
changeQuery(template.searchString)
|
||||
@@ -110,15 +129,21 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
}
|
||||
|
||||
const handleDateChange = ({ to, from }: DatePickerToFrom) => {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
iso_timestamp_start: from || '',
|
||||
iso_timestamp_end: to || '',
|
||||
}))
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, its: from || '', ite: to || '' },
|
||||
})
|
||||
const shouldShowUpgradePrompt = maybeShowUpgradePrompt(from, tier?.key)
|
||||
|
||||
if (shouldShowUpgradePrompt) {
|
||||
setShowUpgradePrompt(!showUpgradePrompt)
|
||||
} else {
|
||||
setParams((prev) => ({
|
||||
...prev,
|
||||
iso_timestamp_start: from || '',
|
||||
iso_timestamp_end: to || '',
|
||||
}))
|
||||
router.push({
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, its: from || '', ite: to || '' },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -139,7 +164,6 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
isLoading={isLoading}
|
||||
warnings={warnings}
|
||||
/>
|
||||
|
||||
<div className="h-48 min-h-[7rem]">
|
||||
<ShimmerLine active={isLoading} />
|
||||
<CodeEditor
|
||||
@@ -158,7 +182,7 @@ export const LogsExplorerPage: NextPageWithLayout = () => {
|
||||
</div>
|
||||
</LoadingOpacity>
|
||||
<div className="mt-2 flex flex-row justify-end">
|
||||
<UpgradePrompt projectRef={ref as string} from={params.iso_timestamp_start || ''} />
|
||||
<UpgradePrompt show={showUpgradePrompt} setShowUpgradePrompt={setShowUpgradePrompt} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,14 +3,9 @@ import { useRouter } from 'next/router'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { Button, Modal } from 'ui'
|
||||
|
||||
import { useStore, useSubscriptionStats } from 'hooks'
|
||||
import { useFreeProjectLimitCheck, useStore } from 'hooks'
|
||||
import { get } from 'lib/common/fetch'
|
||||
import {
|
||||
API_URL,
|
||||
DEFAULT_FREE_PROJECTS_LIMIT,
|
||||
PRICING_TIER_PRODUCT_IDS,
|
||||
STRIPE_PRODUCT_IDS,
|
||||
} from 'lib/constants'
|
||||
import { API_URL, PRICING_TIER_PRODUCT_IDS, STRIPE_PRODUCT_IDS } from 'lib/constants'
|
||||
|
||||
import { BillingLayout } from 'components/layouts'
|
||||
import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog'
|
||||
@@ -21,11 +16,11 @@ import { NextPageWithLayout } from 'types'
|
||||
const BillingUpdate: NextPageWithLayout = () => {
|
||||
const { ui } = useStore()
|
||||
const router = useRouter()
|
||||
const orgSlug = ui.selectedOrganization?.slug
|
||||
const projectRef = ui.selectedProject?.ref
|
||||
|
||||
const subscriptionStats = useSubscriptionStats()
|
||||
const freeProjectsLimit = ui?.profile?.free_project_limit ?? DEFAULT_FREE_PROJECTS_LIMIT
|
||||
const freeProjectsOwned = subscriptionStats.total_active_free_projects ?? 0
|
||||
const { membersExceededLimit } = useFreeProjectLimitCheck(orgSlug as string)
|
||||
const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0
|
||||
|
||||
const [isLoadingProducts, setIsLoadingProducts] = useState(false)
|
||||
const [showConfirmDowngrade, setShowConfirmDowngrade] = useState(false)
|
||||
@@ -97,7 +92,7 @@ const BillingUpdate: NextPageWithLayout = () => {
|
||||
}
|
||||
|
||||
const onConfirmDowngrade = () => {
|
||||
if (freeProjectsOwned >= freeProjectsLimit) {
|
||||
if (hasMembersExceedingFreeTierLimit) {
|
||||
setShowDowngradeError(true)
|
||||
setShowConfirmDowngrade(false)
|
||||
setSelectedPlan(undefined)
|
||||
@@ -143,17 +138,30 @@ const BillingUpdate: NextPageWithLayout = () => {
|
||||
<Modal
|
||||
hideFooter
|
||||
visible={showDowngradeError}
|
||||
size="small"
|
||||
header="Free tier limit exceeded"
|
||||
size="medium"
|
||||
header="Your organization has members who have exceeded their free project limits"
|
||||
onCancel={() => setShowDowngradeError(false)}
|
||||
>
|
||||
<div className="space-y-4 py-4">
|
||||
<Modal.Content>
|
||||
<p className="text-sm text-scale-1100">
|
||||
Your account is entitled up to {freeProjectsLimit} free projects across all
|
||||
organizations you own. You will need to delete or upgrade an existing free project
|
||||
first, before being able to downgrade this project.
|
||||
</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-scale-1100">
|
||||
The following members have reached their maximum limits for the number of active
|
||||
free tier projects within organizations where they are an administrator or owner:
|
||||
</p>
|
||||
<ul className="list-disc pl-5 text-sm text-scale-1100">
|
||||
{(membersExceededLimit || []).map((member, idx: number) => (
|
||||
<li key={`member-${idx}`}>
|
||||
{member.username || member.primary_email} (Limit: {member.free_project_limit}{' '}
|
||||
free projects)
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="text-sm text-scale-1100">
|
||||
These members will need to either delete, pause, or upgrade one or more of these
|
||||
projects before you're able to downgrade this project.
|
||||
</p>
|
||||
</div>
|
||||
</Modal.Content>
|
||||
<Modal.Seperator />
|
||||
<Modal.Content>
|
||||
|
||||
@@ -393,3 +393,28 @@ test('filters accept filterOverride', async () => {
|
||||
expect(get).toHaveBeenCalledWith(expect.stringContaining('myvalue'))
|
||||
})
|
||||
})
|
||||
|
||||
describe.each(['FREE', 'PRO', 'ENTERPRISE'])('upgrade modal for %s', (key) => {
|
||||
beforeEach(() => {
|
||||
useProjectSubscription.mockReturnValue({
|
||||
subscription: {
|
||||
tier: {
|
||||
supabase_prod_id: `tier_${key.toLocaleLowerCase()}`,
|
||||
key,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
test('based on query params', async () => {
|
||||
const router = defaultRouterMock()
|
||||
router.query = {
|
||||
...router.query,
|
||||
q: 'some_query',
|
||||
its: dayjs().subtract(4, 'months').toISOString(),
|
||||
ite: dayjs().toISOString(),
|
||||
}
|
||||
useRouter.mockReturnValue(router)
|
||||
render(<LogsPreviewer projectRef="123" tableName={LogsTableName.EDGE} />)
|
||||
await screen.findByText('Log retention') // assert modal title is present
|
||||
})
|
||||
})
|
||||
@@ -7,6 +7,8 @@ import userEvent from '@testing-library/user-event'
|
||||
import { logDataFixture } from '../../fixtures'
|
||||
import { clickDropdown } from 'tests/helpers'
|
||||
import dayjs from 'dayjs'
|
||||
import { useProjectSubscription } from 'hooks'
|
||||
import UpgradePrompt from 'components/interfaces/Settings/Logs/UpgradePrompt'
|
||||
|
||||
const defaultRouterMock = () => {
|
||||
const router = jest.fn()
|
||||
@@ -137,21 +139,6 @@ test('custom sql querying', async () => {
|
||||
await expect(screen.findByText(/Load older/)).rejects.toThrow()
|
||||
})
|
||||
|
||||
test('datepicker interaction updates query params', async () => {
|
||||
render(<LogsExplorerPage />)
|
||||
clickDropdown(await screen.findByText(/Last day/))
|
||||
userEvent.click(await screen.findByText(/Last 3 days/))
|
||||
|
||||
const router = useRouter()
|
||||
expect(router.push).toBeCalledWith(
|
||||
expect.objectContaining({
|
||||
query: expect.objectContaining({
|
||||
its: expect.any(String),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
test('query warnings', async () => {
|
||||
const router = defaultRouterMock()
|
||||
router.query = {
|
||||
@@ -164,3 +151,42 @@ test('query warnings', async () => {
|
||||
render(<LogsExplorerPage />)
|
||||
await screen.findByText('1 warning')
|
||||
})
|
||||
|
||||
describe.each(['FREE', 'PRO', 'ENTERPRISE'])('upgrade modal for %s', (key) => {
|
||||
beforeEach(() => {
|
||||
useProjectSubscription.mockReturnValue({
|
||||
subscription: {
|
||||
tier: {
|
||||
supabase_prod_id: `tier_${key.toLocaleLowerCase()}`,
|
||||
key,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
test('based on query params', async () => {
|
||||
const router = defaultRouterMock()
|
||||
router.query = {
|
||||
...router.query,
|
||||
q: 'some_query',
|
||||
its: dayjs().subtract(5, 'month').toISOString(),
|
||||
ite: dayjs().toISOString(),
|
||||
}
|
||||
useRouter.mockReturnValue(router)
|
||||
render(<LogsExplorerPage />)
|
||||
await screen.findByText(/Log retention/) // assert modal title is present
|
||||
})
|
||||
|
||||
test('based on datepicker helpers', async () => {
|
||||
render(<LogsExplorerPage />)
|
||||
// click on the dropdown
|
||||
clickDropdown(await screen.findByText('Last 24 hours'))
|
||||
userEvent.click(await screen.findByText('Last 3 days'))
|
||||
|
||||
// only free tier will show modal
|
||||
if (key === 'FREE') {
|
||||
await screen.findByText('Log retention') // assert modal title is present
|
||||
} else {
|
||||
await expect(screen.findByText('Log retention')).rejects.toThrow()
|
||||
}
|
||||
})
|
||||
})
|
||||
+10
-9
@@ -1,20 +1,21 @@
|
||||
import { ApiError, createClient, User } from '@supabase/supabase-js'
|
||||
import { createClient, User, UserResponse } from '@supabase/supabase-js'
|
||||
|
||||
const removeAllUsers = async () => {
|
||||
const sb = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
const sb = createClient(
|
||||
process.env.SUPABASE_URL as string,
|
||||
process.env.SUPABASE_KEY_ADMIN as string
|
||||
)
|
||||
|
||||
const { data: users } = await sb.auth.api.listUsers()
|
||||
const {
|
||||
data: { users },
|
||||
} = await sb.auth.admin.listUsers()
|
||||
|
||||
const promises: Promise<{
|
||||
user: User
|
||||
data: User
|
||||
error: ApiError
|
||||
}>[] = []
|
||||
const promises: Promise<UserResponse>[] = []
|
||||
users.map((u) => {
|
||||
sb.from('profiles')
|
||||
.delete()
|
||||
.match({ id: u.id })
|
||||
.then(() => promises.push(sb.auth.api.deleteUser(u.id)))
|
||||
.then(() => promises.push(sb.auth.admin.deleteUser(u.id)))
|
||||
})
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
|
||||
console.log('Hello from Functions!')
|
||||
|
||||
serve(async (req) => {
|
||||
const { name } = await req.json()
|
||||
const data = {
|
||||
message: `Hello ${name}!`,
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } })
|
||||
})
|
||||
@@ -0,0 +1,319 @@
|
||||
import { params, retries, suite, test } from '@testdeck/jest'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { Severity } from 'allure-js-commons'
|
||||
|
||||
import { AdminUserAttributes, AuthError, SupabaseClient, UserResponse } from '@supabase/supabase-js'
|
||||
|
||||
import { FEATURE } from '../templates/enums'
|
||||
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
|
||||
import { Hooks } from './hooks'
|
||||
|
||||
@suite('auth_admin')
|
||||
class AuthenticationAPI extends Hooks {
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you create user then it has to be in auth db schema')
|
||||
@test
|
||||
async 'create user via admin api'() {
|
||||
const { user, error } = await this.createUserAsAdmin()
|
||||
expect(error).toBeNull()
|
||||
expect(user).not.toBeNull()
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you create user then he can sign in')
|
||||
@test
|
||||
async 'user created by admin can login'() {
|
||||
const { user, error } = await this.createUserAsAdmin()
|
||||
expect(error).toBeNull()
|
||||
expect(user).not.toBeNull()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
|
||||
const {
|
||||
data: { user: createdUser },
|
||||
error: getErr,
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
expect(getErr).toBeNull()
|
||||
expect(createdUser).not.toBeNull()
|
||||
expect(createdUser.id).toBe(user.id)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to create user with anon key then you should get error')
|
||||
@test
|
||||
async 'admin create user with anon key should fail'() {
|
||||
const fakeUser = {
|
||||
email: faker.internet.exampleEmail(),
|
||||
password: faker.internet.password(),
|
||||
email_confirm: true,
|
||||
}
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
|
||||
const {
|
||||
error,
|
||||
data: { user },
|
||||
} = await supabase.auth.admin.createUser(fakeUser)
|
||||
expect(user).toBeNull()
|
||||
expect(error).not.toBeNull()
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to create user as logged in user then you should get error')
|
||||
@test
|
||||
async 'admin create user with logged in user should fail'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const { error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
expect(signInError).toBeNull()
|
||||
|
||||
const fakeUser = {
|
||||
email: faker.internet.exampleEmail(),
|
||||
password: faker.internet.password(),
|
||||
}
|
||||
const {
|
||||
error,
|
||||
data: { user: newUser },
|
||||
} = await supabase.auth.admin.createUser(fakeUser)
|
||||
expect(newUser).toBeNull()
|
||||
expect(error).not.toBeNull()
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you list users then you should get all users')
|
||||
@test
|
||||
async 'list users with service key'() {
|
||||
const { user: user1 } = await this.createUserAsAdmin()
|
||||
const { user: user2 } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
const {
|
||||
data: { users },
|
||||
error,
|
||||
} = await supabase.auth.admin.listUsers()
|
||||
|
||||
expect(error).toBeNull()
|
||||
expect(users).not.toBeNull()
|
||||
expect(users.length).toBeGreaterThanOrEqual(2)
|
||||
expect(users.map((u) => u.id)).toEqual(expect.arrayContaining([user1.id, user2.id]))
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to list user with anon key then you should get error')
|
||||
@test
|
||||
async 'list users with anon key should fail'() {
|
||||
await this.createUserAsAdmin()
|
||||
await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const {
|
||||
data: { users },
|
||||
error,
|
||||
} = await supabase.auth.admin.listUsers()
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
expect(users).toHaveLength(0)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to list user as logged in user then you should get error')
|
||||
@test
|
||||
async 'list users as logged in user should fail'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const { error: signInError } = await supabase.auth.signInWithPassword({
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
})
|
||||
expect(signInError).toBeNull()
|
||||
|
||||
const {
|
||||
data: { users },
|
||||
error,
|
||||
} = await supabase.auth.admin.listUsers()
|
||||
expect(error).not.toBeNull()
|
||||
expect(users).toHaveLength(0)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you get provider url then corresponding auth provider url should be returned')
|
||||
@params.skip({ provider: 'google', options: {}, expectedURL: 'todo' })
|
||||
@params.skip({
|
||||
provider: 'google',
|
||||
options: { redirectTo: 'todo', scopes: 'todo' },
|
||||
expectedURL: 'todo',
|
||||
})
|
||||
@params.skip({ provider: 'twitter', options: {}, expectedURL: 'todo' })
|
||||
// ...
|
||||
async 'get url for provider'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you get user by id he has to be returned')
|
||||
@test
|
||||
async 'get user should work'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
const {
|
||||
data: { user: foundUser },
|
||||
error,
|
||||
} = await supabase.auth.admin.getUserById(user.id)
|
||||
|
||||
expect(error).toBeNull()
|
||||
expect(foundUser).not.toBeNull()
|
||||
expect(foundUser.id).toBe(user.id)
|
||||
expect(foundUser.email).toBe(user.email)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you update user then this user has to be updated')
|
||||
@test
|
||||
async 'update user should work'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
|
||||
const updatedUser = {
|
||||
email: faker.internet.exampleEmail(),
|
||||
phone: faker.phone.phoneNumber('!#!##!######'),
|
||||
}
|
||||
let {
|
||||
data: { user: resultUser },
|
||||
error,
|
||||
} = await this.updateWithRetries(supabase, user.id, updatedUser)
|
||||
|
||||
expect(error).toBeNull()
|
||||
expect(resultUser).not.toBeNull()
|
||||
expect(resultUser.id).toBe(user.id)
|
||||
expect(resultUser.email).toBe(updatedUser.email)
|
||||
expect(resultUser.phone).toBe(updatedUser.phone)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you delete user then this user has to be removed')
|
||||
@test
|
||||
async 'delete user should work'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
const {
|
||||
data: { user: deletedUser },
|
||||
error,
|
||||
} = await supabase.auth.admin.deleteUser(user.id)
|
||||
expect(error).toBeNull()
|
||||
|
||||
const {
|
||||
data: { user: foundUser },
|
||||
error: getError,
|
||||
} = await supabase.auth.admin.getUserById(user.id)
|
||||
expect(getError).not.toBeNull()
|
||||
expect(foundUser).toBeNull()
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTH_ADMIN)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you delete user with anon key you have to receive an error')
|
||||
@test
|
||||
async 'delete user with anon key should fail'() {
|
||||
const { user } = await this.createUserAsAdmin()
|
||||
const { user: villain } = await this.createUserAsAdmin()
|
||||
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: villain.email,
|
||||
password: villain.password,
|
||||
})
|
||||
|
||||
const {
|
||||
data: { user: deletedUser },
|
||||
error,
|
||||
} = await supabase.auth.admin.deleteUser(user.id)
|
||||
|
||||
expect(error).not.toBeNull()
|
||||
expect(deletedUser).toBeNull()
|
||||
|
||||
const sbAdmin = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
const {
|
||||
data: { user: foundUser },
|
||||
error: getError,
|
||||
} = await sbAdmin.auth.admin.getUserById(user.id)
|
||||
expect(getError).toBeNull()
|
||||
expect(foundUser).not.toBeNull()
|
||||
expect(foundUser.email).toBe(user.email)
|
||||
}
|
||||
|
||||
@step('Create a user as admin')
|
||||
async createUserAsAdmin(data: AdminUserAttributes = undefined): Promise<{
|
||||
user: {
|
||||
email: string
|
||||
password: string
|
||||
username: string
|
||||
id: string
|
||||
}
|
||||
error: AuthError
|
||||
}> {
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ADMIN)
|
||||
|
||||
let fakeUser: AdminUserAttributes
|
||||
if (data) {
|
||||
fakeUser = data
|
||||
} else {
|
||||
fakeUser = {
|
||||
email: faker.internet.exampleEmail(),
|
||||
password: faker.internet.password(),
|
||||
email_confirm: true,
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
error,
|
||||
data: { user },
|
||||
} = await supabase.auth.admin.createUser(fakeUser)
|
||||
|
||||
return {
|
||||
error: error,
|
||||
user: {
|
||||
email: user?.email,
|
||||
password: fakeUser.password,
|
||||
username: faker.internet.userName(),
|
||||
id: user?.id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@step('Update user with retries')
|
||||
async updateWithRetries(supabase: SupabaseClient, uid: string, attributes: AdminUserAttributes) {
|
||||
let result: UserResponse
|
||||
for (let i = 1; i < 5; i++) {
|
||||
result = await supabase.auth.admin.updateUserById(uid, attributes)
|
||||
|
||||
if (result.error && result.error.name === 'AuthRetryableFetchError') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000 * i))
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { suite, test } from '@testdeck/jest'
|
||||
import { suite, test, timeout } from '@testdeck/jest'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { Severity } from 'allure-js-commons'
|
||||
|
||||
import { ApiError, Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js'
|
||||
import { AuthChangeEvent, Session } from '@supabase/supabase-js'
|
||||
|
||||
import { FEATURE } from '../templates/enums'
|
||||
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
|
||||
@@ -22,7 +22,10 @@ class Authentication extends Hooks {
|
||||
password: faker.internet.password(),
|
||||
username: faker.internet.userName(),
|
||||
}
|
||||
const { user, session, error: signUpError } = await this.signUp(supabase, fakeUser)
|
||||
const {
|
||||
data: { user, session },
|
||||
error: signUpError,
|
||||
} = await this.signUp(supabase, fakeUser)
|
||||
|
||||
expect(signUpError).toBeNull()
|
||||
expect(user).toBeDefined()
|
||||
@@ -37,7 +40,7 @@ class Authentication extends Hooks {
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('When user sign up then he should not be logged in until he confirms his email')
|
||||
@test
|
||||
async 'new users'() {
|
||||
async 'sing up new user and sign in'() {
|
||||
// sign up user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
|
||||
@@ -47,8 +50,7 @@ class Authentication extends Hooks {
|
||||
username: faker.internet.userName(),
|
||||
}
|
||||
const {
|
||||
user,
|
||||
session: emptySession,
|
||||
data: { user, session: emptySession },
|
||||
error: signUpError,
|
||||
} = await this.signUp(supabase, fakeUser)
|
||||
|
||||
@@ -57,15 +59,22 @@ class Authentication extends Hooks {
|
||||
expect(user.email).toEqual(fakeUser.email.toLowerCase())
|
||||
expect(emptySession).not.toBeNull()
|
||||
|
||||
const { session, error: signInError } = await supabase.auth.signIn({
|
||||
const {
|
||||
data: { session },
|
||||
error: signInError,
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
expect(signInError).toBeNull()
|
||||
expect(session).toBeDefined()
|
||||
await supabase.auth.setSession(session)
|
||||
|
||||
// check if user is signed in
|
||||
const { data: profile, error: errorInsert } = await this.insertProfile(supabase, user, fakeUser)
|
||||
const {
|
||||
data: [profile],
|
||||
error: errorInsert,
|
||||
} = await this.insertProfile(supabase, user, fakeUser)
|
||||
expect(errorInsert).toBeNull()
|
||||
expect(profile.username).toMatch(fakeUser.username)
|
||||
|
||||
@@ -81,7 +90,7 @@ class Authentication extends Hooks {
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('When user sign up with phone then he should be logged in')
|
||||
@test
|
||||
async 'new users by phone'() {
|
||||
async 'create new users by phone auth'() {
|
||||
// sign up user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
|
||||
@@ -90,7 +99,10 @@ class Authentication extends Hooks {
|
||||
username: faker.internet.userName(),
|
||||
phone: faker.phone.phoneNumber('!#!##!######'),
|
||||
}
|
||||
const { user, session, error: signUpError } = await this.signUpByPhone(supabase, fakeUser)
|
||||
const {
|
||||
data: { user, session },
|
||||
error: signUpError,
|
||||
} = await this.signUpByPhone(supabase, fakeUser)
|
||||
|
||||
expect(signUpError).toBeNull()
|
||||
expect(user).toBeDefined()
|
||||
@@ -98,7 +110,10 @@ class Authentication extends Hooks {
|
||||
expect(session).toBeDefined()
|
||||
|
||||
// check if user is signed in
|
||||
const { data: profile, error: errorInsert } = await this.insertProfile(supabase, user, fakeUser)
|
||||
const {
|
||||
data: [profile],
|
||||
error: errorInsert,
|
||||
} = await this.insertProfile(supabase, user, fakeUser)
|
||||
expect(errorInsert).toBeNull()
|
||||
expect(profile.username).toMatch(fakeUser.username)
|
||||
|
||||
@@ -114,17 +129,16 @@ class Authentication extends Hooks {
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('When user is already signed up then he should be able to log in')
|
||||
@test
|
||||
async 'existing users'() {
|
||||
async 'existing users should be able to login'() {
|
||||
// create user
|
||||
const fakeUser = await this.createUser()
|
||||
|
||||
// sign in as user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const {
|
||||
session,
|
||||
user,
|
||||
data: { session, user },
|
||||
error: signInError,
|
||||
} = await supabase.auth.signIn({
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
@@ -135,11 +149,10 @@ class Authentication extends Hooks {
|
||||
expect(user.email).toEqual(fakeUser.email.toLowerCase())
|
||||
|
||||
// check if user is signed in correctly and rls is working
|
||||
const { data: profileInserted, error: errorInsert } = await this.insertProfile(
|
||||
supabase,
|
||||
user,
|
||||
fakeUser
|
||||
)
|
||||
const {
|
||||
data: [profileInserted],
|
||||
error: errorInsert,
|
||||
} = await this.insertProfile(supabase, user, fakeUser)
|
||||
expect(errorInsert).toBeNull()
|
||||
expect(profileInserted.username).toMatch(fakeUser.username)
|
||||
|
||||
@@ -155,7 +168,7 @@ class Authentication extends Hooks {
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user is signed in then he should be able to get his info and metadata')
|
||||
@test
|
||||
async 'get user'() {
|
||||
async 'get user should return logged in user'() {
|
||||
// create user
|
||||
const username = faker.internet.userName()
|
||||
const date = faker.date.recent().toUTCString()
|
||||
@@ -166,16 +179,20 @@ class Authentication extends Hooks {
|
||||
|
||||
// sign in as user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
await supabase.auth.signIn({
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
|
||||
// get signed in user data
|
||||
const user = this.getUser(supabase)
|
||||
const {
|
||||
data: { user },
|
||||
error: getUserErr,
|
||||
} = await this.getUser(supabase)
|
||||
|
||||
log('Check if user is signed in correctly and can get his data')
|
||||
expect(user).toBeDefined()
|
||||
expect(getUserErr).toBeNull()
|
||||
expect(user).not.toBeNull()
|
||||
expect(user.email).toEqual(fakeUser.email.toLowerCase())
|
||||
expect(user.role).toEqual('authenticated')
|
||||
expect(user.aud).toEqual('authenticated')
|
||||
@@ -190,128 +207,124 @@ class Authentication extends Hooks {
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user is signed in then he should be able update himself in auth schema')
|
||||
@test.skip
|
||||
async 'update user'() {
|
||||
async 'update user should update logged in user'() {
|
||||
// create user
|
||||
const fakeUser = await this.createUser()
|
||||
|
||||
// sign in as user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
await supabase.auth.signIn({
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
|
||||
// get signed in user data
|
||||
const user = this.getUser(supabase)
|
||||
const user = await this.getUser(supabase)
|
||||
|
||||
// update user
|
||||
// todo update params
|
||||
const updParams = {
|
||||
email: faker.internet.email(),
|
||||
password: faker.internet.password(),
|
||||
phone: faker.phone.phoneNumber('!#!##!######'),
|
||||
}
|
||||
const {
|
||||
user: updUser,
|
||||
data: updUserData,
|
||||
data: { user: updUser },
|
||||
error: updUserError,
|
||||
} = await this.updateUser(supabase, {})
|
||||
} = await this.updateUser(supabase, updParams)
|
||||
expect(updUserError).toBeNull()
|
||||
expect(updUser).not.toBeNull()
|
||||
expect(updUser.email).toEqual(updParams.email.toLowerCase())
|
||||
expect(updUser.phone).toEqual(updParams.phone)
|
||||
|
||||
// todo check if returned user is updated
|
||||
// get user and check it was updated
|
||||
const updatedUser = await this.getUser(supabase)
|
||||
expect(updatedUser.data.user.email).toEqual(updParams.email.toLowerCase())
|
||||
expect(updatedUser.data.user.phone).toEqual(updParams.phone)
|
||||
|
||||
const updatedUser = this.getUser(supabase)
|
||||
|
||||
// todo check if user is updated on backend
|
||||
// sign in with new credentials
|
||||
await supabase.auth.signOut()
|
||||
const signIn = await supabase.auth.signInWithPassword({
|
||||
email: updParams.email,
|
||||
password: updParams.password,
|
||||
})
|
||||
expect(signIn.error).toBeNull()
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user changes session then he still should be correctly logined')
|
||||
@test.skip
|
||||
async 'set session'() {
|
||||
// todo
|
||||
}
|
||||
@test
|
||||
async 'set session should set new auth'() {
|
||||
// create user
|
||||
const fakeUser = await this.createUser()
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user changes auth then all new requests should have new JWT')
|
||||
@test.skip
|
||||
async 'set auth'() {
|
||||
// todo
|
||||
}
|
||||
// sign in as user
|
||||
const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const {
|
||||
data: { session },
|
||||
} = await sb.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user refreshes session then user and session have to be updated')
|
||||
@test.skip
|
||||
async 'refresh session'() {
|
||||
// todo
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const { error: sessionErr } = await supabase.auth.setSession(session)
|
||||
expect(sessionErr).toBeNull()
|
||||
|
||||
// check if user is signed in correctly and rls is working
|
||||
const { data: profileInserted, error: errorInsert } = await this.insertProfile(
|
||||
supabase,
|
||||
fakeUser,
|
||||
fakeUser
|
||||
)
|
||||
expect(errorInsert).toBeNull()
|
||||
expect(profileInserted).toHaveLength(1)
|
||||
expect(profileInserted[0].username).toMatch(fakeUser.username)
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When user subscribes on auth changes then user has to receive auth updates')
|
||||
@test.skip
|
||||
async 'on auth state changed'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
// steps
|
||||
|
||||
@step('I sign up with a valid email and password')
|
||||
private async signUpByPhone(
|
||||
supabase: SupabaseClient,
|
||||
{
|
||||
phone = faker.phone.phoneNumber(),
|
||||
password = faker.internet.password(),
|
||||
}: {
|
||||
phone?: string
|
||||
password?: string
|
||||
} = {},
|
||||
options: {
|
||||
redirectTo?: string
|
||||
data?: object
|
||||
} = {}
|
||||
): Promise<{
|
||||
user: User
|
||||
session: Session
|
||||
error: ApiError
|
||||
}> {
|
||||
return supabase.auth.signUp(
|
||||
{
|
||||
phone: phone,
|
||||
password: password,
|
||||
},
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
@step('Check if I am logged in by checking if I can insert my profile')
|
||||
private async insertProfile(
|
||||
supabase: SupabaseClient,
|
||||
user: User,
|
||||
fakeUser: {
|
||||
username: string
|
||||
@test
|
||||
async 'on auth state changed should return events'() {
|
||||
// create user
|
||||
const fakeUser = await this.createUser()
|
||||
const events: { event: AuthChangeEvent; token: string }[] = []
|
||||
const onAuthStateChanged = (event: AuthChangeEvent, session: Session) => {
|
||||
log('onAuthStateChanged triggered', event)
|
||||
events.push({ event, token: session?.access_token })
|
||||
}
|
||||
): Promise<{ data: any; error: any }> {
|
||||
return supabase
|
||||
.from('profiles')
|
||||
.insert({
|
||||
id: user.id,
|
||||
username: fakeUser.username,
|
||||
})
|
||||
.single()
|
||||
}
|
||||
|
||||
@step('Check if I am logged in by checking if I can get my profile')
|
||||
private async getUserProfile(supabase: SupabaseClient): Promise<{ data: any }> {
|
||||
return supabase.from('profiles').select().maybeSingle()
|
||||
}
|
||||
// create client and subscribe on auth state changes
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange(onAuthStateChanged)
|
||||
|
||||
@step('Update user info')
|
||||
private async updateUser(
|
||||
supabase: SupabaseClient,
|
||||
attr: UserAttributes
|
||||
): Promise<{
|
||||
data: User
|
||||
user: User
|
||||
error: ApiError
|
||||
}> {
|
||||
return supabase.auth.update(attr)
|
||||
// sign in as user
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
|
||||
// update user
|
||||
const updParams = {
|
||||
email: faker.internet.email(),
|
||||
password: faker.internet.password(),
|
||||
phone: faker.phone.phoneNumber('!#!##!######'),
|
||||
}
|
||||
const { error: updUserError } = await this.updateUser(supabase, updParams)
|
||||
expect(updUserError).toBeNull()
|
||||
|
||||
// remove subscription and sign out
|
||||
subscription.unsubscribe()
|
||||
await supabase.auth.signOut()
|
||||
|
||||
// check if sign in and update events were triggered and sign out event was not triggered
|
||||
expect(events).toHaveLength(2)
|
||||
expect(events.map((e) => e.event)).toEqual(
|
||||
expect.arrayContaining(['SIGNED_IN', 'USER_UPDATED'])
|
||||
)
|
||||
expect(events.map((e) => e.event)).not.toContain('SIGNED_OUT')
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
import { params, suite, test } from '@testdeck/jest'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { Severity } from 'allure-js-commons'
|
||||
|
||||
import { Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js'
|
||||
|
||||
import { FEATURE } from '../templates/enums'
|
||||
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
|
||||
import { Hooks } from './hooks'
|
||||
|
||||
@suite('authentication API')
|
||||
class AuthenticationAPI extends Hooks {
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you create user then it has to be in auth db schema')
|
||||
@test.skip
|
||||
async 'create user'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you create user then he can sign in')
|
||||
@test.skip
|
||||
async 'create user can login'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to create user with anon key then you should get error')
|
||||
@test.skip
|
||||
async 'create user with anon key'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you list users then you should get all users')
|
||||
@test.skip
|
||||
async 'list users'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you try to list user with anon key then you should get error')
|
||||
@test.skip
|
||||
async 'list users with anon key'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you sign up user with email then he should be able to login')
|
||||
@test.skip
|
||||
async 'sign up with email'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you sign in user with email then he should be able to update his profile')
|
||||
@test.skip
|
||||
async 'sign in with email'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you sign up user with phone then he should be able to login')
|
||||
@test.skip
|
||||
async 'sign up with phone'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you sign in user with phone then he should be able to update his profile')
|
||||
@test.skip
|
||||
async 'sign in with phone'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you send magic link then email should be sent to user')
|
||||
@test.skip
|
||||
async 'send magic link'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you send invite then corresponding email should be sent to user')
|
||||
@test.skip
|
||||
async 'send invite link'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you send reset password then corresponding email should be sent to user')
|
||||
@test.skip
|
||||
async 'send reset password'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you sign out user then current session has to be removed')
|
||||
@test.skip
|
||||
async 'sign out'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you get provider url then corresponding auth provider url should be returned')
|
||||
@params.skip({ provider: 'google', options: {}, expectedURL: 'todo' })
|
||||
@params.skip({
|
||||
provider: 'google',
|
||||
options: { redirectTo: 'todo', scopes: 'todo' },
|
||||
expectedURL: 'todo',
|
||||
})
|
||||
@params.skip({ provider: 'twitter', options: {}, expectedURL: 'todo' })
|
||||
// ...
|
||||
async 'get url for provider'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you get user then currently logined user date has to be returned')
|
||||
@test.skip
|
||||
async 'get user'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you update user then currently logined user date has to be updated')
|
||||
@test.skip
|
||||
async 'update user'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you delete user then this user has to be deleted and unable to login')
|
||||
@test.skip
|
||||
async 'delete user'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.CRITICAL)
|
||||
@description('When you delete user with anon key you have to receive an error')
|
||||
@test.skip
|
||||
async 'delete user with anon key'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you change auth cookie then all new requests should have new JWT')
|
||||
@test.skip
|
||||
async 'set auth cookie'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you refreshes session then user and session have to be updated')
|
||||
@test.skip
|
||||
async 'refresh session'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you get user by cookie then the corresponding user data has to be returned')
|
||||
@test.skip
|
||||
async 'get user by cookie'() {
|
||||
// todo
|
||||
}
|
||||
|
||||
@feature(FEATURE.AUTHENTICATION)
|
||||
@severity(Severity.NORMAL)
|
||||
@description('When you generate link then the right link has to be returned')
|
||||
@params.skip({ type: 'signup', options: {}, email: 'todo' })
|
||||
@params.skip({
|
||||
type: 'signup',
|
||||
options: {
|
||||
redirectTo: 'todo',
|
||||
password: 'todo',
|
||||
data: {
|
||||
/* todo */
|
||||
},
|
||||
},
|
||||
email: 'todo',
|
||||
})
|
||||
@params.skip({ type: 'magiclink', options: {}, email: 'todo' })
|
||||
// ...
|
||||
async 'generate link'() {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
import { params, suite, test } from '@testdeck/jest'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { Severity } from 'allure-js-commons'
|
||||
import { exec, ExecException } from 'child_process'
|
||||
import os from 'os'
|
||||
|
||||
import { Session, SupabaseClient, User, UserAttributes } from '@supabase/supabase-js'
|
||||
|
||||
import { FEATURE } from '../templates/enums'
|
||||
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
|
||||
import { Hooks } from './hooks'
|
||||
import path from 'path'
|
||||
|
||||
@suite('functions')
|
||||
class Functions extends Hooks {
|
||||
@@ -15,6 +18,25 @@ class Functions extends Hooks {
|
||||
@description('When you get functions client then you are able to set auth')
|
||||
@test.skip
|
||||
async 'set auth'() {
|
||||
// todo
|
||||
// execute cli command
|
||||
const deno = path.join(os.homedir(), '.supabase', 'deno')
|
||||
const funcPath = path.join(process.cwd(), 'data', 'func.ts')
|
||||
let prom = new Promise<{ error: ExecException; stdout: string; stderr: string }>((resolve) => {
|
||||
exec(`${deno} bundle --no-check=remote --quiet ${funcPath}`, (error, stdout, stderr) =>
|
||||
resolve({ error, stdout, stderr })
|
||||
)
|
||||
})
|
||||
const { supabase } = await this.createSignedInSupaClient()
|
||||
const sb = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_ANON_KEY)
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
sb.functions.setAuth(session.access_token)
|
||||
const { error, stdout, stderr } = await prom
|
||||
expect([null, undefined]).toContain(error)
|
||||
expect([null, undefined]).toContain(stderr)
|
||||
expect(stdout).toBeDefined()
|
||||
sb.functions.invoke('get_user')
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,13 @@ import postgres from 'postgres'
|
||||
import crossFetch from 'cross-fetch'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import {
|
||||
ApiError,
|
||||
AuthResponse,
|
||||
createClient,
|
||||
Session,
|
||||
SupabaseClient,
|
||||
SupabaseClientOptions,
|
||||
User,
|
||||
UserAttributes,
|
||||
UserResponse,
|
||||
} from '@supabase/supabase-js'
|
||||
|
||||
import { JasmineAllureReporter, step } from '../../.jest/jest-custom-reporter'
|
||||
@@ -32,7 +33,14 @@ export abstract class Hooks {
|
||||
}
|
||||
|
||||
@step('Create Supabase client')
|
||||
createSupaClient(url: string, key: string, options: SupabaseClientOptions = {}): SupabaseClient {
|
||||
createSupaClient(
|
||||
url: string,
|
||||
key: string,
|
||||
options: SupabaseClientOptions<'public'> = {}
|
||||
): SupabaseClient {
|
||||
options.auth = options.auth || {}
|
||||
options.auth.persistSession = false
|
||||
|
||||
return createClient(url, key, options)
|
||||
}
|
||||
|
||||
@@ -41,6 +49,7 @@ export abstract class Hooks {
|
||||
email: string
|
||||
password: string
|
||||
username: string
|
||||
id: string
|
||||
}> {
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
|
||||
@@ -48,11 +57,17 @@ export abstract class Hooks {
|
||||
email: faker.internet.exampleEmail(),
|
||||
password: faker.internet.password(),
|
||||
username: faker.internet.userName(),
|
||||
id: '',
|
||||
}
|
||||
const { user, error: signUpError } = await this.signUp(supabase, fakeUser, {
|
||||
const {
|
||||
error: signUpError,
|
||||
data: { user },
|
||||
} = await this.signUp(supabase, fakeUser, {
|
||||
data: data,
|
||||
})
|
||||
expect(signUpError).toBeNull()
|
||||
expect(user).not.toBeNull()
|
||||
fakeUser.id = user.id
|
||||
|
||||
return fakeUser
|
||||
}
|
||||
@@ -92,19 +107,14 @@ export abstract class Hooks {
|
||||
options: {
|
||||
redirectTo?: string
|
||||
data?: object
|
||||
captchaToken?: string
|
||||
} = {}
|
||||
): Promise<{
|
||||
user: User
|
||||
session: Session
|
||||
error: ApiError
|
||||
}> {
|
||||
return supabase.auth.signUp(
|
||||
{
|
||||
email: email,
|
||||
password: password,
|
||||
},
|
||||
options
|
||||
)
|
||||
): Promise<AuthResponse> {
|
||||
return supabase.auth.signUp({
|
||||
email: email,
|
||||
password: password,
|
||||
options: options,
|
||||
})
|
||||
}
|
||||
|
||||
@step('Check if I am being able to log out')
|
||||
@@ -114,7 +124,7 @@ export abstract class Hooks {
|
||||
|
||||
@step('Get user data, if there is a logged in user')
|
||||
getUser(supabase: SupabaseClient) {
|
||||
return supabase.auth.user()
|
||||
return supabase.auth.getUser()
|
||||
}
|
||||
|
||||
@step((user: User) => `Get user by ID (${user.id}) from Supabase auth schema`)
|
||||
@@ -127,4 +137,75 @@ export abstract class Hooks {
|
||||
id = ${user.id}
|
||||
`
|
||||
}
|
||||
|
||||
@step('I sign up with a valid email and password')
|
||||
async signUpByPhone(
|
||||
supabase: SupabaseClient,
|
||||
{
|
||||
phone = faker.phone.phoneNumber(),
|
||||
password = faker.internet.password(),
|
||||
}: {
|
||||
phone?: string
|
||||
password?: string
|
||||
} = {},
|
||||
options: {
|
||||
redirectTo?: string
|
||||
data?: object
|
||||
} = {}
|
||||
): Promise<AuthResponse> {
|
||||
return supabase.auth.signUp({
|
||||
phone: phone,
|
||||
password: password,
|
||||
options: options,
|
||||
})
|
||||
}
|
||||
|
||||
@step('User inserts profile')
|
||||
async insertProfile(
|
||||
supabase: SupabaseClient,
|
||||
user: {
|
||||
id: string
|
||||
},
|
||||
fakeUser: {
|
||||
username: string
|
||||
}
|
||||
): Promise<{ data: any; error: any }> {
|
||||
return await supabase
|
||||
.from('profiles')
|
||||
.insert({
|
||||
id: user.id,
|
||||
username: fakeUser.username,
|
||||
})
|
||||
.select()
|
||||
}
|
||||
|
||||
@step('I can get my profile via postgREST')
|
||||
async getUserProfile(supabase: SupabaseClient): Promise<{ data: any; error: any }> {
|
||||
return supabase.from('profiles').select().maybeSingle()
|
||||
}
|
||||
|
||||
@step('Update user info')
|
||||
async updateUser(supabase: SupabaseClient, attr: UserAttributes): Promise<UserResponse> {
|
||||
return supabase.auth.updateUser(attr)
|
||||
}
|
||||
|
||||
@step('Create signed in supabase client')
|
||||
async createSignedInSupaClient() {
|
||||
// create user
|
||||
const fakeUser = await this.createUser()
|
||||
|
||||
// sign in as user
|
||||
const supabase = this.createSupaClient(process.env.SUPABASE_URL, process.env.SUPABASE_KEY_ANON)
|
||||
const {
|
||||
data: { user },
|
||||
error: signInError,
|
||||
} = await supabase.auth.signInWithPassword({
|
||||
email: fakeUser.email,
|
||||
password: fakeUser.password,
|
||||
})
|
||||
expect(signInError).toBeNull()
|
||||
fakeUser.id = user.id
|
||||
|
||||
return { supabase, user: fakeUser }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import { suite, test, timeout } from '@testdeck/jest'
|
||||
import { faker } from '@faker-js/faker'
|
||||
import { Severity } from 'allure-js-commons'
|
||||
|
||||
import { FEATURE } from '../templates/enums'
|
||||
import { description, feature, log, severity, step } from '../../.jest/jest-custom-reporter'
|
||||
import { Hooks } from './hooks'
|
||||
|
||||
@suite('postgrest')
|
||||
class PostgREST extends Hooks {
|
||||
@feature(FEATURE.POSTGREST)
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('User can insert profile for himself according to RLS to profiles table')
|
||||
@test
|
||||
async 'insert row'() {
|
||||
const { supabase, user } = await this.createSignedInSupaClient()
|
||||
|
||||
// check if user can insert profile for himself
|
||||
const {
|
||||
data: [profileInserted],
|
||||
error: errorInsert,
|
||||
} = await this.insertProfile(supabase, user, user)
|
||||
expect(errorInsert).toBeNull()
|
||||
expect(profileInserted.username).toMatch(user.username)
|
||||
}
|
||||
|
||||
@feature(FEATURE.POSTGREST)
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('User can select his profile from profiles table')
|
||||
@test
|
||||
async 'select row'() {
|
||||
const { supabase, user } = await this.createSignedInSupaClient()
|
||||
|
||||
// insert profile for user
|
||||
await this.insertProfile(supabase, user, user)
|
||||
|
||||
const { data: profileGot, error } = await this.getUserProfile(supabase)
|
||||
expect(error).toBeNull()
|
||||
expect(profileGot.username).toMatch(user.username)
|
||||
}
|
||||
|
||||
@feature(FEATURE.POSTGREST)
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('User can update his profile in profiles table')
|
||||
@test
|
||||
async 'update row'() {
|
||||
const { supabase, user } = await this.createSignedInSupaClient()
|
||||
|
||||
// insert profile for user
|
||||
await this.insertProfile(supabase, user, user)
|
||||
|
||||
const newName = faker.internet.userName()
|
||||
// update profile for user
|
||||
const { data: updUser, error } = await supabase
|
||||
.from('profiles')
|
||||
.update({
|
||||
id: user.id,
|
||||
username: newName,
|
||||
})
|
||||
.select()
|
||||
expect(error).toBeNull()
|
||||
expect(updUser.length).toEqual(1)
|
||||
expect(updUser[0].username).toMatch(newName)
|
||||
|
||||
const { data: profileGot } = await this.getUserProfile(supabase)
|
||||
expect(profileGot.username).toMatch(newName)
|
||||
}
|
||||
|
||||
@feature(FEATURE.POSTGREST)
|
||||
@severity(Severity.BLOCKER)
|
||||
@description('User can delete his profile in profiles table')
|
||||
@test
|
||||
async 'delete row'() {
|
||||
const { supabase, user } = await this.createSignedInSupaClient()
|
||||
|
||||
// insert profile for user
|
||||
await this.insertProfile(supabase, user, user)
|
||||
|
||||
// delete profile for user
|
||||
const { error } = await supabase.from('profiles').delete().eq('id', user.id).select()
|
||||
expect(error).toBeNull()
|
||||
|
||||
const { data: profileGot } = await this.getUserProfile(supabase)
|
||||
expect(profileGot).toBeNull()
|
||||
}
|
||||
}
|
||||
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user