Files
armarium-suite/CHANGELOG.md
T
Daniel Krähenbühl 1a7ef09805 feat: Armarium v1.1.0 — dashboard, auth, 2FA, SMTP, settings, deploy
Dashboard:
- ApexCharts bar chart (income vs fixed costs vs expenses) and donut chart
- KPI cards: income, fixed costs, savings rate with configurable goal
- Greeting with time-of-day and locale-aware date/time display

Authentication & security:
- Email-based login (no username), case-insensitive lookup
- JWT access/refresh tokens with rotation and blacklist
- TOTP 2FA with QR code, backup codes (copy + PDF export)
- 2FA recovery via email code
- Cloudflare Turnstile CAPTCHA on login and register

Email flows:
- Email verification on registration (24h token)
- Password reset flow (15min token, anti-enumeration)
- Brevo SMTP integration with HTML + plaintext email templates
- Notification emails: 2FA recovery, password changed, email changed

Settings page:
- 2FA management (enable/disable, QR, backup codes)
- Active sessions list with per-device revoke
- Data export: ZIP with 6 PDFs via fpdf2
- Notification preferences (3 toggles)
- Danger zone: account deletion with mandatory export + confirmation phrase

UI & layout:
- Sidebar with collapsible/flyout mode, Angular signal-based dropdowns
- Dark mode (class-based), language switcher (DE/FR/IT/EN)
- Mobile-responsive layout with touch-friendly targets
- Roboto font via @fontsource (GDPR-compliant, no Google CDN)
- Pure Tailwind CSS v3

Infrastructure:
- Forgejo Actions CI/CD pipeline (auto-deploy on push to main)
- Gunicorn + Nginx + PostgreSQL production setup
- Rate limiting, HSTS, secure cookies, CSRF protection
2026-05-25 22:46:30 +02:00

23 KiB
Raw Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

[1.1.0] - 2026-05-19

