Skip to content

Commit 53a8de3

Browse files
committed
Add Litestar docs and complete integration guide
1 parent 75ec37a commit 53a8de3

12 files changed

Lines changed: 798 additions & 99 deletions

File tree

docs/src/admin.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,18 @@ urlpatterns += [
8282

8383
Same endpoints as FastAPI. With `user_model` set, each mapping in the list response is enriched with the app's username and email alongside the Hanko data.
8484

85+
### Litestar
86+
87+
```python
88+
from hotosm_auth_litestar import create_admin_mappings_router, setup_auth
89+
90+
admin_router = create_admin_mappings_router(get_db, app_name="my-app")
91+
deps, route_handlers = setup_auth()
92+
app = Litestar(route_handlers=[*route_handlers, admin_router], dependencies=deps)
93+
```
94+
95+
Same endpoints as FastAPI.
96+
8597
---
8698

8799
## Custom Admin Endpoints
@@ -113,6 +125,18 @@ class MyAdminView(APIView):
113125
return Response({"ok": True})
114126
```
115127

128+
### Litestar
129+
130+
```python
131+
from litestar import get
132+
from hotosm_auth_litestar import AdminUser
133+
134+
@get("/api/admin/stats")
135+
async def get_stats(admin: AdminUser) -> dict:
136+
# admin is a HankoUser — email already verified as admin
137+
return {"requested_by": admin.email}
138+
```
139+
116140
---
117141

118142
## Login Backend Config

docs/src/index.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,33 +56,44 @@ flowchart TB
5656
### FastAPI
5757

5858
```python
59-
# main.py
6059
from contextlib import asynccontextmanager
6160
from fastapi import FastAPI
6261
from hotosm_auth import AuthConfig
6362
from hotosm_auth_fastapi import init_auth, CurrentUser, osm_router
6463

6564
@asynccontextmanager
6665
async def lifespan(app: FastAPI):
67-
auth_config = AuthConfig.from_env()
68-
init_auth(auth_config)
66+
init_auth(AuthConfig.from_env())
6967
yield
7068

7169
app = FastAPI(lifespan=lifespan)
72-
7370
app.include_router(osm_router, prefix="/api")
7471

7572
@app.get("/me")
7673
async def me(user: CurrentUser):
7774
return {"id": user.id, "email": user.email}
7875
```
7976

77+
### Litestar
78+
79+
```python
80+
from litestar import Litestar, get
81+
from hotosm_auth_litestar import setup_auth, AuthContext
82+
83+
deps, route_handlers = setup_auth()
84+
85+
@get("/me")
86+
async def me(auth: AuthContext) -> dict:
87+
return {"id": auth.user.id, "email": auth.user.email}
88+
89+
app = Litestar(route_handlers=[*route_handlers, me], dependencies=deps)
90+
```
91+
8092
### Django
8193

8294
```python
8395
# settings.py
8496
INSTALLED_APPS = [..., 'hotosm_auth_django']
85-
8697
MIDDLEWARE = [..., 'hotosm_auth_django.HankoAuthMiddleware']
8798

8899
# views.py

docs/src/integration-guide.md

Lines changed: 107 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Step by step guide to integrate `hotosm-auth` in your project.
88
|-----------|---------------------|------------------|
99
| **FastAPI** | [Simple](#fastapi-simple-integration) | [With Mapping](#fastapi-integration-with-mapping) |
1010
| **Django** | [Simple](#django-simple-integration) | [With Mapping](#django-integration-with-mapping) |
11+
| **Litestar** | [Simple](#litestar-simple-integration) | [With Mapping](#litestar-integration-with-mapping) |
1112
| **Frontend** | [All](#frontend-all) | [All](#frontend-all) |
1213

1314
---
@@ -57,7 +58,8 @@ async def lifespan(app: FastAPI):
5758
app = FastAPI(lifespan=lifespan)
5859

5960
# Mount OSM OAuth routes (optional)
60-
app.include_router(osm_router, prefix="/api/auth/osm")
61+
# router already has prefix="/auth/osm" → routes: /api/auth/osm/login, /api/auth/osm/callback
62+
app.include_router(osm_router, prefix="/api")
6163
```
6264

6365
### Step 3: Protect routes
@@ -233,6 +235,102 @@ if getattr(settings, 'AUTH_PROVIDER', 'legacy') == 'hanko':
233235

234236
---
235237

238+
## Litestar: Simple Integration
239+
240+
For apps **without legacy auth** (e.g.: Field-TM).
241+
242+
### Step 1: Dependency
243+
244+
```toml
245+
# pyproject.toml
246+
dependencies = [
247+
"hotosm-auth[litestar]==0.2.10",
248+
]
249+
```
250+
251+
### Step 2: Initialization
252+
253+
```python
254+
# main.py
255+
from litestar import Litestar
256+
from hotosm_auth_litestar import setup_auth
257+
258+
# setup_auth() loads config from env, returns (deps, route_handlers)
259+
deps, route_handlers = setup_auth()
260+
261+
app = Litestar(route_handlers=route_handlers, dependencies=deps)
262+
```
263+
264+
### Step 3: Protect routes
265+
266+
```python
267+
from litestar import get
268+
from hotosm_auth_litestar import AuthContext, OptionalAuthContext
269+
270+
@get("/me")
271+
async def me(auth: AuthContext) -> dict:
272+
"""Requires authentication."""
273+
return {"user_id": auth.user.id, "email": auth.user.email}
274+
275+
@get("/public")
276+
async def public(optional_auth: OptionalAuthContext) -> dict:
277+
"""Optional auth."""
278+
return {"user": optional_auth.user.email if optional_auth.user else "anonymous"}
279+
```
280+
281+
### Step 4: Environment variables
282+
283+
```bash
284+
HANKO_API_URL=https://login.hotosm.org
285+
COOKIE_SECRET=your-32-byte-secret
286+
287+
# Only if using OSM OAuth
288+
OSM_CLIENT_ID=your-client-id
289+
OSM_CLIENT_SECRET=your-client-secret
290+
```
291+
292+
---
293+
294+
## Litestar: Integration with Mapping
295+
296+
### Steps 1-2: Same as Simple
297+
298+
### Step 3: Custom auth dependency
299+
300+
```python
301+
# auth_deps.py
302+
from litestar import Request
303+
from hotosm_auth_litestar import get_current_user, get_mapped_user_id
304+
305+
async def login_required(request: Request):
306+
hanko_user = await get_current_user(request)
307+
db = request.app.state.db
308+
user_id = await get_mapped_user_id(
309+
hanko_user=hanko_user,
310+
db_conn=db,
311+
app_name="my-app",
312+
auto_create=True,
313+
email_lookup_fn=lookup_user_by_email,
314+
user_creator_fn=create_app_user,
315+
)
316+
return await get_user_by_id(db, user_id)
317+
```
318+
319+
### Step 4: Admin routes (optional)
320+
321+
```python
322+
# main.py
323+
from hotosm_auth_litestar import create_admin_mappings_router
324+
325+
admin_router = create_admin_mappings_router(
326+
get_db, app_name="my-app"
327+
)
328+
deps, route_handlers = setup_auth()
329+
app = Litestar(route_handlers=[*route_handlers, admin_router], dependencies=deps)
330+
```
331+
332+
---
333+
236334
## Frontend (all)
237335

238336
```tsx
@@ -255,11 +353,11 @@ VITE_HANKO_URL=https://login.hotosm.org
255353

256354
## Checklist
257355

258-
| Step | FastAPI Simple | FastAPI+Mapping | Django Simple | Django+Mapping |
259-
|------|----------------|-----------------|---------------|----------------|
260-
| Dependency |||||
261-
| init_auth / middleware | | | | |
262-
| Protect routes | CurrentUser | Override login_required | request.hotosm | request.hotosm |
263-
| Helper functions | - || - ||
264-
| Admin routes | - | Optional | - | Optional |
265-
| AUTH_PROVIDER env | - || - ||
356+
| Step | FastAPI Simple | FastAPI+Mapping | Django Simple | Django+Mapping | Litestar Simple | Litestar+Mapping |
357+
|------|----------------|-----------------|---------------|----------------|-----------------|------------------|
358+
| Dependency |||||||
359+
| Init | init_auth | init_auth | middleware | middleware | setup_auth() | setup_auth() |
360+
| Protect routes | CurrentUser | Override login_required | request.hotosm | request.hotosm | AuthContext | Custom dep |
361+
| Helper functions | - || - || - ||
362+
| Admin routes | - | Optional | - | Optional | - | Optional |
363+
| AUTH_PROVIDER env | - || - || - ||

docs/src/overview.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
| `hotosm_auth` | Core Python package (JWT, config, crypto) |
1414
| `hotosm_auth_fastapi` | FastAPI integration (dependencies, routes) |
1515
| `hotosm_auth_django` | Django integration (middleware, decorators) |
16+
| `hotosm_auth_litestar` | Litestar integration (dependencies, routes) |
1617
| `<hotosm-auth>` | Web component (Lit-based auth UI) |
1718

1819
---
@@ -36,10 +37,15 @@ auth-libs/
3637
│ │ ├── osm_routes.py # /auth/osm/* endpoints
3738
│ │ └── admin_routes.py # User mapping admin API
3839
│ │
39-
│ └── hotosm_auth_django/ # Django integration
40-
│ ├── middleware.py # HankoAuthMiddleware
41-
│ ├── admin_routes.py # Admin URL patterns
42-
│ └── models.py # HankoUserMapping model
40+
│ ├── hotosm_auth_django/ # Django integration
41+
│ │ ├── middleware.py # HankoAuthMiddleware
42+
│ │ ├── admin_routes.py # Admin URL patterns
43+
│ │ └── models.py # HankoUserMapping model
44+
│ │
45+
│ └── hotosm_auth_litestar/ # Litestar integration
46+
│ ├── dependencies.py # AuthContext, setup_auth
47+
│ ├── osm_routes.py # /auth/osm/* endpoints
48+
│ └── admin_routes.py # User mapping admin API
4349
4450
└── web-component/ # Frontend web component
4551
├── src/hanko-auth.ts # Lit web component
@@ -176,13 +182,19 @@ pip install "hotosm-auth[fastapi]==0.2.10"
176182

177183
# With Django
178184
pip install "hotosm-auth[django]==0.2.10"
185+
186+
# With Litestar
187+
pip install "hotosm-auth[litestar]==0.2.10"
179188
```
180189

181190
### pyproject.toml
182191

183192
```toml
184193
dependencies = [
194+
# Pick one:
185195
"hotosm-auth[fastapi]==0.2.10",
196+
# "hotosm-auth[django]==0.2.10",
197+
# "hotosm-auth[litestar]==0.2.10",
186198
]
187199
```
188200

docs/src/python-libs.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,3 +359,101 @@ Run migrations:
359359
```bash
360360
python manage.py migrate hotosm_auth_django
361361
```
362+
363+
---
364+
365+
## Litestar: `hotosm_auth_litestar`
366+
367+
### Setup
368+
369+
```python
370+
# main.py
371+
from litestar import Litestar
372+
from hotosm_auth_litestar import setup_auth
373+
374+
# setup_auth() loads config from env, returns (deps, route_handlers)
375+
deps, route_handlers = setup_auth()
376+
377+
app = Litestar(route_handlers=route_handlers, dependencies=deps)
378+
```
379+
380+
This automatically:
381+
382+
- Loads `AuthConfig.from_env()`
383+
- Registers OSM OAuth routes (`/auth/osm/login`, `/auth/osm/callback`, etc.)
384+
- Sets up `AuthContext` and `OptionalAuthContext` dependencies
385+
386+
### Usage
387+
388+
```python
389+
from litestar import get
390+
from hotosm_auth_litestar import AuthContext, OptionalAuthContext
391+
392+
@get("/me")
393+
async def me(auth: AuthContext) -> dict:
394+
"""Requires authentication."""
395+
return {"user_id": auth.user.id, "email": auth.user.email}
396+
397+
@get("/public")
398+
async def public(optional_auth: OptionalAuthContext) -> dict:
399+
"""Optional auth."""
400+
return {"user": optional_auth.user.email if optional_auth.user else "anonymous"}
401+
```
402+
403+
### Dependencies
404+
405+
| Dependency | Type | Raises |
406+
|------------|------|--------|
407+
| `AuthContext` | `AuthContext` (`.user`, `.osm`) | 401 |
408+
| `OptionalAuthContext` | `OptionalAuthContext` | - |
409+
| `AdminUser` | `HankoUser` | 403 |
410+
411+
### OSM Routes
412+
413+
```
414+
GET /auth/osm/login → Redirect to OSM authorization
415+
GET /auth/osm/callback → Handle OAuth callback, set cookie
416+
GET /auth/osm/status → {"connected": true, "osm_username": "..."}
417+
POST /auth/osm/disconnect → Revoke tokens, clear cookie
418+
```
419+
420+
### Admin Routes
421+
422+
```python
423+
from hotosm_auth_litestar import create_admin_mappings_router, setup_auth
424+
425+
admin_router = create_admin_mappings_router(get_db, app_name="my-app")
426+
deps, route_handlers = setup_auth()
427+
app = Litestar(route_handlers=[*route_handlers, admin_router], dependencies=deps)
428+
```
429+
430+
Endpoints:
431+
432+
```
433+
GET /mappings → List all mappings (paginated)
434+
POST /mappings → Create mapping
435+
GET /mappings/{hanko_id} → Get single mapping
436+
PUT /mappings/{hanko_id} → Update mapping
437+
DELETE /mappings/{hanko_id} → Delete mapping
438+
```
439+
440+
### Mapping Integration
441+
442+
```python
443+
# auth_deps.py
444+
from litestar import Request
445+
from hotosm_auth_litestar import get_current_user, get_mapped_user_id
446+
447+
async def login_required(request: Request):
448+
hanko_user = await get_current_user(request)
449+
db = request.app.state.db
450+
user_id = await get_mapped_user_id(
451+
hanko_user=hanko_user,
452+
db_conn=db,
453+
app_name="my-app",
454+
auto_create=True,
455+
email_lookup_fn=lookup_user_by_email,
456+
user_creator_fn=create_app_user,
457+
)
458+
return await get_user_by_id(db, user_id)
459+
```

0 commit comments

Comments
 (0)