Source code for loom.rest.cors
"""Validated CORS settings for the REST layer.
The framework exposes CORS as configuration, not for convenience but because
the combination every tutorial suggests is unsafe and Starlette does not refuse
it: with ``allow_origins: ["*"]`` **and** ``allow_credentials: true``, Starlette
stops sending the wildcard and instead reflects the request's ``Origin`` back
together with ``Access-Control-Allow-Credentials: true``. The wildcard silently
becomes "any origin, with cookies".
Binding the settings here makes that shape unrepresentable: it fails at config
parse, with the reason.
"""
from __future__ import annotations
from loom.core.config.errors import ConfigError
from loom.core.model import LoomFrozenStruct
WILDCARD_ORIGIN = "*"
_CREDENTIALS_WILDCARD_ERROR = (
"CORS 'allow_origins' contains '*' together with 'allow_credentials: true'. "
"Starlette does not reject that combination: it reflects the caller's Origin "
"header and answers with 'Access-Control-Allow-Credentials: true', so every "
"site becomes able to make credentialed requests to this API. List the exact "
"origins, or turn 'allow_credentials' off."
)
[docs]
class CorsConfig(LoomFrozenStruct, frozen=True, kw_only=True):
"""Validated settings for the CORS middleware.
Binds from the ``app.rest.cors`` config section. The section is optional:
without it no CORS middleware is mounted and the application behaves exactly
as before.
Attributes:
allow_origins: Exact origins allowed to call the API. ``["*"]`` is
only accepted when ``allow_credentials`` is off.
allow_origin_regex: Regular expression matching allowed origins.
Anchored by Starlette; keep it as narrow as the origin list you
would otherwise write.
allow_methods: HTTP methods allowed in cross-origin requests.
allow_headers: Request headers the caller may send.
allow_credentials: Whether cookies and ``Authorization`` may be sent
cross-origin. Mutually exclusive with a wildcard origin.
expose_headers: Response headers the browser may read.
max_age: Seconds a preflight response may be cached.
Raises:
ConfigError: When a wildcard origin is combined with credentials.
Example YAML::
app:
rest:
cors:
allow_origins: ["https://app.example.com"]
allow_credentials: true
allow_methods: [GET, POST]
"""
allow_origins: tuple[str, ...] = ()
allow_origin_regex: str | None = None
allow_methods: tuple[str, ...] = ("GET",)
allow_headers: tuple[str, ...] = ()
allow_credentials: bool = False
expose_headers: tuple[str, ...] = ()
max_age: int = 600
def __post_init__(self) -> None:
if self.allow_credentials and WILDCARD_ORIGIN in self.allow_origins:
raise ConfigError(_CREDENTIALS_WILDCARD_ERROR)