Added

  • Auth: E-Mail-Verifikation bei Registrierung — Token (SHA-256-Hash in DB, 24h gültig) wird per Mail versendet; /verify-email?token= Frontend-Route löst automatisch POST /api/auth/verify-email/ aus
  • Auth: Passwort vergessen / Reset — POST /api/auth/password-reset/ (anti-enumeration); POST /api/auth/password-reset/confirm/ setzt Passwort und invalidiert alle aktiven Sessions; Token (SHA-256-Hash, 15min TTL) via Brevo-Mail mit Link
  • Auth: ForgotPassword-Komponente (/forgot-password), ResetPassword-Komponente (/reset-password), VerifyEmail-Komponente (/verify-email)
  • Auth: "Passwort vergessen?"-Link auf Login-Seite
  • E-Mail-Templates: registration_confirm, password_reset, password_changed, email_changed (je HTML + Plaintext)
  • Backend: finance/email.py — generischer send_email() Helper mit EmailMultiAlternatives
  • Backend: FRONTEND_URL Env-Var für absolute Links in Mails; EMAIL_BACKEND via Env-Var überschreibbar
  • i18n: auth.forgot_password, auth.reset_password, auth.new_password, auth.email_verified + Error-Keys (DE/EN/FR/IT)
  • Feature: Jahresplanung (/financial-year) — Jahres-Dropdown, 3 Summary-Cards, Tabs Einnahmen/Fixkosten mit Inline-CRUD; "Neues Jahr starten" (max. 1 Jahr im Voraus)
  • Backend: FinancialYear, YearlyIncome, YearlyBudgetItem, Household, HouseholdMembership Modelle (Migration 0019)
  • Backend: vollständige REST-API für Jahresplanung und Haushalte inkl. Invite/Accept/Leave/SetRole
  • Backend: Django Management Command migrate_to_financial_year (idempotent, --dry-run)
  • Frontend: FinancialYearService mit allen Typen und API-Methoden
  • Frontend: Household-Sektion auf /financial-year (Gründen, Einladen, Rollen, Annehmen, Verlassen)
  • Sidebar: "Jahresplanung" Nav-Item
  • Dashboard: Einnahmen/Fixkosten aus FinancialYearService statt Account/Budget-Daten
  • Dashboard: Einnahmen vs. Ausgaben — Flowbite-Redesign, 3 Serien, Jahres-Dropdown
  • Dashboard: Fixkostenaufschlüsselung — Pie Chart mit %-Labels, Toggle zur Listenansicht
  • Dashboard: Sparquote — personalisierbarer Ziel-Marker, Settings-Toggle zum Anpassen
  • Security: Cloudflare Turnstile CAPTCHA auf Login + Register
  • Infrastructure: Brevo SMTP (smtp-relay.brevo.com:587), Domain armarium.ch verifiziert (SPF/DKIM)
  • i18n: sidebar.financial_year, financial_year.*, dashboard.*, auth.errors.captcha_failed (DE/EN/FR/IT)
  • Settings: Active Sessions card — lists all logged-in devices (device name, IP, last active); individual revoke and "sign out all others" buttons; current session marked with badge; UserSession model with session_key, refresh_jti, device_name, ip_address; _create_session() called on every successful login (including 2FA and recovery flows)
  • Settings: Data Export — downloads a ZIP containing 6 structured PDFs (Profil, Konten, Budgets, Ausgaben, Transaktionen, Termine) generated server-side with fpdf2; violet header bar, alternating row fill, gray footer with page numbers
  • Settings: Notification Preferences — toggles for "Anstehende Termine", "Budget-Warnungen", "Monatliche Zusammenfassung"; saved via PATCH /api/notifications/prefs/; loaded from profile on page open
  • Settings: Account deletion now requires two steps — (1) mandatory data export, (2) confirmation form with password field (show/hide eye icon) and translated confirmation phrase (profile.delete_account in current language); delete button disabled until both conditions met
  • Settings: After account deletion, user is redirected to https://www.armarium.ch and both storages are cleared
  • Auth: Language switcher (LangSwitcher component) inside the login and register cards (top-left); uses @HostListener for outside-click close and [class] binding to avoid Tailwind dark-mode colon conflicts
  • Auth: "Angemeldet bleiben" checkbox on login with two-line label; toggles between localStorage (persistent) and sessionStorage (session-only); persisted through full 2FA and recovery flows via keepSignedIn parameter
  • Auth: session_key stored alongside JWT tokens in same storage; sent as X-Session-Key header by interceptor so backend can identify current session
  • Backend: PATCH /api/notifications/prefs/ endpoint (NotificationPrefsView)
  • Backend: GET /api/auth/sessions/, DELETE /api/auth/sessions/<key>/, DELETE /api/auth/sessions/revoke-all/ endpoints
  • Backend: GET /api/export/ returns ZIP with 6 PDFs via fpdf2
  • Backend: Admin URL configurable via ADMIN_URL env var (default manage/); obscures the standard /admin/ path from scanners
  • Backend: SMTP email config from env vars (EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_USE_TLS, DEFAULT_FROM_EMAIL); console backend in DEBUG mode
  • Backend: production security block (if not DEBUG) — SECURE_SSL_REDIRECT, HSTS (1 year, preload, subdomains), secure cookies, SECURE_CONTENT_TYPE_NOSNIFF, proxy SSL header
  • Backend: DATA_UPLOAD_MAX_MEMORY_SIZE and FILE_UPLOAD_MAX_MEMORY_SIZE capped at 5 MB
  • Backend: CSRF_TRUSTED_ORIGINS from env var
  • i18n: settings.sessions_*, settings.export_*, settings.notif_*, settings.delete_* keys (DE/FR/IT/EN)
  • i18n: auth.keep_signed_in, auth.keep_signed_in_hint, auth.back_to_login keys (DE/FR/IT/EN)
  • Security: TOTP-based two-factor authentication (2FA) — setup via QR code, verified on login; production-ready with HMAC-signed temp token (5 min expiry), replay protection via totp_last_used_code, and 8 backup codes in XXXXXXXX-XXXXXXXX format (SHA-256 hashed in DB)
  • Security: TwoFactorSetupView, TwoFactorEnableView, TwoFactorDisableView, TwoFactorLoginView backend endpoints; BackupCode model with index on (user, used)
  • Settings page (/settings): 2FA card (enable/disable, QR scan, backup code copy + PDF download) and Danger Zone (account deletion); accessible from navbar avatar menu
  • Login: two-step flow — step 1 credentials, step 2 TOTP/backup code entry when 2FA is active
  • Backup codes: copy to clipboard and PDF download via jsPDF (client-side, no server round-trip)
  • Dashboard: donut chart "Fixed Costs Breakdown" now shows individual budget entry names and amounts (was: grouped by category)
  • Dashboard: toggle button (top-right of donut card) switches between chart view and a scrollable breakdown list with color dot, name, CHF amount and percentage per entry
  • i18n: auth.totp_title, auth.totp_hint, auth.totp_or_backup, auth.back_to_login, auth.errors.invalid_totp keys (DE/FR/IT/EN)
  • i18n: profile.totp_*, profile.backup_* keys for all 2FA labels, backup codes and messages (DE/FR/IT/EN)
  • i18n: settings.subtitle key (DE/FR/IT/EN)

