pargo-auth 0.1.4__tar.gz → 0.1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pargo_auth-0.1.6/.github/workflows/deploy.yml +54 -0
- pargo_auth-0.1.6/DOCUMENTATION/AUTH_SYSTEM.md +195 -0
- pargo_auth-0.1.6/DOCUMENTATION/Plans/2026-02-03_apple-signin.md +110 -0
- pargo_auth-0.1.6/PKG-INFO +214 -0
- pargo_auth-0.1.6/README.md +187 -0
- pargo_auth-0.1.6/app/__init__.py +1 -0
- pargo_auth-0.1.6/app/config.py +24 -0
- pargo_auth-0.1.6/app/main.py +44 -0
- pargo_auth-0.1.6/app/routes/__init__.py +1 -0
- pargo_auth-0.1.6/app/routes/users.py +167 -0
- pargo_auth-0.1.6/app/services/__init__.py +1 -0
- pargo_auth-0.1.6/app/services/db.py +63 -0
- pargo_auth-0.1.6/app/services/user_service.py +182 -0
- pargo_auth-0.1.6/docker/Dockerfile +22 -0
- pargo_auth-0.1.6/requirements-service.txt +7 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/src/pargo_auth/__init__.py +1 -1
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/src/pargo_auth/middleware.py +2 -2
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/src/pargo_auth/models.py +12 -0
- pargo_auth-0.1.6/tests/__init__.py +1 -0
- pargo_auth-0.1.4/PKG-INFO +0 -144
- pargo_auth-0.1.4/README.md +0 -117
- pargo_auth-0.1.4/tests/__init__.py +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/.github/workflows/publish.yml +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/.gitignore +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/LICENSE +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/pyproject.toml +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/pytest.ini +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/src/pargo_auth/exceptions.py +0 -0
- {pargo_auth-0.1.4 → pargo_auth-0.1.6}/tests/test_middleware.py +0 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Deploy Auth Service
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master, main, feature/auth]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
deploy:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Setup SSH
|
|
16
|
+
run: |
|
|
17
|
+
mkdir -p ~/.ssh
|
|
18
|
+
chmod 700 ~/.ssh
|
|
19
|
+
echo "${{ secrets.PARGO_SERVER_DEPLOY_KEY }}" > ~/.ssh/id_ed25519
|
|
20
|
+
chmod 600 ~/.ssh/id_ed25519
|
|
21
|
+
ssh-keyscan -H test.pargo.dk >> ~/.ssh/known_hosts
|
|
22
|
+
|
|
23
|
+
- name: Create directories
|
|
24
|
+
run: ssh thor@test.pargo.dk "mkdir -p /opt/pargo-auth"
|
|
25
|
+
|
|
26
|
+
- name: Deploy code
|
|
27
|
+
run: |
|
|
28
|
+
rsync -avz --delete \
|
|
29
|
+
--exclude '.git' \
|
|
30
|
+
--exclude '.github' \
|
|
31
|
+
--exclude '__pycache__' \
|
|
32
|
+
--exclude '*.pyc' \
|
|
33
|
+
--exclude '.env' \
|
|
34
|
+
--exclude 'dist' \
|
|
35
|
+
--exclude '*.egg-info' \
|
|
36
|
+
./ thor@test.pargo.dk:/opt/pargo-auth/
|
|
37
|
+
|
|
38
|
+
- name: Build and restart container
|
|
39
|
+
run: |
|
|
40
|
+
ssh thor@test.pargo.dk "cd /opt/pargo-llm-compose && \
|
|
41
|
+
docker compose build --no-cache pargo-auth && \
|
|
42
|
+
docker compose up -d pargo-auth"
|
|
43
|
+
|
|
44
|
+
- name: Verify deployment
|
|
45
|
+
run: |
|
|
46
|
+
echo "=== Container status ==="
|
|
47
|
+
ssh thor@test.pargo.dk "docker ps | grep pargo-auth || true"
|
|
48
|
+
echo ""
|
|
49
|
+
echo "=== Recent logs ==="
|
|
50
|
+
sleep 5
|
|
51
|
+
ssh thor@test.pargo.dk "docker logs pargo-auth --tail 20 2>&1 || true"
|
|
52
|
+
echo ""
|
|
53
|
+
echo "=== Health check ==="
|
|
54
|
+
ssh thor@test.pargo.dk "curl -s http://localhost:8040/health || echo 'Health check pending...'"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# PARGO Authentication System
|
|
2
|
+
|
|
3
|
+
This document describes the authentication architecture for PARGO applications.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
PARGO uses a hybrid authentication approach:
|
|
8
|
+
- **Supabase Auth** (EU-hosted) handles identity/login (Google, Apple, Email/Password)
|
|
9
|
+
- **pargo-auth service** validates JWTs and manages user data in our own Postgres
|
|
10
|
+
- **User data** is stored in Hetzner Postgres, giving us full control
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
16
|
+
│ Flutter │────▶│ Supabase │ │ Hetzner │
|
|
17
|
+
│ App │ │ Auth │ │ Postgres │
|
|
18
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
19
|
+
│ │ ▲
|
|
20
|
+
│ │ JWT │
|
|
21
|
+
│ ▼ │
|
|
22
|
+
│ ┌─────────────┐ │
|
|
23
|
+
└───────────▶│ pargo-auth │────────────┘
|
|
24
|
+
API calls │ service │ user data
|
|
25
|
+
└─────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Components
|
|
29
|
+
|
|
30
|
+
### 1. Supabase Auth (Identity Provider)
|
|
31
|
+
- **URL**: `https://cmnowxzhwrskkixukyif.supabase.co`
|
|
32
|
+
- **Purpose**: Handle login flows, issue JWTs
|
|
33
|
+
- **Providers**: Email/Password, Google, Apple (pending)
|
|
34
|
+
- **We use**: Only authentication, NOT Supabase database
|
|
35
|
+
|
|
36
|
+
### 2. pargo-auth Service
|
|
37
|
+
- **URL**: `https://prod.pargo.dk/auth/`
|
|
38
|
+
- **Port**: 8040
|
|
39
|
+
- **Purpose**:
|
|
40
|
+
- Validate Supabase JWTs
|
|
41
|
+
- Provide user management API
|
|
42
|
+
- Store user data in our Postgres
|
|
43
|
+
|
|
44
|
+
### 3. pargo-auth PyPI Package
|
|
45
|
+
- **Package**: `pargo-auth` on PyPI
|
|
46
|
+
- **Purpose**: JWT verification middleware for other PARGO backends
|
|
47
|
+
- **Usage**: `pip install pargo-auth`
|
|
48
|
+
|
|
49
|
+
## API Endpoints
|
|
50
|
+
|
|
51
|
+
### pargo-auth Service
|
|
52
|
+
|
|
53
|
+
| Method | Endpoint | Auth | Description |
|
|
54
|
+
|--------|----------|------|-------------|
|
|
55
|
+
| GET | `/health` | No | Health check |
|
|
56
|
+
| GET | `/v1/me` | Yes | Get current user profile + settings |
|
|
57
|
+
| PATCH | `/v1/me` | Yes | Update user profile |
|
|
58
|
+
| PATCH | `/v1/me/settings` | Yes | Update user settings |
|
|
59
|
+
|
|
60
|
+
### Supabase Auth (via SDK)
|
|
61
|
+
|
|
62
|
+
| Action | SDK Method |
|
|
63
|
+
|--------|------------|
|
|
64
|
+
| Sign up | `supabase.auth.signUp()` |
|
|
65
|
+
| Sign in (email) | `supabase.auth.signInWithPassword()` |
|
|
66
|
+
| Sign in (Google) | `supabase.auth.signInWithOAuth(OAuthProvider.google)` |
|
|
67
|
+
| Sign in (Apple) | `supabase.auth.signInWithOAuth(OAuthProvider.apple)` |
|
|
68
|
+
| Sign out | `supabase.auth.signOut()` |
|
|
69
|
+
| Refresh token | Automatic via SDK |
|
|
70
|
+
|
|
71
|
+
## Database Schema
|
|
72
|
+
|
|
73
|
+
User data is stored in Hetzner Postgres under the `auth` schema:
|
|
74
|
+
|
|
75
|
+
```sql
|
|
76
|
+
-- Main user table
|
|
77
|
+
auth.users (
|
|
78
|
+
id UUID PRIMARY KEY,
|
|
79
|
+
supabase_uid TEXT UNIQUE NOT NULL, -- Links to Supabase
|
|
80
|
+
email TEXT,
|
|
81
|
+
email_verified BOOLEAN,
|
|
82
|
+
display_name TEXT,
|
|
83
|
+
avatar_url TEXT,
|
|
84
|
+
preferred_lang VARCHAR(5) DEFAULT 'da',
|
|
85
|
+
created_at TIMESTAMPTZ,
|
|
86
|
+
updated_at TIMESTAMPTZ,
|
|
87
|
+
last_login_at TIMESTAMPTZ,
|
|
88
|
+
deleted_at TIMESTAMPTZ -- Soft delete
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
-- User settings
|
|
92
|
+
auth.user_settings (
|
|
93
|
+
user_id UUID PRIMARY KEY REFERENCES auth.users(id),
|
|
94
|
+
push_enabled BOOLEAN DEFAULT TRUE,
|
|
95
|
+
email_enabled BOOLEAN DEFAULT TRUE,
|
|
96
|
+
map_style VARCHAR(20) DEFAULT 'standard',
|
|
97
|
+
settings_json JSONB DEFAULT '{}'
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
-- Login providers (Google, Apple, etc.)
|
|
101
|
+
auth.user_identities (
|
|
102
|
+
id UUID PRIMARY KEY,
|
|
103
|
+
user_id UUID REFERENCES auth.users(id),
|
|
104
|
+
provider VARCHAR(50), -- 'google', 'apple', 'email'
|
|
105
|
+
provider_uid TEXT,
|
|
106
|
+
email TEXT,
|
|
107
|
+
created_at TIMESTAMPTZ
|
|
108
|
+
)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## JWT Structure
|
|
112
|
+
|
|
113
|
+
Supabase JWTs contain:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"sub": "c2de269a-b357-4502-8efb-d92a463dda91", // Supabase user ID
|
|
118
|
+
"email": "user@example.com",
|
|
119
|
+
"app_metadata": {
|
|
120
|
+
"provider": "google",
|
|
121
|
+
"providers": ["google"]
|
|
122
|
+
},
|
|
123
|
+
"user_metadata": {
|
|
124
|
+
"full_name": "John Doe",
|
|
125
|
+
"avatar_url": "https://..."
|
|
126
|
+
},
|
|
127
|
+
"exp": 1770096078,
|
|
128
|
+
"iat": 1770092478
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Using pargo-auth in Other Services
|
|
133
|
+
|
|
134
|
+
### Installation
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
pip install pargo-auth
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Required Auth
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from pargo_auth import SupabaseAuth, AuthenticatedUser
|
|
144
|
+
from fastapi import Depends
|
|
145
|
+
|
|
146
|
+
auth = SupabaseAuth()
|
|
147
|
+
|
|
148
|
+
@app.get("/protected")
|
|
149
|
+
async def protected_route(user: AuthenticatedUser = Depends(auth.get_user)):
|
|
150
|
+
return {"user_id": user.sub, "email": user.email}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Optional Auth (for migration)
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
@app.get("/public")
|
|
157
|
+
async def public_route(user: AuthenticatedUser | None = Depends(auth.get_user_optional)):
|
|
158
|
+
if user:
|
|
159
|
+
return {"authenticated": True, "user_id": user.sub}
|
|
160
|
+
return {"authenticated": False}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
### Environment Variables
|
|
166
|
+
|
|
167
|
+
| Variable | Description | Example |
|
|
168
|
+
|----------|-------------|---------|
|
|
169
|
+
| `SUPABASE_URL` | Supabase project URL | `https://xxx.supabase.co` |
|
|
170
|
+
| `DATABASE_URL` | Postgres connection | `postgresql://user:pass@host:5432/db` |
|
|
171
|
+
| `ENV` | Environment | `prod` or `test` |
|
|
172
|
+
|
|
173
|
+
### Supabase Dashboard Settings
|
|
174
|
+
|
|
175
|
+
- **Site URL**: `https://prod.pargo.dk`
|
|
176
|
+
- **Redirect URLs**: `https://prod.pargo.dk/**`
|
|
177
|
+
|
|
178
|
+
## Security Notes
|
|
179
|
+
|
|
180
|
+
1. **JWTs are validated server-side** using Supabase's JWKS endpoint
|
|
181
|
+
2. **Tokens expire after 1 hour** - the SDK handles refresh automatically
|
|
182
|
+
3. **User data is in our control** - Supabase only stores auth credentials
|
|
183
|
+
4. **Soft deletes** preserve data for potential recovery
|
|
184
|
+
|
|
185
|
+
## Current Status
|
|
186
|
+
|
|
187
|
+
| Provider | Status |
|
|
188
|
+
|----------|--------|
|
|
189
|
+
| Email/Password | ✅ Working |
|
|
190
|
+
| Google OAuth | ✅ Working |
|
|
191
|
+
| Apple Sign In | ⏳ Pending setup |
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
*Last updated: 2026-02-03*
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Plan: Add Apple Sign In Support
|
|
2
|
+
|
|
3
|
+
## Status: Pending
|
|
4
|
+
|
|
5
|
+
Apple Sign In is required for iOS App Store compliance (any app with social login must offer Apple).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- [ ] Access to Apple Developer account
|
|
10
|
+
- [ ] PARGO app already registered with App ID
|
|
11
|
+
|
|
12
|
+
## Steps
|
|
13
|
+
|
|
14
|
+
### 1. Create Services ID in Apple Developer Console
|
|
15
|
+
|
|
16
|
+
1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list)
|
|
17
|
+
2. Click **+** → Select **Services IDs** → Continue
|
|
18
|
+
3. Fill in:
|
|
19
|
+
- Description: `PARGO Auth`
|
|
20
|
+
- Identifier: `dk.pargo.auth`
|
|
21
|
+
4. Click **Continue** → **Register**
|
|
22
|
+
|
|
23
|
+
### 2. Configure Services ID for Sign In with Apple
|
|
24
|
+
|
|
25
|
+
1. Click on the Services ID you created
|
|
26
|
+
2. Enable **Sign In with Apple**
|
|
27
|
+
3. Click **Configure**
|
|
28
|
+
4. Set:
|
|
29
|
+
- Primary App ID: Your PARGO app
|
|
30
|
+
- Domains: `cmnowxzhwrskkixukyif.supabase.co`
|
|
31
|
+
- Return URLs: `https://cmnowxzhwrskkixukyif.supabase.co/auth/v1/callback`
|
|
32
|
+
5. Click **Save** → **Continue** → **Save**
|
|
33
|
+
|
|
34
|
+
### 3. Create a Key for Sign In with Apple
|
|
35
|
+
|
|
36
|
+
1. Go to **Keys** in Apple Developer
|
|
37
|
+
2. Click **+**
|
|
38
|
+
3. Fill in:
|
|
39
|
+
- Name: `PARGO Supabase Auth`
|
|
40
|
+
4. Enable **Sign In with Apple**
|
|
41
|
+
5. Click **Configure** → Select your Primary App ID → **Save**
|
|
42
|
+
6. Click **Continue** → **Register**
|
|
43
|
+
7. **IMPORTANT**: Download the `.p8` key file immediately (one-time download!)
|
|
44
|
+
8. Note the **Key ID** shown
|
|
45
|
+
|
|
46
|
+
### 4. Gather Required Information
|
|
47
|
+
|
|
48
|
+
You'll need these values for Supabase:
|
|
49
|
+
|
|
50
|
+
| Item | Where to find it | Example |
|
|
51
|
+
|------|------------------|---------|
|
|
52
|
+
| Services ID | Step 1 identifier | `dk.pargo.auth` |
|
|
53
|
+
| Team ID | Top-right of Apple Developer page | `ABC123XYZ` |
|
|
54
|
+
| Key ID | Shown after creating key | `DEFG456` |
|
|
55
|
+
| Private Key | Contents of `.p8` file | `-----BEGIN PRIVATE KEY-----...` |
|
|
56
|
+
|
|
57
|
+
### 5. Configure Supabase
|
|
58
|
+
|
|
59
|
+
1. Go to [Supabase Auth Providers](https://supabase.com/dashboard/project/cmnowxzhwrskkixukyif/auth/providers)
|
|
60
|
+
2. Find **Apple** and enable it
|
|
61
|
+
3. Enter:
|
|
62
|
+
- **Service ID**: `dk.pargo.auth`
|
|
63
|
+
- **Team ID**: (from Apple Developer)
|
|
64
|
+
- **Key ID**: (from step 3)
|
|
65
|
+
- **Private Key**: (paste entire `.p8` contents)
|
|
66
|
+
4. Click **Save**
|
|
67
|
+
|
|
68
|
+
### 6. Test Apple Sign In
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Open this URL in Safari (Apple Sign In works best in Safari)
|
|
72
|
+
open "https://cmnowxzhwrskkixukyif.supabase.co/auth/v1/authorize?provider=apple&redirect_to=https://prod.pargo.dk/auth/v1/me"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 7. Verify in Database
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Check user was created
|
|
79
|
+
ssh thor@prod.pargo.dk "docker exec -i postgres-server psql -U postgres -d pargo_prod -c \"SELECT email, created_at FROM auth.users ORDER BY created_at DESC LIMIT 3;\""
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## iOS App Configuration
|
|
83
|
+
|
|
84
|
+
For native Apple Sign In in Flutter (recommended for better UX):
|
|
85
|
+
|
|
86
|
+
1. In Xcode, enable **Sign In with Apple** capability
|
|
87
|
+
2. Add to `ios/Runner/Runner.entitlements`:
|
|
88
|
+
```xml
|
|
89
|
+
<key>com.apple.developer.applesignin</key>
|
|
90
|
+
<array>
|
|
91
|
+
<string>Default</string>
|
|
92
|
+
</array>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Notes
|
|
96
|
+
|
|
97
|
+
- Apple Sign In is **required** by App Store if you have any social login
|
|
98
|
+
- Apple may hide user's real email (relay address like `xxx@privaterelay.appleid.com`)
|
|
99
|
+
- First login returns user's name, subsequent logins may not - store it on first login
|
|
100
|
+
- The `.p8` key file can only be downloaded ONCE - store it securely
|
|
101
|
+
|
|
102
|
+
## Estimated Time
|
|
103
|
+
|
|
104
|
+
- Apple Developer setup: 15-20 minutes
|
|
105
|
+
- Supabase configuration: 5 minutes
|
|
106
|
+
- Testing: 10 minutes
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
*Created: 2026-02-03*
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pargo-auth
|
|
3
|
+
Version: 0.1.6
|
|
4
|
+
Summary: Shared authentication middleware for PARGO backend services
|
|
5
|
+
Project-URL: Homepage, https://github.com/pargoorg/pargo-auth
|
|
6
|
+
Project-URL: Repository, https://github.com/pargoorg/pargo-auth
|
|
7
|
+
Author-email: PARGO <dev@pargo.dk>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: cryptography>=41.0.0
|
|
20
|
+
Requires-Dist: fastapi>=0.100.0
|
|
21
|
+
Requires-Dist: httpx>=0.24.0
|
|
22
|
+
Requires-Dist: pyjwt>=2.8.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# pargo-auth
|
|
29
|
+
|
|
30
|
+
Authentication and user management for PARGO. This repo serves two purposes:
|
|
31
|
+
|
|
32
|
+
1. **PyPI Package** (`pargo-auth`) - JWT verification middleware for FastAPI
|
|
33
|
+
2. **User Service** - API for user profile and settings management
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
pargo-auth/
|
|
39
|
+
├── src/pargo_auth/ # Library (published to PyPI)
|
|
40
|
+
│ ├── middleware.py # Supabase JWT verification
|
|
41
|
+
│ ├── models.py # AuthenticatedUser dataclass
|
|
42
|
+
│ └── exceptions.py # Auth errors
|
|
43
|
+
├── app/ # Service (deployed to server)
|
|
44
|
+
│ ├── main.py # FastAPI application
|
|
45
|
+
│ ├── routes/users.py # /v1/me endpoints
|
|
46
|
+
│ └── services/ # Database operations
|
|
47
|
+
├── docker/Dockerfile # Service container
|
|
48
|
+
└── pyproject.toml # Package configuration
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Part 1: PyPI Package
|
|
54
|
+
|
|
55
|
+
The `pargo-auth` package provides JWT verification for any PARGO backend.
|
|
56
|
+
|
|
57
|
+
### Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install pargo-auth
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Usage
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from fastapi import Depends
|
|
67
|
+
from pargo_auth import get_current_user, AuthenticatedUser
|
|
68
|
+
|
|
69
|
+
@app.get("/protected")
|
|
70
|
+
async def protected(user: AuthenticatedUser = Depends(get_current_user)):
|
|
71
|
+
return {"user_id": user.sub, "email": user.email}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Configuration
|
|
75
|
+
|
|
76
|
+
Set environment variables:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
80
|
+
ENV=local # Skip verification in local dev
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### AuthenticatedUser
|
|
84
|
+
|
|
85
|
+
| Field | Type | Description |
|
|
86
|
+
|-------|------|-------------|
|
|
87
|
+
| `sub` | `str` | Supabase user ID (stable, canonical identity) |
|
|
88
|
+
| `email` | `str \| None` | User's email |
|
|
89
|
+
| `email_verified` | `bool` | Whether email is verified |
|
|
90
|
+
| `supabase_uid` | `str` | Alias for `sub` |
|
|
91
|
+
| `raw_claims` | `dict \| None` | Full JWT payload |
|
|
92
|
+
|
|
93
|
+
### Optional Auth
|
|
94
|
+
|
|
95
|
+
For endpoints that work with or without authentication:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from pargo_auth import SupabaseAuth
|
|
99
|
+
|
|
100
|
+
auth = SupabaseAuth()
|
|
101
|
+
|
|
102
|
+
@app.get("/content")
|
|
103
|
+
async def get_content(user: AuthenticatedUser | None = Depends(auth.get_user_optional)):
|
|
104
|
+
if user:
|
|
105
|
+
return {"personalized": True, "user": user.sub}
|
|
106
|
+
return {"personalized": False}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Part 2: User Service
|
|
112
|
+
|
|
113
|
+
The service manages user data in Hetzner Postgres (`auth` schema).
|
|
114
|
+
|
|
115
|
+
### Endpoints
|
|
116
|
+
|
|
117
|
+
| Method | Path | Auth | Description |
|
|
118
|
+
|--------|------|------|-------------|
|
|
119
|
+
| GET | `/v1/me` | Required | Get profile + settings (creates user on first call) |
|
|
120
|
+
| PATCH | `/v1/me` | Required | Update profile |
|
|
121
|
+
| PATCH | `/v1/me/settings` | Required | Update settings |
|
|
122
|
+
| DELETE | `/v1/me` | Required | Soft delete account |
|
|
123
|
+
| GET | `/health` | None | Health check |
|
|
124
|
+
|
|
125
|
+
### Database Schema
|
|
126
|
+
|
|
127
|
+
Uses the `auth` schema in Postgres:
|
|
128
|
+
|
|
129
|
+
- `auth.users` - Core user identity (synced from Supabase JWT)
|
|
130
|
+
- `auth.user_identities` - Login methods (apple, google, email)
|
|
131
|
+
- `auth.user_settings` - App preferences
|
|
132
|
+
- `auth.audit_events` - Security/login event log
|
|
133
|
+
|
|
134
|
+
### Running Locally
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Install dependencies
|
|
138
|
+
pip install -r requirements-service.txt
|
|
139
|
+
pip install -e .
|
|
140
|
+
|
|
141
|
+
# Set environment
|
|
142
|
+
export SUPABASE_URL=https://your-project.supabase.co
|
|
143
|
+
export DATABASE_URL=postgresql://postgres:password@localhost:5432/pargo_test
|
|
144
|
+
export ENV=local
|
|
145
|
+
|
|
146
|
+
# Run
|
|
147
|
+
uvicorn app.main:app --reload --port 8040
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Docker
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
docker build -f docker/Dockerfile -t pargo-auth .
|
|
154
|
+
docker run -p 8040:8040 \
|
|
155
|
+
-e SUPABASE_URL=https://your-project.supabase.co \
|
|
156
|
+
-e DATABASE_URL=postgresql://... \
|
|
157
|
+
pargo-auth
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Response Examples
|
|
161
|
+
|
|
162
|
+
**GET /v1/me**
|
|
163
|
+
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"user": {
|
|
167
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
168
|
+
"supabase_uid": "846eddd7-f5ae-4d72-8082-accde7da68f2",
|
|
169
|
+
"email": "user@example.com",
|
|
170
|
+
"email_verified": true,
|
|
171
|
+
"display_name": "John Doe",
|
|
172
|
+
"preferred_lang": "da",
|
|
173
|
+
"created_at": "2026-02-02T10:00:00Z",
|
|
174
|
+
"last_login_at": "2026-02-02T12:00:00Z"
|
|
175
|
+
},
|
|
176
|
+
"settings": {
|
|
177
|
+
"push_enabled": true,
|
|
178
|
+
"email_enabled": true,
|
|
179
|
+
"map_style": "standard",
|
|
180
|
+
"settings_json": {}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Development
|
|
188
|
+
|
|
189
|
+
### Package Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
# Install in editable mode
|
|
193
|
+
pip install -e ".[dev]"
|
|
194
|
+
|
|
195
|
+
# Run tests
|
|
196
|
+
pytest
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Publishing (automatic)
|
|
200
|
+
|
|
201
|
+
Push to `main` triggers GitHub Actions:
|
|
202
|
+
1. Bumps version
|
|
203
|
+
2. Publishes to PyPI
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Design Decisions
|
|
208
|
+
|
|
209
|
+
| Decision | Rationale |
|
|
210
|
+
|----------|-----------|
|
|
211
|
+
| `supabase_uid` is canonical ID | JWT `sub` is stable even if email changes |
|
|
212
|
+
| User created on first `/me` call | No separate signup flow needed |
|
|
213
|
+
| Soft delete | GDPR compliance, account recovery possible |
|
|
214
|
+
| Separate settings table | Clean separation, flexible JSONB for future |
|