pargo-auth 0.1.4__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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """PARGO Auth - Shared authentication middleware for PARGO backend services."""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.1.6"
4
4
 
5
5
  from .middleware import SupabaseAuth, get_current_user, require_auth
6
6
  from .models import AuthenticatedUser
pargo_auth/middleware.py CHANGED
@@ -81,11 +81,11 @@ class SupabaseAuth:
81
81
  jwks_client = self._get_jwks_client()
82
82
  signing_key = jwks_client.get_signing_key_from_jwt(token)
83
83
 
84
- # Decode and verify
84
+ # Decode and verify (support both RS256 and ES256 - Supabase uses ES256)
85
85
  payload = jwt.decode(
86
86
  token,
87
87
  signing_key.key,
88
- algorithms=["RS256"],
88
+ algorithms=["ES256", "RS256"],
89
89
  issuer=self.issuer,
90
90
  options={
91
91
  "verify_aud": False, # Supabase doesn't always set aud
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.4
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=vchweTgD6p6aKw4AVb62G2lUqvA7ngC7AyePAp2AL-8,454
2
- pargo_auth/exceptions.py,sha256=Qckee29SlPf8VS21s-1gT8j_gCoYKaJhYKet9DE28WE,879
3
- pargo_auth/middleware.py,sha256=fCh41HYMfNQbEieVFnUG6uCl-wr0SQ2KahGX297HCYE,6779
4
- pargo_auth/models.py,sha256=I_mb0PGPy7FHZbMHxs1tR4NekD-cv1Ea4ZSzjStDZo4,548
5
- pargo_auth-0.1.4.dist-info/METADATA,sha256=diGGOxS-tBKP67ud2AMGWvyrBxImKrtXlPcoAihDktc,3612
6
- pargo_auth-0.1.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
7
- pargo_auth-0.1.4.dist-info/licenses/LICENSE,sha256=A-E9k545xbWsw-1DRhKLXcePHJ15tKx_bQl12OabqEY,1062
8
- pargo_auth-0.1.4.dist-info/RECORD,,