This is a combination API backend for workspaces, providing /workspaces* methods, as well as a proxy to the OSM API ("openstreetmap website") plus OSM CGI-map (the C-accelerated methods) that enforces authorization and authentication based on a TDEI/Keycloak JWT token (see main.py for this proxy logic).
This backend is the only entry point to the OSM tier (osm-rails + cgimap,
behind osm-web): in the deployment, the public OSM host routes to this
container, which proxies /api/0.6/* to WS_OSM_HOST (default
http://osm-web). For the OSM services to work, the proxy must uphold the
following contract. CLAUDE.md has the full rationale.
-
Bridge TDEI auth into OSM's OAuth2. osm-rails authenticates the API only via doorkeeper OAuth2 (
oauth_access_tokens); it has no TDEI/JWT path. So on token validation the backend mirrors the TDEI JWT intooauth_access_tokensin the OSM DB (the "token bridge" inapi/core/security.py), and forwards the incomingAuthorization: Bearer <token>header unchanged. Then osm-rails and cgimap authenticate the token via plain OAuth2. Controlled byWS_OSM_TOKEN_BRIDGE_ENABLED(on by default) — with it off, osm-rails returns 401 for TDEI tokens. The backend auto-creates the doorkeeper application (and a system user to own it), so no manual OSM setup is required. -
Provision valid OSM
usersrows. The backend creates OSMusersrows for TDEI users (auth_provider='TDEI',auth_uid= the JWTsub). These must satisfy OSM'sUservalidations — in particularpass_cryptlength 8..255. A too-short value is invisible to cgimap but makes osm-rails operations that re-validate the user fail (e.g. posting a changeset comment or a note), surfacing as "Unable to serialize … without an id". Thealembic_osmmigration*_heal_short_tdei_pass_cryptrepairs legacy rows. -
Carry workspace tenancy. Workspace-scoped OSM requests must include an
X-Workspace: <id>header. The proxy authorizes it against the caller's workspaces and forwards it (it is not stripped) so cgimap/osm-rails scope to theworkspace-<id>schema. A few paths are exempt (TENANT_BYPASSESinapi/main.py): workspace create/delete (PUT/DELETE /api/0.6/workspaces/{id}) and user provisioning during sign-in (PUT /api/0.6/user/{uid}). -
Set the proxy headers. The proxy rewrites
Hostto the OSM host and setsX-Real-IP/X-Forwarded-For/-Host/-Proto, while stripping hop-by-hop headers and any spoofed forwarding headers from the client. It does not stripAuthorizationorX-Workspace. -
Connectivity.
WS_OSM_HOSTmust reachosm-web, and the backend needs bothOSM_DATABASE_URLandTASK_DATABASE_URL— the token bridge and user provisioning write to the OSM database.
developmerge your work here; keep this up to date with the "development" environment / dev tagstagingkeep this up to date with the "staging" environment / stage tagproductionkeep this up to date with the "production" environment / prod tag
cp .env.example .env # edit this file for your config
uv sync
uv run uvicorn api.main:app
Tests are fast and require no database, Docker, or network (see
tests/README.md for the design, and CLAUDE.md for conventions).
uv run pytest # full suite with coverage (configured in pyproject.toml)
uv run pytest --no-cov -q # quick run, no coverage
uv run pytest tests/unit # unit tests only
uv run pytest tests/integration # integration tests only
uv run pytest -k workspaces # filter by keyword
Type-check and format (matches the pre-commit hooks):
uvx pyright --pythonpath .venv/bin/python api tests
uv run black api tests && uv run isort api tests