Skip to content

fix(rules): add missing rule for users/{uid}/preferences subcollection#3

Open
DDancingDeath wants to merge 1 commit into
mainfrom
fix/finance-prefs-rule
Open

fix(rules): add missing rule for users/{uid}/preferences subcollection#3
DDancingDeath wants to merge 1 commit into
mainfrom
fix/finance-prefs-rule

Conversation

@DDancingDeath

Copy link
Copy Markdown
Owner

What this fixes

Custom Finance Accounts (the Finance tab's per-user custom-accounts list) is silently broken in production for all users. The Firestore SDK throws permission-denied when loadFinanceCustomAccounts reads users/{uid}/preferences/financeAccounts; the loader catches the error and returns [], so users see no custom accounts and no UI error. Same path is also written to by saveFinanceCustomAccounts — saves silently fail too.

Root cause

Firestore Security Rules do not propagate parent match blocks to subcollections. The existing match /users/{userId} block (line 79) does not cover /users/{userId}/preferences/{prefId}, so default-deny applies.

How discovered

Found by an automated Playwright walkthrough of every top-level tab on the deployed staging mirror (https://aadhat-staging.web.app), logged in as the staging-readonly user. Console emitted Error loading custom finance accounts: FirebaseError: Missing or insufficient permissions. while every other tab loaded cleanly.

The change

Add a single subcollection match block immediately after /users/{userId}:

match /users/{userId}/preferences/{prefId} {
  allow read, write: if isSignedIn() && request.auth.uid == userId;
}

Strictly additive — does not reduce or modify access on any other path.

Required before merge — Rules Playground validation

Please test in Firebase Console → Firestore → Rules → Rules Playground:

Auth Path Op Expected
uid=A /users/A/preferences/financeAccounts get ALLOW
uid=A /users/A/preferences/financeAccounts create ALLOW
uid=A /users/B/preferences/financeAccounts get DENY
uid=A /users/B/preferences/financeAccounts create DENY
unauthenticated /users/A/preferences/financeAccounts get DENY

Deploy

firebase deploy --only firestore:rules --project aadhat-management

Post-deploy verification

Open the Finance tab in the live prod app as any user — no console error, custom accounts list loads (will likely be empty, since saves were silently failing before this fix).

Without this rule the Finance module's custom-accounts feature is
silently broken in production for ALL users.

ROOT CAUSE
----------
`www/js/firebase/firestore-service.js` reads + writes
`users/{uid}/preferences/financeAccounts` for the Finance tab's
custom-accounts list. The existing `match /users/{userId}` block
does NOT propagate to subcollections in Firestore Security Rules
syntax — each subcollection needs its own `match` block. With no
match for `preferences`, default-deny applies and the SDK throws
`Missing or insufficient permissions`. `loadFinanceCustomAccounts`
catches the error and returns `[]`, so end-users see no custom
accounts and no error.

DISCOVERY
---------
Surfaced by an automated Playwright walkthrough of every tab on the
deployed staging mirror (https://aadhat-staging.web.app) as part of
the Layer-2/Layer-3 verification work. Logged in console as
`Error loading custom finance accounts: FirebaseError: Missing or
insufficient permissions.`.

FIX
---
Add a single `match /users/{userId}/preferences/{prefId}` block
immediately after the existing `/users/{userId}` block. Allows
the owner of the parent doc (uid == userId) to read and write any
preference doc; everyone else is denied. No reduction of existing
access on any other path.

VALIDATION (Rules Playground — RUN BEFORE PUBLISH)
---------------------------------------------------
* uid=A reads  /users/A/preferences/financeAccounts → ALLOW
* uid=A writes /users/A/preferences/financeAccounts → ALLOW
* uid=A reads  /users/B/preferences/financeAccounts → DENY
* uid=A writes /users/B/preferences/financeAccounts → DENY
* unauthenticated read /users/A/preferences/X → DENY

DEPLOY
------
`firebase deploy --only firestore:rules --project aadhat-management`

VERIFICATION POST-DEPLOY
------------------------
1. Open the Finance tab in the live prod app as any user.
2. Expect no console error and any previously-saved custom accounts
   to load (likely empty for users who tried & failed silently before).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
DDancingDeath pushed a commit that referenced this pull request Jun 6, 2026
…rphan visibility

The stocks page was showing inflated quantities for items whose legacy sale
records had a slightly-different-cased name (or stale itemId) than the
canonical catalogue entry. Three independent bugs were collaborating to
hide the discrepancy:

  1. calculateStock applied stock adjustments in storage order (DESC by
     date) instead of chronological order, so a 'set' adjustment used a
     stale snapshot of stock and silently overwrote every event recorded
     after it. Refactored to a single chronological event timeline that
     replays purchases, sales and adjustments in real time order. A 'set'
     adjustment now uses adj.quantity (the user-entered target) rather
     than the stale adj.newStock snapshot.

  2. getItemKey did a case-sensitive name fallback for records without
     itemId, so a sale saved as 'Piyar Dana' produced a different bucket
     from a purchase saved as 'piyar dana'. Normalised with trim() +
     toLowerCase() and added an itemId-fallthrough so stale itemIds also
     resolve to the right bucket.

  3. renderStock then silently dropped any bucket that had no catalogue
     match (if (!item) return;). Orphans are now rendered as red/orange
     'Unmatched' warning rows so data-integrity issues are visible
     instead of hidden, and console.warn lists the orphan keys.

Also adds a debugItemForName helper (window.debugItem) that dumps the
full event timeline for a named item from the DevTools console — used
during diagnosis.

Tests: 114/114 passing (was 112) — 11 chronological-replay tests in
calculateStock.test.js cover bug fixes #1, #2 (case insensitivity for
both name and hindiName), and the on-demand sale-bucket creation that
prevents the silent-drop bug #3.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants