feat: add analysis pages and raster risk map
Ship a new app version with broader analytics, restructured dashboards, and a server-rendered risk map. Frontend: - Add Overview, Demographic, Disease, and Environmental Health analysis pages - Add AnomalyMarkers, CalendarHeatmap, and MetricHeatmapTable components - Rebuild Alerts map onto server-rendered raster risk tiles; expand Monitoring, Trend, and District Comparison views - Extend API client, stores, and TypeScript types Backend: - Add environment router (pollutants, lag correlations) - Add risk_raster util serving XYZ 100m risk tiles - Expand cases endpoints (demographics, seasonality, diagnoses) and insights; harden auth and file-based loaders Data & tooling: - Add processed outpatient/inpatient/combined case parquet (LFS) - Add nested CLAUDE.md guides, pyrightconfig, and test updates
This commit is contained in:
42
backend/auth/CLAUDE.md
Normal file
42
backend/auth/CLAUDE.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Auth — JWT Authentication
|
||||
|
||||
## Stack
|
||||
|
||||
python-jose (JWT signing/verification) + passlib (bcrypt password hashing). Token-based, stateless.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
auth/
|
||||
models.py # Pydantic models: UserCreate, UserLogin, Token, UserOut
|
||||
service.py # Business logic: authenticate_user, create_user, create_access_token
|
||||
dependencies.py # FastAPI Depends: get_current_user, require_admin
|
||||
middleware.py # ASGI middleware (if any global auth checks)
|
||||
router.py # APIRouter: /login, /register, /whoami
|
||||
```
|
||||
|
||||
## Patterns
|
||||
|
||||
- Passwords hashed with bcrypt via `passlib` — never store plaintext
|
||||
- JWT tokens signed with `python-jose`, include `sub` (username) and `exp`
|
||||
- `get_current_user()` is the standard `Depends()` to inject user into endpoints
|
||||
- Auth endpoints return Pydantic models: `Token(access_token=...)`, `UserOut(username=...)`
|
||||
- HTTP status codes: 401 for bad credentials, 409 for duplicate user
|
||||
|
||||
## Usage in Routers
|
||||
|
||||
```python
|
||||
from auth.dependencies import get_current_user
|
||||
|
||||
@router.get("/protected")
|
||||
async def protected_route(current_user = Depends(get_current_user)):
|
||||
...
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- Don't hardcode secret keys — use `Settings` from environment
|
||||
- Don't store tokens client-side without HttpOnly cookies
|
||||
- Don't skip `response_model` on auth endpoints
|
||||
- Don't leak whether username or password was wrong — always "incorrect username or password"
|
||||
- Don't bypass `Depends(get_current_user)` for protected routes
|
||||
@@ -8,7 +8,13 @@ from passlib.context import CryptContext
|
||||
|
||||
logger = logging.getLogger("cbpoa.auth")
|
||||
|
||||
SECRET_KEY = os.getenv("AUTH_SECRET_KEY", "cbpoa-dev-secret-change-in-production")
|
||||
_DEFAULT_SECRET = "cbpoa-dev-secret-change-in-production"
|
||||
SECRET_KEY = os.getenv("AUTH_SECRET_KEY", _DEFAULT_SECRET)
|
||||
if SECRET_KEY == _DEFAULT_SECRET:
|
||||
logger.warning(
|
||||
"AUTH_SECRET_KEY is not set — using the built-in development secret. "
|
||||
"Set AUTH_SECRET_KEY in the environment before deploying; the default is public and allows token forgery."
|
||||
)
|
||||
ALGORITHM = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("AUTH_TOKEN_EXPIRE_MINUTES", "480"))
|
||||
|
||||
@@ -61,6 +67,10 @@ def create_access_token(data: dict) -> str:
|
||||
def decode_access_token(token: str) -> dict | None:
|
||||
try:
|
||||
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
|
||||
# python-jose ignores PyJWT's options={"require": [...]}, so enforce exp manually:
|
||||
# a token with no exp claim would otherwise never expire.
|
||||
if "exp" not in payload:
|
||||
return None
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user