|
| 1 | +# Member Management Bug Fixes PRP |
| 2 | + |
| 3 | +## Goal |
| 4 | + |
| 5 | +Fix three critical bugs in the member management system that are breaking core functionality: |
| 6 | + |
| 7 | +1. **getUserByEmail Error**: Fix `q.auth.admin.getUserByEmail is not a function` error when adding members |
| 8 | +2. **Silent OAuth Failure**: Show proper error messages when users sign in with Google but aren't tenant members |
| 9 | +3. **Password Logic**: Don't require password for existing users when adding them to tenants |
| 10 | + |
| 11 | +## Why |
| 12 | + |
| 13 | +- **Business Critical**: Member addition functionality is completely broken due to admin API calls in client-side code |
| 14 | +- **User Experience**: Users get confused by silent failures during Google OAuth when they lack tenant access |
| 15 | +- **Security Best Practice**: Replace client-side admin API calls with secure database functions |
| 16 | +- **Data Consistency**: Avoid creating duplicate users or requiring passwords for existing accounts |
| 17 | + |
| 18 | +## What |
| 19 | + |
| 20 | +### User-Visible Behavior Changes |
| 21 | + |
| 22 | +1. **Fixed Member Addition**: Adding members via email now works reliably for both new and existing users |
| 23 | +2. **Clear Error Messages**: Users signing in with Google see helpful error messages when they lack tenant access |
| 24 | +3. **Smart Password Logic**: System only requests passwords for genuinely new user accounts |
| 25 | +4. **Proper Access Control**: Database function ensures only tenant owners can look up user information |
| 26 | + |
| 27 | +### Technical Requirements |
| 28 | + |
| 29 | +- Replace `supabase.auth.admin.getUserByEmail()` with secure database function |
| 30 | +- Add tenant membership validation error handling in OAuth flows |
| 31 | +- Update member addition logic to handle existing users correctly |
| 32 | +- Ensure proper RLS policies for new database function |
| 33 | +- Add comprehensive test coverage for edge cases |
| 34 | + |
| 35 | +### Success Criteria |
| 36 | + |
| 37 | +- [ ] Member addition works without admin API errors |
| 38 | +- [ ] Google OAuth shows clear error messages for non-members |
| 39 | +- [ ] Password field only appears for genuinely new users |
| 40 | +- [ ] All existing tests pass |
| 41 | +- [ ] New RLS test covers database function security |
| 42 | +- [ ] UI tests cover error message display |
| 43 | + |
| 44 | +## All Needed Context |
| 45 | + |
| 46 | +### Documentation & References |
| 47 | + |
| 48 | +```yaml |
| 49 | +# MUST READ - Include these in your context window |
| 50 | +- url: https://www.reddit.com/r/Supabase/comments/1cfurdu/method_to_check_if_user_exists_with_an_email/ |
| 51 | + why: Community solution showing database function approach for user lookups |
| 52 | + |
| 53 | +- file: src/lib/member-service.ts:84 |
| 54 | + why: Current broken implementation using admin API on client-side |
| 55 | + |
| 56 | +- file: src/components/Members/MemberAddDialog.tsx:114 |
| 57 | + why: UI component making the failing admin API call |
| 58 | + |
| 59 | +- file: supabase/migrations/20250509034800_create_user.sql |
| 60 | + why: Pattern for security definer functions accessing auth.users |
| 61 | + |
| 62 | +- file: src/pages/tenant/AuthPage.tsx:64-73 |
| 63 | + why: Silent failure location for OAuth tenant membership validation |
| 64 | + |
| 65 | +- file: tests/rls/tenant-members.rls.test.ts |
| 66 | + why: Pattern for testing tenant-specific database functions |
| 67 | + |
| 68 | +- file: src/lib/membership-service.ts:36-77 |
| 69 | + why: Pattern for associating users with tenants (reusable in fixes) |
| 70 | +``` |
| 71 | +
|
| 72 | +### Current Codebase Structure |
| 73 | +
|
| 74 | +```bash |
| 75 | +src/ |
| 76 | +├── lib/ |
| 77 | +│ ├── member-service.ts # BROKEN: Uses admin API (line 84) |
| 78 | +│ ├── membership-service.ts # WORKING: Tenant association logic |
| 79 | +│ └── types.ts # Type definitions to extend |
| 80 | +├── components/Members/ |
| 81 | +│ └── MemberAddDialog.tsx # BROKEN: Calls failing service (line 114) |
| 82 | +├── pages/tenant/ |
| 83 | +│ └── AuthPage.tsx # MISSING: Error display for OAuth failures (line 67) |
| 84 | +└── integrations/supabase/ |
| 85 | + └── client.ts # Standard client (NOT admin) |
| 86 | + |
| 87 | +supabase/migrations/ |
| 88 | +├── 20250509034800_create_user.sql # PATTERN: Security definer functions |
| 89 | +└── [new] get_user_id_by_email.sql # TO CREATE: User lookup function |
| 90 | + |
| 91 | +tests/ |
| 92 | +├── rls/tenant-members.rls.test.ts # PATTERN: RLS function testing |
| 93 | +└── ui/components/Members/ # COVERAGE: Need error case tests |
| 94 | +``` |
| 95 | + |
| 96 | +### Known Gotchas & Library Quirks |
| 97 | + |
| 98 | +```typescript |
| 99 | +// CRITICAL: Supabase Admin API not available in client-side code |
| 100 | +// Current ERROR: supabase.auth.admin.getUserByEmail(email) |
| 101 | +// ❌ This runs in browser and fails with "not a function" |
| 102 | + |
| 103 | +// PATTERN: Security definer functions for auth.users access |
| 104 | +// ✅ Use database functions with SECURITY DEFINER like create_user.sql |
| 105 | +// ✅ Call via supabase.rpc() instead of admin API |
| 106 | + |
| 107 | +// GOTCHA: RLS policies must allow function execution by tenant owners |
| 108 | +// PATTERN: Check existing check_tenant_user_limit for owner validation pattern |
| 109 | + |
| 110 | +// CRITICAL: OAuth callback flow doesn't show user-facing errors |
| 111 | +// Current: console.log() only (line 67 of AuthPage.tsx) |
| 112 | +// Need: Toast notification or error state display |
| 113 | + |
| 114 | +// PASSWORD LOGIC: Don't add password for existing users |
| 115 | +// Check user existence BEFORE showing password fields |
| 116 | +// Update form validation to make password optional for existing users |
| 117 | +``` |
| 118 | + |
| 119 | +## Implementation Blueprint |
| 120 | + |
| 121 | +### Database Function |
| 122 | + |
| 123 | +Create secure user lookup function with proper RLS: |
| 124 | + |
| 125 | +```sql |
| 126 | +-- Migration: supabase/migrations/[timestamp]_get_user_id_by_email.sql |
| 127 | +CREATE OR REPLACE FUNCTION public.get_user_id_by_email(p_email text) |
| 128 | +RETURNS uuid |
| 129 | +LANGUAGE plpgsql |
| 130 | +SECURITY DEFINER |
| 131 | +SET search_path = '' |
| 132 | +AS $$ |
| 133 | +BEGIN |
| 134 | + -- CRITICAL: Only allow tenant owners to call this function |
| 135 | + -- Pattern from check_tenant_user_limit function |
| 136 | + IF NOT public.is_tenant_owner() THEN |
| 137 | + RAISE EXCEPTION 'Access denied: Only tenant owners can look up users'; |
| 138 | + END IF; |
| 139 | + |
| 140 | + RETURN ( |
| 141 | + SELECT id |
| 142 | + FROM auth.users |
| 143 | + WHERE email = p_email |
| 144 | + LIMIT 1 |
| 145 | + ); |
| 146 | +END; |
| 147 | +$$; |
| 148 | + |
| 149 | +-- Grant execution to authenticated users (RLS handles authorization) |
| 150 | +GRANT EXECUTE ON FUNCTION public.get_user_id_by_email(text) TO authenticated; |
| 151 | +``` |
| 152 | + |
| 153 | +### Service Layer Changes |
| 154 | + |
| 155 | +Replace admin API calls with database function: |
| 156 | + |
| 157 | +```typescript |
| 158 | +// src/lib/member-service.ts - MODIFY addMemberToTenant function |
| 159 | +// REPLACE lines 82-88: |
| 160 | +const { data: existingUser, error: userError } = await supabase.auth.admin.getUserByEmail(email); // ❌ REMOVE |
| 161 | + |
| 162 | +// WITH: |
| 163 | +const existingUserId = await supabase |
| 164 | + .rpc("get_user_id_by_email", { |
| 165 | + p_email: email, |
| 166 | + }) |
| 167 | + .then(({ data, error }) => { |
| 168 | + if (error) { |
| 169 | + if (error.code === "P0001") { |
| 170 | + // Access denied |
| 171 | + throw new Error("Only tenant owners can add members"); |
| 172 | + } |
| 173 | + console.error("Error checking user:", error); |
| 174 | + return null; |
| 175 | + } |
| 176 | + return data; |
| 177 | + }); |
| 178 | + |
| 179 | +const userExists = !!existingUserId; |
| 180 | +``` |
| 181 | + |
| 182 | +### OAuth Error Handling |
| 183 | + |
| 184 | +Add tenant membership error messages: |
| 185 | + |
| 186 | +```typescript |
| 187 | +// src/pages/tenant/AuthPage.tsx - MODIFY lines 64-73 |
| 188 | +if (hasAccess) { |
| 189 | + navigate(`/tenant/${slug}`); |
| 190 | +} else if (!inviteToken) { |
| 191 | + // ADD: User-facing error message instead of console.log |
| 192 | + setError(t("auth:noPermissionToEnterChurch")); |
| 193 | + toast({ |
| 194 | + title: t("auth:accessDenied"), |
| 195 | + description: t("auth:contactAdminForAccess", { tenantName }), |
| 196 | + variant: "destructive", |
| 197 | + }); |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +### List of Tasks (Execution Order) |
| 202 | + |
| 203 | +```yaml |
| 204 | +Task 1: Create Database Function |
| 205 | +MODIFY supabase/migrations/[new]_get_user_id_by_email.sql: |
| 206 | + - MIRROR pattern from: supabase/migrations/20250509034800_create_user.sql |
| 207 | + - COPY security definer setup and auth.users access pattern |
| 208 | + - ADD is_tenant_owner() authorization check |
| 209 | + - PRESERVE existing function naming conventions |
| 210 | + |
| 211 | +Task 2: Update Member Service |
| 212 | +MODIFY src/lib/member-service.ts: |
| 213 | + - FIND pattern: "supabase.auth.admin.getUserByEmail(email)" (line 84) |
| 214 | + - REPLACE with: "supabase.rpc('get_user_id_by_email', { p_email: email })" |
| 215 | + - PRESERVE existing error handling structure |
| 216 | + - KEEP associateUserWithTenant call pattern identical |
| 217 | + |
| 218 | +Task 3: Fix Password Logic in UI |
| 219 | +MODIFY src/components/Members/MemberAddDialog.tsx: |
| 220 | + - FIND pattern: "supabase.auth.admin.getUserByEmail(watchEmail)" (line 114) |
| 221 | + - REPLACE with: database function call |
| 222 | + - PRESERVE existing form validation patterns |
| 223 | + - KEEP password field conditional logic structure |
| 224 | + |
| 225 | +Task 4: Add OAuth Error Display |
| 226 | +MODIFY src/pages/tenant/AuthPage.tsx: |
| 227 | + - FIND pattern: "console.log('User is not a member')" (line 67) |
| 228 | + - INJECT error state and toast notification |
| 229 | + - PRESERVE existing navigation logic |
| 230 | + - MIRROR error handling pattern from other auth components |
| 231 | + |
| 232 | +Task 5: Create RLS Test |
| 233 | +CREATE tests/rls/get-user-id-by-email.rls.test.ts: |
| 234 | + - MIRROR pattern from: tests/rls/tenant-members.rls.test.ts |
| 235 | + - TEST tenant owner access allowed |
| 236 | + - TEST non-owner access denied |
| 237 | + - TEST function returns correct user ID |
| 238 | + |
| 239 | +Task 6: Add UI Error Tests |
| 240 | +CREATE tests/ui/pages/tenant/AuthPage.oauth-errors.test.tsx: |
| 241 | + - MIRROR pattern from: tests/ui/pages/tenant/AuthPage.test.tsx |
| 242 | + - TEST error message display for non-members |
| 243 | + - TEST toast notification shows |
| 244 | + - TEST user can retry or navigate away |
| 245 | +``` |
| 246 | +
|
| 247 | +### Integration Points |
| 248 | +
|
| 249 | +```yaml |
| 250 | +DATABASE: |
| 251 | + - migration: "Add get_user_id_by_email function with RLS" |
| 252 | + - permission: "Grant execute to authenticated users" |
| 253 | + - security: "Restrict to tenant owners via is_tenant_owner()" |
| 254 | + |
| 255 | +TRANSLATIONS: |
| 256 | + - add to: public/locales/en/auth.json |
| 257 | + - keys: "accessDenied", "contactAdminForAccess" |
| 258 | + - pattern: Follow existing error message format |
| 259 | + |
| 260 | +SERVICES: |
| 261 | + - modify: src/lib/member-service.ts (replace admin API) |
| 262 | + - preserve: All existing function signatures and return types |
| 263 | + - enhance: Error handling for authorization failures |
| 264 | +``` |
| 265 | +
|
| 266 | +## Validation Loop |
| 267 | +
|
| 268 | +### Level 1: Syntax & Database |
| 269 | +
|
| 270 | +```bash |
| 271 | +# Run migration and check function creation |
| 272 | +npm run db:reset # Apply all migrations including new function |
| 273 | +npm run db:start # Start local Supabase |
| 274 | + |
| 275 | +# Verify function exists and has correct permissions |
| 276 | +# Expected: Function created with SECURITY DEFINER and proper grants |
| 277 | +``` |
| 278 | + |
| 279 | +### Level 2: RLS Security Tests |
| 280 | + |
| 281 | +```bash |
| 282 | +# Test database function security |
| 283 | +./tests/rls/run-rls-tests.sh get-user-id-by-email.rls.test.ts |
| 284 | + |
| 285 | +# Expected: |
| 286 | +# ✅ Tenant owners can call function |
| 287 | +# ✅ Non-owners get access denied error |
| 288 | +# ✅ Function returns correct user ID for existing users |
| 289 | +# ✅ Function returns null for non-existent users |
| 290 | +``` |
| 291 | + |
| 292 | +### Level 3: Service Integration Tests |
| 293 | + |
| 294 | +```bash |
| 295 | +# Test member service with new database function |
| 296 | +npm run test:ui -- --testPathPattern="Members.*test" |
| 297 | + |
| 298 | +# Expected: |
| 299 | +# ✅ Adding existing users works without password |
| 300 | +# ✅ Adding new users requires password |
| 301 | +# ✅ Admin API errors eliminated |
| 302 | +# ✅ Proper error messages for unauthorized access |
| 303 | +``` |
| 304 | + |
| 305 | +### Level 4: OAuth Flow Tests |
| 306 | + |
| 307 | +```bash |
| 308 | +# Test OAuth error handling |
| 309 | +npm run test:ui -- --testPathPattern="AuthPage.*test" |
| 310 | + |
| 311 | +# Expected: |
| 312 | +# ✅ Non-member OAuth shows error message |
| 313 | +# ✅ Toast notification appears |
| 314 | +# ✅ User can see clear next steps |
| 315 | +# ✅ No silent failures remain |
| 316 | +``` |
| 317 | + |
| 318 | +## Final Validation Checklist |
| 319 | + |
| 320 | +- [ ] All tests pass: `npm test` |
| 321 | +- [ ] No linting errors: `npm run lint` |
| 322 | +- [ ] Database migration applies cleanly: `npm run db:reset` |
| 323 | +- [ ] Member addition works in UI: Manual test adding existing user |
| 324 | +- [ ] OAuth errors show properly: Manual test with non-member Google account |
| 325 | +- [ ] RLS policies prevent unauthorized access: RLS test suite passes |
| 326 | +- [ ] No admin API calls remain in client code: Code search verification |
| 327 | + |
| 328 | +## Confidence Score: 9/10 |
| 329 | + |
| 330 | +**High Confidence Factors:** |
| 331 | + |
| 332 | +- Clear replication of existing patterns (security definer functions, RLS tests) |
| 333 | +- Detailed analysis of current failures with specific line references |
| 334 | +- Comprehensive validation loops covering security, functionality, and UX |
| 335 | +- Well-established codebase with consistent patterns to follow |
| 336 | + |
| 337 | +**Minor Risk Factor:** |
| 338 | + |
| 339 | +- Need to verify exact `is_tenant_owner()` function implementation for RLS policy |
| 340 | + |
| 341 | +--- |
| 342 | + |
| 343 | +## Anti-Patterns to Avoid |
| 344 | + |
| 345 | +- ❌ Don't use admin API in client-side code (root cause of current bug) |
| 346 | +- ❌ Don't skip RLS testing for database functions accessing auth.users |
| 347 | +- ❌ Don't leave silent failures - always provide user feedback |
| 348 | +- ❌ Don't create new error handling patterns when existing ones work |
| 349 | +- ❌ Don't modify password logic without proper existing user detection |
| 350 | +- ❌ Don't bypass tenant owner authorization in database functions |
0 commit comments