MyMidas/README.md
megaproxy fe4e69b9ad Complete Phase 3, Phase 5 polish and hardening
Phase 3 — Investments:
- Multi-currency support: holdings track purchase currency, FX rates convert to base for totals
- Capital gains report using UK Section 104 pool method, grouped by tax year
- Capital Gains tab added to Reports page

Phase 5 — Polish & Hardening:
- Mobile-responsive layout: bottom nav, sidebar hidden on mobile, logo in TopBar, compact header buttons, hover-only actions now always visible on touch
- Backup system: encrypted GPG backups via backup.sh, nightly scheduler job, admin API (list/trigger/download/restore), Settings UI with drag-to-restore confirmation
- Docker entrypoint with gosu privilege drop to fix bind-mount ownership on fresh deployments
- OWASP fixes: refresh token now bound to its session (new refresh_token_hash column + migration), CSRF secure flag tied to environment, IP-level rate limiting on login, TOTPEnableRequest Pydantic schema replaces raw dict
- AES-256-GCM key rotation script (rotate_keys.py) with dry-run mode and atomic DB transaction
- CLAUDE.md added for AI-assisted development context
- README updated: correct reverse proxy port, accurate backup/restore commands, key rotation instructions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 14:59:11 +00:00

272 lines
9.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# MyMidas
A self-hosted, single-user personal finance tracker built for security and completeness. Combines budgeting, investment tracking, and ML-powered predictions in one application — things that no single open-source tool does together.
Runs entirely on your own hardware via Docker Compose. Designed for LAN access with VPN for remote, behind an existing reverse proxy (nginx proxy manager, Caddy, etc.).
---
## Features
### Accounts & Transactions
- Multiple account types: Checking, Savings, Cash ISA, Stocks & Shares ISA, Credit Card, Investment, Pension, Crypto Wallet, Loan, Mortgage, and more
- Full transaction history with categories, tags, merchant tracking, and notes
- Transfer detection between accounts
- Recurring transaction rules (rrule)
- Receipt and document attachments on transactions (JPEG, PNG, WebP, PDF — up to 10 MB each)
- CSV import with **auto-detection** for 10 UK bank formats: Monzo, Starling, Revolut, Barclays, Lloyds, NatWest, HSBC, Santander, Nationwide, and generic fallback
- SHA-256 deduplication prevents re-importing the same transactions
### Budgets
- Monthly, weekly, quarterly, and yearly budget periods
- Per-category budget tracking with rollover support
- Visual radial gauges showing spend vs. budget
- Configurable alert threshold (default 80%)
### Investments
- Portfolio tracking with holdings and buy/sell/dividend/split transactions
- Live price feeds: stocks and ETFs via **yfinance**, crypto via **CoinGecko** (refreshed every 15 minutes)
- Hourly FX rates for multi-currency conversion to GBP base
- TWRR and MWRR performance metrics
- Capital gains reporting (short/long-term by tax year)
- OHLCV candlestick charts per asset
- Allocation treemap across the full portfolio
### Reports
Seven report views:
1. Net Worth over time (area chart with time slider)
2. Income vs. Expense (bar chart)
3. Cash Flow (waterfall)
4. Category Breakdown (pie with drilldown)
5. Budget vs. Actual
6. Investment Performance
7. Spending Trends
Export to CSV and PDF.
### ML Predictions
Four prediction engines, all running locally:
| Engine | Algorithm | Output |
|--------|-----------|--------|
| **Spending Forecast** | Meta Prophet per category; SARIMA fallback | 90-day forecast with 80%/95% confidence bands |
| **Net Worth Projection** | Holt-Winters ExponentialSmoothing | 1/3/5-year fan chart, 3 scenarios (conservative/base/optimistic) |
| **Monte Carlo Portfolio** | Geometric Brownian Motion + Cholesky covariance | 1,0005,000 simulations, P10P90 percentile paths |
| **Budget Forecast** | Velocity-based daily projection + normal CDF | Probability of overspend per category this month |
Plus a 30-day cash flow forecast with negative-balance risk flagging.
### Themes
Seven selectable UI themes — pick from the top bar:
| Theme | Style |
|-------|-------|
| **Obsidian** | Dark slate, blue-grey accents |
| **Arctic** | Clean light with ice-blue tones |
| **Midnight** | Deep navy with cyan highlights |
| **Vault** | Forest green, financial gravitas |
| **Terminal** | Phosphor green on black, scanline overlay, monospace |
| **Synthwave** | Neon pink/purple, grid lines, Orbitron headings |
| **Ledger** | Warm paper, serif typography, ruled lines |
---
## Security
Ten independent security layers:
1. **Network isolation** — Postgres and Redis are on an internal Docker bridge, never exposed to the host
2. **TLS** — handled upstream by your reverse proxy; internal traffic is HTTP (LAN-only)
3. **Security headers** — CSP, HSTS, X-Frame-Options DENY, X-Content-Type-Options, form-action, Permissions-Policy
4. **CSRF** — double-submit cookie on all mutating endpoints
5. **Authentication** — Argon2id password hashing; RS256 JWT (15-min access + 7-day HttpOnly refresh cookie); TOTP (RFC 6238) with backup codes; brute-force lockout (exponential backoff via Redis)
6. **Rate limiting** — Redis sliding window: 20/min on login (per IP), 10/min on TOTP, 20/min on predictions, 300/min general API
7. **Field-level encryption** — AES-256-GCM on all PII fields (account names, transaction descriptions, merchant, notes, TOTP secret); IV ‖ ciphertext ‖ tag stored in `bytea`
8. **Database encryption** — pgcrypto as a second layer on backup dumps
9. **Row-level security** — PostgreSQL RLS policies enforce user isolation at the DB layer; buggy queries cannot leak another user's data
10. **Audit log** — every auth event and data mutation is written to an append-only `audit_logs` table; the app role has no UPDATE/DELETE on it
**Single-user hardening:** registration is permanently disabled after the first account is created. Re-enable with `ALLOW_REGISTRATION=true` if needed.
---
## Tech Stack
| Layer | Technology |
|-------|-----------|
| Backend | Python 3.12, FastAPI, SQLAlchemy 2.0 async |
| Frontend | React 18, TypeScript, Vite, TanStack Query v5 |
| Styling | Tailwind CSS v4, shadcn/ui primitives |
| Charts | Recharts (standard), Plotly (Monte Carlo, candlestick) |
| Database | PostgreSQL 16 with pgcrypto and RLS |
| Cache / Sessions | Redis 7 |
| ML | Prophet, statsmodels, NumPy, SciPy |
| Background jobs | APScheduler (in-process) |
| Containerisation | Docker Compose |
---
## Getting Started
### Prerequisites
- Docker and Docker Compose
- An existing reverse proxy (nginx proxy manager, Caddy, Traefik, etc.) pointing at port `8090`
### 1. Clone and configure
```bash
git clone https://git.rdx4.com/megaproxy/MyMidas.git
cd MyMidas
cp .env.example .env
```
Edit `.env` and fill in every value:
```env
# 32-byte hex key — generate with:
# python3 -c "import secrets; print(secrets.token_hex(32))"
ENCRYPTION_KEY=
# Strong random passwords
DB_PASSWORD=
REDIS_PASSWORD=
BACKUP_PASSPHRASE=
BASE_CURRENCY=GBP
ENVIRONMENT=production
ALLOW_REGISTRATION=false # set true only for initial account creation
```
### 2. Generate JWT keys
```bash
mkdir -p secrets
openssl genrsa -out secrets/jwt_private.pem 4096
openssl rsa -in secrets/jwt_private.pem -pubout -out secrets/jwt_public.pem
```
### 3. Start
```bash
docker compose up -d --build
```
All four containers start: `backend`, `frontend`, `postgres`, `redis`.
### 4. Create your account
Temporarily set `ALLOW_REGISTRATION=true` in `.env`, restart the backend, register, then set it back to `false`:
```bash
# In .env: ALLOW_REGISTRATION=true
docker compose up -d backend
# Register via the web UI, then:
# In .env: ALLOW_REGISTRATION=false
docker compose up -d backend
```
### 5. Point your reverse proxy
Forward your domain to `http://<host>:4000`. The frontend nginx serves the React app and proxies all `/api/` requests to the backend internally.
---
## Backups
Encrypted backups run automatically every night at 3 AM (GPG AES-256 symmetric encryption). Backups are stored in `./data/backups/` and retained for 30 days.
**Via the web UI:** Settings → Backups — trigger a manual backup, download a copy, or restore from any listed backup file.
**Manual backup from the command line:**
```bash
docker compose exec backend bash /app/scripts/backup.sh
```
**Manual restore** (stop the backend first to avoid lock contention):
```bash
docker compose stop backend
docker compose run --rm backend bash -c '
export GNUPGHOME=/tmp/.gnupg; mkdir -p $GNUPGHOME && chmod 700 $GNUPGHOME
PG_URL=${DATABASE_URL/postgresql+asyncpg/postgresql}
gpg --batch --yes --no-symkey-cache --pinentry-mode loopback \
--passphrase "$BACKUP_PASSPHRASE" \
--decrypt $(ls /app/backups/*.sql.gz.gpg | tail -1) \
| gunzip | psql "$PG_URL"
'
docker compose start backend
```
## Key Rotation
To rotate the AES-256-GCM encryption key without data loss:
```bash
# Generate a new key
NEW_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
echo "New key: $NEW_KEY"
# Dry-run first — validates decryption works, no DB changes
docker compose exec backend python /app/scripts/rotate_keys.py \
--old-key "$ENCRYPTION_KEY" --new-key "$NEW_KEY" --dry-run
# If dry-run passes, rotate for real (atomic — rolls back on any failure)
docker compose exec backend python /app/scripts/rotate_keys.py \
--old-key "$ENCRYPTION_KEY" --new-key "$NEW_KEY"
# Update ENCRYPTION_KEY in .env, then restart
docker compose up -d backend
```
Do not lose your current `ENCRYPTION_KEY` — without it, all encrypted fields are permanently unreadable.
---
## Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `ENCRYPTION_KEY` | 32-byte hex key for AES-256-GCM field encryption | **required** |
| `DB_PASSWORD` | PostgreSQL password | **required** |
| `REDIS_PASSWORD` | Redis password | **required** |
| `BACKUP_PASSPHRASE` | GPG symmetric passphrase for backup encryption | **required** |
| `BASE_CURRENCY` | ISO 4217 base currency | `GBP` |
| `ENVIRONMENT` | `production` or `development` | `production` |
| `ALLOW_REGISTRATION` | Enable new account creation | `false` |
---
## Project Structure
```
MyMidas/
├── backend/
│ ├── app/
│ │ ├── api/v1/ # REST endpoints
│ │ ├── core/ # Security, encryption, middleware, rate limiting
│ │ ├── db/ # Models, migrations, repositories
│ │ ├── ml/ # Prediction engines
│ │ ├── services/ # Business logic
│ │ └── workers/ # Scheduled jobs (price sync, FX, snapshots, backups)
│ └── alembic/ # Database migrations
├── frontend/
│ └── src/
│ ├── api/ # Typed API client
│ ├── components/ # Shared UI components
│ ├── pages/ # Route-level page components
│ └── store/ # Zustand state (auth, UI/theme)
├── postgres/init/ # PostgreSQL init SQL (extensions, RLS policies)
├── backend/scripts/ # backup.sh, rotate_keys.py, entrypoint.sh
├── secrets/ # JWT keys (git-ignored, generate locally)
└── docker-compose.yml
```
---
## Licence
Personal use. Not intended for public deployment or multi-user hosting.