Skip to content

fix(ui): keep upload history and mapping templates per tenant and user (#176) - #235

Merged
amar-python merged 3 commits into
mainfrom
claude/fix-176-tenant-scoped-history
Nov 10, 2026
Merged

amar-python merged 3 commits into
mainfrom
claude/fix-176-tenant-scoped-history

Conversation

@amar-python

Copy link
Copy Markdown
Owner

Fixes #176: the next person to sign in on a shared browser no longer sees the previous tenant's upload history or saved header-mapping templates.

The leak

The import-history page and the uploader's report read their list from localStorage under one global key, and the saved mapping templates did the same. Sign-out removes only the session token, so a second tenant on the same browser saw the first one's file names, rejected cell values and column names (BR-36). Reproduced in Chromium on the production build before this change: tenant B's history page listed tenant A's file.

#176 was closed as moot because the React screen is being replaced (#212), but it still ships.

Fix

  • New src/lib/user-storage.ts: userKey(base) scopes a key to the signed-in tenant and user, and returns null when nobody is signed in, so nothing is stored.
  • use-local-storage.ts (upload history) and mapping-templates.ts use it. Templates carry column names and types, which are tenant data, so they are fixed in the same change.
  • The old unscoped entries belong to an unknown tenant, so they are dropped on first load instead of being handed to whoever signs in next. Existing users lose their local history and saved templates once.

Keys are per user, not just per tenant: two users of one tenant don't see each other's history either.

Tests

  • tenant-isolation.test.tsx (vitest, 11 tests): own history and templates kept across a reload; another tenant and another user of the same tenant see nothing; a user's data comes back after others have been and gone; nothing stored when signed out; old entries dropped; delete touches only the signed-in user's template. 9 of the 11 fail on main.
  • e2e/test_react_ui.py::test_176_… (Chromium, production build): it was an expected failure and is a plain test now. With the old storage code put back it fails at the leak (expected 0 entries, found 1); with this change it passes. It first checks the history page does list the tenant's own import, so "not listed" means something.
  • mapping-templates.test.ts now signs in first, since signed-out use stores nothing.
  • vitest 186 passed, tsc clean, no new lint errors (the two files with existing formatting errors have the same or fewer). Browser suite 8 passed.
  • The browser tests now upload unique content: M5 rejects an identical file within a tenant as a duplicate.

Not changed

The session token itself is in sessionStorage (per tab), which was already fine.


Generated by Claude Code

#176)

Upload history (file names, rejected cell values) and saved mapping
templates (column names and types) were kept in localStorage under one
global key. Sign-out removes only the session token, so the next person
to sign in on the same browser saw the previous tenant's data (BR-36).

Both now use a key scoped to the signed-in tenant and user, and store
nothing when nobody is signed in. The old unscoped entries belong to an
unknown tenant, so they are dropped on first load, not handed to whoever
signs in next: existing users lose their local history and saved
templates once.

The #176 vitest and browser tests were expected failures; they are plain
tests now. The browser tests upload unique content, since M5 rejects an
identical file within a tenant as a duplicate.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqi8yaiaYWUVH8C4QAY
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqi8yaiaYWUVH8C4QAY
The accessibility stub planted history under the old global localStorage
key. History is per tenant and user now, and the old key is dropped on
load, so three scans (upload page with history, audit log, audit report
menu) found an empty page and timed out waiting for orders.csv. Plant it
under the session's own key.

Reproduced locally with the old stub (3 failed, 10 passed); all 13 pass
with this change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011T6Wqi8yaiaYWUVH8C4QAY
@amar-python
amar-python marked this pull request as ready for review November 10, 2026 07:43
@amar-python
amar-python merged commit db5c9b7 into main Nov 10, 2026
4 checks passed
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.

RV-F1 (BR-36): browser localStorage history leaks across tenants — pending #154 frontend decision

2 participants