docs(code): add concise docstrings
This commit is contained in:
@@ -20,46 +20,73 @@ T = TypeVar("T")
|
||||
|
||||
|
||||
class AuthClient(Protocol):
|
||||
async def request_otp(self, mobile: str, purpose: str) -> dict[str, bool]: ...
|
||||
"""Minimal client contract used by FastAPI routes and tests."""
|
||||
|
||||
async def verify_otp(self, mobile: str, code: str, purpose: str) -> DemoTokenResponse: ...
|
||||
async def request_otp(self, mobile: str, purpose: str) -> dict[str, bool]:
|
||||
"""Ask the auth service to create and dispatch an OTP."""
|
||||
...
|
||||
|
||||
async def refresh_token(self, refresh_token: str) -> DemoTokenResponse: ...
|
||||
async def verify_otp(self, mobile: str, code: str, purpose: str) -> DemoTokenResponse:
|
||||
"""Verify an OTP and return the token pair produced by auth-service."""
|
||||
...
|
||||
|
||||
async def refresh_token(self, refresh_token: str) -> DemoTokenResponse:
|
||||
"""Rotate a refresh token through auth-service."""
|
||||
...
|
||||
|
||||
async def revoke_refresh_token(
|
||||
self, access_token: str, refresh_token: str
|
||||
) -> dict[str, bool]: ...
|
||||
) -> dict[str, bool]:
|
||||
"""Revoke a refresh session using bearer-token metadata."""
|
||||
...
|
||||
|
||||
async def public_ping(self) -> dict[str, str]: ...
|
||||
async def public_ping(self) -> dict[str, str]:
|
||||
"""Call the public demo RPC without authentication."""
|
||||
...
|
||||
|
||||
async def user_only(self, access_token: str) -> dict[str, str]: ...
|
||||
async def user_only(self, access_token: str) -> dict[str, str]:
|
||||
"""Call the user-protected demo RPC with an access token."""
|
||||
...
|
||||
|
||||
async def admin_only(self, access_token: str) -> dict[str, str]: ...
|
||||
async def admin_only(self, access_token: str) -> dict[str, str]:
|
||||
"""Call the admin-protected demo RPC with an access token."""
|
||||
...
|
||||
|
||||
|
||||
class RequestOtpBody(BaseModel):
|
||||
"""HTTP body for starting the mobile OTP login flow."""
|
||||
|
||||
mobile: str = Field(min_length=10, max_length=16)
|
||||
purpose: str = "login"
|
||||
|
||||
|
||||
class VerifyOtpBody(RequestOtpBody):
|
||||
"""HTTP body for verifying a received OTP code."""
|
||||
|
||||
code: str = Field(min_length=6, max_length=6)
|
||||
|
||||
|
||||
class RefreshBody(BaseModel):
|
||||
"""HTTP body carrying the opaque refresh token."""
|
||||
|
||||
refresh_token: str = Field(min_length=20)
|
||||
|
||||
|
||||
class TokenBody(BaseModel):
|
||||
"""HTTP body carrying a bearer access token for demo actions."""
|
||||
|
||||
access_token: str = Field(min_length=20)
|
||||
|
||||
|
||||
class RevokeBody(TokenBody):
|
||||
"""HTTP body for revoking the current refresh session."""
|
||||
|
||||
refresh_token: str = Field(min_length=20)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
"""Create and close shared outbound clients for the demo service."""
|
||||
settings = get_settings()
|
||||
app.state.settings = settings
|
||||
app.state.auth_client = AuthGrpcClient(settings.auth_grpc_target)
|
||||
@@ -81,24 +108,29 @@ app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
|
||||
|
||||
|
||||
def get_auth_client(request: Request) -> AuthClient:
|
||||
"""Resolve the configured auth-service client from application state."""
|
||||
return cast(AuthClient, request.app.state.auth_client)
|
||||
|
||||
|
||||
def get_app_settings(request: Request) -> Settings:
|
||||
"""Resolve immutable runtime settings for request handlers."""
|
||||
return cast(Settings, request.app.state.settings)
|
||||
|
||||
|
||||
def get_debug_redis(request: Request) -> Redis | None:
|
||||
"""Resolve the optional Redis client used only by local debug OTP mode."""
|
||||
return cast(Redis | None, request.app.state.debug_redis)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def index() -> FileResponse:
|
||||
"""Serve the single-page browser demo."""
|
||||
return FileResponse(STATIC_DIR / "index.html")
|
||||
|
||||
|
||||
@app.get("/healthz")
|
||||
async def healthz() -> dict[str, str]:
|
||||
"""Return a lightweight readiness response for Compose and Caddy checks."""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
@@ -107,6 +139,7 @@ async def request_otp(
|
||||
body: RequestOtpBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Forward an OTP request from the browser to the gRPC auth service."""
|
||||
return cast(
|
||||
dict[str, object],
|
||||
await _call_grpc(lambda: client.request_otp(body.mobile, body.purpose)),
|
||||
@@ -118,6 +151,7 @@ async def verify_otp(
|
||||
body: VerifyOtpBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Verify an OTP and return token data to the browser demo."""
|
||||
response = await _call_grpc(lambda: client.verify_otp(body.mobile, body.code, body.purpose))
|
||||
return cast(dict[str, object], asdict(response))
|
||||
|
||||
@@ -127,6 +161,7 @@ async def refresh_token(
|
||||
body: RefreshBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Rotate a refresh token and return a new token pair."""
|
||||
response = await _call_grpc(lambda: client.refresh_token(body.refresh_token))
|
||||
return cast(dict[str, object], asdict(response))
|
||||
|
||||
@@ -136,6 +171,7 @@ async def revoke_refresh_token(
|
||||
body: RevokeBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Revoke the current refresh session through auth-service."""
|
||||
return cast(
|
||||
dict[str, object],
|
||||
await _call_grpc(
|
||||
@@ -146,6 +182,7 @@ async def revoke_refresh_token(
|
||||
|
||||
@app.post("/api/demo/public")
|
||||
async def public_demo(client: Annotated[AuthClient, Depends(get_auth_client)]) -> dict[str, object]:
|
||||
"""Call the public gRPC endpoint to prove unauthenticated access."""
|
||||
return cast(dict[str, object], await _call_grpc(client.public_ping))
|
||||
|
||||
|
||||
@@ -154,6 +191,7 @@ async def user_demo(
|
||||
body: TokenBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Call the user-protected gRPC endpoint with bearer metadata."""
|
||||
return cast(dict[str, object], await _call_grpc(lambda: client.user_only(body.access_token)))
|
||||
|
||||
|
||||
@@ -162,6 +200,7 @@ async def admin_demo(
|
||||
body: TokenBody,
|
||||
client: Annotated[AuthClient, Depends(get_auth_client)],
|
||||
) -> dict[str, object]:
|
||||
"""Call the admin-protected gRPC endpoint with bearer metadata."""
|
||||
return cast(dict[str, object], await _call_grpc(lambda: client.admin_only(body.access_token)))
|
||||
|
||||
|
||||
@@ -171,6 +210,7 @@ async def debug_otp(
|
||||
settings: Annotated[Settings, Depends(get_app_settings)],
|
||||
redis: Annotated[Redis | None, Depends(get_debug_redis)],
|
||||
) -> dict[str, str | None]:
|
||||
"""Expose the last debug OTP in local demo mode only."""
|
||||
if not settings.demo_enable_debug_otp or redis is None:
|
||||
raise HTTPException(status_code=404, detail="debug OTP endpoint is disabled")
|
||||
code = await redis.get(debug_sms_key(mobile))
|
||||
@@ -178,6 +218,7 @@ async def debug_otp(
|
||||
|
||||
|
||||
async def _call_grpc(call: Callable[[], Awaitable[T]]) -> T:
|
||||
"""Execute a gRPC call and translate transport errors to HTTP errors."""
|
||||
try:
|
||||
return await call()
|
||||
except grpc.aio.AioRpcError as exc:
|
||||
@@ -187,6 +228,7 @@ async def _call_grpc(call: Callable[[], Awaitable[T]]) -> T:
|
||||
|
||||
|
||||
def _grpc_to_http_status(code: grpc.StatusCode) -> int:
|
||||
"""Map auth-service gRPC status codes to browser-friendly HTTP statuses."""
|
||||
match code:
|
||||
case grpc.StatusCode.INVALID_ARGUMENT:
|
||||
return 400
|
||||
|
||||
Reference in New Issue
Block a user