Conversation
The /xpos page fell back to a random, unsaved token when the session had none yet. Frappe skips the check while the session has no token, so this worked until something else created the real one (opening the desk in another tab); from then on every POST, including syncing offline invoices, failed with 400 CSRFTokenError. A page served from the offline cache after the session changed had the same problem. - Embed the session's saved token (frappe.sessions.get_csrf_token). - Add GET xpos.api.auth.get_csrf_token; GET is not CSRF-checked. - On CSRFTokenError, fetch the current token and retry the call once.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Keeps the CSRF token on the
/xpospage valid, so POS requests (including offline invoice sync) stop failing with400 CSRFTokenError.Why
xpos/www/xpos.pyfalls back tofrappe.generate_hash()when the session has no CSRF token yet. That token is never saved on the session. Frappe skips the CSRF check while the session has no token, so the POS works at first. Once anything creates the session's real token, every POST from the POS fails with400 CSRFTokenError(Invalid Request), and queued offline invoices can no longer sync. Opening the desk (/app) in another tab is enough to trigger this, and afterMAX_RETRIESthe queued invoices become dead letters.To reproduce:
/xposin one tab. Don't open the desk first./appin a second tab.400 CSRFTokenError.The same failure hits a page served from the service worker's cache after the session's token has changed.
Changes
xpos/www/xpos.py: embeds the token the session keeps, viafrappe.sessions.get_csrf_token()(as the desk does). Guests get an empty token.xpos/api/auth.py: newget_csrf_token, whitelisted forGETonly. GET is not CSRF-checked, so a page holding a stale token can still fetch the current one.frontend/src/services/api.ts: when a call fails withCSRFTokenError,fetchCallfetches the current token (refreshCsrfToken), stores it onwindow.xpos, and retries the call once. A persistent failure, or a failed token fetch (e.g. logged out), still surfaces the original error. The existing error handling moved unchanged intohandleResponse.frontend/tests/csrfRetry.spec.ts(8 tests): retry with the fresh token, reuse of that token for later calls, a single retry only, no retry when the token fetch fails or for other 400 errors, and offline handling.xpos/api/tests/test_csrf_token.py(5 tests): the page embeds the saved token, guests get none, and the endpoint returns the session token, refuses guests and allows GET only.