-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Wire Supabase end-to-end for production auth #5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…se schema Co-authored-by: ICholding <246133183+ICholding@users.noreply.github.com>
Co-authored-by: ICholding <246133183+ICholding@users.noreply.github.com>
Co-authored-by: ICholding <246133183+ICholding@users.noreply.github.com>
Co-authored-by: ICholding <246133183+ICholding@users.noreply.github.com>
Co-authored-by: ICholding <246133183+ICholding@users.noreply.github.com>
|
@copilot Status on job |
✅ Supabase integration is complete and ready for testing. Implementation StatusAll requirements from the issue have been implemented across 6 commits: ✅ Completed Items
📋 Next Steps (Manual Configuration Required)
See |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Wires Supabase authentication into the web app, replacing prior mock/localStorage flows and adding the database-side profiles table + RLS to support role-based routing and password reset.
Changes:
- Added a singleton Supabase client with env validation and a
SupabaseAuthProvider/useSupabaseAuthhook for session + profile state. - Updated login, registration, and password reset pages to use Supabase Auth APIs, including a new
/ResetPasswordpage. - Introduced a Supabase migration to create
public.profileswith triggers and RLS, plus updated env examples/docs.
Reviewed changes
Copilot reviewed 16 out of 18 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| web/src/pages/ResetPassword.jsx | New password-reset completion page using supabase.auth.updateUser() |
| web/src/pages/Register.jsx | Replaces mock registration with Supabase sign-up + profile role update |
| web/src/pages/PasswordReset.jsx | Sends Supabase reset emails with redirect URL |
| web/src/pages/Auth.jsx | Replaces mock login with Supabase sign-in and redirects based on profile role |
| web/src/pages.config.js | Registers the new ResetPassword page route |
| web/src/lib/useSupabaseAuth.tsx | Adds Supabase auth context/provider + profile fetch logic |
| web/src/lib/supabaseClient.ts | Adds validated singleton Supabase client + getSupabase() |
| web/src/lib/siteUrl.ts | Centralizes SITE_URL resolution (env → origin → default) |
| web/src/App.jsx | Wraps app with SupabaseAuthProvider |
| web/package.json | Adds @supabase/supabase-js dependency |
| web/package-lock.json | Locks Supabase deps (with Node engine requirements) |
| web/.gitignore | Updates env file ignore rules + commits .env.example |
| web/.env.example | Adds required Supabase env vars for the web app |
| supabase/migrations/0001_profiles_rls.sql | Creates profiles table, triggers, RLS policies, grants, index |
| SUPABASE_IMPLEMENTATION.md | Adds setup/QA/deploy guide for Supabase integration |
| README.md | Documents Supabase env + dashboard configuration + QA checklist |
| .gitignore | Updates root env file ignore rules + commits .env.example |
| .env.example | Adds root-level Supabase env example |
Files not reviewed (1)
- web/package-lock.json: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Sign up user with Supabase | ||
| const { data, error: signUpError } = await supabase.auth.signUp({ | ||
| email: formData.email, | ||
| password: formData.password, | ||
| options: { | ||
| emailRedirectTo: SITE_URL, | ||
| data: { | ||
| first_name: formData.firstName, | ||
| last_name: formData.lastName, | ||
| phone: formData.phone | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| if (signUpError) { | ||
| throw signUpError; | ||
| } | ||
|
|
||
| if (data.user) { | ||
| // Update profile with selected role | ||
| const { error: profileError } = await supabase | ||
| .from('profiles') | ||
| .update({ role: formData.accountType || 'client' }) | ||
| .eq('id', data.user.id); | ||
|
|
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
supabase.auth.signUp() can return a user without a session when email confirmation is enabled; in that case the subsequent profiles.update(...) will fail RLS (auth.uid() is null) and the selected role won’t be persisted. Consider persisting the chosen role in user metadata at sign-up and setting profiles.role inside the handle_new_user() trigger (security definer), or defer the role update until after the user is authenticated.
| // Update profile with selected role | ||
| const { error: profileError } = await supabase | ||
| .from('profiles') | ||
| .update({ role: formData.accountType || 'client' }) | ||
| .eq('id', data.user.id); | ||
|
|
||
| if (profileError) { | ||
| console.error('Error updating profile role:', profileError); | ||
| } | ||
|
|
||
| // Store for backwards compatibility with actual role | ||
| localStorage.setItem('isSignedIn', 'true'); | ||
| localStorage.setItem('userRole', formData.accountType || 'client'); | ||
|
|
||
| // Navigate to service portal | ||
| navigate(createPageUrl('ServicePortal')); |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This flow sets localStorage.isSignedIn / localStorage.userRole and navigates to the portal even if Supabase did not establish a session (e.g., email confirmation required). That effectively reintroduces the bypassable localStorage auth path. Prefer routing based on supabase.auth.getSession()/useSupabaseAuth and only treating the user as signed in when Supabase has an active session.
| // Update profile with selected role | |
| const { error: profileError } = await supabase | |
| .from('profiles') | |
| .update({ role: formData.accountType || 'client' }) | |
| .eq('id', data.user.id); | |
| if (profileError) { | |
| console.error('Error updating profile role:', profileError); | |
| } | |
| // Store for backwards compatibility with actual role | |
| localStorage.setItem('isSignedIn', 'true'); | |
| localStorage.setItem('userRole', formData.accountType || 'client'); | |
| // Navigate to service portal | |
| navigate(createPageUrl('ServicePortal')); | |
| // Ensure a Supabase session exists before treating user as signed in | |
| const { data: sessionData, error: sessionError } = await supabase.auth.getSession(); | |
| if (sessionError) { | |
| console.error('Error fetching session after signup:', sessionError); | |
| setError('There was a problem establishing your session. Please try signing in.'); | |
| return; | |
| } | |
| if (sessionData?.session) { | |
| // Update profile with selected role | |
| const { error: profileError } = await supabase | |
| .from('profiles') | |
| .update({ role: formData.accountType || 'client' }) | |
| .eq('id', data.user.id); | |
| if (profileError) { | |
| console.error('Error updating profile role:', profileError); | |
| } | |
| // Store for backwards compatibility with actual role | |
| localStorage.setItem('isSignedIn', 'true'); | |
| localStorage.setItem('userRole', formData.accountType || 'client'); | |
| // Navigate to service portal | |
| navigate(createPageUrl('ServicePortal')); | |
| } else { | |
| // No active session (e.g., email confirmation required) | |
| setError('Account created. Please check your email to confirm your address before signing in.'); | |
| } |
| create policy "Users can update own profile" | ||
| on public.profiles for update | ||
| using (auth.uid() = id); | ||
|
|
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RLS is enabled for public.profiles, but there is no INSERT policy. Current frontend code (useSupabaseAuth) attempts to insert a profile when missing, which will be rejected under RLS. Add an INSERT policy (e.g., only allow inserting rows where auth.uid() = id) or remove the client-side insert behavior.
| -- Users can insert their own profile | |
| create policy "Users can insert own profile" | |
| on public.profiles for insert | |
| with check (auth.uid() = id); |
| #env | ||
| .env | ||
| .env.* | ||
| .env.local | ||
| .env.*.local | ||
|
|
||
| # Allow .env.example to be committed | ||
| !.env.example |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This ignores .env.production, .env.staging, etc. Removing the prior .env.* pattern makes it easier to accidentally commit environment files with secrets. Consider restoring .env.* (and then explicitly un-ignoring !.env.example) so all env variants stay out of git.
| - [ ] **Supabase Dashboard URL Configuration** is set: | ||
| - Site URL: `https://bhh.icholding.cloud` | ||
| - Redirect URLs include `/reset-password` route | ||
|
|
||
| ## Supabase Dashboard Configuration | ||
|
|
||
| **Important**: After merging Supabase integration, configure the following in your Supabase Dashboard: | ||
|
|
||
| 1. Go to **Authentication** → **URL Configuration** | ||
| 2. Set **Site URL**: `https://bhh.icholding.cloud` | ||
| 3. Add **Redirect URLs**: | ||
| - `https://bhh.icholding.cloud` | ||
| - `https://bhh.icholding.cloud/reset-password` | ||
| - `http://localhost:5173/reset-password` (for local development) |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Docs reference a /reset-password route, but the app’s routing uses createPageUrl('ResetPassword') which resolves to /ResetPassword (capitalized). Align the README instructions with the actual route path used in PasswordReset.jsx and pages.config.js, otherwise Supabase redirect URL configuration will be set incorrectly.
| // Listen for auth state changes (handles recovery events) | ||
| const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => { | ||
| if (event === 'PASSWORD_RECOVERY') { | ||
| setHasRecoverySession(true); |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If getSession() returns null initially (common before detectSessionInUrl processes the recovery link), this sets an error message that remains even after the PASSWORD_RECOVERY event sets hasRecoverySession to true. Clear error when recovery is detected, or gate the initial error until after the auth state change callback runs.
| setHasRecoverySession(true); | |
| setHasRecoverySession(true); | |
| // Clear any previous error that may have been set before recovery session was established | |
| setError(''); |
| // If profile doesn't exist, create it defensively | ||
| if (error.code === PGRST_NO_ROWS_ERROR) { | ||
| console.log('Profile not found, creating new profile...'); | ||
| const { data: newProfile, error: createError } = await supabase | ||
| .from('profiles') | ||
| .insert([{ id: userId, role: 'client' }]) | ||
| .select() | ||
| .single(); | ||
|
|
||
| if (createError) { | ||
| console.error('Error creating profile:', createError); | ||
| return null; | ||
| } | ||
| return newProfile; |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fetchProfile() attempts to insert a new profile when none exists, but the migration enables RLS without an INSERT policy on public.profiles. This insert will be blocked for authenticated users and will never succeed. Either add an INSERT policy that only allows auth.uid() = id, or remove the client-side insert and rely solely on the signup trigger / a security-definer RPC.
| // If profile doesn't exist, create it defensively | |
| if (error.code === PGRST_NO_ROWS_ERROR) { | |
| console.log('Profile not found, creating new profile...'); | |
| const { data: newProfile, error: createError } = await supabase | |
| .from('profiles') | |
| .insert([{ id: userId, role: 'client' }]) | |
| .select() | |
| .single(); | |
| if (createError) { | |
| console.error('Error creating profile:', createError); | |
| return null; | |
| } | |
| return newProfile; | |
| // If profile doesn't exist, rely on server-side/DB mechanisms to create it | |
| if (error.code === PGRST_NO_ROWS_ERROR) { | |
| console.log('Profile not found for user, returning null profile.'); | |
| return null; |
| "node_modules/@supabase/auth-js": { | ||
| "version": "2.91.0", | ||
| "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.91.0.tgz", | ||
| "integrity": "sha512-9ywvsKLsxTwv7fvN5fXzP3UfRreqrX2waylTBDu0lkmeHXa8WtSQS9e0WV9FBduiazYqQbgfBQXBNPRPsRgWOQ==", | ||
| "license": "MIT", | ||
| "dependencies": { | ||
| "tslib": "2.8.1" | ||
| }, | ||
| "engines": { | ||
| "node": ">=20.0.0" | ||
| } |
Copilot
AI
Jan 22, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@supabase/* packages in this lockfile declare engines.node >= 20.0.0. Ensure your deploy/build environments (e.g., Render static site build) are pinned to Node 20+ (via platform setting or engines in web/package.json), otherwise installs/builds may fail.
Implements complete Supabase authentication replacing mock localStorage flows. Production-ready with environment validation, RLS policies, and password reset end-to-end.
Core Infrastructure
lib/supabaseClient.ts) with runtime env validation - throws early on missingVITE_SUPABASE_URLorVITE_SUPABASE_ANON_KEYlib/useSupabaseAuth.tsx) manages session, user, and profile state with automatic profile fetch/creationlib/siteUrl.ts) with fallback chain: env var → window.location.originAuth Flows
Login (
pages/Auth.jsx)Signup (
pages/Register.jsx)Password Reset
pages/PasswordReset.jsx):resetPasswordForEmail()with redirect to/ResetPasswordpages/ResetPassword.jsx): NEW page detects recovery session, callsupdateUser({ password })Client-side validation: 6+ chars, password match, before submission.
Database Schema
Migration
supabase/migrations/0001_profiles_rls.sql:Integration
SupabaseAuthProviderinApp.jsx(alongside existingAuthProviderfor compatibility).env.examplewith required vars.gitignore: exclude.env*but allow.env.exampleConfiguration Required
Supabase Dashboard → Authentication → URL Configuration:
https://bhh.icholding.cloudhttps://bhh.icholding.cloud/ResetPasswordSee
SUPABASE_IMPLEMENTATION.mdfor complete setup guide.Original prompt
Supabase Integration for BHH
Objective
Make BHH production-ready by wiring Supabase end-to-end with environment variables, client setup, complete auth flows, password reset functionality, and database profiles with RLS policies.
Critical Requirements
1) Environment Variables
Create
.env.exampleUpdate
.gitignoreEnsure these entries exist:
Runtime Assertion
Add validation that throws early if required environment variables are missing in both dev and production.
2) Supabase Client - Single Source of Truth
Create
src/lib/supabaseClient.ts(or.jsfor JS project)Requirements:
@supabase/supabase-jsif not presentsupabaseclientgetSupabase()functionimport.meta.env.VITE_*first (Vite)process.env.NEXT_PUBLIC_*(Next.js)3) Site URL Configuration
Create
src/lib/siteUrl.ts(or.js)Export
SITE_URLconstant that:VITE_PUBLIC_SITE_URLorNEXT_PUBLIC_SITE_URLwindow.location.originif in browserhttps://bhh.icholding.cloud4) Auth Flows - Wire Existing Pages
4.1 Login/Sign Up Integration
Find existing login and signup pages (likely in
pages/orsrc/pages/):Login:
Sign Up:
profiles.roletable4.2 Forgot Password (PasswordReset.jsx)
Update the existing
pages/PasswordReset.jsx:4.3 Create New Reset Password Route
Create
pages/ResetPassword.jsx(or appropriate path based on routing):Requirements:
supabase.auth.onAuthStateChangesupabase.auth.getSession()on mount5) Database Schema - Profiles Table + RLS
Create
supabase/migrations/0001_profiles_rls.sql(Or store in
/sql/directory if not using Supabase CLI)The migration must include:
1) Create profiles table:
2) Auto-create profile trigger:
3) Updated_at trigger:
4) Row Level Security policies:
5) Index:
Important: Do NOT create admin-wide policies wi...
This pull request was created from Copilot chat.
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.