diff --git a/FUTURE.md b/FUTURE.md
index 0f021fe..10c422c 100644
--- a/FUTURE.md
+++ b/FUTURE.md
@@ -3,7 +3,7 @@
**This document tracks potential future enhancements for Bill Tracker.**
**Last Updated:** 2026-05-10
-**Current Version:** v0.23.1
+**Current Version:** v0.23.2
## How to Use This Document
@@ -34,21 +34,8 @@ Items are grouped under their priority section heading (`## 🔴 CRITICAL`, `##
### 🔴 CRITICAL
-### 🔴 Notification Runner Leaks Bill Details Across Users — CRITICAL
-**Priority:** CRITICAL
-**Added:** 2026-05-10 by Prime (code review)
-**Type:** SECURITY
-
-**Description:**
-`services/notificationService.js` loads every active bill globally (`SELECT * FROM bills WHERE active = 1`) and then loops every notification recipient. In per-user notification mode, this can email one user's bill names, due dates, and amounts to every other opted-in user because the recipient is never matched to `bill.user_id`.
-
-**Affected Files:**
-- `services/notificationService.js:180-233`
-
-**Potential Fix:**
-Fetch bills per recipient with `WHERE user_id = ?`, or group active bills by `user_id` and only send each bill to its owner. Add a regression test with two users to prove cross-user notifications cannot occur.
-
-**Severity:** CRITICAL
+### ~~🔴 Notification Runner Leaks Bill Details Across Users — CRITICAL~~ ✅ FIXED (v0.23.2)
+**Moved to HISTORY.md**
### 🟠HIGH
@@ -305,23 +292,8 @@ Add explicit UI copy warning that exports may contain account metadata, and cons
**Severity:** LOW
-### 🔵 Duplicate Local Login Route Increases Auth Drift Risk — LOW
-**Priority:** LOW
-**Added:** 2026-05-10 by Prime (code review)
-**Type:** TECH_DEBT / SECURITY
-
-**Description:**
-Local username/password login logic exists in both `routes/auth.js` and `routes/authLogin.js`. Only `routes/auth.js` appears mounted in `server.js`, while `authLogin.js` is a near-duplicate public login handler. Duplicate auth code can silently drift on rate limiting, audit logging, CSRF exemptions, and error handling.
-
-**Affected Files:**
-- `routes/auth.js:17-44`
-- `routes/authLogin.js:1-39`
-- `server.js:73-75`
-
-**Potential Fix:**
-Delete the unused route module or refactor both route registrations to call one shared login handler. Add a startup/router inventory test so orphan auth routes do not linger.
-
-**Severity:** LOW
+### ~~🔵 Duplicate Local Login Route Increases Auth Drift Risk — LOW~~ ✅ FIXED (v0.23.2)
+**Moved to HISTORY.md**
### Add comprehensive unit and integration tests
diff --git a/HISTORY.md b/HISTORY.md
index dc36717..52bec73 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,19 @@
# Bill Tracker — Changelog
+## v0.23.2
+
+### Security
+- **CRITICAL: Notification privacy leak fix** — In per-user notification mode, bills were sent to all opted-in recipients regardless of ownership. Added ownership filter (`bill.user_id !== recipient.id`) and orphaned bill guard. Security audit by Private_Hudson confirmed the fix is airtight.
+- **Duplicate login route removed** — Deleted `routes/authLogin.js`, consolidating login logic into `routes/auth.js` only.
+
+### Changed
+- `services/notificationService.js`: Added per-user ownership filter and null `user_id` guard in notification runner
+- `routes/authLogin.js`: Removed (consolidated into `routes/auth.js`)
+- `docs/Engineering_Reference_Manual.md`: Removed stale `authLogin.js` duplicate route note, updated version to 0.23.2
+- `README.md`: Updated to reflect current features, env vars, security notes, project structure, and known limitations
+
+---
+
## v0.23.1
### Added
diff --git a/README.md b/README.md
index 4ae339f..b853fc2 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,14 @@
# BillTracker
-
-
+
+
BillTracker is a self-hosted app for tracking recurring bills, monthly payments, due dates, categories, and personal bill history. It runs as a Node/Express server with a React frontend and stores data in SQLite. This product was produced with the assistance of AI.
-Demo Server
-https://t1.scheller.ltd/
-
-Username: guest
-
-Password: guest123
+Demo Server: https://t1.scheller.ltd/
+Username: guest · Password: guest123
## Screenshots
@@ -27,15 +23,16 @@ Password: guest123
## What Is BillTracker?
-
BillTracker helps a household or small self-hosted setup keep bill data in one place:
-- recurring bill records with due day, expected amount, category, notes, autopay details, and optional APR
+- recurring bill records with due day, expected amount, category, notes, autopay details, optional APR, and flexible billing cycles (monthly, weekly, biweekly, quarterly, annual)
+- bill history ranges for tracking which months a bill was active
- monthly tracker with payments, skipped bills, actual monthly amounts, and notes
+- monthly income tracking and starting cash amounts (1st/15th/other)
- calendar view for due dates and payments
- analytics for monthly spending, expected vs actual totals, category spend, and payment history
- categories, profile, display name, notification preferences, password changes, and data tools
-- admin user management, authentication settings, backups, cleanup, and status checks
+- admin user management, authentication settings, auth-mode/OIDC configuration, backups, scheduled backups, cleanup, migration rollback, audit logging, and status checks
## Features
@@ -46,11 +43,12 @@ BillTracker helps a household or small self-hosted setup keep bill data in one p
- User-owned categories
- Settings for theme, currency, date format, and grace period
- Profile page with display name, notification preferences, password change, imports, exports, and import history
-- User exports to Excel workbook or BillTracker user SQLite export
+- User exports to Excel workbook or BillTracker user SQLite export (note: exports currently omit `cycle_type`, `cycle_day`, and `bill_history_ranges`)
- XLSX spreadsheet import with preview and import decisions
- User SQLite import from exports created by this app
-- Admin users, role management, password resets, full database backups/restores, scheduled backups, cleanup, auth settings, and status page
+- Admin users, role management, password resets, full database backups/restores, scheduled backups, cleanup, auth-mode/OIDC configuration, migration rollback, audit logging, and status page
- Local username/password login and optional authentik/OIDC login
+- CSRF protection using double-submit cookie pattern with per-request nonces
## Quick Start
@@ -107,7 +105,16 @@ INIT_ADMIN_USER=admin
INIT_ADMIN_PASS=change-this-password
```
-Remove or change those first-run values after the initial admin account exists.
+To also seed a regular (non-admin) user:
+
+```bash
+INIT_REGULAR_USER=regularuser
+INIT_REGULAR_PASS=changeme123
+```
+
+The regular user password must be at least 8 characters. Seeded users skip the first-login and password-change gates.
+
+Remove or change those first-run values after the initial accounts exist.
## Configuration
@@ -122,9 +129,16 @@ DB_PATH=/path/to/bills.db
BACKUP_PATH=/path/to/backups
INIT_ADMIN_USER=admin
INIT_ADMIN_PASS=change-this-password
+INIT_REGULAR_USER=regularuser
+INIT_REGULAR_PASS=changeme123
+SESSION_CLEANUP_INTERVAL_MS=86400000
HTTPS=true
COOKIE_SECURE=true
CORS_ORIGIN=https://bills.example.com
+CSRF_HTTP_ONLY=true
+CSRF_SAME_SITE=strict
+CSRF_SECURE=true
+CSRF_COOKIE_NAME=bt_csrf_token
```
OIDC environment fallback variables are supported when the matching Admin database setting is blank:
@@ -158,7 +172,9 @@ Optional authentik/OIDC login can be enabled in Admin. OIDC uses authorization c
Admin role is never granted by default through OIDC. Set an authentik admin group in BillTracker; only users whose OIDC `groups` claim includes that configured group become app admins.
-BillTracker includes lockout checks so local login cannot be disabled unless OIDC is configured, enabled, and mapped to an admin group.
+BillTracker enforces lockout checks: local login cannot be disabled unless OIDC is configured, enabled, and mapped to an admin group. This prevents accidental lockout where no login method is available.
+
+The default admin account (created by `INIT_ADMIN_USER`/`INIT_ADMIN_PASS`) is restricted to admin routes only. It cannot access user tracker routes (bills, payments, calendar, etc.). Regular users and promoted admins have full access.
## authentik Setup
@@ -217,11 +233,15 @@ Backups and exports contain sensitive financial data. The code writes SQLite bac
- Auth is required for user data routes.
- Admin routes require an admin session.
-- User-owned bill, category, payment, import, and export routes derive ownership from the authenticated session.
+- The default admin account cannot access user tracker routes.
+- User-owned bill, category, payment, import, and export routes derive ownership from the authenticated session (`req.user.id` in SQL).
+- CSRF protection uses a double-submit cookie pattern: a `bt_csrf_token` cookie is set on responses, and mutating requests must include a matching `x-csrf-token` header. Defaults are `httpOnly`, `sameSite=strict`, and `secure` (overridable via env vars).
- Local login, password change, import, export, admin actions, and OIDC routes have per-IP in-memory rate limits.
- CORS is disabled unless `CORS_ORIGIN` is set.
-- Baseline security headers are sent; HSTS is sent only when `HTTPS=true`.
+- Security headers include Content-Security-Policy with per-request nonces, plus standard hardening headers. HSTS is sent only when `HTTPS=true`.
- Session cookies are `httpOnly`, `sameSite=strict`, and marked secure when `COOKIE_SECURE=true`, `HTTPS=true`, or the request appears to be HTTPS.
+- Password changes rotate the current session ID and invalidate all other sessions.
+- Audit logging records security-sensitive events: login, logout, password changes, role changes, CSRF failures, and migration operations.
- OIDC validation is handled through `openid-client` using discovered provider metadata and JWKS.
- Protect database files, backups, and exports as sensitive financial records.
@@ -236,15 +256,15 @@ Set `CORS_ORIGIN` only when the frontend and backend are served from different o
```text
client/ React app, pages, layout, UI components
db/ SQLite connection, schema, startup migrations
-middleware/ auth checks, rate limits, security headers
+middleware/ auth checks, CSRF, rate limits, security headers
routes/ Express API routes
-services/ auth, OIDC, backups, imports, cleanup, status, notifications
-workers/ daily background tasks
-setup/ first-run admin setup
-scripts/ migrations and smoke/import tests
-public/ legacy static assets
-img/ app/runtime images and source screenshots
-docs/images/ README images
+services/ auth, OIDC, backups, scheduler, imports, cleanup, status, notifications, audit
+workers/ daily background tasks (notifications, cleanup)
+setup/ first-run admin setup
+scripts/ DB migrations, seed data, and smoke/import tests
+public/ legacy static assets
+img/ app-runtime images and source screenshots
+docs/ Engineering Reference Manual, CSRF guide, Authentik integration
```
## Upgrading
@@ -266,9 +286,9 @@ For Docker, pull/rebuild the image, recreate the container, and keep the `/data`
- Admin backups and user exports are not encrypted by the app.
- OIDC single logout is not implemented.
-- Content-Security-Policy is intentionally deferred.
+- User exports (Excel and SQLite) currently omit `cycle_type`, `cycle_day`, and `bill_history_ranges` data.
- Rate limiting is in-memory, so counters reset on restart and are not shared across multiple app instances.
-- authentik live login must be tested in your deployment with your authentik provider.
+- Authentik live login must be tested in your deployment with your authentik provider.
- The XLSX parser dependency has known upstream security advisories; the import route is authenticated, file-size limited, and parses cells as data.
## License
diff --git a/docs/Engineering_Reference_Manual.md b/docs/Engineering_Reference_Manual.md
index cba0999..529bc09 100644
--- a/docs/Engineering_Reference_Manual.md
+++ b/docs/Engineering_Reference_Manual.md
@@ -2,7 +2,7 @@
**Status:** Current code reference
**Last Updated:** 2026-05-10
-**Version:** 0.23.1
+**Version:** 0.23.2
**Primary stack:** Node.js + Express, React + Vite, SQLite via `better-sqlite3`
This manual reflects the current application code in `server.js`, `routes/`, `services/`, `middleware/`, `db/`, `client/`, `package.json`, `Dockerfile`, and `docker-compose.yml`. It is written as a current-state reference, not a changelog.
@@ -236,7 +236,6 @@ Mounted under `/api/auth`.
- Body: `{username, password}`.
- Validation: both required; local login must be enabled.
- Response: sets `bt_session`; `{user}`.
- - Errors: 401 invalid credentials, 403 disabled login.
- `POST /auth/logout`
- Auth: required.
@@ -285,8 +284,6 @@ Mounted under `/api/auth`.
- Validation: username min 3, password min 8, unique username.
- Response: created safe user.
-Server also mounts `routes/authLogin.js` at `/api/auth/login`; that router defines `POST /login`, creating an effective compatibility path `/api/auth/login/login` with the same local-login behavior. The frontend uses `/api/auth/login`.
-
### 5.2 OIDC Auth
Mounted under `/api/auth/oidc`; OIDC rate limiter applies.
@@ -1151,7 +1148,7 @@ These use TanStack Query keys and cache server data for common pages.
### `package.json`
-Version: `0.23.1`.
+Version: `0.23.2`.
Scripts: