alionar opened a new issue, #43550:
URL: https://github.com/apache/superset/issues/43550
### Bug description
A production incident occurred on Superset 4.0.2 when a user saved a dataset
after the cached CSRF token had expired.
The JSON API mutation returned HTTP 400 with `GENERIC_BACKEND_ERROR` and
Issue 1011. `SupersetClient` did not refresh the token or retry the request.
The user saw a generic error and had to reload the page before saving again.
Source inspection confirms that the same behavior remains in the latest
`master` branch:
1. `SupersetClient` caches and reuses the CSRF token.
2. The default token lifetime is one week.
3. Flask-WTF raises `CSRFError` after expiration.
4. JSON requests are converted into a generic HTTP 400 response.
5. `SupersetClient.request()` handles HTTP 401 but not CSRF-related HTTP 400
responses.
### Steps to reproduce
1. Configure a short CSRF lifetime:
```python
WTF_CSRF_TIME_LIMIT = 5
```
2. Restart Superset.
3. Log in and open the dataset editor.
4. Wait longer than five seconds.
5. Modify and save the dataset.
6. Inspect the browser network requests.
### Actual behavior
The save request returns HTTP 400:
```json
{
"errors": [
{
"error_type": "GENERIC_BACKEND_ERROR",
"level": "error",
"extra": {
"issue_codes": [
{
"code": 1011
}
]
}
}
]
}
```
The frontend does not request a new CSRF token or retry the save. Reloading
the page obtains a new token and allows the user to save again.
### Expected behavior
Superset should recover from an expired CSRF token without requiring a page
reload:
1. Return a machine-readable CSRF error type.
2. Fetch a new token from `/api/v1/security/csrf_token/`.
3. Retry the original request once.
4. Prevent refresh and retry loops.
5. Share one refresh request when several requests fail concurrently.
This does not bypass CSRF validation. The client obtains a new token for the
existing authenticated session after Flask-WTF rejects the original request.
### Related work
PR #14675 added login redirection for expired CSRF tokens. The redirect
applies to non-JSON requests. The JSON branch continues to return a generic
HTTP 400 response.
Issue #16565 asked which status code is returned when a token expires, but
it did not address frontend recovery.
### Environment
- Incident environment: Superset 4.0.2
- Current status: behavior confirmed by source inspection on `master`
- Browser: Chrome
- CSRF lifetime: default one week
- Runtime reproduction on `master`: not performed
### Additional context
Relevant source:
- Superset 4.0.2 handler:
https://github.com/apache/superset/blob/4.0.2/superset/views/base.py#L460-L490
- Superset 4.0.2 client:
https://github.com/apache/superset/blob/4.0.2/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts#L203-L215
- Current master handler:
https://github.com/apache/superset/blob/686245a905a60d8ea49b84afacfd6977a65cb5ed/superset/views/error_handling.py#L206-L240
- Current master client:
https://github.com/apache/superset/blob/686245a905a60d8ea49b84afacfd6977a65cb5ed/superset-frontend/packages/superset-ui-core/src/connection/SupersetClientClass.ts#L214-L240
Suggested implementation:
- Return a dedicated CSRF error type.
- Detect it in `SupersetClient`.
- Refresh through `reAuthenticate()`.
- Use a shared refresh promise.
- Retry the original request once.
- Preserve the existing non-JSON login redirect.
--
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]