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.
- trachea_login-0.1.0/.claude/settings.local.json +22 -0
- trachea_login-0.1.0/.env.example +12 -0
- trachea_login-0.1.0/.github/workflows/ci.yml +29 -0
- trachea_login-0.1.0/.github/workflows/publish.yml +36 -0
- trachea_login-0.1.0/.gitignore +16 -0
- trachea_login-0.1.0/Discussion.md +383 -0
- trachea_login-0.1.0/HowToUse.md +947 -0
- trachea_login-0.1.0/PKG-INFO +20 -0
- trachea_login-0.1.0/README.md +133 -0
- trachea_login-0.1.0/docs/superpowers/plans/2026-04-01-trachea-login.md +2695 -0
- trachea_login-0.1.0/docs/superpowers/specs/2026-04-01-trachea-login-design.md +261 -0
- trachea_login-0.1.0/e2e_app.py +48 -0
- trachea_login-0.1.0/e2e_test.py +223 -0
- trachea_login-0.1.0/firebase-service-account.json +13 -0
- trachea_login-0.1.0/pyproject.toml +38 -0
- trachea_login-0.1.0/tests/__init__.py +0 -0
- trachea_login-0.1.0/tests/conftest.py +124 -0
- trachea_login-0.1.0/tests/test_app_repo.py +47 -0
- trachea_login-0.1.0/tests/test_auth_social.py +93 -0
- trachea_login-0.1.0/tests/test_auth_tokens.py +85 -0
- trachea_login-0.1.0/tests/test_auth_username.py +102 -0
- trachea_login-0.1.0/tests/test_config.py +71 -0
- trachea_login-0.1.0/tests/test_dependencies.py +90 -0
- trachea_login-0.1.0/tests/test_exceptions.py +23 -0
- trachea_login-0.1.0/tests/test_firebase.py +50 -0
- trachea_login-0.1.0/tests/test_models.py +44 -0
- trachea_login-0.1.0/tests/test_mongo.py +20 -0
- trachea_login-0.1.0/tests/test_tokens.py +124 -0
- trachea_login-0.1.0/tests/test_user_repo.py +101 -0
- trachea_login-0.1.0/tests/test_users.py +99 -0
- trachea_login-0.1.0/trachea-login.md +718 -0
- trachea_login-0.1.0/trachea-login.postman_collection.json +279 -0
- trachea_login-0.1.0/trachea_login/__init__.py +79 -0
- trachea_login-0.1.0/trachea_login/config.py +33 -0
- trachea_login-0.1.0/trachea_login/db/__init__.py +0 -0
- trachea_login-0.1.0/trachea_login/db/app_repo.py +15 -0
- trachea_login-0.1.0/trachea_login/db/mongo.py +14 -0
- trachea_login-0.1.0/trachea_login/db/user_repo.py +105 -0
- trachea_login-0.1.0/trachea_login/exceptions.py +40 -0
- trachea_login-0.1.0/trachea_login/firebase.py +37 -0
- trachea_login-0.1.0/trachea_login/models.py +75 -0
- trachea_login-0.1.0/trachea_login/routers/__init__.py +0 -0
- trachea_login-0.1.0/trachea_login/routers/auth.py +155 -0
- trachea_login-0.1.0/trachea_login/routers/users.py +85 -0
- 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,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.*
|