Files
takeyourmeds/server/auth.py
T
Frank Schwenk 88bdf6f34b feat: overhaul with modular API, agent endpoints, migrations, backup and tests
- Split monolithic app.py into server/api/ routers
- Add Pydantic schemas for input validation
- Add /agent/* API with separate AGENT_TOKEN auth
- Replace static schema.sql with versioned migrations (datenerhaltend)
- Add tools/backup_db.py for SQLite backups
- Add Dockerfile HEALTHCHECK and compose.yml AGENT_TOKEN env
- Add pytest tests for user and agent API
- Update README with API docs and deployment safety notes
- Bump version to 1.2.0
2026-09-21 12:46:44 +02:00

58 lines
1.9 KiB
Python

from __future__ import annotations
import hmac
import secrets
from datetime import UTC, datetime, timedelta
from typing import Any
import jwt
from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
from server.settings import settings
_bearer = HTTPBearer(auto_error=False)
ALGORITHM = "HS256"
def verify_pin(pin: str) -> bool:
return hmac.compare_digest(pin.strip(), settings.app_pin.strip())
def create_token() -> str:
expire = datetime.now(UTC) + timedelta(days=settings.jwt_expire_days)
payload = {"sub": "user", "exp": expire}
return jwt.encode(payload, settings.jwt_secret, algorithm=ALGORITHM)
def decode_token(token: str) -> dict[str, Any]:
try:
return jwt.decode(token, settings.jwt_secret, algorithms=[ALGORITHM])
except jwt.PyJWTError as exc:
raise HTTPException(status_code=401, detail="Invalid token") from exc
async def require_auth(
creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> dict[str, Any]:
if creds is None or creds.scheme.lower() != "bearer":
raise HTTPException(status_code=401, detail="Missing token")
return decode_token(creds.credentials)
async def require_agent(
creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
) -> dict[str, Any]:
"""Authenticate external agents via AGENT_TOKEN (long-lived, not JWT)."""
if creds is None or creds.scheme.lower() != "bearer":
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing agent token")
token = creds.credentials
if not settings.agent_token:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Agent API not configured",
)
if not secrets.compare_digest(token.strip(), settings.agent_token.strip()):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid agent token")
return {"sub": "agent"}