Dhruv-kys opened a new pull request, #70797:
URL: https://github.com/apache/airflow/pull/70797
FAB's Security pages generated Add/Edit/Show links without the `/auth`
blueprint
prefix when Airflow runs behind a reverse proxy on a URL subpath.
closes: #70389
## Problem
With `[fab] enable_proxy_fix = True` and the proxy sending
`X-Forwarded-Prefix`,
links on `/auth/roles/list/` and `/auth/users/list/` point at
`/<subpath>/roles/add` instead of `/<subpath>/auth/roles/add`. The broken
link
resolves to the React SPA shell, which has no client-side route for
`roles`/`users` and redirects to the Dashboard — so the buttons appear to do
nothing, with no visible error.
## Cause
The Flask app is mounted at `/` inside a FastAPI sub-app
(`fab_auth_manager.py:256`), which is itself mounted at `/auth`
(`api_fastapi/app.py:206`), so Starlette passes `SCRIPT_NAME=/auth` into the
WSGI
environ. Werkzeug's `ProxyFix` then *assigns* `X-Forwarded-Prefix` to
`SCRIPT_NAME` rather than prepending to it:
```python
if x_prefix:
environ["SCRIPT_NAME"] = x_prefix
```
That discards the mount path. Since `url_for` builds every URL as
`SCRIPT_NAME + rule`, all FAB-generated links lose `/auth`. The list pages
themselves keep the prefix because those URLs come from the React shell's
configuration rather than from `url_for`, which is why the bug looks
selective.
## Fix
Wrap `ProxyFix` in a pair of small WSGI middlewares: the outer one stashes
the
mount path and clears `SCRIPT_NAME` so `ProxyFix` sees an empty slot, and the
inner one appends the mount path back once the forwarded prefix has been
applied. The result is always `forwarded_prefix + mount_path`.
An earlier approach re-appended the mount path only when `SCRIPT_NAME`
differed
from its original value. That is wrong when the proxy subpath happens to
equal
the mount path (`X-Forwarded-Prefix: /auth`): the two values match, the guard
skips, and the prefix is silently dropped. The stash/restore pair needs no
such
comparison; that case is covered by the
`subpath-proxy-prefix-matching-mount-path` test.
## Testing
New
`providers/fab/tests/unit/fab/www/extensions/test_init_wsgi_middlewares.py`
covers mounted/unmounted x forwarded-prefix/no-prefix, the equal-prefix edge
case, and proxy-fix disabled. Two cases fail without the fix
(`mounted-behind-subpath-proxy` and
`subpath-proxy-prefix-matching-mount-path`);
the other four pass either way and act as controls against a fix that
over-applies the prefix.
```
uv run --project providers/fab pytest providers/fab/tests/unit/fab/www/
# 67 passed
uv run --project airflow-core pytest
airflow-core/tests/unit/always/test_project_structure.py
# 10 passed, 1 xfailed
```
This also removes the module from the `OVERLOOKED_TESTS` allow-list in
`airflow-core/tests/unit/always/test_project_structure.py`, which the
project is
driving to zero.
---
##### Was generative AI tooling used to co-author this PR?
- [X] Yes — Claude Code (Opus 5)
Generated-by: Claude Code (Opus 5) following [the
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]