ali-ahnaf / ali-ahnaf/pocket_pixel
Add client-side encryption/decryption for Expense and Tag fields (zero-knowledge, frontend)
- Ngôn ngữ chính
- TypeScript
- Star
- 14
- Fork
- 91
- Chỉ số merge pull request
- Không có pull request nào được merge trong 30 ngày
Mô tả
## 🔒 Context / background
Companion to backend issue #180 — **blocked by it**, since it needs the new API contracts and endpoints for encrypted expenses/tags before any of this can be wired up. Once the backend accepts/returns `{ nonce, encryptedData }` instead of plaintext `title`/`amount` (expenses) and `name` (tags), the UI needs to do all the actual encryption and decryption — the server never sees plaintext.
## 🎯 Problem / goal
Encrypt an expense's `title`+`amount` and a tag's `name` in the browser before sending to the API, and decrypt them in the browser after fetching, so the DB/backups never contain plaintext.
## 🛠 Suggested approach
1. Add `libsodium-wrappers` to `packages/ui`.
2. Add a crypto helper module (e.g. `packages/ui/src/lib/crypto/`):
- `encryptExpenseFields(dek, { title, amount })` / `decryptExpenseFields(dek, nonce, encryptedData)` using `crypto_secretbox_easy` / `crypto_secretbox_open_easy`.
- `encryptTagName(dek, name)` / `decryptTagName(dek, nonce, encryptedData)`, same pattern.
- **Generate a fresh random nonce per encryption call** — never reuse a nonce with the same DEK.
3. Derive/unwrap the user's DEK on login (depends on whatever #180 lands for wrapped-DEK storage — likely a client-side KDF over the password, then unwrap the DEK fetched from the backend). Keep the DEK in memory only — the repo currently uses `localStorage` for `auth_token`/`pocket_pixel_profile`; do not add the DEK there.
4. Update transaction create/update (`packages/ui/src/components/LogResourceModal.tsx`) to encrypt `title`+`amount` before calling the API.
5. Update tag create/update (`packages/ui/src/components/AddTagModal.tsx`) to encrypt `name` before calling the API.
6. Update every place that renders/aggregates these fields to decrypt first:
- `packages/ui/src/app/page.tsx` (~lines 34-53 tag/vault display, ~168-177 `totalIncome`/`totalExpenses`/budget % — these become post-decrypt client reduces)
- `packages/ui/src/app/stats/page.tsx` (~lines 136-149 income/expense totals, monthly net, cumulative)
- `packages/ui/src/components/LogResourceModal.tsx` (~line 44 tag autocomplete — filter on decrypted names)
7. Tag names/expense titles are no longer server-sortable; sort/filter the decrypted list client-side after fetch (lists are small — no need for IndexedDB unless that changes later).
8. Add the one-time migration flow: on login, if the backend flags the user as having un-migrated legacy rows, fetch plaintext rows, encrypt client-side, and call the backend's migration-write endpoint (added in #180) before showing the dashboard.
## ✅ Acceptance criteria
- [ ] New expenses/tags are created via the UI with encrypted fields; no plaintext `title`/`amount`/`name` is ever sent in a create/update request body
- [ ] Dashboard (`page.tsx`) and stats (`stats/page.tsx`) correctly decrypt and render titles, amounts, and tag names
- [ ] Totals/sums/budget-progress on both pages are computed client-side after decryption
- [ ] Tag autocomplete (`LogResourceModal.tsx`) filters against decrypted names
- [ ] Existing users are walked through the one-time migration on next login without data loss
- [ ] DEK is held in memory only, never written to `localStorage`/`IndexedDB` in plaintext
- [ ] Playwright e2e coverage added/updated for create → reload → verify decrypted display
## Notes
- Depends entirely on the contracts/endpoints landing in #180 first — don't start API-call changes until those contracts exist in `packages/shared`.
- Related files: `packages/ui/src/app/page.tsx`, `packages/ui/src/app/stats/page.tsx`, `packages/ui/src/components/LogResourceModal.tsx`, `packages/ui/src/components/AddTagModal.tsx`, `packages/ui/src/hooks/useAuth.ts`, `packages/ui/src/lib/api/ApiClient.ts`.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Đánh giá
Issue này chưa được đánh giá.