Changed

  • .env.example: FRONTEND_URL und EMAIL_BACKEND Variablen ergänzt
  • Profile-Model: email_verify_token neu als SHA-256-Hash gespeichert; email_verify_token_expires (24h TTL) hinzugefügt (Migration 0020)
  • UI: migrated frontend to Flowbite design system — custom Tailwind v4 theme (budget-app-theme.css) with violet primary color, consistent rounded-lg cards, Flowbite outline SVG icons throughout
  • Navbar: avatar dropdown and notification panel converted from Flowbite JS (data-dropdown-toggle) to Angular state management (avatarDropdownOpen signal + backdrop <div> for outside-click close); eliminates Flowbite JS runtime dependency
  • Navbar: sun/moon/bell icons replaced with Flowbite outline variants; logout entry in avatar dropdown now shows arrow-right-to-bracket icon
  • Sidebar: navigation icons restored to fill style (fill="currentColor" viewBox="0 0 20 20") for consistency with pre-migration appearance; Settings link added to mobile drawer after Profile link
  • Dashboard: greeting H1 changed to font-light (weight 300)
  • Mobile — tables: non-critical columns hidden on small screens (hidden sm:table-cell for category, hidden md:table-cell for account) in expense and transaction lists
  • Mobile — touch targets: all icon-only buttons (edit, delete, modal close, calendar navigation arrows) updated to p-2 minimum (was p-1.5)
  • Mobile — form grids: grid-cols-2 changed to grid-cols-1 sm:grid-cols-2 in expense modals and profile form
  • Mobile — budget entries: min-w-0 flex-1 and truncate on name/account spans prevent overflow on narrow screens
  • Mobile — calendar: day cells min-h-[48px] sm:min-h-[64px], day detail drawer w-full sm:w-80
  • Mobile — dashboard: KPI card padding p-3 sm:p-5, KPI values text-xl sm:text-2xl
  • Mobile — login: OTP digit inputs h-10 w-10 sm:h-12 sm:w-12; backup code field placeholder removed (hint text above suffices)
  • Mobile — settings: recovery email row flex-col sm:flex-row so button stacks below input on narrow screens
  • Search: placeholder text styled placeholder-gray-400 (was unstyled)
  • Typography: Roboto font self-hosted via @fontsource/roboto (300/400/500/700 weights) — no Google Fonts CDN, DSGVO/nDSG compliant; replaces Inter which was defined in the theme but never actually loaded
  • Typography: unified font-size system — page title H1 text-2xl (24px), card/section H2 headers text-base (16px), sidebar navigation text-sm (14px), savings rate display text-3xl (30px); applied across Dashboard, Budgets, Expenses, Calendar, Profile, Settings and Sidebar
  • Profile: password change fields no longer show browser autofill suggestions (autocomplete="new-password"); show/hide eye icon added to both password fields
  • Sidebar: version number updated to 1.1.0
  • Login: registration is email-only — username field removed; backend auto-sets username=email; RegisterSerializer updated accordingly
  • Login: font sizes increased throughout login/register flow (text-xstext-sm, text-smtext-base, card max-w-smmax-w-md)
  • Settings: 2FA card and Danger Zone remain; Recovery Email, Active Sessions, Data Export, Notification Preferences added above Danger Zone
  • Settings: Danger Zone delete flow is now three-step (export → credentials + phrase → redirect)
  • LogoutView: added permission_classes = [AllowAny] so logout works even when access token is expired; now also deletes UserSession by JTI and X-Session-Key
  • ChangePasswordView: invalidates and blacklists all other active sessions on password change
  • authInterceptor: sends X-Session-Key header on all internal API requests
  • Auth: completeLogin() and storeTokens() accept optional session_key; logout() clears session key from both storages
  • Auth: refreshToken() bug fixed — was storing tokens.access twice instead of tokens.access and tokens.refresh
  • Login endpoint POST /api/auth/token/ replaced with custom LoginView supporting 2FA challenge response
  • Profile page: 2FA and Danger Zone sections removed and moved to new Settings page
  • Authenticator app recommendation updated to: Proton Pass, Aegis (Android), Raivo OTP (iOS)
  • Calendar: holidays and school holidays now shown in the current app language; reloaded automatically on language change via translate.onLangChange subscription
  • Calendar: OpenHolidays API requests include languageIsoCode parameter; cache key includes language
  • Calendar: today's date shown as violet ring/outline only (not filled) to prevent white-on-white hover text
  • Notifications: "Mark as read" checkmark button per notification (replaces X icon)
  • Notifications: "Mark all as read" button in panel header
  • i18n: nav.mark_read, nav.mark_all_read keys (DE/FR/IT/EN)
  • Calendar: live holiday and school holiday data via OpenHolidays API (openholidaysapi.org, AGPL-3.0) with automatic fallback to static data on API failure
  • Calendar: in-memory cache per year/canton to avoid redundant API requests
  • Budgets: show info modal when no accounts exist, with link button to accounts page
  • Expenses: show info modal when no accounts exist, with link button to accounts page
  • i18n: common.no_accounts_title, common.no_accounts_text, common.go_to_accounts keys (DE/FR/IT/EN)
  • Login: authentication changed from username-based to email-based; password manager only needs email + password
  • Mobile sidebar drawer: Notifications, Dark/Light toggle, Profile link and Logout moved from navbar into the sidebar so all user actions are accessible via the hamburger menu

