Skip to content

fix(pos): keep the POS page's CSRF token valid - #146

Draft
phronesis wants to merge 1 commit into
kodlyft:developfrom
VigesimalTech:fix/pos-csrf-token
Draft

phronesis wants to merge 1 commit into
kodlyft:developfrom
VigesimalTech:fix/pos-csrf-token

Conversation

@phronesis

Copy link
Copy Markdown

What

Keeps the CSRF token on the /xpos page valid, so POS requests (including offline invoice sync) stop failing with 400 CSRFTokenError.

Why

xpos/www/xpos.py falls back to frappe.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 with 400 CSRFTokenError (Invalid Request), and queued offline invoices can no longer sync. Opening the desk (/app) in another tab is enough to trigger this, and after MAX_RETRIES the queued invoices become dead letters.

To reproduce:

  1. Log in and open /xpos in one tab. Don't open the desk first.
  2. Open /app in a second tab.
  3. Back in the POS, submit an invoice or sync an offline one. The request fails with 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, via frappe.sessions.get_csrf_token() (as the desk does). Guests get an empty token.
  • xpos/api/auth.py: new get_csrf_token, whitelisted for GET only. 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 with CSRFTokenError, fetchCall fetches the current token (refreshCsrfToken), stores it on window.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 into handleResponse.
  • Tests:
    • 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.

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant