Skip to content

Commit ca913a0

Browse files
authored
feat: enable hosting custom Swagger UI (#53)
Given that upstream APIs often do not support specifying `init_oauth` (which makes sense, being that these APIs typically don't have builtin authentication strategy within the vanilla application), we permit creating our own Swagger UI within this service. related to #15
1 parent d0b9099 commit ca913a0

File tree

5 files changed

+67
-2
lines changed

5 files changed

+67
-2
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ The application is configurable via environment variables.
145145
- **Type:** JSON object
146146
- **Required:** No, defaults to `null` (disabled)
147147
- **Example:** `{"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "Paste your raw JWT here. This API uses Bearer token authorization.\n"}`
148+
- **`SWAGGER_UI_ENDPOINT`**, path of Swagger UI, used for augmenting spec response with auth configuration
149+
- **Type:** string or null
150+
- **Required:** No, defaults to `/api.html`
151+
- **Example:** `/api`
152+
- **`SWAGGER_UI_INIT_OAUTH`**, initialization options for the [Swagger UI OAuth2 configuration](https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/)
153+
- **Type:** JSON object
154+
- **Required:** No, defaults to `null` (disabled)
155+
- **Example:** `{"clientId": "stac-auth-proxy", "usePkceWithAuthorizationCodeGrant": true}`
148156
- Filtering
149157
- **`ITEMS_FILTER_CLS`**, CQL2 expression generator for item-level filtering
150158
- **Type:** JSON object with class configuration

src/stac_auth_proxy/app.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from starlette_cramjam.middleware import CompressionMiddleware
1414

1515
from .config import Settings
16-
from .handlers import HealthzHandler, ReverseProxyHandler
16+
from .handlers import HealthzHandler, ReverseProxyHandler, SwaggerUI
1717
from .middleware import (
1818
AddProcessTimeHeaderMiddleware,
1919
ApplyCql2FilterMiddleware,
@@ -78,6 +78,18 @@ async def lifespan(app: FastAPI):
7878
# Handlers (place catch-all proxy handler last)
7979
#
8080

81+
if settings.swagger_ui_endpoint:
82+
assert (
83+
settings.openapi_spec_endpoint
84+
), "openapi_spec_endpoint must be set when using swagger_ui_endpoint"
85+
app.add_route(
86+
settings.swagger_ui_endpoint,
87+
SwaggerUI(
88+
openapi_url=settings.openapi_spec_endpoint,
89+
init_oauth=settings.swagger_ui_init_oauth,
90+
).route,
91+
include_in_schema=False,
92+
)
8193
if settings.healthz_prefix:
8294
app.include_router(
8395
HealthzHandler(upstream_url=str(settings.upstream_url)).router,

src/stac_auth_proxy/config.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,12 @@ class Settings(BaseSettings):
4545
check_conformance: bool = True
4646
enable_compression: bool = True
4747

48+
# OpenAPI / Swagger UI
4849
openapi_spec_endpoint: Optional[str] = Field(pattern=_PREFIX_PATTERN, default=None)
4950
openapi_auth_scheme_name: str = "oidcAuth"
5051
openapi_auth_scheme_override: Optional[dict] = None
52+
swagger_ui_endpoint: Optional[str] = None
53+
swagger_ui_init_oauth: dict = Field(default_factory=dict)
5154

5255
# Auth
5356
enable_authentication_extension: bool = True

src/stac_auth_proxy/handlers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
from .healthz import HealthzHandler
44
from .reverse_proxy import ReverseProxyHandler
5+
from .swagger_ui import SwaggerUI
56

6-
__all__ = ["ReverseProxyHandler", "HealthzHandler"]
7+
__all__ = ["ReverseProxyHandler", "HealthzHandler", "SwaggerUI"]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
In order to allow customization fo the Swagger UI's OAuth2 configuration, we support
3+
overriding the default handler. This is useful for adding custom parameters such as
4+
`usePkceWithAuthorizationCodeGrant` or `clientId`.
5+
6+
See:
7+
- https://swagger.io/docs/open-source-tools/swagger-ui/usage/oauth2/
8+
"""
9+
10+
from dataclasses import dataclass, field
11+
from typing import Optional
12+
13+
from fastapi.openapi.docs import get_swagger_ui_html
14+
from starlette.requests import Request
15+
from starlette.responses import HTMLResponse
16+
17+
18+
@dataclass
19+
class SwaggerUI:
20+
"""Swagger UI handler."""
21+
22+
openapi_url: str
23+
title: Optional[str] = "STAC API"
24+
init_oauth: dict = field(default_factory=dict)
25+
parameters: dict = field(default_factory=dict)
26+
oauth2_redirect_url: str = "/docs/oauth2-redirect"
27+
28+
async def route(self, req: Request) -> HTMLResponse:
29+
"""Route handler."""
30+
root_path = req.scope.get("root_path", "").rstrip("/")
31+
openapi_url = root_path + self.openapi_url
32+
oauth2_redirect_url = self.oauth2_redirect_url
33+
if oauth2_redirect_url:
34+
oauth2_redirect_url = root_path + oauth2_redirect_url
35+
return get_swagger_ui_html(
36+
openapi_url=openapi_url,
37+
title=f"{self.title} - Swagger UI",
38+
oauth2_redirect_url=oauth2_redirect_url,
39+
init_oauth=self.init_oauth,
40+
swagger_ui_parameters=self.parameters,
41+
)

0 commit comments

Comments
 (0)