trachea-login 0.1.0__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.
Files changed (45) hide show
  1. trachea_login-0.1.0/.claude/settings.local.json +22 -0
  2. trachea_login-0.1.0/.env.example +12 -0
  3. trachea_login-0.1.0/.github/workflows/ci.yml +29 -0
  4. trachea_login-0.1.0/.github/workflows/publish.yml +36 -0
  5. trachea_login-0.1.0/.gitignore +16 -0
  6. trachea_login-0.1.0/Discussion.md +383 -0
  7. trachea_login-0.1.0/HowToUse.md +947 -0
  8. trachea_login-0.1.0/PKG-INFO +20 -0
  9. trachea_login-0.1.0/README.md +133 -0
  10. trachea_login-0.1.0/docs/superpowers/plans/2026-04-01-trachea-login.md +2695 -0
  11. trachea_login-0.1.0/docs/superpowers/specs/2026-04-01-trachea-login-design.md +261 -0
  12. trachea_login-0.1.0/e2e_app.py +48 -0
  13. trachea_login-0.1.0/e2e_test.py +223 -0
  14. trachea_login-0.1.0/firebase-service-account.json +13 -0
  15. trachea_login-0.1.0/pyproject.toml +38 -0
  16. trachea_login-0.1.0/tests/__init__.py +0 -0
  17. trachea_login-0.1.0/tests/conftest.py +124 -0
  18. trachea_login-0.1.0/tests/test_app_repo.py +47 -0
  19. trachea_login-0.1.0/tests/test_auth_social.py +93 -0
  20. trachea_login-0.1.0/tests/test_auth_tokens.py +85 -0
  21. trachea_login-0.1.0/tests/test_auth_username.py +102 -0
  22. trachea_login-0.1.0/tests/test_config.py +71 -0
  23. trachea_login-0.1.0/tests/test_dependencies.py +90 -0
  24. trachea_login-0.1.0/tests/test_exceptions.py +23 -0
  25. trachea_login-0.1.0/tests/test_firebase.py +50 -0
  26. trachea_login-0.1.0/tests/test_models.py +44 -0
  27. trachea_login-0.1.0/tests/test_mongo.py +20 -0
  28. trachea_login-0.1.0/tests/test_tokens.py +124 -0
  29. trachea_login-0.1.0/tests/test_user_repo.py +101 -0
  30. trachea_login-0.1.0/tests/test_users.py +99 -0
  31. trachea_login-0.1.0/trachea-login.md +718 -0
  32. trachea_login-0.1.0/trachea-login.postman_collection.json +279 -0
  33. trachea_login-0.1.0/trachea_login/__init__.py +79 -0
  34. trachea_login-0.1.0/trachea_login/config.py +33 -0
  35. trachea_login-0.1.0/trachea_login/db/__init__.py +0 -0
  36. trachea_login-0.1.0/trachea_login/db/app_repo.py +15 -0
  37. trachea_login-0.1.0/trachea_login/db/mongo.py +14 -0
  38. trachea_login-0.1.0/trachea_login/db/user_repo.py +105 -0
  39. trachea_login-0.1.0/trachea_login/exceptions.py +40 -0
  40. trachea_login-0.1.0/trachea_login/firebase.py +37 -0
  41. trachea_login-0.1.0/trachea_login/models.py +75 -0
  42. trachea_login-0.1.0/trachea_login/routers/__init__.py +0 -0
  43. trachea_login-0.1.0/trachea_login/routers/auth.py +155 -0
  44. trachea_login-0.1.0/trachea_login/routers/users.py +85 -0
  45. trachea_login-0.1.0/trachea_login/tokens.py +122 -0
