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
This commit is contained in:
+141
-1
@@ -5,7 +5,147 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
## [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-xs`→`text-sm`, `text-sm`→`text-base`, card `max-w-sm`→`max-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: `LangSwitcher` — `current` 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
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user