This commit is contained in:
kaspa 2026-05-04 13:38:19 -05:00
parent cb0e119941
commit 0c7824c52f
11 changed files with 172 additions and 514 deletions

View File

@ -1,5 +1,11 @@
# Bill Tracker — Changelog # Bill Tracker — Changelog
## Unreleased
### Changed
- Rewrote README.md as a simpler self-hosting guide based on the implemented app, setup, auth, data, and security behavior.
- Updated Admin authentik/OIDC issuer help text to show the authentik discovery URL example and clarify that issuer base or full discovery URL can be used.
## v0.18.1 ## v0.18.1
### Changed ### Changed

672
README.md
View File

@ -1,434 +1,44 @@
# Bill Tracker # BillTracker
Bill Tracker is a self-hosted month-to-month bill tracking app for managing recurring bills, payments, monthly bill history, user-owned financial data, imports/exports, backups, and admin operations. ![BillTracker logo](docs/images/logo_cut.png)
The app is built as a full-stack JavaScript project with a React/Vite frontend, Node/Express backend, and SQLite database. 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.
--- ## Screenshots
![Tracker screenshot](docs/images/tracker.png)
![Analytics screenshot](docs/images/Analytics.png)
![Calendar screenshot](docs/images/Calendar.png)
## 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
- monthly tracker with payments, skipped bills, actual monthly amounts, and notes
- 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
## Features ## Features
### Monthly Bill Tracking - Tracker for month-by-month bill status, payment entry, notes, skipped bills, overdue totals, and month navigation
- Bills page for creating, editing, deactivating, reactivating, deleting, and controlling inactive bill history
- Track bills by selected month and year - Calendar page with a monthly grid, bill due dates, payments, and progress summary
- View active bills for the current month - Analytics page with date range, category/bill filters, charts, heatmap, and print output
- Record payments
- Track actual monthly bill amounts
- Add month-specific notes
- Mark bills as skipped for a specific month
- Separate global bill templates from monthly bill state
### Bills
- Create and edit recurring bill templates
- Bill due day is stored as a day of the month, such as `1`, `15`, or `31`
- Supports month-end clamping for due days like `31`
- Optional credit card interest/APR field
- Active/inactive bill support
- Safe deactivate/reactivate flow
- Destructive delete flow with confirmation
- Historical visibility metadata and range support for inactive bills
### Payments
- Add, update, delete, and restore payments
- Payments are scoped to the signed-in user
- Payment data is included in tracker totals and exports
- Payment history remains separate from global bill templates
### Categories
- User-owned categories - User-owned categories
- Categories can be associated with bills - Settings for theme, currency, date format, and grace period
- Category bill counts are supported - Profile page with display name, notification preferences, password change, imports, exports, and import history
- Categories are scoped to the signed-in user - User exports to Excel workbook or BillTracker user SQLite export
- XLSX spreadsheet import with preview and import decisions
### Monthly Bill State - 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
Each bill can have month-specific state: - Local username/password login and optional authentik/OIDC login
- `actual_amount` ## Quick Start
- `notes`
- `is_skipped`
- year/month association
This allows one bill template to have separate history for every month.
---
## User Accounts and Profile
Bill Tracker supports local username/password accounts and optional authentik/OIDC login.
Profile features include:
- Display name
- Notification preferences
- Password change
- User data exports
- User SQLite import
- Spreadsheet import history
The display name is shown in the top navigation where available. If no display name is set, the app falls back to the username.
---
## Authentication
### Local Login
Local username/password login is supported and remains available by default.
Local users can:
- log in with username/password
- change their password
- complete first-login password reset flows
- use all normal user-owned bill tracking features
### authentik / OIDC Login
The backend is prepared for authentik using OpenID Connect.
Supported OIDC behavior:
- Authorization Code flow
- PKCE
- state validation
- nonce validation
- JWKS-backed ID token signature verification via `openid-client`
- issuer validation
- audience validation
- expiration validation
- authentik group-to-role mapping
- auto-provisioning of valid authentik users
- local session creation after successful OIDC login
Admin role is never granted by default. A user becomes admin through the configured authentik admin group only.
### Admin-Controlled Login Methods
Admins can configure login methods from the Admin panel:
- Enable/disable local username/password login
- Enable/disable authentik/OIDC login
- Configure authentik issuer URL
- Configure client ID
- Configure client secret
- Configure redirect URI
- Configure scopes
- Configure admin group
- Configure auto-provisioning
Lockout protection prevents disabling local login unless authentik is configured, enabled, and has an admin group mapping.
---
## Data Ownership
Bill Tracker uses user-owned data separation.
The following data is scoped to the signed-in user:
- Bills
- Categories
- Payments
- Monthly bill state
- Imports
- Exports
- Tracker views
Users cannot select another `user_id` from the client. Backend routes derive ownership from the authenticated session.
Admin full database backup/restore is separate from user-owned import/export.
---
## Import and Export
### XLSX Spreadsheet Import
Bill Tracker supports importing historical bill data from Google Sheets `.xlsx` exports.
The XLSX import flow includes:
- Upload spreadsheet
- Preview before applying
- Multi-sheet parsing
- Month/year detection from sheet names
- Bill match recommendations
- Match existing bill
- Create new bill
- Skip row
- Bulk skip
- Bulk create new bills
- Ambiguous row blocking
- Import history
Supported sheet/tab patterns include examples like:
- `Jan 2026`
- `January 2026`
- `2026-01`
- `01-2026`
- `2026 May`
- `Bills May 2026`
- `May` with default year
Non-data sheets like `Summary`, `Totals`, `Dashboard`, `Notes`, `Categories`, `Settings`, `Overview`, and `Template` are skipped or treated as non-month sheets.
### User SQLite Export
Users can export their own data as a SQLite database.
The user export includes only that users safe bill-tracker data, such as:
- Bills
- Categories
- Payments
- Monthly bill state
- Notes
- Export metadata
It does not include:
- Password hashes
- Sessions
- Cookies
- Admin settings
- SMTP credentials
- Backup files
- Server paths
- Other users data
### User SQLite Import
Users can import a SQLite export created by this app.
The user SQLite import flow includes:
- Upload user SQLite export
- Validate SQLite file
- Confirm export format is `user_data`
- Preview before apply
- Apply only after confirmation
- Create missing records
- Skip conflicts by default
- No overwrite by default
- Import history recording
This is not a full-system restore and does not use the admin backup import path.
### Excel Databook Export
Users can export an Excel workbook containing their own bill-tracker data for records and review.
---
## Admin Tools
Admin users have access to an Admin area while still keeping access to the normal user app.
Admin tools include:
### User/Admin Management
- Admin-only routes
- Admin-only navigation
- Normal app navigation remains available for admins
### Authentication Methods
Admins can configure:
- Local login enabled/disabled
- authentik/OIDC login enabled/disabled
- authentik provider settings
- auto-provisioning
- admin group mapping
### Database Backups
Admin full database tools are separate from user data tools.
Admin backup features include:
- Manual database backup
- Backup listing
- Backup download
- Backup restore
- Pre-restore backup creation
- SQLite integrity checks
- Path traversal protection
- Managed backup directory
Full database backups are admin-only.
### Cleanup and Maintenance
Admin cleanup tools include:
- Expired import session cleanup
- Temporary export file cleanup
- Backup partial cleanup
- Optional import history trimming
- Manual cleanup run
- Cleanup status and last-result display
Cleanup tasks run through the daily worker and are non-fatal.
---
## Status Page
The Status page provides safe read-only operational information.
It may show:
- App status
- Runtime status
- Database status
- Daily worker status
- Maintenance cleanup status
- Notification status
- Backup status
- Server clock
- Tracker health
- Recent errors
The Status page does not expose:
- Admin controls
- Cleanup settings controls
- Secrets
- Stack traces
- Absolute filesystem paths
---
## Security
Bill Tracker includes several security hardening measures:
- Cookie-based authentication
- Production cookie secure flag support
- `sameSite: strict`
- Route-level authentication middleware
- Admin-only middleware
- User ownership enforcement
- Rate limiting on sensitive endpoints
- CORS allowlist support through configuration
- Global security headers
- Settings filtering to avoid leaking secrets
- Backup path traversal protection
- SQLite integrity check before restore
- User imports scoped to signed-in user
- User exports exclude auth/admin/system data
- OIDC token verification through `openid-client`
- authentik admin role mapping is explicit and not granted by default
- Error responses avoid leaking stack traces and internal paths
### Known Security Notes
- Downloaded exports and backups contain sensitive financial data and should be protected.
- Admin full database backups are not encrypted by default.
- CSP is deferred because it requires auditing Vite, Tailwind, Radix, and shadcn output.
- authentik live login must be tested against a real authentik instance in the target deployment.
- OIDC single logout is not currently implemented.
---
## Tech Stack
### Frontend
- React
- Vite
- Tailwind CSS
- shadcn/Radix-style components
- Material Design shadcn theme
- OKLCH theme tokens
- lucide-react icons
### Backend
- Node.js
- Express
- CommonJS modules
- better-sqlite3
- bcryptjs
- cookie-parser
- express-rate-limit
- openid-client
### Database
- SQLite
- App-managed schema/migrations on startup
- User-owned relational data
- Admin backup support
---
## Project Structure
Typical project structure:
```text
client/
components/
contexts/
lib/
pages/
api.js
db/
database.js
schema.sql
middleware/
requireAuth.js
rateLimiter.js
securityHeaders.js
routes/
auth.js
authOidc.js
admin.js
bills.js
categories.js
payments.js
tracker.js
profile.js
import.js
export.js
settings.js
status.js
version.js
services/
authService.js
backupService.js
cleanupService.js
notificationService.js
oidcService.js
statusService.js
userDbImportService.js
workers/
dailyWorker.js
scripts/
test-import.js
test-oidc-smoke.js
setup/
firstRun.js
```
---
## Installation
Install dependencies: Install dependencies:
@ -436,24 +46,12 @@ Install dependencies:
npm install npm install
``` ```
Run the app in development: Run the API and Vite frontend for development:
```bash ```bash
npm run dev npm run dev
``` ```
Run only the API in development:
```bash
npm run dev:api
```
Run only the UI in development:
```bash
npm run dev:ui
```
Build the frontend: Build the frontend:
```bash ```bash
@ -466,129 +64,183 @@ Start the production server:
npm start npm start
``` ```
--- The production server serves `dist/` and listens on `PORT`, defaulting to `3000`.
## Common Commands Useful scripts present in this repo:
```bash ```bash
npm run dev
npm run dev:api npm run dev:api
npm run dev:ui npm run dev:ui
npm run build npm run build
npm start npm start
```
Useful test/smoke scripts if present:
```bash
node scripts/test-import.js node scripts/test-import.js
node scripts/test-oidc-smoke.js node scripts/test-oidc-smoke.js
node scripts/test-cookie-options.js
``` ```
--- ## Docker
Docker files are included. The compose file runs the published image on host port `3030` and stores app data under `/data` in the container.
```bash
docker compose up -d
```
On first start without an existing database, create the admin account with:
```bash
INIT_ADMIN_USER=admin
INIT_ADMIN_PASS=change-this-password
```
Remove or change those first-run values after the initial admin account exists.
## Configuration ## Configuration
Common environment variables may include: Most app settings are configured in the web UI. User-facing settings live under Settings/Profile. Server-wide settings such as users, backups, cleanup, and authentication methods live in Admin.
Real environment variables used by the app:
```bash ```bash
PORT=3000 PORT=3000
NODE_ENV=production NODE_ENV=production
DB_PATH=/path/to/bills.db
BACKUP_PATH=/path/to/backups
INIT_ADMIN_USER=admin
INIT_ADMIN_PASS=change-this-password
HTTPS=true HTTPS=true
COOKIE_SECURE=true
CORS_ORIGIN=https://bills.example.com CORS_ORIGIN=https://bills.example.com
``` ```
### authentik / OIDC OIDC environment fallback variables are supported when the matching Admin database setting is blank:
authentik can now be configured from the Admin panel. Environment variables may still be used as fallback/bootstrap values when database settings are blank.
Possible OIDC-related variables:
```bash ```bash
OIDC_ENABLED=true OIDC_PROVIDER_NAME=authentik
OIDC_ISSUER_URL=https://auth.example.com/application/o/bill-tracker/ OIDC_ISSUER_URL=https://yourURL.com/application/o/bills/.well-known/openid-configuration
OIDC_CLIENT_ID=your-client-id OIDC_CLIENT_ID=<client-id>
OIDC_CLIENT_SECRET=your-client-secret OIDC_CLIENT_SECRET=<client-secret>
OIDC_TOKEN_AUTH_METHOD=client_secret_basic
OIDC_REDIRECT_URI=https://bills.example.com/api/auth/oidc/callback OIDC_REDIRECT_URI=https://bills.example.com/api/auth/oidc/callback
OIDC_SCOPES="openid email profile groups" OIDC_SCOPES="openid email profile groups"
OIDC_ADMIN_GROUP=bill-tracker-admins OIDC_ADMIN_GROUP=bill-tracker-admins
OIDC_DEFAULT_ROLE=user
OIDC_AUTO_PROVISION=true OIDC_AUTO_PROVISION=true
OIDC_PROVIDER_NAME=authentik
``` ```
Database-backed Admin settings take precedence over environment fallback values once configured. Database-backed Admin settings take precedence over environment fallback values.
Never expose `OIDC_CLIENT_SECRET` to the frontend. ## Authentication
--- BillTracker supports local username/password login by default. Admins can create users, reset user passwords, promote/demote users, and configure login methods.
Optional authentik/OIDC login can be enabled in Admin. OIDC uses authorization code flow with PKCE, state and nonce validation, and `openid-client` token validation. OIDC users can be auto-provisioned when enabled.
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.
## authentik Setup ## authentik Setup
In authentik: In authentik, create an OAuth2/OpenID provider/application for BillTracker:
1. Create an OAuth2/OpenID Provider. - Client type: confidential
2. Use a confidential client. - Redirect URI: `https://bills.example.com/api/auth/oidc/callback`
3. Use Authorization Code flow. - Scopes: `openid email profile groups`
4. Set redirect URI to: - Groups claim: make sure authentik sends `groups`
- Admin group: create or choose the authentik group that should become BillTracker admins
In BillTracker, go to Admin -> Authentication Methods and set:
- Provider name: `authentik`
- Issuer/discovery URL: `https://yourURL.com/application/o/bills/.well-known/openid-configuration`
- Client ID and client secret from authentik
- Redirect URI matching the authentik allowed redirect URI
- Scopes: `openid email profile groups`
- Admin group: the exact authentik group name for BillTracker admins
- Auto-provision users: enabled if you want valid authentik users created on first login
The backend accepts either the provider issuer base URL or the full discovery URL. For authentik, the full discovery URL example is:
```text ```text
https://your-domain.example/api/auth/oidc/callback https://yourURL.com/application/o/bills/.well-known/openid-configuration
``` ```
5. Include scopes: Keep local login enabled until you have tested authentik login with an admin-group user.
## Data, Imports, Exports, And Backups
BillTracker stores data in SQLite. By default the database is `db/bills.db`; set `DB_PATH` for a different location. In Docker, the image sets `DB_PATH=/data/db/bills.db` and `BACKUP_PATH=/data/backups`.
User data is scoped to the signed-in user. User exports include bills, categories, payments, monthly bill state, notes, and export metadata. They do not include password hashes, sessions, admin settings, SMTP credentials, backup files, server paths, or other users' data.
Data tools:
- XLSX spreadsheet import with preview before apply
- user SQLite import from BillTracker user exports
- user SQLite export
- Excel workbook export
- import history
- admin full database backup, import, download, restore, delete, scheduled backups, and retention
Backups and exports contain sensitive financial data. The code writes SQLite backup files with restrictive file permissions, but backup/export encryption is not implemented. Protect downloaded files and backup storage yourself.
## Security Notes
- 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.
- 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`.
- Session cookies are `httpOnly`, `sameSite=strict`, and marked secure when `COOKIE_SECURE=true`, `HTTPS=true`, or the request appears to be HTTPS.
- OIDC validation is handled through `openid-client` using discovered provider metadata and JWKS.
- Protect database files, backups, and exports as sensitive financial records.
## Reverse Proxy And HTTPS
Run BillTracker behind HTTPS for normal use. If TLS terminates at a reverse proxy, forward `X-Forwarded-Proto: https` so secure-cookie detection can work. You can also set `HTTPS=true` or `COOKIE_SECURE=true`.
Set `CORS_ORIGIN` only when the frontend and backend are served from different origins. For the normal same-origin deployment, leave it unset.
## Project Structure
```text ```text
openid email profile groups client/ React app, pages, layout, UI components
db/ SQLite connection, schema, startup migrations
middleware/ auth checks, 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
``` ```
6. Ensure authentik sends a `groups` claim. ## Upgrading
7. Create or choose an authentik group for Bill Tracker admins.
8. Configure the same admin group in Bill Tracker Admin settings.
9. Keep local login enabled until authentik login is tested.
10. Only disable local login after confirming an authentik admin can access the Admin panel.
--- For a direct Node install:
## Data Model Highlights ```bash
git pull
npm install
npm run build
npm start
```
Core data areas include: Restart your process manager after building. The app initializes the SQLite schema and runs additive migrations on startup; the Docker entrypoint also runs `scripts/migrate-db.js` before starting unless `RUN_DB_MIGRATIONS=false`.
- `users` For Docker, pull/rebuild the image, recreate the container, and keep the `/data` volume mounted.
- `bills`
- `categories`
- `payments`
- `monthly_bill_state`
- `bill_history_ranges`
- `import_sessions`
- `import_history`
- `settings`
- `oidc_states`
Bills are recurring templates. Month-specific values belong in monthly bill state. ## Known Limitations
--- - Admin backups and user exports are not encrypted by the app.
- OIDC single logout is not implemented.
## Backup vs Export - Content-Security-Policy is intentionally deferred.
- Rate limiting is in-memory, so counters reset on restart and are not shared across multiple app instances.
Bill Tracker intentionally separates full-system backups from user exports. - 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.
### Admin Backup
- Full SQLite database
- Admin-only
- Can be restored
- Includes system data
- Used for disaster recovery
### User Export
- Signed-in users own data only
- Can be downloaded by the user
- Can be imported back through user SQLite import
- Does not include auth/admin/system data
---
## License ## License

View File

@ -757,20 +757,20 @@ function AuthMethodsCard() {
/> />
</FieldRow> </FieldRow>
<FieldRow label="Issuer URL"> <FieldRow label="Issuer / discovery URL">
<div className="space-y-1"> <div className="space-y-1">
<Input <Input
value={form.oidc_issuer_url} value={form.oidc_issuer_url}
onChange={e => set('oidc_issuer_url', e.target.value)} onChange={e => set('oidc_issuer_url', e.target.value)}
placeholder="https://auth.example.com/application/o/bill-tracker/" placeholder="https://yourURL.com/application/o/bills/.well-known/openid-configuration"
className="max-w-xl h-8 text-sm" className="max-w-xl h-8 text-sm"
/> />
<p className={issuerEndpointWarning ? 'text-xs text-amber-500' : 'text-xs text-muted-foreground'}> <p className={issuerEndpointWarning ? 'text-xs text-amber-500' : 'text-xs text-muted-foreground'}>
Use the authentik provider issuer URL, not the authorize/token/userinfo endpoint. Use the authentik provider issuer URL or full discovery URL, for example https://yourURL.com/application/o/bills/.well-known/openid-configuration.
</p> </p>
{issuerEndpointWarning && ( {issuerEndpointWarning && (
<p className="text-xs text-amber-500"> <p className="text-xs text-amber-500">
This looks like an authorization endpoint. In authentik, copy the OpenID Configuration Issuer value. This looks like an authorization endpoint. In authentik, copy the provider issuer or OpenID Configuration URL.
</p> </p>
)} )}
</div> </div>

View File

Before

Width:  |  Height:  |  Size: 320 KiB

After

Width:  |  Height:  |  Size: 320 KiB

View File

Before

Width:  |  Height:  |  Size: 202 KiB

After

Width:  |  Height:  |  Size: 202 KiB

BIN
docs/images/logo_cut.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View File

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 167 KiB

BIN
img/Analytics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

BIN
img/Calendar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

BIN
img/tracker.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB