* infra(terraform): manage finance session secret via random_password
Replace the hand-rolled variable (with insecure hardcoded default) with a
random_password resource so Terraform auto-generates a 48-char secret and
owns the finance-api-secrets k8s Secret lifecycle.
To rotate: terraform taint random_password.finance_session_secret && terraform apply
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(finance): active sessions panel + account deletion with full data purge
Sessions panel (/account):
- AuthSession now stores IPAddress and Device (browser + OS hint)
populated from X-Forwarded-For / User-Agent on every login
- Lists all active sessions with device icon, IP, sign-in time
- Current session badge ("This device") — cannot be self-revoked
- DELETE /sessions/:id revokes any other session (user-scoped)
Account deletion (POST /account/delete):
- Password accounts require password confirmation
- OAuth accounts require typing email address to confirm
- deleteAllUserData purges all 12 finance collections + user record
in a single call: accounts, categories, transactions, trades,
ticker_mappings, goals, import_schedules, properties, loans,
permissions, households, sessions → then the user itself
- Clears session cookie and redirects to login with success message
Infrastructure:
- findAuthUserByID added to store + storeIface
- getSessionsByUserID, deleteSessionForUser added to store + storeIface
- contains() added to template FuncMap
- accountTmpl registered; GET /account, POST /account/delete,
DELETE /sessions/:id routes wired
- 🔐 nav icon links to /account page
- Full EN + PT i18n coverage for all new strings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(finance): expand unit test coverage from ~55% to 64.7%
- Add handler_coverage_test.go (~3300 lines) covering auth flows,
org request lifecycle, CSV bank import, property/loan views,
fiscal year operations, session management, and cross-handler
consistency (values shown on one page match actions on others)
- Add handler_org_test.go (~1800 lines) covering the full org
handler surface: teams, members, invites, events, budget lines,
tx requests (all status transitions), ledger, analysis, and reports
- Extend handler_test.go mockStore with: properties/loans slice fields,
authUsers map with session-aware lookup, household field, org maps,
and updateFiscalYearStatusErr for error-path testing
- Fix nav bar: Business and Account links now show active state and
use i18n keys (removes hardcoded emoji); add account key to en/pt locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces properties and loans as first-class financial entities:
- models_property.go: Property, Loan, LoanView, PropertyView, PropertyData
- store_property.go: full CRUD for finance_properties + finance_loans collections
- handler_property.go: GET/POST /property with add/edit/delete for both entities;
amortization helpers (EMI, remaining months, total interest)
- templates/property.html: summary equity cards, property cards with equity bar
and linked loan details, standalone loan cards with payoff progress
- base.html: "Property" nav link added to desktop and mobile drawer
- storeIface + mockStore updated with 10 new property/loan methods
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(k8s): expose / without auth so homepage is publicly reachable
Adds a second Ingress (api-public) for the exact path / with no
forward-auth middleware. Traefik prefers the Exact match for the root,
while the Prefix ingress (with auth) still protects all other routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: homepage renders correctly at / for unauthenticated visitors
Two fixes:
1. Added parseStandalone() helper — parseTmpl() roots on "" but ParseFS()
stores standalone (no {{define}}) files under their base filename, so
Execute() ran the empty root and returned Content-Length: 0.
2. Added router.priority: 100 annotation to api-public ingress so Traefik
picks the Exact / rule over the Prefix / rule (Traefik ranks by rule
string length by default, which made PathPrefix beat Path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(k8s): remove forward-auth middleware from finance ingress
The app now handles its own auth at /auth/login — Traefik no longer
needs to forward-auth requests, which was causing redirects to
auth.homelab.local instead of finance.homelab.local.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(auth): harden authentication for cloud deployment
1. Secure cookie flag — set when BASE_URL starts with https://
2. SameSite=Strict on session cookie (was Lax)
3. Rate limiter — per-IP, 10 failures → 15-min lockout, auto-cleanup goroutine
4. Session rotation on login — old session deleted before issuing new one
(prevents session fixation attacks)
5. bcrypt cost 12 (was DefaultCost/10, OWASP minimum for cloud)
6. Security headers middleware on all responses:
X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
Permissions-Policy, Content-Security-Policy, HSTS (when HTTPS)
7. Structured audit logging — login success/failure/lockout with IP + email
8. Google OAuth state cookie gets Secure flag too
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: public landing page with auth-conditional state
Rewrites homepage.html as a full marketing landing page serving both
unauthenticated visitors (Sign In CTA) and authenticated users (Personal
+ Business portal links). Fixes handler to pass UserID so auth-conditional
rendering activates correctly.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(k8s): expose / without auth so homepage is publicly reachable
Adds a second Ingress (api-public) for the exact path / with no
forward-auth middleware. Traefik prefers the Exact match for the root,
while the Prefix ingress (with auth) still protects all other routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: homepage renders correctly at / for unauthenticated visitors
Two fixes:
1. Added parseStandalone() helper — parseTmpl() roots on "" but ParseFS()
stores standalone (no {{define}}) files under their base filename, so
Execute() ran the empty root and returned Content-Length: 0.
2. Added router.priority: 100 annotation to api-public ingress so Traefik
picks the Exact / rule over the Prefix / rule (Traefik ranks by rule
string length by default, which made PathPrefix beat Path).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: self-contained auth — email/password + Google OAuth, HMAC session cookies
Embeds a full authentication system into the finance API so it can be
deployed as a standalone container without any external auth dependency.
- Email/password registration and login with bcrypt hashing
- Google OAuth 2.0 (GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET env vars)
- HMAC-SHA256 signed session cookies (SESSION_SECRET env var, 30-day TTL)
- Sessions stored in MongoDB finance_sessions with TTL index auto-expiry
- Users stored in MongoDB finance_users with unique email index
- /auth/login, /auth/register, /auth/logout, /auth/oauth/google routes
- authMW now redirects to /auth/login?next=... instead of auth.homelab.local
- getAuth() resolves session cookie first, falls back to X-Auth-* headers
- Default categories seeded automatically on new account creation
- seed.go checks finance_users before the shared legacy users collection
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: homepage sign-in links point to /auth/login instead of auth.homelab.local
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(k8s): remove forward-auth middleware from finance ingress
The app now handles its own auth at /auth/login — Traefik no longer
needs to forward-auth requests, which was causing redirects to
auth.homelab.local instead of finance.homelab.local.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Goals are stored as EventGoal items embedded in the event document.
During active fiscal years, members can check/uncheck goals inline.
Goals can be added and deleted while the event is not yet approved.
Co-authored-by: Gonçalo Rodrigues <guga@Goncalos-MacBook-Pro.local>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds multi-tenant organisation support inside the existing finance namespace.
Users can create organisations, invite others via a copy-paste token link,
and manage teams/members with RBAC (admin, finance, member, viewer).
Fiscal year lifecycle is gated: activation requires all planned events to
be approved first. All org data lives in `org_`-prefixed MongoDB collections.
New files:
- models_org.go — domain types (Org, OrgTeam, OrgMember, OrgInvite,
FiscalYear, OrgEvent, BudgetLine, EventComment,
TxRequest with full StatusLog audit trail, etc.)
- store_org.go — MongoDB store methods for all org collections
- handler_org.go — HTTP handlers + RegisterOrgRoutes(); join invite
route lives at /join/{token} to avoid ServeMux
conflict with /orgs/{slug}/... wildcard routes
- templates/org_*.html — list, create, home, teams, members, invite, join
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When a holding has no price (ISIN not in the built-in map and Yahoo
rejects the raw ISIN), the portfolio page now shows an amber banner
listing each missing ISIN with an inline text input and a "Look up"
link to Yahoo Finance symbol search.
Submitting the form POSTs to /portfolio/ticker which upserts the mapping
into a finance_ticker_mappings collection keyed by (user_id, ISIN).
On the next page load custom mappings are resolved first, before the
hardcoded isinToTicker table, so user overrides always win.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tax Summary (/tax): annual gross income from Income transactions,
FIFO capital gains/losses from trades, expenses by category, CSV export
- Household Mode (/household): link a partner account, combined
income/expense/disposable view for current month, shared goals list
- Auto Import (/auto-import): schedule recurring CSV imports with
account, format and optional source URL; delete schedules; webhook docs
- New store methods for households and import_schedules collections
- storeIface extended; mockStore updated; all tests still pass
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>