Add CORS support - #90
Merged
Merged
Conversation
Registers Starlette's CORSMiddleware so that a browser based client can make Bearer-token calls from another origin. Preflight OPTIONS requests are answered above the router, so they are not made to authenticate, and error responses carry the CORS headers a client needs in order to read them. The allowed origins are read from a JSON file named by the new, optional CORS_ALLOWED_ORIGINS_FILE variable. With it unset no middleware is registered at all, so a deployment that has not opted in is unchanged. Deploying with CORS enabled needs the origins file bind-mounted into the container.
Rebased the CORS branch when Sentry branch was merged to develop. This refactor removes the resulting code duplication. Sentry and CORS are both configured before the FastAPI application object exists, so each read the environment directly rather than through Context, and each had its own copy of the reader. The CORS copy took the env file as a parameter so that it could be tested against a temporary file; that version is now the shared one. No behaviour changes: the precedence is the same as before, and as Context's.
simon-20
approved these changes
Sep 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses issue #88.
The new IATI Dashboard is a browser-based SPA. It authenticates via SSO and calls this API with
Authorization: Bearer <token>, from a different origin so the browser's Same-Origin Policy blocks every call unless the API explicitly permits that origin via CORS.Acceptance criteria from the issue:
Access-Control-Allow-*headersAccess-Control-Allow-OriginArchitecture / scope decisions
CORS_ALLOWED_ORIGINS_FILEis unsetContext. Starlette refuses middleware once the apphas started, and that includes the lifespan where
Contextis built so the origins must beloaded earlier.
CORSMiddlewarewith no origins would still intercept preflights, turning today's405onOPTIONSinto a400. Guarding the empty case keeps opted-out deployments byte-identical.OPTIONSis not inallow_methods, though spec lists it. It's inert: the list is checked againstAccess-Control-Request-Method, which never namesOPTIONS, because the preflight is theOPTIONSrequest. Adding or removing it changes only the advertised header string. The behaviour the requirement wants is met and tested.scheme://host[:port], lower case, no trailing slash, no path becauseCORSMiddlewarematches by exact string equality, so a trailing slash would silently never match. Malformed entries are rejected at startup and the error names the offending ones.Testing
342 passed, 1 skipped (pre-existing).
flake8,black --check,isort --check-only,mypy .andbanditall clean.
New coverage: origins-file loading and validation (shape, format, wildcards, unreadable and malformed files); preflight success for every served verb and refusal for one that isn't; headers present on authenticated requests, absent for unlisted origins, absent entirely when no
Originis sent; CORS headers on 401 and on handled 500s; the no-CORS default; and thatsrc/main.pyactually wires the middleware up.Notes for the reviewer
The third commit touches
sentry.py. Sentry and CORS each had their own copy of the environment reader. It's now shared. Duplication resulted from sequencing of the Sentry and CORS work and the need to rebase fromdevelopbefore committing CORS.