Spaces:
Running
Running
Z User
feat: ABM Engine v1 β OpenSearch store + Apify enrichment + ICP scoring + k-NN memory + optional Neo4j mirror
fb0ec21 Download app/auth.py from sitcod3/abm-engine: direct link, hf CLI and curl.
- Browser
- Download file 4.64 kB
-
https://huggingface.co/spaces/sitcod3/abm-engine/resolve/main/app/auth.py
- Command line
-
hf download hf://spaces/sitcod3/abm-engine/app/auth.py
-
curl -L -o auth.py https://huggingface.co/spaces/sitcod3/abm-engine/resolve/main/app/auth.py
4.64 kB
| """Supabase JWT verification β same identity plane as the validator engine. | |
| The ABM engine is a product backend: the site attaches the user's Supabase | |
| session JWT (Authorization: Bearer) and this module verifies it against the | |
| project's public JWKS endpoint. Mirrors app/auth.py of modul-x (ES256/RS256, | |
| audience "authenticated"). A static X-API-Key (settings.api_key) is accepted | |
| for service-to-service calls; both are optional while enforcement is off. | |
| """ | |
| import logging | |
| from typing import Optional | |
| import httpx | |
| from fastapi import Request | |
| from fastapi.responses import JSONResponse | |
| from jose import jwt as jose_jwt | |
| from jose import jwk as jose_jwk | |
| from jose.exceptions import JWTError | |
| from .config import settings | |
| logger = logging.getLogger("abm.auth") | |
| _jwks_cache: Optional[dict] = None | |
| def enforcement_on() -> bool: | |
| return (settings.license_enforcement or "").strip().lower() == "true" | |
| async def _get_jwks() -> dict: | |
| global _jwks_cache | |
| if _jwks_cache is None: | |
| url = f"{settings.supabase_url}/auth/v1/.well-known/jwks.json" | |
| async with httpx.AsyncClient(timeout=10.0) as client: | |
| res = await client.get(url) | |
| res.raise_for_status() | |
| _jwks_cache = res.json() | |
| return _jwks_cache # type: ignore[return-value] | |
| async def verify_supabase_jwt(token: str) -> dict: | |
| jwks = await _get_jwks() | |
| last_error: Optional[Exception] = None | |
| for key_data in jwks.get("keys", []): | |
| try: | |
| key = jose_jwk.construct(key_data) | |
| claims = jose_jwt.decode( | |
| token, | |
| key, | |
| algorithms=["ES256", "RS256"], | |
| audience="authenticated", | |
| ) | |
| if not claims.get("sub"): | |
| raise JWTError("JWT has no sub claim") | |
| return claims | |
| except JWTError as exc: | |
| last_error = exc | |
| continue | |
| raise JWTError(f"JWT verification failed: {last_error}") | |
| async def has_abm_license(user_id: str) -> bool: | |
| """Active 'abm-engine' license in the central project (service-role read). | |
| Falls back to modul-x license so existing customers are not locked out | |
| during rollout.""" | |
| service_key = settings.supabase_service_role_key | |
| if not service_key: | |
| return False | |
| headers = {"apikey": service_key, "Authorization": f"Bearer {service_key}"} | |
| try: | |
| async with httpx.AsyncClient(timeout=10.0) as client: | |
| res = await client.get( | |
| f"{settings.supabase_url}/rest/v1/licenses", | |
| params={"user_id": f"eq.{user_id}", "status": "eq.active", "select": "product"}, | |
| headers=headers, | |
| ) | |
| if res.status_code != 200: | |
| return False | |
| products = {row.get("product") for row in res.json()} | |
| return bool(products & {"abm-engine", "modul-x", "combo-full-bridge"}) | |
| except Exception: | |
| logger.warning("license lookup failed", exc_info=True) | |
| return False | |
| def _api_key_ok(request: Request) -> bool: | |
| supplied = request.headers.get("x-api-key", "") | |
| return bool(settings.api_key) and supplied == settings.api_key | |
| async def authenticate(request: Request) -> dict: | |
| """Authenticate a paid-route request. | |
| Accepts, in order: X-API-Key (service calls), Supabase session JWT. | |
| Returns {"user_id", "email", "via"}. Raises PermissionError on failure β | |
| the route layer maps it to 401/402. When enforcement is off, an invalid | |
| JWT still authenticates anonymously so demo deployments keep working. | |
| """ | |
| if _api_key_ok(request): | |
| return {"user_id": "service", "email": None, "via": "api-key"} | |
| header = request.headers.get("authorization", "") | |
| token = header[7:].strip() if header.lower().startswith("bearer ") else "" | |
| if token and settings.supabase_url: | |
| try: | |
| claims = await verify_supabase_jwt(token) | |
| user_id = claims.get("sub", "") | |
| if enforcement_on() and not await has_abm_license(user_id): | |
| raise PermissionError("402:No active ABM Engine license β see /pricing.") | |
| return {"user_id": user_id, "email": claims.get("email"), "via": "jwt"} | |
| except PermissionError: | |
| raise | |
| except JWTError: | |
| if enforcement_on(): | |
| raise PermissionError("401:Invalid or expired token β sign in again.") | |
| return {"user_id": "anonymous", "email": None, "via": "anonymous"} | |
| if enforcement_on(): | |
| raise PermissionError("401:Missing bearer token β sign in first.") | |
| return {"user_id": "anonymous", "email": None, "via": "anonymous"} | |