@@ -0,0 +1,22 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "WebFetch(domain:raw.githubusercontent.com)",
5
+ "Bash(venv/bin/python -c \"import sys; print\\(sys.version\\)\")",
6
+ "Bash(venv/bin/pytest --collect-only)",
7
+ "Bash(python -m pytest tests/test_exceptions.py -v)",
8
+ "Bash(python3 -m pytest tests/test_exceptions.py -v)",
9
+ "Bash(pip3 show:*)",
10
+ "Bash(venv/bin/pytest tests/test_exceptions.py -v)",
11
+ "Bash(python -m pytest tests/test_config.py -v)",
12
+ "Bash(pytest tests/test_config.py -v)",
13
+ "Bash(python3 -m pytest tests/test_config.py -v)",
14
+ "Bash(.venv/bin/pytest tests/test_config.py -v)",
15
+ "Bash(venv/bin/pytest tests/test_config.py -v)",
16
+ "Bash(ls \"/Volumes/SSD-Yogesh 1/Study-Material-Mac/Trachea-Inc/trachea-login/\"*.json)",
17
+ "Bash(python3:*)",
18
+ "Bash(curl -s https://pypi.org/pypi/trachea-login/json)",
19
+ "Bash(python -m build)"
20
+ ]
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ # Required
2
+ TRACHEA_APP_ID=app1
3
+ TRACHEA_APP_SECRET=your-app-secret-key
4
+ TRACHEA_MONGO_URI=mongodb+srv://user:pass@cluster.mongodb.net/trachea
5
+ TRACHEA_FIREBASE_CREDENTIALS=path/to/firebase-service-account.json
6
+ TRACHEA_JWT_SECRET=your-super-secret-jwt-key-min-32-chars
7
+
8
+ # Optional (defaults shown)
9
+ TRACHEA_ACCESS_TOKEN_EXPIRE_MINUTES=15
10
+ TRACHEA_REFRESH_TOKEN_EXPIRE_DAYS=30
11
+ TRACHEA_PASSWORD_MIN_LENGTH=8
12
+ TRACHEA_DB_NAME=trachea
@@ -0,0 +1,29 @@
1
+ # .github/workflows/ci.yml
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches: [main]
7
+ pull_request:
8
+ branches: [main]
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.10", "3.11", "3.12"]
16
+
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Set up Python ${{ matrix.python-version }}
21
+ uses: actions/setup-python@v5
22
+ with:
23
+ python-version: ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: pip install -e ".[dev]"
27
+
28
+ - name: Run tests with coverage
29
+ run: pytest tests/ -v --cov=trachea_login --cov-report=term-missing --cov-fail-under=80
@@ -0,0 +1,36 @@
1
+ # .github/workflows/publish.yml
2
+ name: Publish to PyPI
3
+
4
+ on:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ jobs:
10
+ publish:
11
+ runs-on: ubuntu-latest
12
+ environment: pypi
13
+ permissions:
14
+ id-token: write
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.11"
23
+
24
+ - name: Install build tools
25
+ run: pip install build
26
+
27
+ - name: Run tests
28
+ run: |
29
+ pip install -e ".[dev]"
30
+ pytest tests/ -v --cov=trachea_login --cov-fail-under=80
31
+
32
+ - name: Build package
33
+ run: python -m build
34
+
35
+ - name: Publish to PyPI
36
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .env
5
+ *.egg-info/
6
+ .pytest_cache/
7
+ .coverage
8
+ dist/
9
+ build/
10
+ venv/
11
+ .venv/
12
+ .env
13
+
14
+ # macOS
15
+ .DS_Store
16
+ **/.DS_Store
@@ -0,0 +1,383 @@
1
+ # trachea-login — Design Discussion Log
2
+
3
+ > **Session Date:** 2026-04-01
4
+ > **Participants:** Architect (Claude), Product Owner
5
+ > **Purpose:** Design and architecture decisions for the `trachea-login` Python package
6
+
7
+ ---
8
+
9
+ ## Session Summary
10
+
11
+ This session covers all architectural discussions, questions, decisions, and rationale for the `trachea-login` package design. Use this as a reference for onboarding, future decisions, and Confluence knowledge base.
12
+
13
+ ---
14
+
15
+ ## Q1 — Package Type: Embedded Library vs Microservice
16
+
17
+ **Question asked:**
18
+ > Should `trachea-login` be an embedded library, a standalone auth microservice, or both?
19
+
20
+ **Options presented:**
21
+
22
+ | Option | Description |
23
+ |---|---|
24
+ | A) Embedded library | Installed via pip into each app; each app runs its own auth endpoints |
25
+ | B) Standalone microservice | One central server deployed independently; apps call it over HTTP |
26
+ | C) Both | Library that can be embedded OR deployed as a standalone service |
27
+
28
+ **Decision:** Option A — Embedded library
29
+
30
+ **Refinement:** Designed as a **mountable FastAPI router** (not plain library):
31
+ - `pip install trachea-login` → mount router in 2 lines
32
+ - All apps share same MongoDB cluster
33
+ - Can be wrapped as standalone service later if needed (YAGNI)
34
+
35
+ **Rationale:** Best of both worlds. Simple integration, shared DB, extensible to microservice later.
36
+
37
+ ---
38
+
39
+ ## Q2 — Web Framework
40
+
41
+ **Question asked:**
42
+ > What Python web frameworks do your apps use?
43
+
44
+ **Options presented:**
45
+
46
+ | Option | Framework |
47
+ |---|---|
48
+ | A | FastAPI (all or most) |
49
+ | B | Flask (all or most) |
50
+ | C | Django (all or most) |
51
+ | D | Mixed |
52
+ | E | Not sure / framework-agnostic |
53
+
54
+ **Decision:** FastAPI
55
+
56
+ ---
57
+
58
+ ## Q2b — Firebase Integration
59
+
60
+ **Topic raised by product owner:**
61
+ > Can we use Firebase Authentication somehow?
62
+
63
+ **Architect recommendation:**
64
+ Use Firebase as the **identity provider** for social logins and email/password. After Firebase verifies identity:
65
+ - `trachea-login` receives Firebase ID token from client
66
+ - Verifies token server-side using Firebase Admin SDK
67
+ - Creates/updates MongoDB user record with `AppId`
68
+ - Issues its own app JWT (access + refresh tokens)
69
+
70
+ **Benefits:**
71
+ - Firebase handles OAuth complexity (Google, Facebook, Apple, email/password)
72
+ - Mobile clients use native Firebase SDK — zero custom OAuth code
73
+ - trachea-login owns user data and tokens — decoupled from Firebase after login
74
+ - If Firebase is replaced later, only the login step changes
75
+
76
+ **Flow:**
77
+ ```
78
+ Firebase handles identity → trachea-login handles user data + app context → MongoDB stores everything
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Q3 — Token Strategy
84
+
85
+ **Question asked:**
86
+ > After Firebase verifies the user, what token should subsequent API calls use?
87
+
88
+ **Options presented:**
89
+
90
+ | Option | Description |
91
+ |---|---|
92
+ | A | Firebase ID tokens only — always pass Firebase token |
93
+ | B | trachea-login issues its own JWT (access + refresh) after Firebase verification |
94
+ | C | Same as B + supports email/password natively without Firebase |
95
+
96
+ **Decision:** Option B
97
+
98
+ **Rationale:**
99
+ - Apps are decoupled from Firebase after initial login
100
+ - Full control over token expiry, payload, and revocation
101
+ - If Firebase is swapped out, only the login step changes
102
+
103
+ **Follow-up question from product owner:**
104
+ > If we take B, will users be able to login/register via email/password?
105
+
106
+ **Answer:** Yes — via **Firebase email/password auth** (B1):
107
+ - Client authenticates with Firebase using email+password → gets Firebase ID Token
108
+ - Same flow as social login from trachea-login's perspective
109
+ - Firebase handles password reset emails and email verification automatically
110
+ - Consistent single flow for all auth methods
111
+
112
+ ---
113
+
114
+ ## Q4 — Role-Based Access Control (RBAC)
115
+
116
+ **Question asked:**
117
+ > Do your apps need role-based access control?
118
+
119
+ **Options presented:**
120
+
121
+ | Option | Description |
122
+ |---|---|
123
+ | A | No roles — authentication only |
124
+ | B | Simple roles — admin, user, guest per app |
125
+ | C | Fine-grained permissions — per action per app per user |
126
+ | D | Not sure — keep extensible |
127
+
128
+ **Follow-up question from product owner:**
129
+ > If I choose No roles, can I still do free user or paid users?
130
+
131
+ **Architect clarification:**
132
+ - Free vs Paid is a **subscription plan/tier**, not a role
133
+ - Roles = what actions a user can perform
134
+ - Plan = what features they have access to based on payment
135
+ - Both needed to cover real-world requirements
136
+
137
+ **Decision:** Simple roles (Option B) with two fields:
138
+
139
+ | Field | Values |
140
+ |---|---|
141
+ | `role` | `admin`, `member`, `guest` |
142
+ | `plan` | `free`, `pro`, `enterprise` |
143
+
144
+ Both stored in MongoDB per user per app. Both included in JWT payload — no MongoDB hit on every request.
145
+
146
+ ---
147
+
148
+ ## Q5 — User Data Fields
149
+
150
+ **Question asked:**
151
+ > What profile information should trachea-login store in MongoDB?
152
+
153
+ **Options presented:**
154
+
155
+ | Option | Fields |
156
+ |---|---|
157
+ | A | Minimal: email, name, avatar, provider |
158
+ | B | Standard: A + phone, date_of_birth, address |
159
+ | C | Custom fields: apps define extra fields |
160
+ | D | A + C: minimal core + custom metadata |
161
+
162
+ **Decision:** Option B — Standard fields
163
+
164
+ **Fields included:**
165
+ - `email`, `name`, `avatar`, `provider`
166
+ - `phone`, `date_of_birth`
167
+ - `address` (street, city, state, country, zip)
168
+
169
+ ---
170
+
171
+ ## Q6 — App Registration (AppId Management)
172
+
173
+ **Question asked:**
174
+ > How should apps identify themselves to trachea-login?
175
+
176
+ **Options presented:**
177
+
178
+ | Option | Description |
179
+ |---|---|
180
+ | A | Config at initialization — pass app_id + secret in code/env vars |
181
+ | B | Apps registered in MongoDB — `apps` collection with app_id, secret, name, domains |
182
+ | C | Both — env vars + MongoDB validation at startup |
183
+
184
+ **Follow-up question from product owner:**
185
+ > Which option is easier to maintain from B and C?
186
+
187
+ **Architect recommendation:** **Option B** (with env vars pattern)
188
+
189
+ **Rationale:**
190
+ - Option C has a maintenance trap: two sources of truth (env vars + MongoDB) can drift
191
+ - Option B = single source of truth (MongoDB)
192
+ - Secrets still stay out of code: read from env vars, passed to trachea-login, validated against MongoDB
193
+ - Central registry — disable/update App1 from MongoDB without touching App1's code
194
+
195
+ **Pattern:**
196
+ ```bash
197
+ # App1/.env
198
+ APP_ID=app1
199
+ APP_SECRET=your-secret-key
200
+ ```
201
+ App reads from env vars → passes to TracheaLogin() → validated against MongoDB at startup.
202
+
203
+ ---
204
+
205
+ ## Q7 — Cross-App User Identity
206
+
207
+ **Question asked:**
208
+ > What should happen when a user who exists in App1 logs into App2 for the first time?
209
+
210
+ **Options presented:**
211
+
212
+ | Option | Description |
213
+ |---|---|
214
+ | A | Separate user records — App1 and App2 users fully isolated |
215
+ | B | Linked user — one user document, array of apps they belong to |
216
+ | C | Architect's call |
217
+
218
+ **Follow-up question from product owner:**
219
+ > Which one is easier to maintain in the long run?
220
+
221
+ **Decision:** Option B — Linked user
222
+
223
+ **Rationale:**
224
+
225
+ | Concern | Option A | Option B |
226
+ |---|---|---|
227
+ | Profile update | Must update in every app | One write, all apps see it |
228
+ | Account deletion | Orphaned records across apps | One document deleted |
229
+ | Same email in 2 apps | Don't know it's same person | Naturally linked via firebase_uid |
230
+ | Cross-app analytics | Very difficult | Simple query |
231
+ | Firebase UID mapping | One UID → multiple documents (unnatural) | One UID → one document (natural) |
232
+
233
+ **User document structure:**
234
+ ```json
235
+ {
236
+ "firebase_uid": "abc123",
237
+ "email": "user@example.com",
238
+ "apps": [
239
+ { "app_id": "app1", "role": "admin", "plan": "pro" },
240
+ { "app_id": "app2", "role": "member", "plan": "free" }
241
+ ]
242
+ }
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Q8 — Username-Only Registration
248
+
249
+ **Question raised by product owner:**
250
+ > If some user wants to register without email/password or social login, but with username and password only — is it possible?
251
+
252
+ **Architect answer:** Yes — as a **native auth path** in trachea-login.
253
+
254
+ **Why Firebase can't handle this:**
255
+ Firebase requires an email address for all auth methods. Username-only login must be handled natively.
256
+
257
+ **Additional context provided by product owner:**
258
+ - Username/password is for **free users only**
259
+ - No forget password needed (no email = no reset email)
260
+ - Once user moves to paid plan, email becomes required
261
+ - Apps should control which auth methods they allow
262
+
263
+ **Impact on data model:**
264
+
265
+ | Field | Change |
266
+ |---|---|
267
+ | `email` | Optional for free/username users; required when upgrading to paid |
268
+ | `username` | New field — unique, optional, only for username auth |
269
+ | `password_hash` | New field — bcrypt hashed, only for username auth |
270
+ | `provider` | New value: `"username"` |
271
+ | `firebase_uid` | `null` for username users |
272
+
273
+ **App-level auth method control:**
274
+ ```json
275
+ "allowed_auth_methods": ["google", "facebook", "apple", "password", "username"]
276
+ ```
277
+ Each app decides which methods it enables.
278
+
279
+ ---
280
+
281
+ ## Architecture Approach Selection
282
+
283
+ **Three approaches were evaluated:**
284
+
285
+ ### Approach 1 — Thin Wrapper (Firebase-heavy)
286
+ - trachea-login is a minimal router
287
+ - All auth logic lives in Firebase
288
+ - **Rejected:** Apps permanently coupled to Firebase, limited customization
289
+
290
+ ### Approach 2 — Layered Auth Package (selected)
291
+ - Firebase handles identity verification only
292
+ - trachea-login owns tokens, user data, roles, plans
293
+ - Firebase is swappable after the login step
294
+ - **Selected:** Clean separation, full control, extensible
295
+
296
+ ### Approach 3 — Auth Microservice Kit
297
+ - Package + CLI to run as standalone service
298
+ - **Rejected for now:** YAGNI — can add CLI later if needed
299
+
300
+ ---
301
+
302
+ ## Final Design Summary
303
+
304
+ | Decision | Choice |
305
+ |---|---|
306
+ | Package type | Pip-installable, mountable FastAPI router |
307
+ | Identity provider | Firebase (social + email/password) |
308
+ | Native auth | Username + password (free users only, no Firebase) |
309
+ | Token strategy | App-owned JWT — access (15min) + refresh (30d, rotated) |
310
+ | Refresh tokens | Stored hashed in MongoDB, TTL auto-expiry, rotation on use |
311
+ | Database | MongoDB (Motor async), shared across all apps |
312
+ | Multi-app user model | Single user document, `apps[]` array per user |
313
+ | Roles | `role` (admin/member/guest) + `plan` (free/pro/enterprise) |
314
+ | App registration | MongoDB `apps` collection, validated at startup |
315
+ | Auth methods | Controlled per app via `allowed_auth_methods` |
316
+ | Email requirement | Optional for free/username; required for paid plan |
317
+ | PyPI | Published as `trachea-login`, auto-publish via GitHub Actions on tag |
318
+ | Python version | >= 3.10 |
319
+ | Key dependencies | FastAPI, Motor, Firebase Admin SDK, python-jose, passlib, pydantic-settings |
320
+
321
+ ---
322
+
323
+ ## Auth Method Rules (Quick Reference)
324
+
325
+ | Auth Method | Email Required | Firebase Used | Plan Allowed | Password Storage |
326
+ |---|---|---|---|---|
327
+ | `google` | Auto-filled | Yes | All | Firebase |
328
+ | `facebook` | Auto-filled | Yes | All | Firebase |
329
+ | `apple` | Auto-filled | Yes | All | Firebase |
330
+ | `password` | Yes | Yes | All | Firebase |
331
+ | `username` | No | No | `free` only | bcrypt in MongoDB |
332
+
333
+ ---
334
+
335
+ ## Error Codes (Quick Reference)
336
+
337
+ | Error Code | HTTP | When |
338
+ |---|---|---|
339
+ | `APP_NOT_FOUND` | 404 | app_id not registered |
340
+ | `APP_INACTIVE` | 403 | app is disabled |
341
+ | `AUTH_METHOD_NOT_ALLOWED` | 403 | app doesn't allow this method |
342
+ | `INVALID_FIREBASE_TOKEN` | 401 | Firebase verification failed |
343
+ | `INVALID_CREDENTIALS` | 401 | Wrong username or password |
344
+ | `USERNAME_TAKEN` | 409 | Username already exists |
345
+ | `EMAIL_TAKEN` | 409 | Email already registered |
346
+ | `INVALID_TOKEN` | 401 | JWT invalid or malformed |
347
+ | `TOKEN_EXPIRED` | 401 | JWT expired |
348
+ | `REFRESH_TOKEN_INVALID` | 401 | Refresh token not found or revoked |
349
+ | `REFRESH_TOKEN_REUSE` | 401 | Reuse attack — all tokens revoked |
350
+ | `INSUFFICIENT_ROLE` | 403 | Role too low |
351
+ | `INSUFFICIENT_PLAN` | 403 | Plan too low |
352
+ | `EMAIL_REQUIRED_FOR_PAID` | 422 | No email when upgrading plan |
353
+ | `USER_INACTIVE` | 403 | Account deactivated |
354
+ | `VALIDATION_ERROR` | 422 | Request body invalid |
355
+
356
+ ---
357
+
358
+ ## API Routes (Quick Reference)
359
+
360
+ | Method | Endpoint | Auth | Description |
361
+ |---|---|---|---|
362
+ | `POST` | `/auth/login` | No | Firebase token OR username/password |
363
+ | `POST` | `/auth/register` | No | Username/password only |
364
+ | `POST` | `/auth/refresh` | No | Rotate refresh token |
365
+ | `POST` | `/auth/logout` | Yes | Revoke all refresh tokens |
366
+ | `GET` | `/users/me` | Yes | Get current user profile |
367
+ | `PATCH` | `/users/me` | Yes | Update profile |
368
+ | `DELETE` | `/users/me` | Yes | Deactivate account |
369
+ | `PATCH` | `/users/me/plan` | Yes | Upgrade plan |
370
+
371
+ ---
372
+
373
+ ## MongoDB Collections (Quick Reference)
374
+
375
+ | Collection | Purpose | Key Indexes |
376
+ |---|---|---|
377
+ | `users` | All user data across all apps | firebase_uid (sparse unique), email (sparse unique), username (sparse unique), apps.app_id |
378
+ | `apps` | Registered apps | app_id (unique) |
379
+ | `refresh_tokens` | Active refresh tokens | token_hash, expires_at (TTL) |
380
+
381
+ ---
382
+
383
+ *End of discussion log. For full technical spec see `trachea-login.md` in project root.*