380 Commits
Author SHA1 Message Date
Nick SzaunigandClaude Sonnet 5 985ea09556 fix: allow entering a queen's hatch year and marking color/number
The backend has always accepted hatchYear/markingColor/markingNumber
(Queen DTOs, Prisma schema), and the queens list even already renders
a "Schlupfjahr" field - but neither the create nor the edit form ever
exposed inputs for them, so no user could actually set hatchYear
through the UI. That silently broke the OLD_QUEEN dashboard alert
just added: it can only ever fire for colonies whose queen has a
hatchYear, and there was no way to record one.

Both forms now include Schlupfjahr, Zeichenfarbe and Zeichennummer.
Marking color is auto-suggested from the hatch year using the
internationally standardized queen marking color convention (last
digit of the year -> white/yellow/red/green/blue), editable like any
other suggestion.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 21:44:29 +02:00
Nick SzaunigandClaude Sonnet 5 edf13d3ae4 feat: warn on the dashboard when a queen is 3+ years old
Queen.hatchYear was already captured but never used anywhere - no
signal existed for "this colony's queen is overdue for evaluation or
replacement," despite that being one of the most common productivity
and swarming risk factors in beekeeping. Adds an OLD_QUEEN dashboard
alert alongside the existing overdue-task/swarm-check ones, flagging
any active colony whose current queen has completed 3+ seasons.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 18:43:37 +02:00
Nick SzaunigandClaude Sonnet 5 8c4ae64e76 docs: record the Wartezeit design assumption in ARCHITECTURE.md
Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 02:51:15 +02:00
Nick SzaunigandClaude Sonnet 5 d42f8331f9 feat: warn against harvesting honey during a treatment's Wartezeit
Treatments (Ameisensäure, Oxalsäure, etc.) can require a legally
mandated withdrawal period before honey from that colony may be sold.
Nothing in the app tracked this before, so a beekeeper had no
safeguard against harvesting into an active withdrawal window.

Treatments now optionally record withdrawalPeriodDays (from the
product's Packungsbeilage). The new harvest form fetches each
candidate colony's active withdrawal window and shows an inline
warning naming the product and the date it clears, computed as
max(endDate ?? date) + withdrawalPeriodDays across a colony's
treatments, restricted to windows still in the future.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 02:50:34 +02:00
Nick SzaunigandClaude Sonnet 5 0881a38800 feat: warn on the dashboard when a swarm check is overdue
Schwarmkontrolle already captures nextCheckDate per check, but it was
only ever visible by opening that one colony's tab. During swarm
season a missed follow-up check can mean losing half the colony, so
this now surfaces as a dashboard alert (OVERDUE_SWARM_CHECK) like
overdue tasks already do - only the most recent check per colony is
considered, so a completed follow-up clears the warning.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-06 02:35:17 +02:00
Nick SzaunigandClaude Sonnet 5 acbf5f16ce fix: add apple-touch-icon for iOS "Add to Home Screen"
iOS Safari ignores the web manifest for home-screen icons entirely and
needs its own apple-touch-icon link, which Next.js only emits when
app/apple-icon.tsx (or a literal apple-icon.png) exists. Without it,
iOS falls back to a screenshot of the open page instead of the brand
mark. Mirrors the existing public/icon.svg design via next/og.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-05 18:06:22 +02:00
Nick SzaunigandClaude Sonnet 5 81986b3275 feat: seed a starter annual task-template calendar
TaskTemplate had zero seed data - a fresh deployment showed a
completely empty recurring-task list with no hint of what to even put
there. Seeds 12 YEARLY templates, one per month, covering the standard
German-beekeeping annual cycle (Frühjahrsdurchsicht, Schwarmkontrolle,
Einfütterung, Varroa-Restentmilbung/Oxalsäure, Wintersitz-Kontrolle,
etc.) so a new beekeeper gets a ready-made starting point instead of a
blank page. Runs once (guarded by a taskTemplate.count() check, same
idempotency shape as the existing admin-user seed) and only ever adds
rows - admins can edit or deactivate any of them afterward.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-05 17:58:28 +02:00
Nick SzaunigandClaude Sonnet 5 c4f970f071 feat: add wind speed and a same-day hive-inspection verdict to weather
Temperature and rain were already tracked, but wind matters just as
much for whether it's safe/sensible to open a hive - bees get more
defensive and frames are harder to handle in wind. Adds
wind_speed_10m_max to the Open-Meteo request and a derived
inspectionSuitable flag per day (>=15C, <=1mm rain, <=25km/h wind;
null when a value is missing rather than guessing). The apiary weather
card now shows a clear "good/not good for a Durchsicht today" banner
plus wind alongside each day's forecast.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-05 17:54:31 +02:00
Nick SzaunigandClaude Sonnet 5 1f67cd5548 feat: extend offline queueing to all quick-capture forms
Only Durchsicht had an offline fallback (queue to IndexedDB, sync on
reconnect); Behandlung, Fütterung, Varroamessung, Schwarmkontrolle and
Ernte just failed outright with no signal - exactly the situation a
beekeeper is in at the actual hive. Generalizes lib/offline/db.ts and
sync.ts to six queues (version-bumped IndexedDB schema, existing queued
inspections preserved), wires the same "server rejected it vs. no
network" fallback into all five remaining */new pages, and updates the
sync badge to count/drain all six.

Also: doc-hygiene pass (ARCHITECTURE.md/TASKS.md) to replace the stale
pg_dump/CronJob backup plan with the in-app BackupModule that actually
shipped earlier this session.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-05 17:45:32 +02:00
Nick SzaunigandClaude Sonnet 5 ac45514356 fix: remove label cut lines and zero-pad printed dates
Backend Image / build-and-push (push) Successful in 2m26s
Frontend Image / build-and-push (push) Successful in 1m56s
The dashed rectangle drawn around every label slot (a cut/crop line)
wasn't needed and is gone, along with the width/height params that
existed only to draw it. Dates on the label (bottling date, MHD) now
use { day: '2-digit', month: '2-digit' } so single-digit values print
with a leading zero (05.03.2026, not 5.3.2026) - the default 'numeric'
formatting was dropping it.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-05 16:44:25 +02:00
Nick SzaunigandClaude Sonnet 5 1054a845ef feat: allow a honey batch to be split across multiple storage containers
Backend Image / build-and-push (push) Successful in 3m9s
Frontend Image / build-and-push (push) Successful in 2m45s
A batch that doesn't fit in one container (e.g. 125 kg across 5 x 25 kg
buckets) had no way to be represented - HoneyBatch.storageContainerId
is a single nullable FK. Adds HoneyBatchContainerAllocation as an
additive join table (container + amount per batch), independent of the
existing single-container/relocate() flow which still covers the
simple one-container case unchanged.

