pargo-auth 0.1.5__py3-none-any.whl → 0.1.6__py3-none-any.whl
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/__init__.py +1 -1
- pargo_auth/models.py +12 -0
- pargo_auth-0.1.6.dist-info/METADATA +214 -0
- pargo_auth-0.1.6.dist-info/RECORD +8 -0
- pargo_auth-0.1.5.dist-info/METADATA +0 -144
- pargo_auth-0.1.5.dist-info/RECORD +0 -8
- {pargo_auth-0.1.5.dist-info → pargo_auth-0.1.6.dist-info}/WHEEL +0 -0
- {pargo_auth-0.1.5.dist-info → pargo_auth-0.1.6.dist-info}/licenses/LICENSE +0 -0
pargo_auth/__init__.py
CHANGED
pargo_auth/models.py
CHANGED
|
@@ -19,3 +19,15 @@ class AuthenticatedUser:
|
|
|
19
19
|
def supabase_uid(self) -> str:
|
|
20
20
|
"""Alias for sub - the Supabase user ID."""
|
|
21
21
|
return self.sub
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def is_anonymous(self) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Check if this is an anonymous user (signed in with signInAnonymously).
|
|
27
|
+
|
|
28
|
+
Anonymous users have valid JWTs but no email/provider linked.
|
|
29
|
+
They can later upgrade to a real account by linking email/Google/Apple.
|
|
30
|
+
"""
|
|
31
|
+
if self.raw_claims:
|
|
32
|
+
return self.raw_claims.get("is_anonymous", False)
|
|
33
|
+
return False
|
|
@@ -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 |
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
pargo_auth/__init__.py,sha256=B0pqW0WDlIrWYcDEFBhRJSawoV7g9H_BrhSffFqzJeI,454
|
|
2
|
+
pargo_auth/exceptions.py,sha256=Qckee29SlPf8VS21s-1gT8j_gCoYKaJhYKet9DE28WE,879
|
|
3
|
+
pargo_auth/middleware.py,sha256=nxBD5s5Gz4jdsRRudBnU8r8CxG11MzRSiS2DK90kTvM,6841
|
|
4
|
+
pargo_auth/models.py,sha256=UuikVTf96X_4v0-05u_dzW_Tnjeg-G15TD27yznf4nw,976
|
|
5
|
+
pargo_auth-0.1.6.dist-info/METADATA,sha256=CNH73EFgRjqyoTDjEGXTCS7F3jmbkpSDl4ug1y6tUDs,5581
|
|
6
|
+
pargo_auth-0.1.6.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
7
|
+
pargo_auth-0.1.6.dist-info/licenses/LICENSE,sha256=A-E9k545xbWsw-1DRhKLXcePHJ15tKx_bQl12OabqEY,1062
|
|
8
|
+
pargo_auth-0.1.6.dist-info/RECORD,,
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: pargo-auth
|
|
3
|
-
Version: 0.1.5
|
|
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
|
-
Shared authentication middleware for PARGO backend services. Verifies Supabase JWTs and provides FastAPI dependencies.
|
|
31
|
-
|
|
32
|
-
## Installation
|
|
33
|
-
|
|
34
|
-
```bash
|
|
35
|
-
pip install pargo-auth
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## Quick Start
|
|
39
|
-
|
|
40
|
-
```python
|
|
41
|
-
from fastapi import FastAPI, Depends
|
|
42
|
-
from pargo_auth import get_current_user, AuthenticatedUser
|
|
43
|
-
|
|
44
|
-
app = FastAPI()
|
|
45
|
-
|
|
46
|
-
@app.get("/me")
|
|
47
|
-
async def get_me(user: AuthenticatedUser = Depends(get_current_user)):
|
|
48
|
-
return {
|
|
49
|
-
"id": user.sub,
|
|
50
|
-
"email": user.email,
|
|
51
|
-
}
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## Configuration
|
|
55
|
-
|
|
56
|
-
Set these environment variables:
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
SUPABASE_URL=https://your-project.supabase.co
|
|
60
|
-
ENV=local # Skip auth verification in local dev
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Usage Patterns
|
|
64
|
-
|
|
65
|
-
### Basic: Protect individual endpoints
|
|
66
|
-
|
|
67
|
-
```python
|
|
68
|
-
from pargo_auth import get_current_user, AuthenticatedUser
|
|
69
|
-
|
|
70
|
-
@app.get("/protected")
|
|
71
|
-
async def protected(user: AuthenticatedUser = Depends(get_current_user)):
|
|
72
|
-
return {"user_id": user.sub}
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### Protect entire router
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from fastapi import APIRouter, Depends
|
|
79
|
-
from pargo_auth import require_auth
|
|
80
|
-
|
|
81
|
-
router = APIRouter(dependencies=[require_auth()])
|
|
82
|
-
|
|
83
|
-
@router.get("/data")
|
|
84
|
-
async def get_data(): # Auth already required by router
|
|
85
|
-
return {"data": "secret"}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
### Optional auth (different behavior for logged-in vs anonymous)
|
|
89
|
-
|
|
90
|
-
```python
|
|
91
|
-
from pargo_auth import SupabaseAuth, AuthenticatedUser
|
|
92
|
-
|
|
93
|
-
auth = SupabaseAuth()
|
|
94
|
-
|
|
95
|
-
@app.get("/content")
|
|
96
|
-
async def get_content(user: AuthenticatedUser | None = Depends(auth.get_user_optional)):
|
|
97
|
-
if user:
|
|
98
|
-
return {"content": "personalized", "user": user.sub}
|
|
99
|
-
return {"content": "generic"}
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### Custom instance (non-default config)
|
|
103
|
-
|
|
104
|
-
```python
|
|
105
|
-
from pargo_auth import SupabaseAuth
|
|
106
|
-
|
|
107
|
-
auth = SupabaseAuth(
|
|
108
|
-
supabase_url="https://custom.supabase.co",
|
|
109
|
-
skip_verification_in_dev=False, # Always verify, even locally
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
@app.get("/strict")
|
|
113
|
-
async def strict_endpoint(user = Depends(auth.get_user)):
|
|
114
|
-
return {"user": user.sub}
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
## AuthenticatedUser
|
|
118
|
-
|
|
119
|
-
The `AuthenticatedUser` object contains:
|
|
120
|
-
|
|
121
|
-
| Field | Type | Description |
|
|
122
|
-
|-------|------|-------------|
|
|
123
|
-
| `sub` | `str` | Supabase user ID (stable, use as canonical identity) |
|
|
124
|
-
| `email` | `str \| None` | User's email (if available) |
|
|
125
|
-
| `email_verified` | `bool` | Whether email is verified |
|
|
126
|
-
| `raw_claims` | `dict \| None` | Full JWT payload for custom claims |
|
|
127
|
-
|
|
128
|
-
## Legacy Support
|
|
129
|
-
|
|
130
|
-
For backwards compatibility, the middleware also checks for `x-user-id` header if no Bearer token is present. This allows gradual migration from the old auth system.
|
|
131
|
-
|
|
132
|
-
## Development
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Install dev dependencies
|
|
136
|
-
pip install -e ".[dev]"
|
|
137
|
-
|
|
138
|
-
# Run tests
|
|
139
|
-
pytest
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
## License
|
|
143
|
-
|
|
144
|
-
MIT
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
pargo_auth/__init__.py,sha256=uBhCVtKdxIfPYlVHE1ZwRqeDqaPtQHq4XJtxYR9ik4A,454
|
|
2
|
-
pargo_auth/exceptions.py,sha256=Qckee29SlPf8VS21s-1gT8j_gCoYKaJhYKet9DE28WE,879
|
|
3
|
-
pargo_auth/middleware.py,sha256=nxBD5s5Gz4jdsRRudBnU8r8CxG11MzRSiS2DK90kTvM,6841
|
|
4
|
-
pargo_auth/models.py,sha256=I_mb0PGPy7FHZbMHxs1tR4NekD-cv1Ea4ZSzjStDZo4,548
|
|
5
|
-
pargo_auth-0.1.5.dist-info/METADATA,sha256=XVezqiXsGWJxL2gXqeIz6PpJKJ8wNpuB6y8IVV1eUgo,3612
|
|
6
|
-
pargo_auth-0.1.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
7
|
-
pargo_auth-0.1.5.dist-info/licenses/LICENSE,sha256=A-E9k545xbWsw-1DRhKLXcePHJ15tKx_bQl12OabqEY,1062
|
|
8
|
-
pargo_auth-0.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|