Security

  • Password Reset invalidiert alle aktiven Sessions des Nutzers (analog zu ChangePasswordView)
  • Email-Verify-Token: SHA-256-Hash statt Klartext in DB; Ablaufzeit 24h
  • VerifyEmailView + PasswordResetConfirmView mit AuthThrottle (5/min) gesichert

Fixed

  • Security: TwoFactorRecoverConfirmView now verifies temp_token (password proof) before accepting a recovery code — previously anyone with a valid recovery code could bypass authentication entirely
  • Security: ProfileView.delete now requires password verification (check_password) before deletion; returns 403 on failure
  • Backend: URL conflict — api/notifications/ was mapped to both NotificationsView and NotificationPrefsView; prefs endpoint moved to api/notifications/prefs/
  • Backend: fpdf2 export — em/en dash characters (, ) caused FPDFUnicodeEncodingException with Helvetica (Latin-1 only); fixed with safe() helper using encode('latin-1', errors='replace') and replaced placeholder dashes with ASCII -
  • Backend: migration 0017 (finance_profile.notif_deadlines etc.) was not applied on server restart, causing 500 on login; now documented that python manage.py migrate must be run after deployment
  • Auth: LangSwitcher[class.dark:text-violet-400] Angular binding error caused by Tailwind dark-mode colon in class name; fixed by using itemClass() method with [class] binding
  • Auth: LangSwitchercurrent signal initialized before langService was available; fixed by setting default 'de' and calling this.current.set(langService.current) in constructor body
  • Dashboard: bar chart right padding increased so December bars are no longer clipped at the container edge
  • Calendar: legend block removed from footer
  • Calendar: ZH Spring Holidays 2026 corrected to 20.04.02.05. in static fallback data (source: OpenHolidays API)
  • Calendar: date input now renders in the app language format (lang attribute bound to current language)
  • Calendar: deadline type dropdown now shows placeholder on open; defaults to "other" if none selected
  • Calendar: title input placeholder text styled correctly in gray
  • Login: show/hide password toggle with eye icon
  • Register: show/hide password toggle on both password fields, independently toggleable
  • Register: hint text below username field explaining it is used as in-app display name (DE/FR/IT/EN)
  • i18n: nav.dark_mode, nav.light_mode keys for sidebar mobile theme toggle (DE/FR/IT/EN)
  • i18n: auth.username_hint key for register page (DE/FR/IT/EN)
  • Mobile navbar: right-side icons (notifications, theme toggle, avatar) caused the navbar to wrap to a second line on narrow screens, pushing page content below the fixed offset; icons are now hidden on mobile and integrated into the sidebar drawer
  • Mobile notification panel: was full-width and flush against the top of the screen; now has left-4/right-4 margins, top-20 offset and rounded-xl corners
  • Auth: new EmailAuthBackend performs case-insensitive email lookup so login works regardless of capitalisation
  • Login/Register: placeholder texts removed from all username and password fields

