mirror of
https://github.com/james-m-jordan/morphik-core.git
synced 2025-05-09 19:32:38 +00:00
70 lines
2.7 KiB
Python
70 lines
2.7 KiB
Python
![]() |
from datetime import UTC, datetime
|
|||
|
|
|||
|
import jwt
|
|||
|
from fastapi import Header, HTTPException
|
|||
|
|
|||
|
from core.config import get_settings
|
|||
|
from core.models.auth import AuthContext, EntityType
|
|||
|
|
|||
|
__all__ = ["verify_token"]
|
|||
|
|
|||
|
# Load settings once at import time
|
|||
|
settings = get_settings()
|
|||
|
|
|||
|
|
|||
|
async def verify_token(authorization: str = Header(None)) -> AuthContext: # noqa: D401 – FastAPI dependency
|
|||
|
"""Return an :class:`AuthContext` for a valid JWT bearer *authorization* header.
|
|||
|
|
|||
|
In *dev_mode* we skip cryptographic checks and fabricate a permissive
|
|||
|
context so that local development environments can quickly spin up
|
|||
|
without real tokens.
|
|||
|
"""
|
|||
|
|
|||
|
# ------------------------------------------------------------------
|
|||
|
# 1. Development shortcut – trust everyone when *dev_mode* is active.
|
|||
|
# ------------------------------------------------------------------
|
|||
|
if settings.dev_mode:
|
|||
|
return AuthContext(
|
|||
|
entity_type=EntityType(settings.dev_entity_type),
|
|||
|
entity_id=settings.dev_entity_id,
|
|||
|
permissions=set(settings.dev_permissions),
|
|||
|
user_id=settings.dev_entity_id, # In dev mode, entity_id == user_id
|
|||
|
)
|
|||
|
|
|||
|
# ------------------------------------------------------------------
|
|||
|
# 2. Normal token verification flow
|
|||
|
# ------------------------------------------------------------------
|
|||
|
if not authorization:
|
|||
|
raise HTTPException(
|
|||
|
status_code=401,
|
|||
|
detail="Missing authorization header",
|
|||
|
headers={"WWW-Authenticate": "Bearer"},
|
|||
|
)
|
|||
|
|
|||
|
if not authorization.startswith("Bearer "):
|
|||
|
raise HTTPException(status_code=401, detail="Invalid authorization header")
|
|||
|
|
|||
|
token = authorization[7:] # Strip "Bearer " prefix
|
|||
|
|
|||
|
try:
|
|||
|
payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
|
|||
|
except jwt.InvalidTokenError as exc:
|
|||
|
raise HTTPException(status_code=401, detail=str(exc)) from exc
|
|||
|
|
|||
|
# Check expiry manually – jwt.decode does *not* enforce expiry on psycopg2.
|
|||
|
if datetime.fromtimestamp(payload["exp"], UTC) < datetime.now(UTC):
|
|||
|
raise HTTPException(status_code=401, detail="Token expired")
|
|||
|
|
|||
|
# Support both legacy "type" and new "entity_type" fields
|
|||
|
entity_type_field = payload.get("type") or payload.get("entity_type")
|
|||
|
if entity_type_field is None:
|
|||
|
raise HTTPException(status_code=401, detail="Missing entity type in token")
|
|||
|
|
|||
|
return AuthContext(
|
|||
|
entity_type=EntityType(entity_type_field),
|
|||
|
entity_id=payload["entity_id"],
|
|||
|
app_id=payload.get("app_id"),
|
|||
|
permissions=set(payload.get("permissions", ["read"])),
|
|||
|
user_id=payload.get("user_id", payload["entity_id"]),
|
|||
|
)
|