saurabhdamle11 opened a new pull request, #43655:
URL: https://github.com/apache/superset/pull/43655
### SUMMARY
Fixes #43550.
When a cached CSRF token expired, JSON API mutations failed with an HTTP 400
`GENERIC_BACKEND_ERROR` (issue code 1011). `SupersetClient` handled HTTP 401
but
not CSRF-related 400s, so it neither refreshed the token nor retried the
request.
The user saw a generic error and had to reload the page before the action
(e.g.
saving a dataset) would succeed.
This change lets Superset recover from an expired CSRF token in JSON requests
without a page reload:
- `views/error_handling.py` now returns a dedicated, machine-readable
`CSRF_ERROR` type for JSON requests instead of a generic backend error. The
existing non-JSON login redirect (from #14675) is unchanged. `CSRF_ERROR`
is
added to `errors.py` and to `SAFE_ERROR_TYPES` in `error_sanitization.py`
so
the type survives guest-user sanitization.
- `SupersetClientClass` detects that error type, fetches a fresh token from
`/api/v1/security/csrf_token/`, and retries the original request once.
- A shared refresh promise ensures that when several requests fail
concurrently, only one token refresh is issued rather than one per request.
Retry is capped at a single attempt, so a request that fails again cannot
trigger a refresh/retry loop.
- Added a `CsrfErrorMessage` component, registered in `setupErrorMessages`,
for
the case where recovery is not possible — the replay was rejected too, most
often because the session itself is gone.
**Why a new `CSRF_ERROR` rather than the existing `FRONTEND_CSRF_ERROR`:**
`FRONTEND_CSRF_ERROR` is a client-originated marker and is never emitted by
the
backend. This is a server-emitted rejection from Flask-WTF, so it needs its
own
type; reusing the frontend one would conflate two different origins.
**Security note:** this does not bypass or weaken CSRF validation. The
refresh
only runs *after* Flask-WTF has already rejected the request, and only when
the
server explicitly identifies the failure as `CSRF_ERROR`. The new token is
issued against the existing authenticated session, the refresh runs in the
same-origin application context (a cross-site attacker cannot execute it or
read
the response), and the single-retry cap means a genuinely forged request
fails
after one replay rather than looping.
### BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF
N/A. The recovery is transparent; the visible change is the absence of an
error. The one new visual surface is `CsrfErrorMessage`, shown only when the
token refresh itself fails.
### TESTING INSTRUCTIONS
1. Set a short CSRF lifetime in `superset_config.py`:
```python
WTF_CSRF_TIME_LIMIT = 5
2. Restart Superset, log in, and open the dataset editor.
3. Wait longer than 5 seconds, then modify and save the dataset.
4. Confirm the save succeeds without a page reload. In the Network tab,
verify a
request to /api/v1/security/csrf_token/ is issued and the original save
request is retried once and succeeds.
5. Trigger several mutations concurrently after expiry and confirm only one
token refresh request is issued.
6. Confirm non-JSON requests still redirect to login on CSRF expiry
(unchanged).
7. Run the added tests:
pytest tests/unit_tests/views/test_error_handling.py
cd superset-frontend
npm run test --
packages/superset-ui-core/test/connection/SupersetClientCsrfRecovery.test.ts
npm run test -- src/components/ErrorMessage/CsrfErrorMessage.test.tsx
ADDITIONAL INFORMATION
- [x] Has associated issue: #43550
- [ ] Required feature flags:
- [x] Changes UI
- [ ] Includes DB Migration (follow approval process in SIP-59)
- [ ] Migration is atomic, supports rollback & is backwards-compatible
- [ ] Confirm DB migration upgrade and downgrade tested
- [ ] Runtime estimates and downtime expectations provided
- [ ] Introduces new feature or API
- [ ] Removes existing feature or API
Note for reviewers: this adds a public refreshCSRFToken() method on
SupersetClientClass and a new CSRF_ERROR member to the exported
SupersetApiErrorType / ErrorTypeEnum in @superset-ui/core. Both are
additive and backwards-compatible, but they are consumable surface for
plugins.
--
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]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]