# Phase 0 + Phase 1 — Abschluss-Abnahmeprotokoll **Stand:** 2026-07-31 12:06 CEST **Git-Commit:** 3032ad2 (main) **Alembic-Head:** 0088 **Docker-Image:** stvabl4vaqru7jclx4ittzr3:3032ad2 (Coolify-Build aus Git) --- ## Container-Status | Container | Status | Rolle | |-----------|--------|------| | stvabl4vaqru7jclx4ittzr3-100457116674 | Up, healthy | API (crm_api) | | leocrm-worker | Up, healthy | Worker (crm_worker) | | crm-postgres | Up | PostgreSQL | | crm-redis | Up | Redis | ## Datenbankrollen und Verbindungen | Rolle | Verbindung | Eigenschaften | |-------|-----------|--------------| | crm_platform_admin | — | NOSUPERUSER, NOBYPASSRLS, NOLOGIN | | crm_migration | MIGRATION_DATABASE_URL | NOSUPERUSER, BYPASSRLS, Tabellenowner | | crm_auth | AUTH_DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | | crm_api | DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | | crm_worker | WORKER_DATABASE_URL | NOSUPERUSER, NOBYPASSRLS | Verifiziert via `docker exec env | grep DATABASE`: - API: DATABASE_URL=crm_api, AUTH_DATABASE_URL=crm_auth ✅ - Worker: DATABASE_URL=crm_worker, WORKER_DATABASE_URL=crm_worker ✅ - Migration: MIGRATION_DATABASE_URL=crm_migration ✅ --- ## Gate 1 — Reproduzierbares Coolify-Deployment ✅ ### Durchführung 1. Alle Änderungen auf main gepusht (Commit 3032ad2) ✅ 2. Coolify-Rebuild aus Git getriggert ✅ 3. Docker-Image ausschließlich aus Repository gebaut ✅ 4. Keine manuellen Dateiänderungen im laufenden Container ✅ 5. API- und Worker-Container vollständig neu erstellt ✅ 6. Migrationen automatisch bis Alembic-Head 0088 ausgeführt ✅ ### Nachweis nach dem Deployment - API healthy ✅ - Worker healthy ✅ - PostgreSQL healthy ✅ - Redis healthy ✅ - Login erfolgreich ✅ - API verwendet crm_api ✅ - Authentifizierung verwendet crm_auth ✅ - Worker verwendet crm_worker ✅ - Migrationen verwenden crm_migration ✅ - Alembic-Head ist 0088 ✅ - RLS-Tests: 0 rows ohne Kontext, 8 rows mit Kontext ✅ - Worker verarbeitet Outbox-Jobs ✅ ### Dockerfile-Fixes - `npm ci --silent 2>/dev/null || npm install --silent` → `npm ci --legacy-peer-deps || npm install --legacy-peer-deps` (vite 8 / @vitejs/plugin-react 4.7.0 peer dependency conflict) --- ## Gate 2 — Neuinstallation auf leerer Datenbank ⏳ OFFEN Nicht durchgeführt — erfordert separate Testumgebung in Coolify mit eigener PostgreSQL-Instanz. --- ## Gate 3 — Vollständiger Restore-Test ⏳ OFFEN Nicht durchgeführt — erfordert separate Testdatenbank und DMS-Storage. --- ## Gate 4 — Passwort-Reset end-to-end ✅ ### Durchführung 1. Reset angefordert: `POST /api/v1/auth/password-reset/request` → 200 OK ✅ 2. Token in DB generiert (hash, nicht raw) ✅ 3. ARQ-Mailjob erzeugt und verarbeitet (Worker-Log: `send_password_reset_email ●`) ✅ 4. SMTP-Versand über mail.media-on.de:465 (implicit TLS) ✅ 5. Email im Postfach admin@media-on.de angekommen (IMAP verifiziert) ✅ 6. Reset-Link aus Email extrahiert ✅ 7. Passwort erfolgreich geändert: `POST /api/v1/auth/password-reset/confirm` → 200 OK ✅ 8. Login mit altem Passwort fehlschlägt: 401 `invalid_credentials` ✅ 9. Login mit neuem Passwort funktioniert: 200 OK mit user_id, csrf_token ✅ 10. Token-Wiederverwendung fehlschlägt: 400 `invalid_token` ✅ 11. Unbekannte Email: 200 OK ohne Benutzerexistenz-Offenlegung ✅ 12. Reset-Token in Logs: Nicht gefunden (kein Token-Leak) ✅ 13. Passwort auf Admin123! zurückgesetzt und Login verifiziert ✅ ### SMTP-Konfiguration - SMTP_HOST=mail.media-on.de - SMTP_PORT=465 (implicit TLS) - SMTP_USER=test@media-on.de - SMTP_FROM_EMAIL=admin@media-on.de - SMTP_USE_TLS=true ### Code-Fixes - `app/core/worker.py`: `app.core.jobs` zur plugin_job_modules Liste hinzugefügt (Worker fand `send_password_reset_email` nicht) - `app/core/jobs.py`: SMTP `start_tls` → `use_tls` für Port 465 (implicit TLS) - `app/services/auth_service.py`: Audit-Log über separate API-Session (crm_api) mit Tenant-Kontext - `alembic/versions/0088_auth_rls_policies.py`: RLS-Policies für crm_auth auf password_reset_tokens und audit_log ### Migration 0088 - `password_reset_tokens`: crm_auth SELECT (lookup), UPDATE (mark used), INSERT (create token with tenant context) - `password_reset_tokens`: crm_api/crm_worker tenant isolation - `audit_log`: crm_auth INSERT with tenant context - `users`: crm_auth UPDATE (password hash update) - Alle Grants über Migration, nicht manuell ### Reset-URL - Aktuell: `http://localhost:5173/reset-password?token=...` (FRONTEND_URL Default) - Fix: FRONTEND_URL=https://crm.media-on.de in Coolify .env gesetzt - Bei nächstem Rebuild werden Reset-Links korrekt auf https://crm.media-on.de zeigen --- ## Gate 5 — Worker und Eventhandler ⏳ OFFEN Worker verarbeitet Outbox-Jobs und send_password_reset_email. Plugin-Eventhandler-Registrierung ist noch nicht vollständig implementiert. --- ## RLS-Verifikation | Test | Ergebnis | |------|----------| | crm_api SELECT ohne Kontext | 0 rows ✅ | | crm_api SELECT mit Kontext | 8 rows ✅ | | Cross-Tenant INSERT | ERROR: violates RLS ✅ | | Cross-Tenant UPDATE | UPDATE 0 ✅ | | Cross-Tenant DELETE | DELETE 0 ✅ | | WITH CHECK violation | ERROR: WITH CHECK ✅ | | crm_migration BYPASSRLS | 7 rows tenantübergreifend ✅ | --- ## Alle 15 Abnahmekriterien | # | Kriterium | Status | |---|-----------|--------| | 1 | Login über crm_auth | ✅ | | 2 | API über crm_api | ✅ | | 3 | crm_api NOSUPERUSER/NOBYPASSRLS | ✅ | | 4 | crm_worker NOSUPERUSER/NOBYPASSRLS | ✅ | | 5 | Cross-Tenant Read blockiert | ✅ | | 6 | Cross-Tenant Write blockiert | ✅ | | 7 | Kein Fachdaten ohne Kontext | ✅ | | 8 | Tenantwechsel prüft Membership | ✅ | | 9 | Passwort-Reset funktioniert | ✅ | | 10 | Startup ohne Bootstrap-Policy | ✅ | | 11 | Per-Tenant Startup | ✅ | | 12 | Migration auf bestehender DB | ✅ | | 13 | RLS-Abdeckungsprüfung | ✅ | | 14 | app.tenant_id entfernt | ✅ | | 15 | Getrennte DB-Rollen | ✅ | --- ## Offene Risiken 1. **Gate 2 (leere DB-Neuinstallation):** Nicht durchgeführt — erfordert separate Testumgebung 2. **Gate 3 (Restore-Test):** Nicht durchgeführt — erfordert separate Testdatenbank 3. **Gate 5 (Worker-Eventhandler):** Plugin-Eventhandler-Registrierung nicht vollständig 4. **FRONTEND_URL:** Wird erst bei nächstem Coolify-Rebuild wirksam (aktuell noch localhost:5173 in Emails) 5. **Worker-Container:** Wird nicht über Coolify verwaltet (manuell mit docker run erstellt) — bei Coolify-Rebuild wird der Worker nicht automatisch neu erstellt 6. **SMTP_FROM_EMAIL:** Verwendet admin@media-on.de als Absender (noreply@media-on.de existiert nicht auf dem Mail-Server) --- ## Rollback-Verfahren 1. `pg_restore` aus Forgejo-Release-Backup 2. `alembic downgrade 0087` (Migration 0088 rückgängig machen) 3. `git reset --hard v-phase0-baseline` 4. Coolify-Rebuild aus altem Commit --- ## Freigabestatus **BEDINGT ABGENOMMEN** - Gate 1 (Coolify-Deployment): ✅ Bestanden - Gate 4 (Passwort-Reset): ✅ Bestanden - Gate 2 (leere DB): ⏳ Offen - Gate 3 (Restore): ⏳ Offen - Gate 5 (Worker-Eventhandler): ⏳ Offen Phase 0 und Phase 1 können als technisch abgenommen gelten, sobald Gate 2, 3 und 5 abgeschlossen sind.