[1.0.1] - 2026-04-14

Fixed

  • Production deployment: replaced hardcoded http://127.0.0.1:8000 with relative paths (/api, /api/auth) in ApiService and AuthService so the frontend works on any server
  • Production deployment: avatar image URLs in profile.ts and navbar.ts now use the relative path returned by the backend instead of prepending http://127.0.0.1:8000
  • Sidebar: Budgets and Accounts submenus not expanding on mobile — replaced Flowbite data-collapse-toggle with Angular signal-based state (budgetsOpen, accountsOpen in SidebarService)

Added

  • nginx.conf: reverse proxy config — serves Angular static build, proxies /api/ to Gunicorn on port 8000, serves /media/ as static files

[1.0.0] - 2026-04-13

Added

  • Branding: app renamed to "Armarium"; browser tab title updated
  • Branding: Logo_horizontal.svg in navbar, Logo_vertikal.svg on login/register, Icon.svg as favicon
  • Branding: navbar logo inverts colors in dark mode via dark:invert
  • Responsive layout: mobile-first redesign across all pages
  • Responsive layout: sidebar becomes a slide-in overlay drawer on mobile (hamburger in navbar)
  • Responsive layout: mobile backdrop closes sidebar on outside click or navigation
  • Responsive layout: main content padding reduced on mobile (p-4 lg:p-8), sidebar margin shifted to lg: breakpoint
  • Responsive layout: tables (accounts, expenses, transactions) get min-w and overflow-x-auto for horizontal scroll on mobile
  • Responsive layout: notifications panel becomes a full-width top drawer on mobile (fixed top-[57px] left-0 right-0), dropdown on desktop
  • Responsive layout: desktop sidebar toggle button hidden on mobile (hidden lg:flex)
  • Database: migrated from SQLite to PostgreSQL; connection configured via .env variables (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST, DB_PORT)
  • Database: added psycopg2-binary as PostgreSQL adapter; added .env.example for onboarding
  • i18n: full 4-language support (DE/FR/IT/EN) via ngx-translate; translation files in assets/i18n/
  • i18n: language selector in Profile settings, persisted to backend Profile.language field and localStorage
  • i18n: browser language auto-detection on Login and Register pages
  • i18n: LanguageService for centralised language init, detection and switching
  • i18n: canton names translated per UI language via canton_names keys; ZH = "Zürich" (DE), "Zuerich" (EN), "Zurigo" (IT), "Zurich" (FR)
  • Calendar year view: clicking a month card opens a modal with an enlarged month view
  • Sidebar collapse toggle in navbar: sidebar can be collapsed to icon-only view and expanded again
  • Collapsed sidebar: flyout submenus for Budgets and Accounts with backdrop to close on outside click
  • Collapsed sidebar: tooltips on hover for all navigation icons
  • Dark/Light mode toggle in navbar with sun/moon icons; preference persisted in localStorage (dark mode default)
  • iCal feed: GET /api/calendar/ical-url/ returns personal iCal feed URL per user
  • iCal feed: GET /api/calendar/ical/<user_id>/<token>/ serves .ics file with deadlines and expense due dates (HMAC-SHA256 token auth)
  • Calendar: "Subscribe" button in header opens popup with iCal feed URL and copy button

