Cleanup
This commit is contained in:
51
packages/supabase/package.json
Normal file
51
packages/supabase/package.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@kit/supabase",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"clean": "git clean -xdf .turbo node_modules",
|
||||
"format": "prettier --check \"**/*.{ts,tsx}\"",
|
||||
"lint": "eslint .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"prettier": "@kit/prettier-config",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./middleware-client": "./src/clients/middleware.client.ts",
|
||||
"./server-actions-client": "./src/clients/server-actions.client.ts",
|
||||
"./route-handler-client": "./src/clients/route-handler.client.ts",
|
||||
"./server-component-client": "./src/clients/server-component.client.ts",
|
||||
"./browser-client": "./src/clients/browser.client.ts",
|
||||
"./check-requires-mfa": "./src/check-requires-mfa.ts",
|
||||
"./require-auth": "./src/require-auth.ts",
|
||||
"./hooks/*": "./src/hooks/*.ts",
|
||||
"./components/*": "./src/components/*.tsx",
|
||||
"./database": "./src/database.types.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kit/prettier-config": "0.1.0",
|
||||
"@kit/eslint-config": "0.2.0",
|
||||
"@kit/tailwind-config": "0.1.0",
|
||||
"@kit/tsconfig": "0.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@tanstack/react-query": "^5.28.6",
|
||||
"@supabase/ssr": "^0.1.0",
|
||||
"@supabase/supabase-js": "^2.39.8",
|
||||
"@epic-web/invariant": "^1.0.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"extends": [
|
||||
"@kit/eslint-config/base",
|
||||
"@kit/eslint-config/react"
|
||||
]
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/supabase/src/check-requires-mfa.ts
Normal file
23
packages/supabase/src/check-requires-mfa.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import type { SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
const ASSURANCE_LEVEL_2 = 'aal2';
|
||||
|
||||
/**
|
||||
* @name checkRequiresMultiFactorAuthentication
|
||||
* @description Checks if the current session requires multi-factor authentication.
|
||||
* We do it by checking that the next assurance level is AAL2 and that the current assurance level is not AAL2.
|
||||
* @param client
|
||||
*/
|
||||
export async function checkRequiresMultiFactorAuthentication(
|
||||
client: SupabaseClient,
|
||||
) {
|
||||
const assuranceLevel = await client.auth.mfa.getAuthenticatorAssuranceLevel();
|
||||
|
||||
if (assuranceLevel.error) {
|
||||
throw new Error(assuranceLevel.error.message);
|
||||
}
|
||||
|
||||
const { nextLevel, currentLevel } = assuranceLevel.data;
|
||||
|
||||
return nextLevel === ASSURANCE_LEVEL_2 && nextLevel !== currentLevel;
|
||||
}
|
||||
20
packages/supabase/src/clients/browser.client.ts
Normal file
20
packages/supabase/src/clients/browser.client.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { invariant } from '@epic-web/invariant';
|
||||
import { createBrowserClient } from '@supabase/ssr';
|
||||
|
||||
import { Database } from '../database.types';
|
||||
|
||||
let client: ReturnType<typeof createBrowserClient>;
|
||||
|
||||
export function getSupabaseBrowserClient<GenericSchema = Database>() {
|
||||
const SUPABASE_URL = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
const SUPABASE_ANON_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||
|
||||
invariant(SUPABASE_URL, `Supabase URL was not provided`);
|
||||
invariant(SUPABASE_ANON_KEY, `Supabase Anon key was not provided`);
|
||||
|
||||
if (client) return client;
|
||||
|
||||
client = createBrowserClient<GenericSchema>(SUPABASE_URL, SUPABASE_ANON_KEY);
|
||||
|
||||
return client;
|
||||
}
|
||||
65
packages/supabase/src/clients/middleware.client.ts
Normal file
65
packages/supabase/src/clients/middleware.client.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { type NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
import { type CookieOptions, createServerClient } from '@supabase/ssr';
|
||||
|
||||
import { Database } from '../database.types';
|
||||
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
|
||||
|
||||
/**
|
||||
* Creates a middleware client for Supabase.
|
||||
*
|
||||
* @param {NextRequest} request - The Next.js request object.
|
||||
* @param {NextResponse} response - The Next.js response object.
|
||||
*/
|
||||
export function createMiddlewareClient<GenericSchema = Database>(
|
||||
request: NextRequest,
|
||||
response: NextResponse,
|
||||
) {
|
||||
const keys = getSupabaseClientKeys();
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
|
||||
cookies: getCookieStrategy(request, response),
|
||||
});
|
||||
}
|
||||
|
||||
function getCookieStrategy(request: NextRequest, response: NextResponse) {
|
||||
return {
|
||||
set: (name: string, value: string, options: CookieOptions) => {
|
||||
request.cookies.set({ name, value, ...options });
|
||||
|
||||
response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers,
|
||||
},
|
||||
});
|
||||
|
||||
response.cookies.set({
|
||||
name,
|
||||
value,
|
||||
...options,
|
||||
});
|
||||
},
|
||||
get: (name: string) => {
|
||||
return request.cookies.get(name)?.value;
|
||||
},
|
||||
remove: (name: string, options: CookieOptions) => {
|
||||
request.cookies.set({
|
||||
name,
|
||||
value: '',
|
||||
...options,
|
||||
});
|
||||
|
||||
response = NextResponse.next({
|
||||
request: {
|
||||
headers: request.headers,
|
||||
},
|
||||
});
|
||||
|
||||
response.cookies.set({
|
||||
name,
|
||||
value: '',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
61
packages/supabase/src/clients/route-handler.client.ts
Normal file
61
packages/supabase/src/clients/route-handler.client.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
import type { CookieOptions } from '@supabase/ssr';
|
||||
import { createServerClient } from '@supabase/ssr';
|
||||
import 'server-only';
|
||||
|
||||
import { Database } from '../database.types';
|
||||
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
|
||||
|
||||
/**
|
||||
* @name getSupabaseRouteHandlerClient
|
||||
* @description Get a Supabase client for use in the Route Handler Routes
|
||||
*/
|
||||
export function getSupabaseRouteHandlerClient<GenericSchema = Database>(
|
||||
params = {
|
||||
admin: false,
|
||||
},
|
||||
) {
|
||||
const keys = getSupabaseClientKeys();
|
||||
|
||||
if (params.admin) {
|
||||
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(
|
||||
`[Dev Only] You are using the Supabase Service Role. Make sure it's the right call.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!serviceRoleKey) {
|
||||
throw new Error('Supabase Service Role Key not provided');
|
||||
}
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, serviceRoleKey, {
|
||||
auth: {
|
||||
persistSession: false,
|
||||
},
|
||||
cookies: {},
|
||||
});
|
||||
}
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
|
||||
cookies: getCookiesStrategy(),
|
||||
});
|
||||
}
|
||||
|
||||
function getCookiesStrategy() {
|
||||
const cookieStore = cookies();
|
||||
|
||||
return {
|
||||
set: (name: string, value: string, options: CookieOptions) => {
|
||||
cookieStore.set({ name, value, ...options });
|
||||
},
|
||||
get: (name: string) => {
|
||||
return cookieStore.get(name)?.value;
|
||||
},
|
||||
remove: (name: string, options: CookieOptions) => {
|
||||
cookieStore.set({ name, value: '', ...options });
|
||||
},
|
||||
};
|
||||
}
|
||||
67
packages/supabase/src/clients/server-actions.client.ts
Normal file
67
packages/supabase/src/clients/server-actions.client.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
import { createServerClient } from '@supabase/ssr';
|
||||
import 'server-only';
|
||||
|
||||
import { Database } from '../database.types';
|
||||
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
|
||||
|
||||
const createServerSupabaseClient = <GenericSchema = Database>() => {
|
||||
const keys = getSupabaseClientKeys();
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
|
||||
cookies: getCookiesStrategy(),
|
||||
});
|
||||
};
|
||||
|
||||
export const getSupabaseServerActionClient = <
|
||||
GenericSchema = Database,
|
||||
>(params?: {
|
||||
admin: false;
|
||||
}) => {
|
||||
const keys = getSupabaseClientKeys();
|
||||
const admin = params?.admin ?? false;
|
||||
|
||||
if (admin) {
|
||||
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(
|
||||
`[Dev Only] You are using the Supabase Service Role. Make sure it's the right call.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!serviceRoleKey) {
|
||||
throw new Error('Supabase Service Role Key not provided');
|
||||
}
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, serviceRoleKey, {
|
||||
auth: {
|
||||
persistSession: false,
|
||||
},
|
||||
cookies: {},
|
||||
});
|
||||
}
|
||||
|
||||
return createServerSupabaseClient<GenericSchema>();
|
||||
};
|
||||
|
||||
function getCookiesStrategy() {
|
||||
const cookieStore = cookies();
|
||||
|
||||
return {
|
||||
get: (name: string) => {
|
||||
return cookieStore.get(name)?.value;
|
||||
},
|
||||
set: (name: string, value: string, options: object) => {
|
||||
cookieStore.set({ name, value, ...options });
|
||||
},
|
||||
remove: (name: string, options: object) => {
|
||||
cookieStore.set({
|
||||
name,
|
||||
value: '',
|
||||
...options,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
54
packages/supabase/src/clients/server-component.client.ts
Normal file
54
packages/supabase/src/clients/server-component.client.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
import { createServerClient } from '@supabase/ssr';
|
||||
import 'server-only';
|
||||
|
||||
import { Database } from '../database.types';
|
||||
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
|
||||
|
||||
/**
|
||||
* @name getSupabaseServerComponentClient
|
||||
* @description Get a Supabase client for use in the Server Components
|
||||
*/
|
||||
export const getSupabaseServerComponentClient = <GenericSchema = Database>(
|
||||
params = {
|
||||
admin: false,
|
||||
},
|
||||
) => {
|
||||
const keys = getSupabaseClientKeys();
|
||||
|
||||
if (params.admin) {
|
||||
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(
|
||||
`[Dev Only] You are using the Supabase Service Role. Make sure it's the right call.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!serviceRoleKey) {
|
||||
throw new Error('Supabase Service Role Key not provided');
|
||||
}
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, serviceRoleKey, {
|
||||
auth: {
|
||||
persistSession: false,
|
||||
},
|
||||
cookies: {},
|
||||
});
|
||||
}
|
||||
|
||||
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
|
||||
cookies: getCookiesStrategy(),
|
||||
});
|
||||
};
|
||||
|
||||
function getCookiesStrategy() {
|
||||
const cookieStore = cookies();
|
||||
|
||||
return {
|
||||
get: (name: string) => {
|
||||
return cookieStore.get(name)?.value;
|
||||
},
|
||||
};
|
||||
}
|
||||
96
packages/supabase/src/components/auth-change-listener.tsx
Normal file
96
packages/supabase/src/components/auth-change-listener.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
|
||||
import { isBrowser } from '@supabase/ssr';
|
||||
|
||||
import { useSupabase } from '../hooks/use-supabase';
|
||||
import {
|
||||
useRevalidateUserSession,
|
||||
useUserSession,
|
||||
} from '../hooks/use-user-session';
|
||||
|
||||
function AuthRedirectListener({
|
||||
children,
|
||||
appHomePath,
|
||||
}: React.PropsWithChildren<{
|
||||
appHomePath: string;
|
||||
}>) {
|
||||
const client = useSupabase();
|
||||
const pathName = usePathname();
|
||||
const router = useRouter();
|
||||
const revalidateUserSession = useRevalidateUserSession();
|
||||
const session = useUserSession();
|
||||
const accessToken = session.data?.access_token;
|
||||
|
||||
useEffect(() => {
|
||||
// keep this running for the whole session unless the component was unmounted
|
||||
const listener = client.auth.onAuthStateChange((_, user) => {
|
||||
// log user out if user is falsy
|
||||
// and if the current path is a private route
|
||||
const shouldRedirectUser = !user && isPrivateRoute(pathName);
|
||||
|
||||
if (shouldRedirectUser) {
|
||||
// send user away when signed out
|
||||
window.location.assign('/');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
const isOutOfSync = user?.access_token !== accessToken;
|
||||
|
||||
if (isOutOfSync) {
|
||||
void router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (user && isPrivateRoute(pathName)) {
|
||||
return revalidateUserSession();
|
||||
}
|
||||
});
|
||||
|
||||
// destroy listener on un-mounts
|
||||
return () => listener.data.subscription.unsubscribe();
|
||||
}, [
|
||||
client.auth,
|
||||
router,
|
||||
accessToken,
|
||||
revalidateUserSession,
|
||||
pathName,
|
||||
appHomePath,
|
||||
]);
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
export function AuthChangeListener({
|
||||
children,
|
||||
appHomePath,
|
||||
}: React.PropsWithChildren<{
|
||||
appHomePath: string;
|
||||
privateRoutes?: string[];
|
||||
}>) {
|
||||
const shouldActivateListener = isBrowser();
|
||||
|
||||
// we only activate the listener if
|
||||
// we are rendering in the browser
|
||||
if (!shouldActivateListener) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthRedirectListener appHomePath={appHomePath}>
|
||||
{children}
|
||||
</AuthRedirectListener>
|
||||
);
|
||||
}
|
||||
|
||||
function isPrivateRoute(path: string) {
|
||||
// TODO: use config
|
||||
const prefixes = ['/home', '/admin', '/password-reset'];
|
||||
|
||||
return prefixes.some((prefix) => path.startsWith(prefix));
|
||||
}
|
||||
948
packages/supabase/src/database.types.ts
Normal file
948
packages/supabase/src/database.types.ts
Normal file
@@ -0,0 +1,948 @@
|
||||
export type Json =
|
||||
| string
|
||||
| number
|
||||
| boolean
|
||||
| null
|
||||
| { [key: string]: Json | undefined }
|
||||
| Json[]
|
||||
|
||||
export type Database = {
|
||||
graphql_public: {
|
||||
Tables: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
graphql: {
|
||||
Args: {
|
||||
operationName?: string
|
||||
query?: string
|
||||
variables?: Json
|
||||
extensions?: Json
|
||||
}
|
||||
Returns: Json
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
CompositeTypes: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
public: {
|
||||
Tables: {
|
||||
account_roles: {
|
||||
Row: {
|
||||
account_id: string
|
||||
id: number
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
id?: number
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
id?: number
|
||||
role?: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "account_roles_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "account_roles_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "account_roles_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
accounts: {
|
||||
Row: {
|
||||
created_at: string | null
|
||||
created_by: string | null
|
||||
email: string | null
|
||||
id: string
|
||||
is_personal_account: boolean
|
||||
name: string
|
||||
picture_url: string | null
|
||||
primary_owner_user_id: string
|
||||
slug: string | null
|
||||
updated_at: string | null
|
||||
updated_by: string | null
|
||||
}
|
||||
Insert: {
|
||||
created_at?: string | null
|
||||
created_by?: string | null
|
||||
email?: string | null
|
||||
id?: string
|
||||
is_personal_account?: boolean
|
||||
name: string
|
||||
picture_url?: string | null
|
||||
primary_owner_user_id?: string
|
||||
slug?: string | null
|
||||
updated_at?: string | null
|
||||
updated_by?: string | null
|
||||
}
|
||||
Update: {
|
||||
created_at?: string | null
|
||||
created_by?: string | null
|
||||
email?: string | null
|
||||
id?: string
|
||||
is_personal_account?: boolean
|
||||
name?: string
|
||||
picture_url?: string | null
|
||||
primary_owner_user_id?: string
|
||||
slug?: string | null
|
||||
updated_at?: string | null
|
||||
updated_by?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "accounts_created_by_fkey"
|
||||
columns: ["created_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_primary_owner_user_id_fkey"
|
||||
columns: ["primary_owner_user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_updated_by_fkey"
|
||||
columns: ["updated_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
accounts_memberships: {
|
||||
Row: {
|
||||
account_id: string
|
||||
account_role: Database["public"]["Enums"]["account_role"]
|
||||
created_at: string
|
||||
created_by: string | null
|
||||
updated_at: string
|
||||
updated_by: string | null
|
||||
user_id: string
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
account_role: Database["public"]["Enums"]["account_role"]
|
||||
created_at?: string
|
||||
created_by?: string | null
|
||||
updated_at?: string
|
||||
updated_by?: string | null
|
||||
user_id: string
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
account_role?: Database["public"]["Enums"]["account_role"]
|
||||
created_at?: string
|
||||
created_by?: string | null
|
||||
updated_at?: string
|
||||
updated_by?: string | null
|
||||
user_id?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_created_by_fkey"
|
||||
columns: ["created_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_updated_by_fkey"
|
||||
columns: ["updated_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "accounts_memberships_user_id_fkey"
|
||||
columns: ["user_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
billing_customers: {
|
||||
Row: {
|
||||
account_id: string
|
||||
customer_id: string
|
||||
email: string | null
|
||||
id: number
|
||||
provider: Database["public"]["Enums"]["billing_provider"]
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
customer_id: string
|
||||
email?: string | null
|
||||
id?: number
|
||||
provider: Database["public"]["Enums"]["billing_provider"]
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
customer_id?: string
|
||||
email?: string | null
|
||||
id?: number
|
||||
provider?: Database["public"]["Enums"]["billing_provider"]
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "billing_customers_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "billing_customers_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "billing_customers_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
config: {
|
||||
Row: {
|
||||
billing_provider:
|
||||
| Database["public"]["Enums"]["billing_provider"]
|
||||
| null
|
||||
enable_account_billing: boolean | null
|
||||
enable_organization_accounts: boolean | null
|
||||
enable_organization_billing: boolean | null
|
||||
}
|
||||
Insert: {
|
||||
billing_provider?:
|
||||
| Database["public"]["Enums"]["billing_provider"]
|
||||
| null
|
||||
enable_account_billing?: boolean | null
|
||||
enable_organization_accounts?: boolean | null
|
||||
enable_organization_billing?: boolean | null
|
||||
}
|
||||
Update: {
|
||||
billing_provider?:
|
||||
| Database["public"]["Enums"]["billing_provider"]
|
||||
| null
|
||||
enable_account_billing?: boolean | null
|
||||
enable_organization_accounts?: boolean | null
|
||||
enable_organization_billing?: boolean | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
invitations: {
|
||||
Row: {
|
||||
account_id: string
|
||||
created_at: string
|
||||
email: string
|
||||
id: number
|
||||
invite_token: string
|
||||
invited_by: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
updated_at: string
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
created_at?: string
|
||||
email: string
|
||||
id?: number
|
||||
invite_token: string
|
||||
invited_by: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
updated_at?: string
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
created_at?: string
|
||||
email?: string
|
||||
id?: number
|
||||
invite_token?: string
|
||||
invited_by?: string
|
||||
role?: Database["public"]["Enums"]["account_role"]
|
||||
updated_at?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "invitations_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invitations_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invitations_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "invitations_invited_by_fkey"
|
||||
columns: ["invited_by"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "users"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
role_permissions: {
|
||||
Row: {
|
||||
id: number
|
||||
permission: Database["public"]["Enums"]["app_permissions"]
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Insert: {
|
||||
id?: number
|
||||
permission: Database["public"]["Enums"]["app_permissions"]
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Update: {
|
||||
id?: number
|
||||
permission?: Database["public"]["Enums"]["app_permissions"]
|
||||
role?: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
subscriptions: {
|
||||
Row: {
|
||||
account_id: string
|
||||
billing_customer_id: number
|
||||
billing_provider: Database["public"]["Enums"]["billing_provider"]
|
||||
cancel_at_period_end: boolean
|
||||
created_at: string
|
||||
currency: string
|
||||
id: string
|
||||
interval: string
|
||||
interval_count: number
|
||||
last_payment_at: string | null
|
||||
period_ends_at: string | null
|
||||
period_starts_at: string | null
|
||||
price_amount: number | null
|
||||
product_id: string
|
||||
status: Database["public"]["Enums"]["subscription_status"]
|
||||
trial_ends_at: string | null
|
||||
trial_starts_at: string | null
|
||||
updated_at: string
|
||||
variant_id: string
|
||||
}
|
||||
Insert: {
|
||||
account_id: string
|
||||
billing_customer_id: number
|
||||
billing_provider: Database["public"]["Enums"]["billing_provider"]
|
||||
cancel_at_period_end: boolean
|
||||
created_at?: string
|
||||
currency: string
|
||||
id: string
|
||||
interval: string
|
||||
interval_count: number
|
||||
last_payment_at?: string | null
|
||||
period_ends_at?: string | null
|
||||
period_starts_at?: string | null
|
||||
price_amount?: number | null
|
||||
product_id: string
|
||||
status: Database["public"]["Enums"]["subscription_status"]
|
||||
trial_ends_at?: string | null
|
||||
trial_starts_at?: string | null
|
||||
updated_at?: string
|
||||
variant_id: string
|
||||
}
|
||||
Update: {
|
||||
account_id?: string
|
||||
billing_customer_id?: number
|
||||
billing_provider?: Database["public"]["Enums"]["billing_provider"]
|
||||
cancel_at_period_end?: boolean
|
||||
created_at?: string
|
||||
currency?: string
|
||||
id?: string
|
||||
interval?: string
|
||||
interval_count?: number
|
||||
last_payment_at?: string | null
|
||||
period_ends_at?: string | null
|
||||
period_starts_at?: string | null
|
||||
price_amount?: number | null
|
||||
product_id?: string
|
||||
status?: Database["public"]["Enums"]["subscription_status"]
|
||||
trial_ends_at?: string | null
|
||||
trial_starts_at?: string | null
|
||||
updated_at?: string
|
||||
variant_id?: string
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "subscriptions_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "subscriptions_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_account_workspace"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "subscriptions_account_id_fkey"
|
||||
columns: ["account_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "user_accounts"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
{
|
||||
foreignKeyName: "subscriptions_billing_customer_id_fkey"
|
||||
columns: ["billing_customer_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "billing_customers"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
user_account_workspace: {
|
||||
Row: {
|
||||
id: string | null
|
||||
name: string | null
|
||||
picture_url: string | null
|
||||
subscription_status:
|
||||
| Database["public"]["Enums"]["subscription_status"]
|
||||
| null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
user_accounts: {
|
||||
Row: {
|
||||
id: string | null
|
||||
name: string | null
|
||||
picture_url: string | null
|
||||
role: Database["public"]["Enums"]["account_role"] | null
|
||||
slug: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
}
|
||||
Functions: {
|
||||
add_invitations_to_account:
|
||||
| {
|
||||
Args: {
|
||||
account_slug: string
|
||||
invitations: Database["public"]["CompositeTypes"]["invitation"][]
|
||||
}
|
||||
Returns: Database["public"]["Tables"]["invitations"]["Row"][]
|
||||
}
|
||||
| {
|
||||
Args: {
|
||||
account_slug: string
|
||||
invitations: Database["public"]["CompositeTypes"]["invitation"][]
|
||||
}
|
||||
Returns: {
|
||||
account_id: string
|
||||
created_at: string
|
||||
email: string
|
||||
id: number
|
||||
invite_token: string
|
||||
invited_by: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
create_account: {
|
||||
Args: {
|
||||
account_name: string
|
||||
}
|
||||
Returns: {
|
||||
created_at: string | null
|
||||
created_by: string | null
|
||||
email: string | null
|
||||
id: string
|
||||
is_personal_account: boolean
|
||||
name: string
|
||||
picture_url: string | null
|
||||
primary_owner_user_id: string
|
||||
slug: string | null
|
||||
updated_at: string | null
|
||||
updated_by: string | null
|
||||
}
|
||||
}
|
||||
create_invitation: {
|
||||
Args: {
|
||||
account_id: string
|
||||
email: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Returns: {
|
||||
account_id: string
|
||||
created_at: string
|
||||
email: string
|
||||
id: number
|
||||
invite_token: string
|
||||
invited_by: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
updated_at: string
|
||||
}
|
||||
}
|
||||
get_account_invitations: {
|
||||
Args: {
|
||||
account_slug: string
|
||||
}
|
||||
Returns: {
|
||||
email: string
|
||||
account_id: string
|
||||
invited_by: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
inviter_name: string
|
||||
inviter_email: string
|
||||
}[]
|
||||
}
|
||||
get_account_members: {
|
||||
Args: {
|
||||
account_slug: string
|
||||
}
|
||||
Returns: {
|
||||
id: string
|
||||
user_id: string
|
||||
account_id: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
primary_owner_user_id: string
|
||||
name: string
|
||||
email: string
|
||||
picture_url: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}[]
|
||||
}
|
||||
get_config: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: Json
|
||||
}
|
||||
get_user_accounts: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: {
|
||||
created_at: string | null
|
||||
created_by: string | null
|
||||
email: string | null
|
||||
id: string
|
||||
is_personal_account: boolean
|
||||
name: string
|
||||
picture_url: string | null
|
||||
primary_owner_user_id: string
|
||||
slug: string | null
|
||||
updated_at: string | null
|
||||
updated_by: string | null
|
||||
}[]
|
||||
}
|
||||
has_permission: {
|
||||
Args: {
|
||||
user_id: string
|
||||
account_id: string
|
||||
permission_name: Database["public"]["Enums"]["app_permissions"]
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
has_role_on_account: {
|
||||
Args: {
|
||||
account_id: string
|
||||
account_role?: Database["public"]["Enums"]["account_role"]
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
is_account_owner: {
|
||||
Args: {
|
||||
account_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
is_set: {
|
||||
Args: {
|
||||
field_name: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
is_team_member: {
|
||||
Args: {
|
||||
account_id: string
|
||||
user_id: string
|
||||
}
|
||||
Returns: boolean
|
||||
}
|
||||
organization_account_workspace: {
|
||||
Args: {
|
||||
account_slug: string
|
||||
}
|
||||
Returns: {
|
||||
id: string
|
||||
name: string
|
||||
picture_url: string
|
||||
slug: string
|
||||
role: Database["public"]["Enums"]["account_role"]
|
||||
primary_owner_user_id: string
|
||||
subscription_status: Database["public"]["Enums"]["subscription_status"]
|
||||
permissions: Database["public"]["Enums"]["app_permissions"][]
|
||||
}[]
|
||||
}
|
||||
unaccent: {
|
||||
Args: {
|
||||
"": string
|
||||
}
|
||||
Returns: string
|
||||
}
|
||||
unaccent_init: {
|
||||
Args: {
|
||||
"": unknown
|
||||
}
|
||||
Returns: unknown
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
account_role: "owner" | "member"
|
||||
app_permissions:
|
||||
| "roles.manage"
|
||||
| "billing.manage"
|
||||
| "settings.manage"
|
||||
| "members.manage"
|
||||
| "invites.manage"
|
||||
billing_provider: "stripe" | "lemon-squeezy" | "paddle"
|
||||
subscription_status:
|
||||
| "active"
|
||||
| "trialing"
|
||||
| "past_due"
|
||||
| "canceled"
|
||||
| "unpaid"
|
||||
| "incomplete"
|
||||
| "incomplete_expired"
|
||||
| "paused"
|
||||
}
|
||||
CompositeTypes: {
|
||||
invitation: {
|
||||
email: string | null
|
||||
role: Database["public"]["Enums"]["account_role"] | null
|
||||
}
|
||||
}
|
||||
}
|
||||
storage: {
|
||||
Tables: {
|
||||
buckets: {
|
||||
Row: {
|
||||
allowed_mime_types: string[] | null
|
||||
avif_autodetection: boolean | null
|
||||
created_at: string | null
|
||||
file_size_limit: number | null
|
||||
id: string
|
||||
name: string
|
||||
owner: string | null
|
||||
owner_id: string | null
|
||||
public: boolean | null
|
||||
updated_at: string | null
|
||||
}
|
||||
Insert: {
|
||||
allowed_mime_types?: string[] | null
|
||||
avif_autodetection?: boolean | null
|
||||
created_at?: string | null
|
||||
file_size_limit?: number | null
|
||||
id: string
|
||||
name: string
|
||||
owner?: string | null
|
||||
owner_id?: string | null
|
||||
public?: boolean | null
|
||||
updated_at?: string | null
|
||||
}
|
||||
Update: {
|
||||
allowed_mime_types?: string[] | null
|
||||
avif_autodetection?: boolean | null
|
||||
created_at?: string | null
|
||||
file_size_limit?: number | null
|
||||
id?: string
|
||||
name?: string
|
||||
owner?: string | null
|
||||
owner_id?: string | null
|
||||
public?: boolean | null
|
||||
updated_at?: string | null
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
migrations: {
|
||||
Row: {
|
||||
executed_at: string | null
|
||||
hash: string
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
Insert: {
|
||||
executed_at?: string | null
|
||||
hash: string
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
Update: {
|
||||
executed_at?: string | null
|
||||
hash?: string
|
||||
id?: number
|
||||
name?: string
|
||||
}
|
||||
Relationships: []
|
||||
}
|
||||
objects: {
|
||||
Row: {
|
||||
bucket_id: string | null
|
||||
created_at: string | null
|
||||
id: string
|
||||
last_accessed_at: string | null
|
||||
metadata: Json | null
|
||||
name: string | null
|
||||
owner: string | null
|
||||
owner_id: string | null
|
||||
path_tokens: string[] | null
|
||||
updated_at: string | null
|
||||
version: string | null
|
||||
}
|
||||
Insert: {
|
||||
bucket_id?: string | null
|
||||
created_at?: string | null
|
||||
id?: string
|
||||
last_accessed_at?: string | null
|
||||
metadata?: Json | null
|
||||
name?: string | null
|
||||
owner?: string | null
|
||||
owner_id?: string | null
|
||||
path_tokens?: string[] | null
|
||||
updated_at?: string | null
|
||||
version?: string | null
|
||||
}
|
||||
Update: {
|
||||
bucket_id?: string | null
|
||||
created_at?: string | null
|
||||
id?: string
|
||||
last_accessed_at?: string | null
|
||||
metadata?: Json | null
|
||||
name?: string | null
|
||||
owner?: string | null
|
||||
owner_id?: string | null
|
||||
path_tokens?: string[] | null
|
||||
updated_at?: string | null
|
||||
version?: string | null
|
||||
}
|
||||
Relationships: [
|
||||
{
|
||||
foreignKeyName: "objects_bucketId_fkey"
|
||||
columns: ["bucket_id"]
|
||||
isOneToOne: false
|
||||
referencedRelation: "buckets"
|
||||
referencedColumns: ["id"]
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
Views: {
|
||||
[_ in never]: never
|
||||
}
|
||||
Functions: {
|
||||
can_insert_object: {
|
||||
Args: {
|
||||
bucketid: string
|
||||
name: string
|
||||
owner: string
|
||||
metadata: Json
|
||||
}
|
||||
Returns: undefined
|
||||
}
|
||||
extension: {
|
||||
Args: {
|
||||
name: string
|
||||
}
|
||||
Returns: string
|
||||
}
|
||||
filename: {
|
||||
Args: {
|
||||
name: string
|
||||
}
|
||||
Returns: string
|
||||
}
|
||||
foldername: {
|
||||
Args: {
|
||||
name: string
|
||||
}
|
||||
Returns: string[]
|
||||
}
|
||||
get_size_by_bucket: {
|
||||
Args: Record<PropertyKey, never>
|
||||
Returns: {
|
||||
size: number
|
||||
bucket_id: string
|
||||
}[]
|
||||
}
|
||||
search: {
|
||||
Args: {
|
||||
prefix: string
|
||||
bucketname: string
|
||||
limits?: number
|
||||
levels?: number
|
||||
offsets?: number
|
||||
search?: string
|
||||
sortcolumn?: string
|
||||
sortorder?: string
|
||||
}
|
||||
Returns: {
|
||||
name: string
|
||||
id: string
|
||||
updated_at: string
|
||||
created_at: string
|
||||
last_accessed_at: string
|
||||
metadata: Json
|
||||
}[]
|
||||
}
|
||||
}
|
||||
Enums: {
|
||||
[_ in never]: never
|
||||
}
|
||||
CompositeTypes: {
|
||||
[_ in never]: never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PublicSchema = Database[Extract<keyof Database, "public">]
|
||||
|
||||
export type Tables<
|
||||
PublicTableNameOrOptions extends
|
||||
| keyof (PublicSchema["Tables"] & PublicSchema["Views"])
|
||||
| { schema: keyof Database },
|
||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? keyof (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
||||
Database[PublicTableNameOrOptions["schema"]]["Views"])
|
||||
: never = never,
|
||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? (Database[PublicTableNameOrOptions["schema"]]["Tables"] &
|
||||
Database[PublicTableNameOrOptions["schema"]]["Views"])[TableName] extends {
|
||||
Row: infer R
|
||||
}
|
||||
? R
|
||||
: never
|
||||
: PublicTableNameOrOptions extends keyof (PublicSchema["Tables"] &
|
||||
PublicSchema["Views"])
|
||||
? (PublicSchema["Tables"] &
|
||||
PublicSchema["Views"])[PublicTableNameOrOptions] extends {
|
||||
Row: infer R
|
||||
}
|
||||
? R
|
||||
: never
|
||||
: never
|
||||
|
||||
export type TablesInsert<
|
||||
PublicTableNameOrOptions extends
|
||||
| keyof PublicSchema["Tables"]
|
||||
| { schema: keyof Database },
|
||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
||||
: never = never,
|
||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
Insert: infer I
|
||||
}
|
||||
? I
|
||||
: never
|
||||
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
||||
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
||||
Insert: infer I
|
||||
}
|
||||
? I
|
||||
: never
|
||||
: never
|
||||
|
||||
export type TablesUpdate<
|
||||
PublicTableNameOrOptions extends
|
||||
| keyof PublicSchema["Tables"]
|
||||
| { schema: keyof Database },
|
||||
TableName extends PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? keyof Database[PublicTableNameOrOptions["schema"]]["Tables"]
|
||||
: never = never,
|
||||
> = PublicTableNameOrOptions extends { schema: keyof Database }
|
||||
? Database[PublicTableNameOrOptions["schema"]]["Tables"][TableName] extends {
|
||||
Update: infer U
|
||||
}
|
||||
? U
|
||||
: never
|
||||
: PublicTableNameOrOptions extends keyof PublicSchema["Tables"]
|
||||
? PublicSchema["Tables"][PublicTableNameOrOptions] extends {
|
||||
Update: infer U
|
||||
}
|
||||
? U
|
||||
: never
|
||||
: never
|
||||
|
||||
export type Enums<
|
||||
PublicEnumNameOrOptions extends
|
||||
| keyof PublicSchema["Enums"]
|
||||
| { schema: keyof Database },
|
||||
EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database }
|
||||
? keyof Database[PublicEnumNameOrOptions["schema"]]["Enums"]
|
||||
: never = never,
|
||||
> = PublicEnumNameOrOptions extends { schema: keyof Database }
|
||||
? Database[PublicEnumNameOrOptions["schema"]]["Enums"][EnumName]
|
||||
: PublicEnumNameOrOptions extends keyof PublicSchema["Enums"]
|
||||
? PublicSchema["Enums"][PublicEnumNameOrOptions]
|
||||
: never
|
||||
|
||||
20
packages/supabase/src/get-supabase-client-keys.ts
Normal file
20
packages/supabase/src/get-supabase-client-keys.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { invariant } from '@epic-web/invariant';
|
||||
|
||||
/**
|
||||
* Returns and validates the Supabase client keys from the environment.
|
||||
*/
|
||||
export function getSupabaseClientKeys() {
|
||||
const env = process.env;
|
||||
|
||||
invariant(env.NEXT_PUBLIC_SUPABASE_URL, `Supabase URL not provided`);
|
||||
|
||||
invariant(
|
||||
env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
`Supabase Anon Key not provided`,
|
||||
);
|
||||
|
||||
return {
|
||||
url: env.NEXT_PUBLIC_SUPABASE_URL,
|
||||
anonKey: env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
};
|
||||
}
|
||||
26
packages/supabase/src/hooks/use-fetch-mfa-factors.ts
Normal file
26
packages/supabase/src/hooks/use-fetch-mfa-factors.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
import { useFactorsMutationKey } from './use-user-factors-mutation-key';
|
||||
|
||||
function useFetchAuthFactors() {
|
||||
const client = useSupabase();
|
||||
const queryKey = useFactorsMutationKey();
|
||||
|
||||
const queryFn = async () => {
|
||||
const { data, error } = await client.auth.mfa.listFactors();
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryKey,
|
||||
queryFn,
|
||||
});
|
||||
}
|
||||
|
||||
export default useFetchAuthFactors;
|
||||
40
packages/supabase/src/hooks/use-request-reset-password.ts
Normal file
40
packages/supabase/src/hooks/use-request-reset-password.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
interface Params {
|
||||
email: string;
|
||||
redirectTo: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name useRequestResetPassword
|
||||
* @description Requests a password reset for a user. This function will
|
||||
* trigger a password reset email to be sent to the user's email address.
|
||||
* After the user clicks the link in the email, they will be redirected to
|
||||
* /password-reset where their password can be updated.
|
||||
*/
|
||||
export function useRequestResetPassword() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'reset-password'];
|
||||
|
||||
const mutationFn = async (params: Params) => {
|
||||
const { error, data } = await client.auth.resetPasswordForEmail(
|
||||
params.email,
|
||||
{
|
||||
redirectTo: params.redirectTo,
|
||||
},
|
||||
);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn,
|
||||
mutationKey,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
interface Credentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function useSignInWithEmailPassword() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'sign-in-with-email-password'];
|
||||
|
||||
const mutationFn = async (credentials: Credentials) => {
|
||||
const response = await client.auth.signInWithPassword(credentials);
|
||||
|
||||
if (response.error) {
|
||||
throw response.error.message;
|
||||
}
|
||||
|
||||
const user = response.data?.user;
|
||||
const identities = user?.identities ?? [];
|
||||
|
||||
// if the user has no identities, it means that the email is taken
|
||||
if (identities.length === 0) {
|
||||
throw new Error('User already registered');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return useMutation({ mutationKey, mutationFn });
|
||||
}
|
||||
46
packages/supabase/src/hooks/use-sign-in-with-otp.ts
Normal file
46
packages/supabase/src/hooks/use-sign-in-with-otp.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type {
|
||||
AuthError,
|
||||
SignInWithPasswordlessCredentials,
|
||||
} from '@supabase/gotrue-js';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
export function useSignInWithOtp() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'sign-in-with-otp'];
|
||||
|
||||
const mutationFn = async (credentials: SignInWithPasswordlessCredentials) => {
|
||||
const result = await client.auth.signInWithOtp(credentials);
|
||||
|
||||
if (result.error) {
|
||||
if (shouldIgnoreError(result.error)) {
|
||||
console.warn(
|
||||
`Ignoring error during development: ${result.error.message}`,
|
||||
);
|
||||
|
||||
return {} as never;
|
||||
}
|
||||
|
||||
throw result.error.message;
|
||||
}
|
||||
|
||||
return result.data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn,
|
||||
mutationKey,
|
||||
});
|
||||
}
|
||||
|
||||
export default useSignInWithOtp;
|
||||
|
||||
function shouldIgnoreError(error: AuthError) {
|
||||
return isSmsProviderNotSetupError(error);
|
||||
}
|
||||
|
||||
function isSmsProviderNotSetupError(error: AuthError) {
|
||||
return error.message.includes(`sms Provider could not be found`);
|
||||
}
|
||||
25
packages/supabase/src/hooks/use-sign-in-with-provider.ts
Normal file
25
packages/supabase/src/hooks/use-sign-in-with-provider.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { SignInWithOAuthCredentials } from '@supabase/gotrue-js';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
export function useSignInWithProvider() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'sign-in-with-provider'];
|
||||
|
||||
const mutationFn = async (credentials: SignInWithOAuthCredentials) => {
|
||||
const response = await client.auth.signInWithOAuth(credentials);
|
||||
|
||||
if (response.error) {
|
||||
throw response.error.message;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn,
|
||||
mutationKey,
|
||||
});
|
||||
}
|
||||
16
packages/supabase/src/hooks/use-sign-out.ts
Normal file
16
packages/supabase/src/hooks/use-sign-out.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
import { useRevalidateUserSession } from './use-user-session';
|
||||
|
||||
export function useSignOut() {
|
||||
const client = useSupabase();
|
||||
const revalidateUserSession = useRevalidateUserSession();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async () => {
|
||||
await client.auth.signOut();
|
||||
await revalidateUserSession();
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
interface Credentials {
|
||||
email: string;
|
||||
password: string;
|
||||
emailRedirectTo: string;
|
||||
}
|
||||
|
||||
export function useSignUpWithEmailAndPassword() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'sign-up-with-email-password'];
|
||||
|
||||
const mutationFn = async (params: Credentials) => {
|
||||
const { emailRedirectTo, ...credentials } = params;
|
||||
|
||||
const response = await client.auth.signUp({
|
||||
...credentials,
|
||||
options: {
|
||||
emailRedirectTo,
|
||||
},
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw response.error.message;
|
||||
}
|
||||
|
||||
const user = response.data?.user;
|
||||
const identities = user?.identities ?? [];
|
||||
|
||||
// if the user has no identities, it means that the email is taken
|
||||
if (identities.length === 0) {
|
||||
throw new Error('User already registered');
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationKey,
|
||||
mutationFn,
|
||||
});
|
||||
}
|
||||
8
packages/supabase/src/hooks/use-supabase.ts
Normal file
8
packages/supabase/src/hooks/use-supabase.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { getSupabaseBrowserClient } from '../clients/browser.client';
|
||||
import { Database } from '../database.types';
|
||||
|
||||
export function useSupabase<Schema = Database>() {
|
||||
return useMemo(() => getSupabaseBrowserClient<Schema>(), []);
|
||||
}
|
||||
31
packages/supabase/src/hooks/use-update-user-mutation.ts
Normal file
31
packages/supabase/src/hooks/use-update-user-mutation.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import type { UserAttributes } from '@supabase/gotrue-js';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
type Params = UserAttributes & { redirectTo: string };
|
||||
|
||||
export function useUpdateUser() {
|
||||
const client = useSupabase();
|
||||
const mutationKey = ['auth', 'update-user'];
|
||||
|
||||
const mutationFn = async (attributes: Params) => {
|
||||
const { redirectTo, ...params } = attributes;
|
||||
|
||||
const response = await client.auth.updateUser(params, {
|
||||
emailRedirectTo: redirectTo,
|
||||
});
|
||||
|
||||
if (response.error) {
|
||||
throw response.error;
|
||||
}
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationKey,
|
||||
mutationFn,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { useUserSession } from './use-user-session';
|
||||
|
||||
export function useFactorsMutationKey() {
|
||||
const user = useUserSession();
|
||||
const userId = user?.data?.user.id;
|
||||
|
||||
return ['mfa-factors', userId];
|
||||
}
|
||||
34
packages/supabase/src/hooks/use-user-session.ts
Normal file
34
packages/supabase/src/hooks/use-user-session.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
const queryKey = ['supabase:session'];
|
||||
|
||||
export function useUserSession() {
|
||||
const supabase = useSupabase();
|
||||
const queryFn = async () => {
|
||||
const { data, error } = await supabase.auth.getSession();
|
||||
console.log(data, error);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data.session;
|
||||
};
|
||||
|
||||
return useQuery({ queryKey, queryFn });
|
||||
}
|
||||
|
||||
export function useRevalidateUserSession() {
|
||||
const client = useQueryClient();
|
||||
|
||||
return useCallback(
|
||||
() =>
|
||||
client.invalidateQueries({
|
||||
queryKey,
|
||||
}),
|
||||
[client],
|
||||
);
|
||||
}
|
||||
27
packages/supabase/src/hooks/use-user.ts
Normal file
27
packages/supabase/src/hooks/use-user.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
export function useUser() {
|
||||
const client = useSupabase();
|
||||
const queryKey = ['user'];
|
||||
|
||||
const queryFn = async () => {
|
||||
const response = await client.auth.getUser();
|
||||
|
||||
if (response.error) {
|
||||
return Promise.reject(response.error);
|
||||
}
|
||||
|
||||
if (response.data?.user) {
|
||||
return response.data.user;
|
||||
}
|
||||
|
||||
return Promise.reject('Unexpected result format');
|
||||
};
|
||||
|
||||
return useQuery({
|
||||
queryFn,
|
||||
queryKey,
|
||||
});
|
||||
}
|
||||
26
packages/supabase/src/hooks/use-verify-otp.ts
Normal file
26
packages/supabase/src/hooks/use-verify-otp.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { VerifyOtpParams } from '@supabase/gotrue-js';
|
||||
|
||||
import { useMutation } from '@tanstack/react-query';
|
||||
|
||||
import { useSupabase } from './use-supabase';
|
||||
|
||||
export function useVerifyOtp() {
|
||||
const client = useSupabase();
|
||||
|
||||
const mutationKey = ['verify-otp'];
|
||||
|
||||
const mutationFn = async (params: VerifyOtpParams) => {
|
||||
const { data, error } = await client.auth.verifyOtp(params);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
return useMutation({
|
||||
mutationFn,
|
||||
mutationKey,
|
||||
});
|
||||
}
|
||||
94
packages/supabase/src/require-auth.ts
Normal file
94
packages/supabase/src/require-auth.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import type { Session, SupabaseClient } from '@supabase/supabase-js';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { checkRequiresMultiFactorAuthentication } from './check-requires-mfa';
|
||||
|
||||
const MULTI_FACTOR_AUTH_VERIFY_PATH = z
|
||||
.string()
|
||||
.default('/auth/verify')
|
||||
.parse(process.env.MULTI_FACTOR_AUTH_VERIFY_PATH);
|
||||
|
||||
const SIGN_IN_PATH = z
|
||||
.string()
|
||||
.default('/auth/sign-in')
|
||||
.parse(process.env.SIGN_IN_PATH);
|
||||
|
||||
/**
|
||||
* @name requireAuth
|
||||
* @description Require a session to be present in the request
|
||||
* @param client
|
||||
* @param verifyFromServer
|
||||
*/
|
||||
export async function requireAuth(
|
||||
client: SupabaseClient,
|
||||
verifyFromServer = true,
|
||||
): Promise<
|
||||
| {
|
||||
error: null;
|
||||
data: Session;
|
||||
}
|
||||
| (
|
||||
| {
|
||||
error: AuthenticationError;
|
||||
data: null;
|
||||
redirectTo: string;
|
||||
}
|
||||
| {
|
||||
error: MultiFactorAuthError;
|
||||
data: null;
|
||||
redirectTo: string;
|
||||
}
|
||||
)
|
||||
> {
|
||||
const { data, error } = await client.auth.getSession();
|
||||
|
||||
if (!data.session || error) {
|
||||
return {
|
||||
data: null,
|
||||
error: new AuthenticationError(),
|
||||
redirectTo: SIGN_IN_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
const requiresMfa = await checkRequiresMultiFactorAuthentication(client);
|
||||
|
||||
// If the user requires multi-factor authentication,
|
||||
// redirect them to the page where they can verify their identity.
|
||||
if (requiresMfa) {
|
||||
return {
|
||||
data: null,
|
||||
error: new MultiFactorAuthError(),
|
||||
redirectTo: MULTI_FACTOR_AUTH_VERIFY_PATH,
|
||||
};
|
||||
}
|
||||
|
||||
if (verifyFromServer) {
|
||||
const { data: user, error } = await client.auth.getUser();
|
||||
|
||||
if (!user || error) {
|
||||
return {
|
||||
data: null,
|
||||
error: new AuthenticationError(),
|
||||
redirectTo: SIGN_IN_PATH,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
error: null,
|
||||
data: data.session,
|
||||
};
|
||||
}
|
||||
|
||||
class AuthenticationError extends Error {
|
||||
constructor() {
|
||||
super(`Authentication required`);
|
||||
}
|
||||
}
|
||||
|
||||
class MultiFactorAuthError extends Error {
|
||||
constructor() {
|
||||
super(`Multi-factor authentication required`);
|
||||
}
|
||||
}
|
||||
8
packages/supabase/tsconfig.json
Normal file
8
packages/supabase/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@kit/tsconfig/base.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
|
||||
},
|
||||
"include": ["*.ts", "src"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
Reference in New Issue
Block a user