feat: API gateway with passkey (WebAuthn) authentication
This commit is contained in:
parent
f218865872
commit
e0d138c457
9 changed files with 907 additions and 2 deletions
68
services/api_gateway/auth/middleware.py
Normal file
68
services/api_gateway/auth/middleware.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
"""Auth middleware — FastAPI dependency for JWT-based authentication."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import Depends, HTTPException, status
|
||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||
|
||||
import jwt as pyjwt
|
||||
|
||||
from services.api_gateway.auth.jwt import decode_token
|
||||
from services.api_gateway.config import ApiGatewayConfig
|
||||
|
||||
# Shared config instance — injected via FastAPI dependency override in tests.
|
||||
_config: ApiGatewayConfig | None = None
|
||||
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
def get_config() -> ApiGatewayConfig:
|
||||
"""Return the singleton config. Overridden in tests."""
|
||||
global _config
|
||||
if _config is None:
|
||||
_config = ApiGatewayConfig()
|
||||
return _config
|
||||
|
||||
|
||||
async def get_current_user(
|
||||
credentials: HTTPAuthorizationCredentials | None = Depends(security),
|
||||
config: ApiGatewayConfig = Depends(get_config),
|
||||
) -> dict:
|
||||
"""FastAPI dependency that extracts and validates a Bearer JWT.
|
||||
|
||||
Returns the decoded token payload (contains ``sub``, ``username``, etc.)
|
||||
on success. Raises a 401 ``HTTPException`` for missing, expired, or
|
||||
invalid tokens.
|
||||
"""
|
||||
if credentials is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Missing authorization header",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token = credentials.credentials
|
||||
try:
|
||||
payload = decode_token(token, config)
|
||||
except pyjwt.ExpiredSignatureError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Token has expired",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
except pyjwt.InvalidTokenError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
# Ensure it is an access token, not a refresh token
|
||||
if payload.get("type") != "access":
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid token type",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
return payload
|
||||
Loading…
Add table
Add a link
Reference in a new issue