/** *
*

Official Supabase adapter for Auth.js / NextAuth.js.

* * * *
* * ## Installation * * ```bash npm2yarn * npm install @supabase/supabase-js @auth/supabase-adapter * ``` * * @module @auth/supabase-adapter */ import { createClient } from "@supabase/supabase-js" import { type Adapter, type AdapterSession, type AdapterUser, type VerificationToken, isDate, } from "@auth/core/adapters" export function format(obj: Record): T { for (const [key, value] of Object.entries(obj)) { if (value === null) { delete obj[key] } if (isDate(value)) { obj[key] = new Date(value) } } return obj as T } /** * This is the interface of the Supabase adapter options. **/ export interface SupabaseAdapterOptions { /** * The URL of your Supabase database **/ url: string /** * The secret to grant access to the database **/ secret: string } export function SupabaseAdapter(options: SupabaseAdapterOptions): Adapter { const { url, secret } = options const supabase = createClient(url, secret, { db: { schema: "next_auth" }, global: { headers: { "X-Client-Info": "@auth/supabase-adapter" } }, auth: { persistSession: false }, }) return { async createUser(user) { const { data, error } = await supabase .from("users") .insert({ ...user, emailVerified: user.emailVerified?.toISOString(), }) .select() .single() if (error) throw error return format(data) }, async getUser(id) { const { data, error } = await supabase .from("users") .select() .eq("id", id) .maybeSingle() if (error) throw error if (!data) return null return format(data) }, async getUserByEmail(email) { const { data, error } = await supabase .from("users") .select() .eq("email", email) .maybeSingle() if (error) throw error if (!data) return null return format(data) }, async getUserByAccount({ providerAccountId, provider }) { const { data, error } = await supabase .from("accounts") .select("users (*)") .match({ provider, providerAccountId }) .maybeSingle() if (error) throw error if (!data || !data.users) return null return format(data.users) }, async updateUser(user) { const { data, error } = await supabase .from("users") .update({ ...user, emailVerified: user.emailVerified?.toISOString(), }) .eq("id", user.id) .select() .single() if (error) throw error return format(data) }, async deleteUser(userId) { const { error } = await supabase.from("users").delete().eq("id", userId) if (error) throw error }, async linkAccount(account) { const { error } = await supabase.from("accounts").insert(account) if (error) throw error }, async unlinkAccount({ providerAccountId, provider }) { const { error } = await supabase .from("accounts") .delete() .match({ provider, providerAccountId }) if (error) throw error }, async createSession({ sessionToken, userId, expires }) { const { data, error } = await supabase .from("sessions") .insert({ sessionToken, userId, expires: expires.toISOString() }) .select() .single() if (error) throw error return format(data) }, async getSessionAndUser(sessionToken) { const { data, error } = await supabase .from("sessions") .select("*, users(*)") .eq("sessionToken", sessionToken) .maybeSingle() if (error) throw error if (!data) return null const { users: user, ...session } = data return { user: format( user as Database["next_auth"]["Tables"]["users"]["Row"][] ), session: format(session), } }, async updateSession(session) { const { data, error } = await supabase .from("sessions") .update({ ...session, expires: session.expires?.toISOString(), }) .eq("sessionToken", session.sessionToken) .select() .single() if (error) throw error return format(data) }, async deleteSession(sessionToken) { const { error } = await supabase .from("sessions") .delete() .eq("sessionToken", sessionToken) if (error) throw error }, async createVerificationToken(token) { const { data, error } = await supabase .from("verification_tokens") .insert({ ...token, expires: token.expires.toISOString(), }) .select() .single() if (error) throw error // eslint-disable-next-line @typescript-eslint/no-unused-vars const { id, ...verificationToken } = data return format(verificationToken) }, async useVerificationToken({ identifier, token }) { const { data, error } = await supabase .from("verification_tokens") .delete() .match({ identifier, token }) .select() .maybeSingle() if (error) throw error if (!data) return null // eslint-disable-next-line @typescript-eslint/no-unused-vars const { id, ...verificationToken } = data return format(verificationToken) }, } } interface Database { next_auth: { Tables: { accounts: { Row: { id: string type: string | null provider: string | null providerAccountId: string | null refresh_token: string | null access_token: string | null expires_at: number | null token_type: string | null scope: string | null id_token: string | null session_state: string | null oauth_token_secret: string | null oauth_token: string | null userId: string | null } Insert: { id?: string type?: string | null provider?: string | null providerAccountId?: string | null refresh_token?: string | null access_token?: string | null expires_at?: number | null token_type?: string | null scope?: string | null id_token?: string | null session_state?: string | null oauth_token_secret?: string | null oauth_token?: string | null userId?: string | null } Update: { id?: string type?: string | null provider?: string | null providerAccountId?: string | null refresh_token?: string | null access_token?: string | null expires_at?: number | null token_type?: string | null scope?: string | null id_token?: string | null session_state?: string | null oauth_token_secret?: string | null oauth_token?: string | null userId?: string | null } } sessions: { Row: { expires: string | null sessionToken: string | null userId: string | null id: string } Insert: { expires?: string | null sessionToken?: string | null userId?: string | null id?: string } Update: { expires?: string | null sessionToken?: string | null userId?: string | null id?: string } } users: { Row: { name: string | null email: string | null emailVerified: string | null image: string | null id: string } Insert: { name?: string | null email?: string | null emailVerified?: string | null image?: string | null id?: string } Update: { name?: string | null email?: string | null emailVerified?: string | null image?: string | null id?: string } } verification_tokens: { Row: { id: number identifier: string | null token: string | null expires: string | null } Insert: { id?: number identifier?: string | null token?: string | null expires?: string | null } Update: { id?: number identifier?: string | null token?: string | null expires?: string | null } } } Views: { [_ in never]: never } Functions: { uid: { Args: Record Returns: string } } Enums: { [_ in never]: never } } }