auth-fixes (#3)
## Summary
Resolve `{ENV_VAR}` placeholders in the OpenAPI spec before serving it. Replace the monolithic `x-introspect-url` extension with composable `x-server-url` + individual `x-*-path` fields so auth endpoints are configurable per-environment without hardcoding.
## Changes
- **`openapi_first/app.py`** — add `_resolve_spec_env_vars()` that replaces `{ENV_VAR}` patterns (e.g. `{AUTH_SERVER}`) with the corresponding OS environment variable before returning the spec JSON. Called in `__init__` after spec load.
- **`openapi_first/security.py`** — build the introspection URL dynamically from `x-server-url` + `x-introspect-path` extensions on the `bearerAuth` security scheme, instead of reading a single `x-introspect-url`.
## Migration
Existing specs using `x-introspect-url: "https://auth.example.com/introspect"` must switch to the new extension format:
```yaml
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
x-server-url: "{AUTH_SERVER}"
x-login-path: "/login"
x-register-path: "/register"
x-logout-path: "/logout"
x-me-path: "/me"
x-introspect-path: "/introspect"
Reviewed-on: #3
Co-authored-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
Co-committed-by: Vishesh 'ironeagle' Bangotra <aetoskia@gmail.com>
This commit is contained in:
@@ -32,12 +32,19 @@ Notes:
|
||||
- Interpret OpenAPI semantics beyond routing metadata.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi.routing import APIRoute
|
||||
|
||||
from .errors import MissingOperationHandler
|
||||
|
||||
|
||||
def bind_routes(app, spec: dict, routes_module) -> None:
|
||||
def bind_routes(
|
||||
app,
|
||||
spec: dict,
|
||||
routes_module,
|
||||
security_deps: dict[str, list[Any]] | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Bind OpenAPI operations to FastAPI routes.
|
||||
|
||||
@@ -49,18 +56,24 @@ def bind_routes(app, spec: dict, routes_module) -> None:
|
||||
routes_module (module):
|
||||
Python module containing handler functions. Each handler's name MUST
|
||||
exactly match an OpenAPI `operationId`.
|
||||
security_deps (dict | None):
|
||||
Optional mapping of ``METHOD:/path`` → ``list[Depends(...)]``
|
||||
generated from the spec's ``securitySchemes`` and per-operation
|
||||
``security`` fields.
|
||||
|
||||
Raises:
|
||||
MissingOperationHandler:
|
||||
If an `operationId` is missing from the spec or if no corresponding
|
||||
If an ``operationId`` is missing from the spec or if no corresponding
|
||||
handler function exists in the routes module.
|
||||
|
||||
Notes:
|
||||
**Responsibilities:**
|
||||
|
||||
- Iterates through the OpenAPI specification paths and methods.
|
||||
- Resolves each `operationId` to a handler function, and registers
|
||||
a corresponding `APIRoute` on the FastAPI application.
|
||||
- Resolves each ``operationId`` to a handler function, and registers
|
||||
a corresponding ``APIRoute`` on the FastAPI application.
|
||||
- Injects FastAPI ``Depends()`` for each security requirement found
|
||||
on the operation or inherited from the top-level ``security`` field.
|
||||
|
||||
**Guarantees:**
|
||||
|
||||
@@ -70,6 +83,7 @@ def bind_routes(app, spec: dict, routes_module) -> None:
|
||||
"""
|
||||
|
||||
paths = spec.get("paths", {})
|
||||
security_deps = security_deps or {}
|
||||
|
||||
for path, methods in paths.items():
|
||||
for http_method, operation in methods.items():
|
||||
@@ -90,10 +104,14 @@ def bind_routes(app, spec: dict, routes_module) -> None:
|
||||
operation_id=operation_id,
|
||||
)
|
||||
|
||||
key = f"{http_method.upper()}:{path}"
|
||||
deps = security_deps.get(key, [])
|
||||
|
||||
route = APIRoute(
|
||||
path=path,
|
||||
endpoint=endpoint,
|
||||
methods=[http_method.upper()],
|
||||
dependencies=deps,
|
||||
name=operation_id,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user