Skip to content

Commit 9e929bc

Browse files
committed
fix: tenant member management
1 parent b43af1a commit 9e929bc

File tree

7 files changed

+650
-16
lines changed

7 files changed

+650
-16
lines changed

PRPs/member-management-bugs-fix.md

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
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

Comments
 (0)