fix(providers): clearer toast on 403 when removing a provider key#2949
fix(providers): clearer toast on 403 when removing a provider key#2949Sanjays2402 wants to merge 2 commits into
Conversation
The Remove button under Settings -> Providers calls POST /api/providers/delete, which runs through _check_csrf. When the CSRF cookie/header pair has drifted (typically a tab opened before the most recent login or cookie rotation), the server returns 403 with the string 'Cross-origin request rejected'. That string reads like a reverse-proxy deployment problem and gives the user no next step (nesquena#2572). Surface a recovery-shaped toast on 403 from this endpoint: 'Session expired. Reload the page and try again.' The underlying server response is unchanged so logs/diagnostics still see the original string; only the user-facing toast is replaced for this code path. Verified locally by patching _check_csrf to return False, clicking Remove on a provider card, and confirming the toast now reads the new message instead of the raw cross-origin string. Refs nesquena#2572
SummaryThanks @Sanjays2402 — picking the small JS-only slice off #2572 is exactly the right scope, and the catch shape (status check + fallback to the existing There is one concern: the server already distinguishes three CSRF rejection reasons, and a uniform "Session expired" toast masks the more useful one. Code reference
def _csrf_rejection_error(handler) -> str:
reason = getattr(handler, _CSRF_FAILURE_ATTR, "")
if reason == "origin_mismatch":
return "Cross-origin mismatch - check reverse proxy headers"
if reason == "token_mismatch":
return "Session expired - reload the page"
return "Cross-origin request rejected"That string is what if not _csrf_exempt_path(parsed.path) and not _check_csrf(handler):
return j(handler, {"error": _csrf_rejection_error(handler)}, status=403)And let message=text;
try{const j=JSON.parse(text);message=j.error||j.message||text;}catch(e){}
const err=new Error(message);
err.status=res.status;So in the
Diagnosis / RecommendationUse the server's own message rather than overwriting it. The whole point of the three-way distinction landing in }catch(e){
// /api/providers/delete returns a CSRF-shaped 403 with the actual reason
// already in the error message (see api/routes.py:_csrf_rejection_error:
// "Session expired", "Cross-origin mismatch — check reverse proxy headers",
// or the fallback). Pass it through so deployment-shape failures keep
// their actionable hint (#2572).
if(e&&e.status===403){
showToast(e.message||'Session expired. Reload the page and try again.',6000,'error');
}else{
showToast('Error: '+e.message);
}
if(els.saveBtn){els.saveBtn.disabled=false;els.saveBtn.textContent=t('providers_save');}
}That keeps the recovery-shaped toast for the legitimately-stale-tab case ( Optional polish: since Test planAfter the fix, three cases (no server changes needed):
The non-403 path (server 500, network error) should still hit CHANGELOG entry can stay the same; the user-visible improvement is real, just don't claim a single substituted string. Maybe reword to "Removing a provider key now surfaces the server's specific CSRF rejection reason (session expired, origin mismatch, etc.) instead of a generic raw string..." |
…essage (review feedback from @nesquena-hermes)
|
Right call, fixed in 0211e1e. Now passes |
Picks up one of the small fix candidates from #2572: when the Remove click in Settings → Providers hits a 403 (CSRF cookie/header drift, classic stale-tab signature), show a recovery-shaped toast instead of the raw 'Cross-origin request rejected' string. The raw string reads like a reverse-proxy deployment bug and leaves the user nowhere to go.
This is the JS-only sub-fix the issue calls out as not needing to be combined with the deeper investigation; it doesn't change the server response, doesn't paper over the origin-mismatch deployment case (any non-403 error still falls through to the existing 'Error: …' toast carrying the server message), and doesn't touch the CSRF code itself.
Verified locally by patching
_check_csrfto return False, clicking Remove on a provider card, and confirming the toast now reads 'Session expired. Reload the page and try again.' Reverted the patch and confirmed the success path still removes the key and refreshes the dropdown caches.Refs #2572