diff --git a/HISTORY.md b/HISTORY.md index ba87ef5..d400c7b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ ### πŸ› QA Fixes +- **[Privacy] The version check is now opt-out-able** β€” the privacy policy described the external version check as "optional", but there was no way to disable it (it hit a hardcoded upstream host whenever the About/Status/version page loaded). Added an **admin toggle**: an `update_check_enabled` setting gates the request in `services/updateCheckService.js` (default on β€” when off, **no external request is made**), exposed via `GET`/`PUT /api/about-admin/update-check-setting` and a switch on the admin **System Status** page. Privacy policy updated to state an admin can disable it. Test: `tests/updateCheckOptOut.test.js`. (was QA-B16-01) - **[Security] Bill name could inject HTML into reminder emails** β€” `buildEmailHtml` (`services/notificationService.js`) escaped the bill name in the detail table but interpolated it **raw** into the reminder message line (`${bill.name} is due…`), so a bill named `` landed unescaped in the email HTML. Self-XSS (reminder emails go to the bill's owner), but a clear inconsistent-escaping bug β€” now escaped everywhere. Covered by `tests/notificationDelivery.test.js`. (was QA-B14-04) - **[Notifications] "Send test push" was completely broken** β€” `services/notificationService.js` attached its `_push` export (the ntfy/Gotify/Discord/Telegram helpers) *before* the final `module.exports = {…}`, which clobbered it, so `require('…/notificationService')._push` was `undefined`. `routes/notifications.js` (`const { sendTestPush } = require(…)._push || {}`) therefore always hit `throw 'Push service not initialised'` β†’ **`POST /api/notifications/test-push` always returned 500** for every user testing their push channel. Scheduled reminders were unaffected (they call `sendPushToUser` in-scope). Moved the `_push` assignment after the reassignment. Covered by `tests/notificationDelivery.test.js` (per-channel payloads, dispatch, error handling, and a check that the auth token never leaks into the message body). (was QA-B10-01) - **[Summary/Analytics] Non-monthly bills were counted in every month** β€” the Summary expense list/total and the Analytics "expected vs actual" line both counted annual (and off-month quarterly) bills for months they weren't due, over-stating the obligation and disagreeing with the Tracker (e.g. a yearly insurance bill inflated every month). Both `routes/summary.js` and `services/analyticsService.js` now gate bills by `resolveDueDate` β€” the same occurrence check the Tracker uses. Guarded by Tracker↔Summary and Tracker↔Analytics reconciliation checks in `e2e/api.probe.spec.js`. The SimpleFIN bank-tracking `unpaid_this_month` metric had the same gap and is fixed the same way (fetch + JS `resolveDueDate` filter, since SQL can't call it), covered by `tests/summaryBankTracking.test.js`. (was QA-B5-01, QA-B5-02, QA-B5-03) diff --git a/client/api.js b/client/api.js index 81b2be2..091eb17 100644 --- a/client/api.js +++ b/client/api.js @@ -344,6 +344,8 @@ export const api = { roadmap: (refresh = false) => get(`/about-admin/roadmap${refresh ? '?refresh=1' : ''}`), updateStatus: () => get('/version/update-status'), checkForUpdates: () => post('/about-admin/check-updates'), + getUpdateCheckSetting: () => get('/about-admin/update-check-setting'), + setUpdateCheckSetting: (enabled) => put('/about-admin/update-check-setting', { enabled }), devLog: () => get('/about-admin/dev-log'), version: () => get('/version'), releaseHistory: () => get('/version/history'), diff --git a/client/pages/StatusPage.jsx b/client/pages/StatusPage.jsx index 8ce7814..018bc52 100644 --- a/client/pages/StatusPage.jsx +++ b/client/pages/StatusPage.jsx @@ -9,6 +9,7 @@ import { toast } from 'sonner'; import { api } from '@/api'; import { cn, fmtUptime, fmtBytes } from '@/lib/utils'; import { Button } from '@/components/ui/button'; +import { Switch } from '@/components/ui/switch'; import { MarkdownText } from '@/components/MarkdownText'; // ─── Helpers ────────────────────────────────────────────────────────────────── @@ -128,7 +129,7 @@ function SkeletonCard() { const CATEGORY_ORDER = ['Added', 'Changed', 'Fixed', 'Removed', 'Deprecated', 'Security']; -function UpdateCard({ update, onCheckNow, checking }) { +function UpdateCard({ update, onCheckNow, checking, enabled = true, onToggle }) { const hasUpdate = !!update.has_update; const isKnown = update.up_to_date !== null && update.up_to_date !== undefined; const hasError = !!update.error; @@ -173,8 +174,15 @@ function UpdateCard({ update, onCheckNow, checking }) { {update.error && (

{update.error}

)} +
+ + Automatic version check + External request; off = no phone-home + + +
-