Rework env management (checking env requirement in a single place)
Add a helper function to provide env in root loader
Make Supabase admin client a request scoped instance
Make a unique point to get Supabase client (working for client and server side)
isBrowser checks also that typeof process === "undefined" (in vitest, typeof document is defined)
Previously, supabaseAdmin (which has full admin privileges) was a singleton.
In a moment of inattention and depending on how it's used, It could result in a Supabase session leak
Previously :
supabaseAdmin.auth.api.signInWithEmail() // ✅ no user session is stored in client instance
// ⚠️ Danger zone
supabaseAdmin.auth.signIn() // ❌ user session is stored in client instance and is shared over multiple concurrent requests and from different users
// somewhere else in a `loader`/ `action`
supabaseAdmin.auth.user() // can be you or the last signIn user
Now, getSupabaseAdmin() returns a unique client :
getSupabaseAdmin().auth.api.signInWithEmail() // ✅ no user session is stored in client instance
getSupabaseAdmin().auth.signIn() // ✅ user session is stored in client instance but client instance is unique for each request
isBrowser
checks also thattypeof process === "undefined"
(in vitest,typeof document
is defined)Previously,
supabaseAdmin
(which has full admin privileges) was a singleton. In a moment of inattention and depending on how it's used, It could result in a Supabase session leakPreviously :
Now,
getSupabaseAdmin()
returns a unique client :❌ This is not the way ❌ :
💡As much as you can, prefer using non admin supabase client
getSupabase()
untilservice_role
is required for managing your DB (example that requires supabase admin client)getSupabaseAdmin()
only works in server land (throw in browser land)getSupabase()
is safe in server land and browser land, it accepts a useraccessToken
(To use RLS features)Issue Demo : why getting a new client per request will save your production
https://user-images.githubusercontent.com/20722140/184351345-2e637f59-1471-4cda-92ee-a5438a5cdc8f.mp4
NB :
Given this implementation
Supabase sdk init : ```js // supabase.server.ts export supabaseAdmin = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE); export supabase = createClient(SUPABASE_URL, SUPABASE_ANON_PUBLIC); ``` By inadvertence, you sign in your user like that : ```js // ❌ Don't do that // route/login.tsx > loader // import { supabase } from "~/supabase.server" const { session, error } = await supabase.auth.signIn({ email, password, }); ``` instead of : ```js // route/login.tsx > loader // import { supabase } from "~/supabase.server" const { session, error } = await supabase.auth.api.ignInWithEmail( email, password, ); ``` ```js // route/notes.tsx > loader // import { supabase } from "~/supabase.server" // fetch notes with RLS const { data: notes, error } = await supabase .from("Note") .select("id, title"); return json({ notes }); ``` And this is what happen : on refresh, another user can see your notes 😱