- GET/PUT/DELETE /honey-batches/:id/containers[/:containerId], reusing
  the existing honey-batches:relocate permission.
- Assigning a container is an upsert (@@unique on batch+container), and
  the total assigned is validated against the batch's own amount when
  known.
- New "Aufteilung auf mehrere Behälter" card on the honey batch detail
  page, alongside (not replacing) the existing storage card.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 19:44:33 +02:00
Nick SzaunigandClaude Sonnet 5 c91dc9ebf1 fix: add missing Ernten/Schleuderungen list and detail views
The sidebar nav sent both entries straight to their "new" creation
form, with no list page to ever browse what had been created -
Schleuderung creation didn't even redirect to a detail page, just to
/colonies, so a saved extraction left no trace anywhere in the UI.

- New /harvests and /extractions list pages (mirrors the existing
  products/apiaries list pattern).
- New /extractions/[id] detail page + edit dialog, matching the
  harvests one that already existed but had nothing linking to it.
- Sidebar "Ernten"/"Schleuderungen" now point at the list pages instead
  of the creation forms.
- Extraction creation now redirects to its detail page instead of
  /colonies.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 19:44:19 +02:00
Nick SzaunigandClaude Sonnet 5 e133a677ab refactor: reuse the SSO app registration for M365 calendar sync
One Entra enterprise application now covers both interactive login and
the calendar sync's client-credentials Graph access, instead of
requiring a second app registration. GraphClientService reads
OIDC_ISSUER_URL/OIDC_CLIENT_ID/OIDC_CLIENT_SECRET (same as SSO) and
derives the tenant ID from the issuer URL's path, rather than three
dedicated M365_CALENDAR_* env vars. The app registration just needs the
"Calendars.ReadWrite" Application permission added alongside its
existing delegated login scopes.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 14:35:18 +02:00
Nick SzaunigandClaude Sonnet 5 e7b157ec35 feat: sync calendar entries to a Microsoft 365 group calendar
Backend Image / build-and-push (push) Successful in 8m34s
Frontend Image / build-and-push (push) Successful in 8m41s
Adds a one-way push (ApiCore -> M365) of Aufgaben/Durchsichten/
Behandlungen/Fütterungen/Ernten/Königinnen-Zuordnungen into the calendar
of a Microsoft 365 group, via the Graph API.

- CalendarSyncModule (app-only client-credentials Graph auth, separate
  from the interactive SSO app registration; new env vars
  M365_CALENDAR_TENANT_ID/_CLIENT_ID/_CLIENT_SECRET).
- Outbox + minute-interval cron worker (CalendarSyncOutboxEntry) instead
  of a synchronous push, since Graph calls are too slow/flaky to block a
  domain create request on; failed syncs retry up to 5 times.
- Per-source-type toggle plus a group-ID field, configurable in
  Verwaltung > Automatisierung > Kalender-Sync (Verwaltung > Backup, an
  env var still wins on those fields where one applies to related config).
- Hooked into Tasks/Inspections/Treatments/Feedings/Harvests/Queens at
  their existing create (and Task update/complete) points; entries render
  as all-day Graph events to avoid timezone ambiguity.
- Documents the design decision in ARCHITECTURE.md (Annahme 9), since
  this is a net-new capability the lastenheft doesn't scope.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-04 09:23:35 +02:00
Nick SzaunigandClaude Sonnet 5 28ae0a2723 fix: lay out herkunft social links in a uniform count-aware grid
2 links center side-by-side, 3 sit in one row, and 4 form a centered
2x2 block - previously flex-wrap could leave an uneven number stranded
awkwardly on its own line.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 20:13:18 +02:00
Nick Szaunig 412d033d8d feat: add website social link, fix herkunft origin display, GUI backup schedule, and format-based batch numbering
Backend Image / build-and-push (push) Successful in 2m15s
Frontend Image / build-and-push (push) Successful in 2m17s
- SiteSettings gains websiteUrl (e.g. online shop link) alongside the
  existing social links, editable in Verwaltung > Automatisierung.
- /herkunft/[lot] now shows the apiary's Standort instead of its internal
  Name (so multiple apiaries at one location read as one origin), adds
  small icons per social link, and surfaces previously-fetched-but-unused
  data (Honigsorte, Abfülldatum, stat cards) to make the page a richer
  customer-facing info page.
- Backup schedule (path/cron/retention) is now also configurable via the
  GUI (Verwaltung > Backup), stored in a new BackupSettings singleton;
  an env var always wins over the GUI value per field, and the scheduler
  reloads its cron job live when GUI settings change.
- New Automatisierung rules let an admin define a format (e.g.
  "WH-{YYYY}-{SEQ:4}") that auto-generates HoneyBatch.batchNumber and
  BottlingBatch.lotNumber on creation, with an atomic per-rule sequence
  counter and a manual reset. Manual entry still works when the rule is
  inactive.
2026-09-03 13:37:09 +02:00
Nick Szaunig 7b23862a41 feat: expand backup to all data and add automatic scheduled backups
Backend Image / build-and-push (push) Successful in 2m16s
Frontend Image / build-and-push (push) Successful in 1m56s
Backup export/import now covers Role, User, RolePermission,
Notification, AuditLog and Document (with actual file content), not
just business data - matching "wirklich alle Daten". Role/User use
natural keys (role.key, resource+action) so a bundle restores cleanly
into a different database.

Adds BackupSchedulerService (@nestjs/schedule) for cron-based automatic
backups to a local path (BACKUP_STORAGE_PATH), with retention pruning
and a file-mtime guard against duplicate runs across replicas. New
GET /backup/schedule and POST /backup/schedule/run-now endpoints, plus
a status card and manual trigger in the frontend Backup settings page.
2026-09-03 11:33:48 +02:00
Nick SzaunigandClaude Sonnet 5 d998f611e2 feat: Verwaltungsmenü zu 3 Sammelseiten umgebaut - Benutzer & Rollen, Automatisierung, Backup
Backend Image / build-and-push (push) Successful in 2m17s
Frontend Image / build-and-push (push) Successful in 2m18s
Menü unter "Verwaltung" wie gewünscht neu strukturiert:

- "Benutzer & Rollen" (/settings/users-roles): Benutzerverwaltung und
  Rollen & Rechte als zwei Reiter auf einer Seite (vorher zwei
  getrennte Menüpunkte).
- "Automatisierung" (/settings/automation): Webhooks, Wiederkehrende
  Aufgaben, Etikettenvorlagen und Social Media Links als Reiter
  gebündelt - dazu (eigene Ergänzung, da sonst zwei fast gleich
  benannte Menüpunkte "Automatisierung"/"Automatisierungen"
  nebeneinander gestanden hätten) auch die bestehenden
  Automatisierungsregeln als fünfter Reiter "Regeln".
