mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-21 02:26:42 +02:00
Projects included: - maerchenzauber (NestJS backend + Expo mobile + SvelteKit web + Astro landing) - manacore (Expo mobile + SvelteKit web + Astro landing) - manadeck (NestJS backend + Expo mobile + SvelteKit web) - memoro (Expo mobile + SvelteKit web + Astro landing) This commit preserves the current state before monorepo restructuring. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
177 lines
5.5 KiB
Markdown
177 lines
5.5 KiB
Markdown
# Character Sharing with Service Account
|
|
|
|
## Overview
|
|
Character sharing uses **authenticated requests** where users must be logged in, but the backend uses the **service account** (service role key) to bypass RLS when fetching shared characters.
|
|
|
|
## Architecture
|
|
|
|
**Security Model:**
|
|
- ✅ User must be authenticated to view shared characters
|
|
- ✅ Backend uses service account to bypass RLS
|
|
- ✅ No special RLS policies needed
|
|
- ✅ All character data remains protected by default RLS
|
|
|
|
**Benefits:**
|
|
- Know who is accessing shared characters (analytics, abuse prevention)
|
|
- No public endpoints exposing database directly
|
|
- Simpler RLS policy management
|
|
- Users must be logged in to import (already required for credits)
|
|
|
|
## Required RLS Policies
|
|
|
|
### User-Owned Characters Policy
|
|
Standard RLS policy - users can only read their own characters:
|
|
|
|
```sql
|
|
-- Policy name: "Users can read their own characters"
|
|
CREATE POLICY "users_read_own_characters"
|
|
ON characters
|
|
FOR SELECT
|
|
USING (auth.uid() = user_id);
|
|
```
|
|
|
|
**Note:** No additional policies needed! The backend service account bypasses RLS entirely.
|
|
|
|
## Testing
|
|
|
|
### Test 1: Verify Authenticated Access to Shared Character
|
|
```bash
|
|
# Test authenticated user accessing a shared character
|
|
curl -X GET "http://localhost:3002/character/shared/{characterId}" \
|
|
-H "Authorization: Bearer {token}" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Expected: 200 OK with character data (any character ID)
|
|
```
|
|
|
|
### Test 2: Verify Unauthenticated Access is Blocked
|
|
```bash
|
|
# Test without auth token
|
|
curl -X GET "http://localhost:3002/character/shared/{characterId}" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Expected: 401 Unauthorized
|
|
```
|
|
|
|
### Test 3: Verify User-Owned Characters Still Work
|
|
```bash
|
|
# Test authenticated user accessing their own characters
|
|
curl -X GET "http://localhost:3002/character" \
|
|
-H "Authorization: Bearer {token}" \
|
|
-H "Content-Type: application/json"
|
|
|
|
# Expected: 200 OK with list of user's characters
|
|
```
|
|
|
|
## Backend Changes Made
|
|
|
|
### 1. Added Admin Client to SupabaseProvider
|
|
**File**: `backend/src/supabase/supabase.provider.ts`
|
|
- Added `getAdminClient()` method
|
|
- Uses `MAERCHENZAUBER_SUPABASE_SERVICE_ROLE_KEY`
|
|
- Bypasses all RLS policies
|
|
|
|
### 2. Updated getSharedCharacter
|
|
**File**: `backend/src/core/services/supabase-jsonb-auth.service.ts`
|
|
- Changed from `getClient()` to `getAdminClient()`
|
|
- Uses service account for database access
|
|
- User still must be authenticated to call endpoint
|
|
|
|
### 3. CharacterController
|
|
**File**: `backend/src/character/character.controller.ts`
|
|
- Kept `@UseGuards(AuthGuard)` - requires authentication
|
|
- Added `@CurrentUser()` to track who accesses shared characters
|
|
|
|
## Mobile App Changes
|
|
|
|
### 1. Created Deeplink Route
|
|
**File**: `mobile/app/share/character/[id].tsx`
|
|
- Handles `maerchenzauber://share/character/{id}` deeplinks
|
|
- Redirects to character preview page
|
|
- Added error handling to prevent crashes
|
|
|
|
### 2. Improved Error Handling
|
|
- Added try/catch blocks
|
|
- User-friendly error messages
|
|
- Graceful fallback to home screen on errors
|
|
|
|
## Deployment Steps
|
|
|
|
1. **Update Backend Code**
|
|
```bash
|
|
cd backend
|
|
npm run build
|
|
npm run start:prod
|
|
```
|
|
|
|
2. **Update RLS Policies in Supabase Dashboard**
|
|
- Go to Supabase Dashboard > Authentication > Policies
|
|
- Add the "public_read_shared_characters" policy
|
|
- Verify existing user policies remain active
|
|
|
|
3. **Deploy Backend to Cloud Run**
|
|
```bash
|
|
cd backend
|
|
gcloud builds submit --tag gcr.io/PROJECT_ID/storyteller-backend
|
|
gcloud run deploy storyteller-backend --image gcr.io/PROJECT_ID/storyteller-backend
|
|
```
|
|
|
|
4. **Build and Deploy Mobile App**
|
|
```bash
|
|
cd mobile
|
|
eas build --platform ios --profile production
|
|
eas submit --platform ios
|
|
```
|
|
|
|
## Verification Checklist
|
|
|
|
- [ ] Backend compiles without TypeScript errors
|
|
- [ ] Public endpoint is accessible without authentication
|
|
- [ ] Authenticated users can still access their own characters
|
|
- [ ] RLS policy allows reading shared characters
|
|
- [ ] RLS policy blocks reading private characters
|
|
- [ ] Deeplink opens app successfully
|
|
- [ ] Character preview loads without crashing
|
|
- [ ] Error handling works for invalid character IDs
|
|
- [ ] Import functionality still requires authentication (10 credits)
|
|
|
|
## Security Considerations
|
|
|
|
1. **Authentication Required**
|
|
- User must be logged in to view shared characters
|
|
- Prevents anonymous abuse
|
|
- Tracks who accesses what (analytics)
|
|
|
|
2. **Service Account Usage**
|
|
- Backend uses service role key to bypass RLS
|
|
- Only exposed through authenticated endpoints
|
|
- No direct database access from frontend
|
|
|
|
3. **Import Requirements**
|
|
- Still requires authentication
|
|
- Costs 10 credits per import
|
|
- Prevents importing own characters
|
|
- Checks for duplicate imports
|
|
|
|
4. **RLS Still Protects Data**
|
|
- Users can only modify their own characters
|
|
- Default RLS policies remain unchanged
|
|
- Service account only used for read operations on shared endpoint
|
|
|
|
## Troubleshooting
|
|
|
|
### Issue: "Character not found or not available for sharing"
|
|
- **Cause**: RLS policy not configured correctly
|
|
- **Fix**: Add the `public_read_shared_characters` policy
|
|
|
|
### Issue: "401 Unauthorized" when accessing shared endpoint
|
|
- **Cause**: `@Public()` decorator not applied
|
|
- **Fix**: Verify `PublicAuthGuard` is being used and `@Public()` is on endpoint
|
|
|
|
### Issue: App crashes when opening deeplink
|
|
- **Cause**: Missing mobile route file
|
|
- **Fix**: Ensure `mobile/app/share/character/[id].tsx` exists
|
|
|
|
### Issue: "Unmatched Route" error
|
|
- **Cause**: Expo Router can't find the route
|
|
- **Fix**: Restart Expo dev server with `npx expo start -c`
|