Changed

  • Calendar day detail: replaced bottom panel with a slide-in drawer from the right side
  • All UI strings replaced with i18n translation keys (DE/FR/IT/EN) across all pages and components

Fixed

  • Profile: canton and language selects not saving — replaced [value]/(change) with [ngModel]/(ngModelChange) for reliable Angular select binding
  • Calendar: canton selector not reflecting the profile canton on load
  • Sidebar collapse button was not wired to any action
  • Sidebar flyout submenus were clipped inside collapsed sidebar due to overflow-y-auto
  • iCal feed: removed unused pytz import that caused ModuleNotFoundError
  • i18n: translations not loading due to src/assets missing from angular.json asset sources

[0.1.0] - 2026-03-08

Added

Backend (Django)

  • Initialized Django project with core configuration app and finance app
  • Configured SQLite3 as development database
  • Integrated Django REST Framework with JWT authentication via djangorestframework-simplejwt
  • Enabled JWT token blacklist for secure logout and token rotation
  • Configured django-cors-headers to allow requests from Angular frontend (localhost:4200)
  • Implemented rate limiting: 5/min (auth), 200/min (authenticated users), 20/min (anonymous)
  • Configured environment variables via python-dotenv (SECRET_KEY, DEBUG, ALLOWED_HOSTS, CORS)
  • Added media file handling for avatar image uploads

Data Models

  • Account asset, expense, and revenue account types with balance tracking
  • Transaction double-entry bookkeeping with source and destination accounts
  • Budget 7 categories (fixed expenses, mobile/internet, subscriptions, leisure, tax reserves, insurance, loans)
  • Expense 10 categories with optional due date and notes
  • Profile user profile with avatar (image + color), name, email, and canton (all 26 Swiss cantons)
  • Deadline 5 types (tax, insurance, invoice, personal, other)

REST API

  • POST /api/auth/register/ user registration
  • POST /api/auth/token/ JWT login
  • POST /api/auth/token/refresh/ token refresh
  • POST /api/auth/logout/ logout with token blacklist
  • POST /api/auth/password/ password change
  • GET/POST/PUT/DELETE /api/accounts/ account management
  • GET/POST/PUT/DELETE /api/transactions/ transaction management
  • GET/POST/PUT/DELETE /api/budgets/ budget management
  • GET/POST/PUT/DELETE /api/expenses/ expense management
  • GET/POST/PUT/DELETE /api/deadlines/ deadline management
  • GET/PUT/DELETE /api/profile/ user profile with avatar upload

Frontend (Angular)

  • Initialized Angular 21.2 project using standalone component architecture
  • Integrated Tailwind CSS v3.4 for utility-first styling
  • Integrated Flowbite 4.0.1 as UI component library (initialized via initFlowbite() in shell)
  • Integrated ApexCharts 3.46.0 for data visualization
  • Added typings.d.ts for Flowbite TypeScript module declaration
  • Implemented ApiService for all HTTP communication with the Django REST API
  • Implemented AuthService for JWT token storage and management
  • Implemented authGuard to protect routes requiring authentication
  • Implemented authInterceptor to attach Bearer token to all outgoing requests and handle 401 errors
  • Configured SidebarService for sidebar open/close state management

Pages & Components

  • Login and Register pages with JWT-based authentication
  • Shell layout with responsive sidebar and navbar (user avatar, canton display)
  • Dashboard with KPIs (total income, fixed costs, expenses, available balance, savings rate) and ApexCharts bar and donut charts
  • Accounts page with full CRUD for asset, expense, and revenue accounts
  • Budgets page with category grouping and budget suggestions per category
  • Expenses page with category filtering and due date tracking
  • Transactions page with double-entry transaction management
  • Calendar page with year and month view, Swiss public holidays and school holidays by canton (20252026), expense due dates and personal deadlines
  • Profile page with avatar upload, color selection, name, email, and canton selection