- "Backup" (/settings/backup): neuer voller Daten-Export/Import
  (Commit zuvor) plus der bisherige CSV-Import, beides auf einer Seite.

Aktiver Reiter wird als ?tab=... in der URL gehalten statt nur im
Client-State, damit z.B. "Speichern" im Etiketten-Designer wieder auf
den richtigen Reiter zurückführt.

Technisch: bestehende Seiteninhalte in Reiter-Komponenten extrahiert
(PageHeader entfernt, Rest unverändert übernommen), Dialog-Unterkomponenten
in die neuen Ordner verschoben (git mv, Historie bleibt erhalten). Die
Etiketten-Editor-Routen (/settings/label-templates/new, /[id]) bleiben
unverändert bestehen, nur der Rücksprung nach dem Speichern zeigt jetzt
auf den neuen Reiter. settings/layout.tsx's Berechtigungsliste um
label-templates:read, site-settings:read, backup:export/import ergänzt -
schließt nebenbei eine bestehende Lücke (eine Rolle mit ausschließlich
diesen Rechten wurde vorher faelschlich von der ganzen
Verwaltungssektion ausgesperrt.

Neuer Reiter-Baustein src/components/tab-switcher.tsx (generisch, nach
Vorbild von ActiveHistoryTabs). sidebar.tsx's NavItem.permission
unterstützt jetzt auch ein Array (irgendeine der Berechtigungen reicht),
fuer die neuen gebündelten Menüpunkte.

Lokal verifiziert: typecheck/lint/test/build fehlerfrei, Build zeigt
alle neuen Routen korrekt, Live-Test im Browser (ohne Login) zeigt
sauberen Redirect zu /login ohne Konsolenfehler.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 10:37:22 +02:00
Nick SzaunigandClaude Sonnet 5 2f223b6941 feat: Backend-Modul fuer vollstaendiges Daten-Backup (Export/Import)
Neues Modul src/backup/ fuer Backups und Datenuebertragung zwischen
Deployments: GET /backup/export liefert ein JSON-Bundle mit allen
fachlichen Daten (Bienenstaende bis Etikettenvorlagen, ~30 Tabellen),
POST /backup/import spielt so ein Bundle wieder ein.

Bewusst NICHT enthalten: User/Rollen/Rechte (werden ueber die
Benutzerverwaltung gepflegt, Permission-Zeilen werden ohnehin bei
jedem Start aus dem Code neu synchronisiert), Audit-Log (unveraenderliche
Historie), Notifications (fluechtig), Dokumente (nur die DB-Zeile waere
gesichert, nicht die tatsaechliche Datei auf der Festplatte).

Import laeuft in einer Transaktion, Tabellen in Abhaengigkeitsreihenfolge
(z.B. Bienenstand vor Volk, Volk vor Durchsicht). Selbstreferenzen
(Koenigin -> Mutterkoenigin) werden in zwei Durchgaengen aufgeloest,
damit die Reihenfolge im Export keine Rolle spielt. userId-Felder, die
im Zielsystem nicht existieren (z.B. Uebertragung auf ein anderes
Deployment), werden auf den ausfuehrenden Admin umgebogen statt die
Fremdschluessel-Pruefung scheitern zu lassen. AutomationRule wird ueber
triggerType statt id abgeglichen, da diese Zeilen code-fest sind.

Neue Berechtigungen backup:export/backup:import. 8 neue Unit-Tests
fuer die Sonderfaelle (Selbstreferenz, userId-Remapping,
AutomationRule-Abgleich, Audit-Eintrag).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 10:22:15 +02:00
Nick SzaunigandClaude Sonnet 5 390ec32f50 feat: Etikettendruck prueft auch gedrehte Anordnung fuer bessere Blattausnutzung
Backend Image / build-and-push (push) Successful in 2m25s
Frontend Image / build-and-push (push) Successful in 2m5s
Im automatischen Modus (Spalten/Zeilen leer) wird jetzt zusaetzlich
berechnet, wie viele Etiketten pro Blatt passen, wenn man sie um 90
Grad dreht (Breite/Hoehe vertauscht). Passt die gedrehte Anordnung mehr
drauf, wird sie verwendet - die Etiketten selbst bleiben inhaltlich
unveraendert (nur auf dem Blatt gedreht), jedes einzelne Etikett bleibt
beim Abziehen normal lesbar.

Bei explizit gesetzten Spalten/Zeilen (z.B. fuer ein vorgedrucktes
Avery-Raster) greift die Drehung nicht - ein reales Blatt hat feste,
nicht drehbare Felder.

Rotation erfolgt via pdfkit-Rotation um die Mitte des Feldes: ein
Etikett, exakt 90 Grad um seinen eigenen Mittelpunkt gedreht, ergibt
immer eine vertauschte Bounding-Box an derselben Stelle - unabhaengig
von der Drehrichtungskonvention. 2 neue Tests belegen ueber die
Seitenzahl, dass die Drehung greift, wenn sie mehr Etiketten
unterbringt, und dass ein explizites Raster unangetastet bleibt.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 08:14:51 +02:00
Nick SzaunigandClaude Sonnet 5 1e53758753 fix: proxy.ts redirete staendig zurueck zu /login, obwohl eingeloggt
Backend Image / build-and-push (push) Successful in 2m19s
Frontend Image / build-and-push (push) Successful in 2m2s
Der voran gegangene Fix (proxy.ts nach src/ verschoben, damit Next.js
sie ueberhaupt erkennt) hat eine zweite, bislang tote Stelle live
geschaltet: eine Cookie-Pruefung auf "refreshToken", die zu /login
umleitet, wenn das Cookie fehlt - fuer /settings, /apiaries, /colonies,
/queens.

Dieses Cookie wird vom BACKEND gesetzt (eigene Domain,
apicorebackend.* statt apicore.*, Pfad /api/v1/auth) und ist dem
Browser zufolge nie Teil einer Anfrage an das Frontend - proxy.ts lief
auf der Frontend-Domain und konnte dieses Cookie strukturell nie sehen.
Solange die Datei am falschen Ort lag und nie ausgefuehrt wurde, blieb
das folgenlos; sobald sie lief, hat sie jeden Aufruf dieser Seiten
serverseitig zurueck zu /login geschickt, unabhaengig vom tatsaechlichen
Login-Status.

Die Cookie-Pruefung komplett entfernt statt repariert: es gibt keinen
Weg, ein cross-origin gesetztes Cookie hier sichtbar zu machen, ohne
seinen Schutz (Pfad-Scoping) aufzuweichen. Die echte Session lebt
ohnehin nur clientseitig (Access-Token im Speicher, Refresh-Cookie nur
fuer Backend-Aufrufe) - src/app/(app)/layout.tsx prueft das bereits
korrekt und leitet bei Bedarf weiter. proxy.ts setzt jetzt nur noch die
Security-Header.

Lokal verifiziert: /settings/users ohne jedes Cookie liefert jetzt 200
statt eines Redirects, CSP zeigt weiterhin den korrekten
Laufzeit-API_URL.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-03 01:20:51 +02:00
Nick SzaunigandClaude Sonnet 5 c95e1c4b18 fix: Login komplett kaputt seit dem Security-Header-Commit - CSP blockierte jede API-Anfrage
Backend Image / build-and-push (push) Successful in 2m16s
Frontend Image / build-and-push (push) Successful in 2m1s
Root Cause, zweifach:

1. proxy.ts lag im Projekt-Root statt unter src/ (wo app/ tatsaechlich
   liegt). Next.js hat die Datei dadurch nie erkannt - ueberprueft via
   Build-Output ("Proxy (Middleware)" fehlte komplett) und via
   routes-manifest.json. Das betraf nicht nur die neuen
   Security-Header, sondern auch die schon laenger bestehende
   Login-Redirect-Logik, die dadurch vermutlich nie serverseitig
   gegriffen hat - vorher unbemerkt, weil Client-seitige Auth-Checks
   das kaschiert haben.

2. Die CSP (mit dynamischem connect-src aus API_URL) lag in
   next.config.ts's headers() - die wird beim Docker-Build EINMAL
   aufgeloest und ins Standalone-Output eingefroren, nicht bei jedem
   Container-Start neu gelesen, wie im Code-Kommentar faelschlich
   behauptet. Jedes Deployment bekam dadurch permanent
   "connect-src http://localhost:3001" (den Fallback, da API_URL beim
   Build nicht gesetzt ist) - unabhaengig vom tatsaechlich im Pod
   gesetzten API_URL. Jede Anfrage vom Frontend ans Backend (Login,
   SSO-Status, Herkunftsseite) wurde dadurch von jedem Browser
   CSP-blockiert.

Fix: CSP + Security-Header ziehen nach proxy.ts (jetzt korrekt unter
src/) um - Proxy laeuft mit Node.js-Runtime frisch bei jeder Anfrage,
liest process.env.API_URL also korrekt zur Laufzeit. next.config.ts
behaelt nur noch die statische sw.js-Cache-Control-Regel.

Lokal end-to-end verifiziert: Build zeigt jetzt "ƒ Proxy (Middleware)"
(vorher komplett abwesend), CSP-Header zeigt nach Server-Start einen
distinct getesteten API_URL-Wert korrekt an (nicht mehr den
Localhost-Fallback), und ein echter Login-Klick im Browser schickt die
Anfrage jetzt an die richtige, zur Laufzeit gesetzte Backend-Origin.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 21:44:23 +02:00
Nick SzaunigandClaude Sonnet 5 cd24523ea5 fix: PermissionsGuard blockiert seit dem Fail-Closed-Umbau alle @Public-Routen
Backend Image / build-and-push (push) Successful in 3m14s
Frontend Image / build-and-push (push) Successful in 1m54s
Beim Umstellen von PermissionsGuard auf Fail-Closed wurde der
@Public()-Check vergessen. JwtAuthGuard ueberspringt bei @Public()
zwar die Authentifizierung, aber PermissionsGuard laeuft als
eigenstaendiger, separat registrierter APP_GUARD trotzdem mit - und
verweigerte seitdem jede Route ohne @RequirePermission/
@AnyAuthenticatedUser, selbst wenn sie @Public() war. Betroffen: health
und health/ready (die gemeldeten 403), aber ebenso Login, SSO-Flow und
die oeffentliche Herkunftsseite.

PermissionsGuard prueft jetzt @Public() als erstes und laesst diese
Routen durch, genau wie JwtAuthGuard es bereits tut. Regressionstest
ergaenzt.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 20:46:24 +02:00
Nick SzaunigandClaude Sonnet 5 351f9ef34c feat: Abfüllungs-Archiv und Social-Media-Links auf der Herkunftsseite
Abfüllungen hatten keine Übersichtsseite - die Navigation ging direkt
auf /bottling/new, sodass eine angelegte Abfüllung danach nirgends
mehr sichtbar war. Neue Seite /bottling listet alle Abfüllungen
(Archiv), Klick führt zur bestehenden Detail-/Bearbeiten-Seite.
Navigationslink zeigt jetzt auf die Liste statt direkt auf "Neu".

Neues Modul site-settings (Singleton-Tabelle SiteSettings) für
YouTube-/Instagram-/TikTok-Links, verwaltbar unter Verwaltung > Website
(neue Berechtigung site-settings:read/update). Die öffentliche
Herkunftsseite (/herkunft/[lot]) zeigt die konfigurierten Links jetzt
zusätzlich zur Chargen-Herkunft an - PublicService liefert sie über
das bereits oeffentliche /public/batches/:lotNumber mit aus, keine
zusaetzliche unauthentifizierte Route noetig.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 19:57:29 +02:00
Nick SzaunigandClaude Sonnet 5 33fa768062 fix: Mindestlaenge fuer JWT_ACCESS_SECRET beim Start erzwungen
Backend Image / build-and-push (push) Successful in 2m13s
Frontend Image / build-and-push (push) Successful in 1m59s
JWT_ACCESS_SECRET war zwar als Pflicht-Env-Var geprueft, aber ohne
Mindestlaenge - ein kurzer/erratbarer Wert haette nicht nur das
Faelschen von Access-/MFA-/SSO-Tokens fuer jeden Nutzer erlaubt,
sondern auch (ueber mfa-crypto.util.ts, das den AES-Schluessel fuer
gespeicherte TOTP-Secrets aus genau diesem Wert ableitet) das
Entschluesseln aller gespeicherten MFA-Secrets.

main.ts bricht den Start jetzt mit klarer Fehlermeldung ab, wenn
JWT_ACCESS_SECRET kuerzer als 32 Zeichen ist. .env.example und das
K8s-Secret-Template zeigen jetzt einen ausreichend langen Platzhalter
mit Erzeugungshinweis (openssl rand -hex 32).

Betrifft nur main.ts's bootstrap() - e2e-Tests bootstrappen ueber
test/utils/create-test-app.ts direkt via configureApp() und durchlaufen
diese Pruefung nicht, daher keine Testanpassung noetig.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 15:17:39 +02:00
Nick SzaunigandClaude Sonnet 5 4bd7e9791a fix: Robustheits-Luecken aus zweitem, unabhaengigem Sicherheitsaudit behoben
Ein zweiter, unabhaengiger Audit-Durchlauf (frischer Blick, keine
Vorkenntnis meiner eigenen Fixes) bestaetigte alle bisherigen Fixes als
korrekt verdrahtet und fand 3 LOW-severity Robustheitsluecken - keine
davon war ausnutzbar, aber alle sind jetzt behoben:

- HarvestsService.create/ExtractionsService.create pruefen jetzt, dass
  referenzierte colonyIds/harvestIds tatsaechlich existieren, bevor die
  Transaktion laeuft - vorher fuehrte eine unbekannte Id zu einem
  ungefangenen 500 mit rohem Prisma-Fehler statt einer sauberen 400.
- Etiketten-Bildelemente durchlaufen jetzt denselben Magic-Byte-Check
  wie Avatar-/Dokument-Uploads, statt pdfkits Bildparser ungepruefte
  Bytes zu uebergeben.
- Webhook-Secret-Redaktion im Audit-Log nutzt jetzt explizites delete
  statt sich auf Prismas JSON-Serialisierung zu verlassen, die
  undefined-Werte verwirft (fragil, falls sich die Serialisierung mal
  aendert).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 15:13:02 +02:00
Nick SzaunigandClaude Sonnet 5 f57cffe6a8 fix: Rechteausweitung ueber Rollen-Rechtezuweisung geschlossen
RolesService.create/update uebernahm eine explizit uebergebene
permissionKeys-Liste ungeprueft. Ein Nutzer mit nur roles:update
konnte damit JEDER Rolle - auch der eigenen, ohne users:assign-role -
beliebige weitere Rechte (bis hin zu allen 92 Katalog-Eintraegen)
hinzufuegen, da das Bearbeiten der eigenen Rollen-Rechte nicht ueber
User.roleId laeuft und daher vom bereits gefixten
users:assign-role-Check nicht erfasst wurde.

Neue Regel (Standard-RBAC-Prinzip "keine Rechte ueber das eigene
Rechte-Set hinaus vergeben"): create/update pruefen jetzt, dass der
aufrufende Nutzer jedes zu vergebende Recht selbst bereits besitzt,
bevor die Rolle angelegt/geaendert wird.

roles.service.ts hatte bislang keine Unit-Tests; neue Spec-Datei deckt
den Fix sowie die uebrigen Methoden (create/update/deactivate/
reactivate) ab.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 14:58:44 +02:00
Nick SzaunigandClaude Sonnet 5 8b8616ce03 fix: Etiketten werden beim Drucken abgeschnitten, wenn Spalten/Zeilen zu gross gewaehlt sind
Eine explizit im Etiketten-Designer eingetragene Spalten-/Zeilenzahl
wurde beim PDF-Rendern ungeprueft uebernommen. Passt sie nicht auf ein
A4-Blatt (z.B. 90mm breite Etiketten x 4 Spalten bei 5mm Rand), ragen
die hinteren Spalten/Zeilen ueber den Blattrand hinaus - der Drucker
schneidet sie dann einfach ab, statt sie zu drucken.

- Backend (label-templates.service.ts): Spalten/Zeilen werden jetzt auf
  das tatsaechlich auf A4 passende Maximum begrenzt, bevor gerendert
  wird - betrifft auch bereits gespeicherte Vorlagen mit zu grossen
  Werten.
- Frontend (label-designer.tsx): zeigt die maximal passende Spalten-/
  Zeilenzahl direkt im Seiten-Layout-Panel an und warnt inline, wenn
  der eingetragene Wert das Blatt sprengen wuerde.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 14:53:15 +02:00
Nick SzaunigandClaude Sonnet 5 19d5e5d65c fix: PermissionsGuard schliesst jetzt statt bei fehlendem Decorator zu oeffnen
PermissionsGuard erlaubte bisher jede Route ohne @RequirePermission
automatisch fuer jeden eingeloggten Nutzer (fail-open) und verliess sich
allein auf test/authorization-coverage.e2e-spec.ts, um eine vergessene
Decorator-Angabe vor dem Deploy zu erwischen - der Guard selbst hatte
kein Default-Deny.

Neuer Decorator @AnyAuthenticatedUser() macht das bisher stillschweigend
erlaubte Verhalten explizit fuer die 12 Routen, die es tatsaechlich
brauchen (ProfileController komplett, NotificationsController komplett,
AuthController.me - alle bereits vorher auf der Allowlist des e2e-Tests).
PermissionsGuard verweigert jetzt jede Route, die weder
@RequirePermission noch @AnyAuthenticatedUser traegt - eine vergessene
Decorator-Angabe schlaegt jetzt zur Laufzeit fehl (403), nicht mehr nur
im Test. Der e2e-Test prueft jetzt denselben Decorator-Vertrag statt
einer separat gepflegten Allowlist, kann also nicht mehr vom
Guard-Verhalten abweichen.

Neue Unit-Tests fuer PermissionsGuard decken beide Codepfade und das
Fail-Closed-Verhalten ab.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 14:44:28 +02:00
Nick SzaunigandClaude Sonnet 5 b7ff92abd3 fix: SSO-MFA-Umgehung, Datei-Magic-Bytes und oeffentliches Swagger geschlossen
Weitere vom Sicherheitsaudit gemeldete Luecken behoben, die bisher als
niedrige Prioritaet offen gelassen wurden:

- SSO-Login (Entra ID) prüft jetzt lokale MFA genauso wie der
  Passwort-Login: ist mfaEnabled gesetzt, liefert /auth/sso/complete
  denselben mfaRequired-Codepfad wie /auth/login, bevor Tokens
  ausgestellt werden. Frontend (Login-Seite, AuthContext) zeigt in dem
  Fall den bestehenden Code-Eingabe-Schritt statt die Anmeldung
  stillschweigend abzuschliessen.
- Avatar- und Dokument-Uploads pruefen jetzt die tatsaechlichen
  Datei-Bytes (Magic Numbers fuer PNG/JPEG/WEBP/PDF) statt nur dem
  client-angegebenen MIME-Typ/data:-URL-Praefix zu vertrauen.
- Swagger-UI (/api/v1/docs) ist jetzt standardmaessig deaktiviert
  (ENABLE_SWAGGER=true erforderlich), da sie zuvor das komplette
  API-Schema ungeschuetzt oeffentlich preisgab.

npm-audit-Warnungen (deepmerge-ts/mysql2 unter prisma) bewusst nicht
gefixt: kein stabiles Prisma-Release loest sie bislang auf, nur eine
Release-Candidate-Hauptversion - das Risiko eines Downgrades/RC-Upgrades
der Datenbankschicht uebersteigt den Nutzen fuer eine Kette, die zur
Laufzeit ohnehin nicht erreichbar ist (nur Prisma-CLI, nur MySQL-Treiber,
dieses Projekt nutzt Postgres).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 09:09:58 +02:00
Nick SzaunigandClaude Sonnet 5 1b16b49913 fix: Rechteausweitung ueber Rollenzuweisung und weitere Auth-Haertung
Ein Sicherheitsaudit deckte auf, dass jede Rolle mit users:update sich
selbst per PATCH /users/:id auf eine Admin-Rolle heraufstufen konnte,
da roleId ohne eigene Berechtigung und ohne Selbstbezug-Pruefung
akzeptiert wurde. Ausserdem lag ein fest im Code hinterlegtes
Standard-Admin-Passwort vor, das bei fehlender SEED_ADMIN_PASSWORD-Env
greift.

- Neue Berechtigung users:assign-role, getrennt von users:create/update,
  fuer jede Rollenzuweisung erforderlich
- Selbstaenderung der eigenen Rolle serverseitig blockiert (auch fuer
  Admins), ebenso Selbst-Reset der eigenen MFA ueber die Admin-Route
- Seed erzeugt bei fehlender Env ein zufaelliges statt eines fixen
  Passworts
- Wiederverwendung eines bereits rotierten Refresh-Tokens (Diebstahl-
  Indiz) widerruft nun alle Sessions des betroffenen Nutzers
- Rate-Limit auf MFA-Enrollment-Bestaetigung ergaenzt

Co-Authored-By: Claude Sonnet 5 <[email protected]>
2026-09-02 08:57:42 +02:00
Nick Szaunig 7bd2a3dc3a feat: Sicherheits-Header fuer das Frontend ergaenzt
apicore.encry.de lieferte bisher gar keine Security-Header aus (per
curl bestaetigt) - CSP, X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, HSTS fehlten komplett, waehrend das Backend das
laengst per helmet() macht. Ergaenzt fuer alle Routen. Bewusst ohne
CSP-Nonce (haette dynamic rendering fuer jede Seite erzwungen und
damit die bestehende statische Optimierung gekostet) - stattdessen
script-src/style-src 'unsafe-inline', aber default-src/connect-src/
img-src/object-src/frame-ancestors/form-action/base-uri strikt
eingeschraenkt. Lokal mit dem echten Production-Build (standalone
server + next.config.ts) verifiziert: Login-Seite rendert/hydratisiert
sauber, keine CSP-Verstoesse in der Konsole, Header per curl bestaetigt.
2026-09-02 08:45:56 +02:00
Nick Szaunig 135738fdfc fix: Profilbild-Upload auf JSON/Base64 umgestellt statt multipart
POST /profile/avatar schlug live mit net::ERR_FAILED fehl (Verbindung
abgebrochen, keine Antwort) - selbst bei einer winzigen Testdatei.
CORS-Preflight lief sauber durch, nur der eigentliche multipart-POST
nicht; passt zum bereits dokumentierten Cloudflare-Verhalten bei
multipart-Uploads (siehe .gitea/workflows/*-image.yml). Umgangen durch
Umstellung auf denselben JSON-Content-Type, der fuer alle anderen
Endpunkte nachweislich zuverlaessig funktioniert: Bild wird
clientseitig als data:-URL kodiert und im JSON-Body gesendet.
Globales JSON-Body-Limit auf 5mb angehoben (deckt 2MB-Bild plus
Base64-Overhead).
2026-09-02 08:32:08 +02:00
Nick Szaunig abe1f6e8cf fix: Etiketten-Import zeigt konkreten Grund statt generischem Fehler
Eine KI-erzeugte JSON ohne Kenntnis des echten Schemas (z.B. eigene
Feldnamen wie xMm/yMm oder erfundene Elementtypen wie rectangle/svg)
scheiterte bisher mit "Datei enthält keine gültige Etikettenvorlage"
ohne Hinweis, was falsch ist. Jetzt wird pro Element benannt, was
fehlt oder welcher Typ nicht unterstützt wird.
2026-09-01 20:13:36 +02:00
Nick Szaunig c6941a797f feat: Etiketten-Seitenlayout auf dem A4-Bogen frei waehlbar
Backend Image / build-and-push (push) Successful in 2m16s
Frontend Image / build-and-push (push) Successful in 2m0s
- LabelTemplate bekommt columns/rows (optional, leer = automatisch
  passend), plus konfigurierbare Raender/Abstaende - damit ein Etikett
  exakt auf das vorgedruckte Raster eines physischen Etikettenbogens
  (z.B. Avery) gelegt werden kann statt nur automatisch berechnet
- Startposition beim Drucken waehlbar, fuer bereits angebrochene Boegen
  (erste Seite startet ab der gewaehlten Position, Folgeseiten wieder
  von vorne)
2026-09-01 19:43:01 +02:00
Nick Szaunig 9659b8d266 feat: Etiketten-Editor erheblich verbessert - Import/Export, Zoom, Duplizieren
- Vorlage als JSON exportieren/importieren, damit man sich ein Etikett
  auch per KI erstellen und danach importieren kann
- Zoom-Regler fuer den Canvas (50-200%), da Etikettgroessen stark variieren
- X/Y-Positionsfelder pro Element fuer praezise Platzierung statt nur Drag
- Element duplizieren-Button
- Entf/Backspace loescht das ausgewaehlte Element
2026-09-01 19:21:24 +02:00
Nick Szaunig 55b6dff31b fix: Kontomenü stürzt beim Öffnen ab (Base UI error #31)
Backend Image / build-and-push (push) Successful in 3m9s
Frontend Image / build-and-push (push) Successful in 2m55s
DropdownMenuLabel (rendert Menu.GroupLabel) stand direkt im
DropdownMenuContent statt in einem DropdownMenuGroup - Base UI wirft
dafür beim Öffnen einen ungefangenen Fehler, der den kompletten
React-Baum crasht. Das war die eigentliche Ursache des seit Sitzungsbeginn
gemeldeten "This page couldn't load" beim Klick auf das Profilbild
oben rechts, nicht der Service Worker (der war schon korrekt).
2026-09-01 17:23:39 +02:00
Nick Szaunig 938239e16e fix: fehlende Dateien fuer Etiketten-Elemente-Umbau nachreichen
Backend Image / build-and-push (push) Successful in 2m19s
Frontend Image / build-and-push (push) Successful in 1m55s
git add -u im letzten Commit hat neue (untracked) Dateien übersprungen:
die Migration für LabelTemplate.elements, die neuen Typ-/DTO-Dateien und
den kompletten Drag-and-Drop-Editor fehlten dadurch im Build.
2026-09-01 16:59:48 +02:00
Nick Szaunig 5f4e1a7ba9 fix: Bug fixed für Rollen
Backend Image / build-and-push (push) Failing after 1m12s
Frontend Image / build-and-push (push) Successful in 1m14s
2026-09-01 16:54:42 +02:00
Nick Szaunig 8432313fa9 feat: Profilseite, Etikettendruck mit QR-Code, mehr Automatisierungen und Entra-ID-SSO
Backend Image / build-and-push (push) Successful in 2m28s
Frontend Image / build-and-push (push) Successful in 2m10s
- Profilseite mit MFA-Einrichtung, Passwort/E-Mail/Name aendern, Konto deaktivieren
- Bearbeiten-Buttons fuer Bienenstaende/Voelker/Koeniginnen/Aufgaben-Datensaetze
- Restliche englische Enum-Werte durch deutsche Labels ersetzt (SelectValue-Fix)
- Sidebar-Eintraege fuer Ernten/Schleuderungen ergaenzt
- Automatisierungen von 3 auf 6 Regeln erweitert (Schwarmstimmung, kritischer Gesundheitsstatus, Lagerbestand)
- Responsive Grid-Layout fuer Volk-/Koeniginnen-/Honigchargen-/Bienenstand-Detailseiten
- Etikettenvorlagen mit Platzhaltern inkl. QR-Code (verweist auf Herkunftsseite) und PDF-Druck
- Entra-ID-SSO (Authorization Code + PKCE, kein Auto-Provisioning, Secrets nur ueber Environment-Variablen)
2026-08-31 18:56:08 +02:00
Nick Szaunig 0a52b29c57 fix: Bug fixed für alle Seiten und das Profil und label
Backend Image / build-and-push (push) Successful in 2m44s
Frontend Image / build-and-push (push) Successful in 2m47s
2026-08-27 15:15:55 +02:00
Nick Szaunig 133edcd5ba fix: fehlende Dateien fuer Volk-Ueberarbeitung nachreichen
Backend Image / build-and-push (push) Successful in 1m16s
Frontend Image / build-and-push (push) Successful in 3m7s
labels.ts und die ColonyCondition-Migration waren untracked und fehlten
im letzten Commit, dadurch schlug der Docker-Build mit "Module not
found: @/lib/colonies/labels" fehl.
2026-08-27 10:51:07 +02:00
Nick Szaunig 8520678a48 Volk Überarbeittung und Englisch Varaiblen ersetzt
Backend Image / build-and-push (push) Successful in 3m43s
Frontend Image / build-and-push (push) Failing after 1m10s
2026-08-27 10:38:06 +02:00
Nick Szaunig 9282777ac9 Env Update
Backend Image / build-and-push (push) Successful in 3m8s
Frontend Image / build-and-push (push) Successful in 2m24s
2026-08-27 08:32:47 +02:00
Nick Szaunig f2b9993427 fix: OOM-Crash beim Seed-Schritt durch Vorkompilieren beheben
Backend Image / build-and-push (push) Successful in 3m11s
Frontend Image / build-and-push (push) Successful in 2m46s
ts-node prisma/seed.ts lief bisher live im Container - der
TypeScript-Compiler dafuer braucht deutlich mehr Heap als das
512Mi-Pod-Limit, daher sporadische "JavaScript heap out of memory"-
Crashes beim Start (manchmal lief's, manchmal nicht, reine
GC-Zufaelligkeit an der Speichergrenze).

Fix: seed.ts wird jetzt wie main.ts beim Docker-Build vorkompiliert
(tsconfig.seed.json, rootDir bewusst auf prisma/ statt Projekt-Root
gesetzt, damit der relative Import '../generated/prisma' nach dem
Kompilieren weiterhin auf denselben Pfad zeigt). Laufzeit-CMD startet
nur noch plain `node dist/seed.js` - kein TypeScript-Compiler mehr im
Container. ts-node/typescript zurueck zu devDependencies (nur noch
fuer den Build-Schritt gebraucht, nicht mehr zur Laufzeit).

incremental in tsconfig.seed.json explizit deaktiviert - ein stale
tsconfig.seed.tsbuildinfo liess tsc beim zweiten Testlauf sonst
faelschlich "nichts zu tun" melden (derselbe Fallstrick wie bei
tsconfig.build.tsbuildinfo).

Verifiziert: tsc/eslint clean, 304/304 Tests, Build reproduzierbar
(zweimal hintereinander ohne Cache-Reset getestet), node dist/seed.js
lief erfolgreich gegen die echte lokale DB (erkannte den bestehenden
Admin korrekt und legte ihn nicht erneut an).
2026-08-27 08:02:58 +02:00
Nick Szaunig ce3a0c44ac feat: globales HTTP-Access-Log und Exception-Logging
Backend Image / build-and-push (push) Successful in 2m50s
Frontend Image / build-and-push (push) Successful in 2m22s
Bisher waren 4xx-Fehler (falsches Passwort, Validierungsfehler,
fehlende Berechtigung) im Log komplett unsichtbar - Nest loggt
standardmaessig nur unbehandelte 5xx-Fehler. Ein Login, das aus
Infra-Gruenden fehlschlaegt (siehe TLS/Cloudflare-Vorfall), war dadurch
allein aus den Logs nicht diagnostizierbar.

- AllExceptionsFilter (global): loggt jede Exception mit Methode/Pfad/
  Status (4xx als warn, 5xx als error mit Stacktrace). Client-Antwort
  bleibt unveraendert - HttpExceptions liefern weiter exakt ihren
  bisherigen Body, unbehandelte Fehler weiter nur eine generische
  500-Meldung ohne interne Details.
- requestLoggerMiddleware (global): eine Zeile pro Request (Methode,
  Pfad, Status, Dauer), damit auch erfolgreiche Requests im Log
  auftauchen - vorher gab's dafuer gar keine Spur.
- AuthService.login()/verifyMfaAndLogin(): konkrete Ursache pro
  Fehlschlag (unbekannter User, deaktiviert, falsches Passwort,
  falscher MFA-Code) zusaetzlich zum bestehenden Audit-Log-Eintrag.

Nirgends werden Passwoerter, Tokens oder Request-Bodies geloggt - nur
Methode, Pfad, Status, Dauer und die (bereits client-sichere)
Fehlermeldung.

Verifiziert: tsc/eslint clean, 304/304 Tests (7 neu), Build clean.
2026-08-27 07:29:51 +02:00
Nick Szaunig 05876838b0 feat: Admin-Seed automatisch beim Deploy ausfuehren
Backend Image / build-and-push (push) Successful in 2m35s
Frontend Image / build-and-push (push) Successful in 2m46s
Bisher wurde nirgends automatisch ein erster Admin-User angelegt -
prisma/seed.ts existierte, war aber weder im Dockerfile-CMD noch sonst
irgendwo verdrahtet. Auf einer frischen Datenbank haette also niemand
sich einloggen koennen.

- Dockerfile-CMD: migrate:deploy && seed && node dist/main.js
- ts-node/typescript von devDependencies zu dependencies verschoben
  (seed.ts laeuft als rohes TypeScript zur Laufzeit, kein
  Kompilierschritt), tsconfig.json zusaetzlich ins Runtime-Image kopiert
- seed.ts gegen Unique-Constraint-Race bei mehreren gleichzeitig
  startenden Pod-Replicas gehaertet (P2002 abgefangen statt Absturz)
- Login-Banner im Log nur beim tatsaechlichen Erst-Anlegen (URL aus
  FRONTEND_ORIGIN, Benutzername, Passwort) - bewusst nicht bei jedem
  Neustart, sonst wuerden veraltete/geaenderte Zugangsdaten weiter
  geloggt und Credentials landen unnoetig dauerhaft in den Logs

Seed ist bereits idempotent per E-Mail-Check (auch bei deaktivierten
Usern), UsersService.assertNotLastSystemRoleUser verhindert ohnehin
das Deaktivieren des letzten aktiven Admins - kein Aenderungsbedarf
dort, siehe Konversation.

Verifiziert: tsc/eslint clean, 297/297 Tests, Build clean. Migration
und Schema live gegen die lokale DB bestaetigt (ueber WSL direkt, da
Windows->WSL-Portweiterleitung weiterhin kaputt ist); der eigentliche
Seed-Lauf selbst konnte nicht End-to-End gegen die DB verifiziert
werden (gleiches Verbindungsproblem), Banner-Ausgabe isoliert
smoke-getestet.
2026-08-27 06:38:23 +02:00
Nick Szaunig 62004483cf feat: MFA (TOTP) und Self-Service-Profilseite
Backend Image / build-and-push (push) Successful in 2m57s
Frontend Image / build-and-push (push) Successful in 2m30s
Benutzer koennen jetzt selbst Zwei-Faktor-Authentifizierung einrichten
und ihr Profil verwalten (Name, E-Mail, Passwort, Profilbild).

Backend:
- Neues MfaModule (src/auth/mfa/): AES-256-GCM-verschluesselte
  TOTP-Secrets (otplib), QR-Code-Erzeugung, kurzlebige Setup-/
  Challenge-JWTs fuer den 2-Schritt-Login-Flow
- Neues ProfileModule (src/profile/): PATCH /profile,
  POST /profile/password, POST/DELETE/GET /profile/avatar,
  POST /profile/mfa/enroll(/confirm), POST /profile/mfa/disable -
  alle self-scoped ueber actor.id, ohne @RequirePermission (siehe
  ALLOWLIST in authorization-coverage.e2e-spec.ts)
- AuthService.login() zweistufig: bei aktivem MFA erst
  { mfaRequired, mfaToken } statt Tokens, neuer Endpunkt
  POST /auth/mfa/verify schliesst den Flow ab
- Admin-Recovery: POST /users/:id/mfa/reset (users:update) fuer
  verlorene MFA-Geraete, da es keinen E-Mail-Reset-Flow gibt

Frontend:
- Login-Seite mit zweitem Schritt fuer den TOTP-Code
- Neue Profilseite (/profile, ausserhalb des admin-only
  Settings-Bereichs): Profilbild, Name/E-Mail, Passwort, MFA-Ein-/
  Ausrichtung mit QR-Code-Dialog
- Header zeigt jetzt Avatar mit Dropdown (Profil/Abmelden) statt
  nur E-Mail-Text
- Admin-Users-Tabelle zeigt MFA-Status + Reset-Button

Haertung nach Security-Review (Backend/Frontend-Diff, kein CRITICAL,
mehrere MEDIUM behoben):
- Access-Token wird bei Passwortwechsel sofort ungueltig
  (User.passwordChangedAt + JWT-iat-Vergleich in
  PermissionResolverService.isTokenValid, vorher isUserActive) statt
  erst nach bis zu 15 Minuten
- E-Mail-Aenderung verlangt jetzt aktuelles Passwort
- POST /profile/password und /profile/mfa/disable haben jetzt das
  gleiche strikte Throttling wie Login (SENSITIVE_ACTION_THROTTLE)
- Kein stiller leerer Verschluesselungs-Key-Fallback mehr (wirft
  jetzt explizit)

Bewusst zurueckgestellt (dokumentiert, kein aktiver Exploit-Pfad):
dedizierter Verschluesselungs-Key fuer MFA-Secrets statt
JWT_ACCESS_SECRET-Ableitung, dedizierte users:mfa-reset-Permission
statt users:update, separate JWT-Secrets pro Token-Zweck.

Verifiziert: tsc/eslint clean, 297/297 Backend- + 31/31
Frontend-Unit-Tests, Build clean auf beiden Projekten. e2e weiterhin
durch bekanntes WSL2/Postgres-Problem blockiert. Unabhaengiges
Security- und Code-Quality-Review durchgefuehrt (0 CRITICAL, 0 HIGH).
2026-08-27 00:25:34 +02:00
Nick Szaunig 6776bdfe73 fix: Root und 404 leiten unangemeldete User direkt zu /login
Backend Image / build-and-push (push) Successful in 2m52s
Frontend Image / build-and-push (push) Successful in 2m38s
Bisher gingen "/" und unbekannte URLs immer erst zu /dashboard, das
dann bei fehlendem Login zu /login weiterleitete - unnoetiger Umweg.
Beide pruefen jetzt selbst den Auth-Status (gleiches Muster wie
(app)/layout.tsx: waehrend isLoading nichts tun, danach je nach User
direkt zu /dashboard oder /login).
2026-08-26 23:21:06 +02:00
Nick Szaunig b3157484c4 feat: Unbekannte und nicht erlaubte Seiten leiten zum Dashboard um
Backend Image / build-and-push (push) Successful in 2m55s
Frontend Image / build-and-push (push) Successful in 2m23s
- src/app/not-found.tsx: jede nicht existierende Route landet auf
  /dashboard statt der Next.js-Standard-404-Seite (der bestehende
  Auth-Guard in (app)/layout.tsx schickt nicht angemeldete User von
  dort automatisch weiter zu /login).
- settings/layout.tsx: fehlt die Berechtigung fuer den
  Einstellungen-Bereich, wird jetzt zu /dashboard umgeleitet statt nur
  einen Hinweistext anzuzeigen (gleiches Redirect-Muster wie der
  bestehende Login-Guard).

Login-nach-Anmeldung-Redirect zu /dashboard und der Auth-Gate in
(app)/layout.tsx bestanden bereits.
2026-08-26 23:14:47 +02:00