21 KiB
Boilerplate-Mapping: M37 — Plattform-Admin
Modul-ID: M37 (Detail-Spec) / Milestones M034–M041 (Block 1, Pflichtenheft 37.x)
Name: Plattform-Admin (admin.hivegrid.de)
Reuse-Level: medium
Verifiziert direkt gegen
/Users/raffaelsheikh/Documents/GitHub/boilerplate(Schema, Routes, Services). Inventur-Dateien als Ausgangspunkt, alle Aussagen mit eigenem Grep/Read gegengeprueft.
Kernbefund (zuerst lesen)
Die Boilerplate enthaelt eine substanzielle, fertige Admin-Welt (eigene Admin-Subdomain-Routing-Schicht, Admin-Sidebar, Admin-Login inkl. SSO + MFA-Step-up, RBAC, tamper-evidenter Audit-Trail, Impersonation-Infrastruktur, Lead-Inbox, Support-Tickets, Subscription-/Metrics-/Dashboard-Routen). Damit sind Themenblöcke E, F, G(lese), H, I, L, M, O zu grossen Teilen aus Vorhandenem ableitbar.
ABER: Die zentrale Architektur-Prämisse von M37 wird vom Boilerplate-Modell verletzt. Spec Block A/B verlangt eine eigene, parallele User-Klasse AdminUser mit eigenem password_hash, totp_secret, eigener AdminSession, eigener AdminRole/AdminPermission-RBAC und eigener AdminAuditLog-Tabelle. Im Boilerplate ist AdminUser jedoch kein eigenständiges Identity-Modell, sondern eine 1:1-Erweiterung von User:
// prisma/schema/accounts.prisma:112
model AdminUser {
userId String @id @unique
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
ssoProvider String? ssoSubjectId String? ssoGroups String[] lastSsoLoginAt DateTime?
}
D.h. Admin-Auth, Passwort, 2FA und Rollen laufen heute über dasselbe User-/UserRole-/Permission-Fundament wie Tenant-User (Admin-Status = User hat AdminUser-Zeile). Das ist exakt der „Generalschlüssel zwischen den Welten", den Spec Block A.2/A.3 und Milestone M034 (37.1) verbieten.
Konsequenz für die Planung: Vor jeder Übernahme muss eine Architektur-Entscheidung fallen:
- (a) Spec-treu (empfohlen lt. Spec): neue, vollständig getrennte
AdminUser-Identität +AdminSession+AdminRole/AdminPermission/AdminAuditLogneu bauen. Boilerplate-Admin-Auth dient dann nur als Vorlage (Login-Form, MFA-Flow, Rate-Limit, Session-Cookie-Policy), nicht 1:1. - (b) Boilerplate-treu: das bestehende
User+AdminUser-Extension-Modell behalten und Spec Block A/B abschwächen (Change-Request, da es Pflichtenheft 37.1 widerspricht).
Dieser Punkt ist der Haupt-Grund, warum Reuse medium und nicht high ist.
Übernehmbar aus Boilerplate
| Boilerplate-Pfad | Was | Wie nutzbar |
|---|---|---|
apps/web/app/routes/admin.tsx + apps/web/app/routes/admin/ (Tree) |
Komplette Admin-Route-Welt unter eigener Surface (accounts, users, subscriptions, roles-and-permissions, audit-trails, metrics, feature-flags, marketing-leads, help-desk, settings, events) inkl. host-aware Subdomain-Routing-Tests (admin.host-aware.test.ts) |
als-vorlage (Routen-Skelett + Subdomain-Trennung direkt; Inhalte HiveGrid-fachlich füllen) |
apps/web/app/application/sidebar/AdminSidebar.tsx + SidebarItem.ts + SidebarGroupDto.ts |
Statische, permission-getaggte Admin-Sidebar in Segmenten (Manage/Build/Market/Settings), data-ui-id-IDs, adminPath()-host-aware |
anpassen (Sidebar-Hauptpunkte aus Block E übernehmen; HiveGrid nutzt eigene packages/ui-hivegrid/src/app-shell/sidebar.config.ts — INTEGRATION-CONTRACTS §2) |
packages/url/src/canonical.ts (adminPath/adminUrl) + packages/config/src/routing.ts + packages/config/src/session-cookie.ts |
Host-aware Admin-URL-Builder, Domain-Routing, domain-/pfad-gelockte Session-Cookie-Policy | 1:1 (Cookie-Trennung admin. vs app. = Block A.3, M034) |
apps/web/app/routes/auth.admin.login.tsx, auth.admin.callback.tsx, auth.admin.logout.tsx |
Eigener Admin-Login-/Logout-Flow (heute via Authentik-OIDC) | als-vorlage (Flow-Gerüst; HiveGrid braucht Email+Passwort+TOTP statt OIDC — Block C; Magic-Link für Admin verbieten) |
apps/web/app/services/auth/admin-mfa-gate.server.ts (requireAdminMfaForAction, SENSITIVE_ADMIN_ACTIONS) + apps/web/app/utils/auth/mfa/* (enforcement.server, step-up.server) |
MFA-Step-up-Gate für sensible Admin-Aktionen — Taxonomie enthält bereits impersonation.start, tenant.delete, permission.grant, audit.export, data.export |
anpassen (deckt Block C 2FA-Pflicht + Block Q Reason-Gates fachlich; auf neue AdminUser-Identität umhängen) |
apps/api/src/auth/login-rate-limit.ts (createFixedWindowRateLimiter) + packages/shared-store (createFixedWindow/SlidingWindowRateLimiter) |
Login-Rate-Limit (IP + Account-Fingerprint, Redis) | anpassen (Spec C: 5/15min Lock, Captcha ab 3., 10/24h → locked) |
apps/web/app/utils/session.server.ts (Impersonation-State) + apps/web/app/utils/session.impersonation.test.ts |
Impersonation-Session-State (impersonatingFromUserId/StartedAt, Validierung) |
anpassen (Block G Impersonate; Spec: max 30 Min Cap + Reason-Pflicht ergänzen) |
apps/web/app/components/impersonation-banner.tsx + apps/web/app/root.tsx (Wiring) + root.impersonation-banner.test.tsx |
Rotes Klartext-Impersonation-Banner in der App-Shell (i18n, „Acting as … from …", Exit) | anpassen (Block G/M037: Banner-Text „Plattform-Support … Ticket #123" + Ticket-/Reason-Bezug) |
prisma/schema/accounts.prisma:266 ImpersonationAudit + packages/db/src/impersonation-audit.server.ts |
Impersonation-Audit-Tabelle (actorUserId/targetUserId/tenantId/action/reason/requestPath/metadata, SetNull) |
1:1 (deckt M037 Audit-Spur inkl. Reason) |
prisma/schema/core.prisma:55 AuditEvent + packages/db/src/audit-events.server.ts + packages/db/src/audit-hash.ts + packages/foundation/src/audit.ts (auditEvent, withAuditMiddleware) |
Tamper-evidenter, cross-tenant-fähiger Audit-Trail (Hash-Kette, tenantId?, actorType, action, targetType/Id, metadata, reiche Indizes) |
1:1 als AdminAuditLog-Ersatz — tenantId=null = plattform-/operator-weit deckt Block B AdminAuditLog fachlich vollständig ab; HiveGrid muss kein neues Audit-Schema bauen, nur action_type/target_kind-Vokabular definieren |
apps/web/app/routes/admin/audit-trails.tsx + audit-export.tsx/.helpers.server.ts + packages/db/src/logs.server.ts (getAdminAuditLogList, serializeAdminAuditLogsCsv) |
Cross-Tenant-Audit-Liste mit Filter (Akteur/Action/Zeitraum) + CSV-Export, permission-gated (admin.auditTrails.view) |
anpassen (Block L fast vollständig; Volltext-Suche in summary_text + JSON-Export ergänzen) |
packages/exports/src/audit-pdf.ts (serializeAuditEventsToPdf) |
Compliance-Audit-PDF (SOC2/HIPAA/GDPR-Evidence) | 1:1 (Block L Compliance-Export) |
prisma/schema/permissions.prisma (Role/Permission/RolePermission/UserRole/Group) + apps/web/app/routes/admin/accounts/roles-and-permissions.tsx + components/core/roles/* + packages/db/src/permissions-seed.server.ts |
Editierbares RBAC mit UI-Rollen-Editor + Permission-Seed | als-vorlage (Block D Bundle-Modell; bei Architektur (a) als AdminRole/AdminPermission parallel neu, Struktur 1:1 übertragbar) |
apps/web/app/utils/services/adminDashboardService.ts + packages/db/src/kpi.server.ts (getAdminTenantKpiCounts, getAdminActiveUserKpiCounts) + apps/web/app/utils/services/.server/subscriptionService.ts (getMrr) |
Admin-Dashboard-KPIs: Tenant-Count + Trend, MRR, aktive User | anpassen (Block F: MRR/ARPU/Churn/Expansion + Health-Kacheln + Sales-Pipeline ergänzen) |
apps/web/app/routes/admin/accounts/index.tsx + $id.tsx (Tenant) + users.tsx/users.test.tsx + blacklist.tsx + __ip-addresses/ |
Tenant-Liste/-Detail + User-Verwaltung + IP-Logs + Blacklist | anpassen (Block G/H: Status-Wechsel mit Reason, Notes, Trial-Verlängerung, Bulk-Actions ergänzen) |
apps/web/app/routes/admin/accounts/subscriptions.tsx + subscriptions/$id.tsx + subscriptions-revenue.tsx + prisma/schema/subscriptions.prisma (14 Modelle) + apps/web/app/utils/stripe.server.ts + packages/entitlements |
Vollständiger Stripe-Billing-Stack + Admin-Subscription-Routen + MRR | anpassen (Block I: manueller Override, Force-Sync, Refund, Sonderpreis-Vermerk ergänzen) |
prisma/schema/featureFlags.prisma (3 Modelle, append-only Audit) + packages/feature-flags (Engine v2, Targeting, Cache) + apps/web/app/routes/admin/feature-flags.tsx |
Feature-Flag-Engine + Admin-Verwaltung | 1:1 (Block J = Verweis auf Modul 32; Admin-Routes liegen bereits unter admin/feature-flags) |
prisma/schema/marketingLeads.prisma (Lead/LeadEvent/TenantAcquisition/MarketingIntegration, operator-scoped, kein tenantId) + apps/web/app/routes/admin/marketing-leads/index.tsx+$id.tsx + apps/web/app/utils/leads/* + packages/marketing-outbound |
Operator-scoped Lead-Inbox + Detail + Status-Pipeline + First-Touch-Attribution + Convert-to-Tenant (convertedToTenantId) + Activity-Timeline (LeadEvent) + Outbound-Adapter |
anpassen (Block M: deckt Lead-Inbox/Detail/Promote-to-Tenant/Activity-Stream sehr nah; fehlt: AdminDeal-Pipeline/Kanban, assigned_to, industry/company_size-Enums) |
prisma/schema/helpDesk.prisma (Feedback, SupportTicket mit source/status/priority/inboundMessageId-Dedup) + apps/web/app/modules/helpDesk/* + apps/web/app/routes/admin/help-desk.tsx + components/FeedbackTable.tsx |
Support-Ticket-Aufnahme inkl. Inbound-Email-Dedup + PII-Redaction + Admin-Inbox | anpassen (Block O: Konversation/Messages, Tags[], Assignee, Reply-Templates, Internal-Notes fehlen → erweitern) |
apps/web/app/routes/admin/metrics/ (summary.tsx, logs.tsx, settings.tsx) + packages/metrics (prom-client, AI-Cost-Counter) + apps/worker/src/admin-server.ts/health-server.ts + infra/grafana (queue-health, business-kpi Dashboards) |
Metrics-Admin-Routen + Worker-Status/Health-Server + Grafana-Dashboards | anpassen (Block K Health-Dashboard: Service-Status + Error-Rate als Vorlage; BullMQ-Queue-Steuerung-UI + KWP-Sync neu) |
prisma/schema/queue.prisma (QueueFailedJob BullMQ-DLQ, ExportJob) + apps/worker/src/jobs/queue-failed-jobs* + packages/db/src/queue-failed-jobs.server.ts |
Failed-Job-Tracking (DLQ) | anpassen (Block K Worker-Queues: Liste/Retry/Pause-UI darauf aufbauen) |
packages/foundation/src/permissions.ts (withTenantScope/withPermission/requirePermission) + apps/web/app/utils/helpers/.server/PermissionsService.ts (verifyUserHasPermission) |
Permission-Gating + 404-statt-403 (Existenz-Versteckung) | 1:1 (Querschnitt; Block D-Durchsetzung + AGENTS DoD C.17) |
packages/foundation/src/notifications.ts (notify/bundleNotifications) + prisma/schema/notifications.prisma + prisma/schema/email.prisma (Campaign/OutboundEmail) |
Notifications + Outbound-Email/Kampagnen (für Bulk-Email an Tenant-Admins, AVV-Re-Akzeptanz-Mails) | 1:1 / anpassen (Block G Bulk-Email, Block P AVV-Notification) |
packages/exports (CSV/JSON/XLSX) + apps/worker/src/jobs/{export,dsgvo-export}.ts + prisma/schema/queue.prisma ExportJob |
DSGVO-/Daten-Export-Pipeline (Tenant-Daten-Export-Anstoss, Block G) | 1:1 |
packages/foundation/src/errors.ts (CatalogError) + packages/view-models (VMState, ActionCallbacks, RegistryGate) + packages/i18n |
Klartext-Diagnostik, View-Model-Vertrag, i18n | 1:1 (Querschnitt; Block Q Klartext-Hinweise, AGENTS DoD A/E) |
Vorhandene Daten-Modelle (konkret, mit Pfad)
| Modell | Pfad | M37-Bezug |
|---|---|---|
AdminUser |
prisma/schema/accounts.prisma:112 |
nur Teilabdeckung — 1:1-Extension von User (userId @id + SSO-Felder). Kein eigenes passwordHash/totpSecret/status/disabledReason. Block B AdminUser braucht NEUES Modell oder Spec-Abschwächung. |
User |
prisma/schema/accounts.prisma:1 |
Auth-Fundament: passwordHash, totpSecretEncrypted, totpEnabledAt, mfaRecoveryCodeHashes, DSGVO-Flags. Vorlage für AdminUser-Auth-Felder. |
ImpersonationAudit |
prisma/schema/accounts.prisma:266 |
deckt M037-Audit (actor/target/tenant/action/reason/requestPath/metadata). |
AuditEvent |
prisma/schema/core.prisma:55 |
deckt AdminAuditLog fachlich (Hash-Kette, tenantId?, actor/target/action/metadata, Indizes). Kein neues Schema nötig. |
Role/Permission/RolePermission/UserRole/Group/GroupUser |
prisma/schema/permissions.prisma:1-78 |
RBAC-Fundament. Vorlage für AdminRole/AdminPermission (Block D). |
Tenant, TenantUser, TenantUserInvitation |
prisma/schema/accounts.prisma:129/286/298 |
Tenant-Verwaltung Block G/H (Status via active, Membership, Invitations). |
SubscriptionProduct…TenantSubscription, TenantFeatureOverride, Credit (14) |
prisma/schema/subscriptions.prisma |
Subscription-Management Block I (Stripe-IDs, Plan, Override). |
FeatureFlag/FeatureFlagFilter/FeatureFlagAudit |
prisma/schema/featureFlags.prisma |
Block J (Modul 32). |
Lead/LeadEvent/TenantAcquisition/MarketingIntegration |
prisma/schema/marketingLeads.prisma:14-143 |
Block M Lead-Inbox/Activity/Convert (operator-scoped, kein tenantId). Kein AdminDeal. |
Feedback/SupportTicket |
prisma/schema/helpDesk.prisma:1/12 |
Block O (Aufnahme + Inbound-Email-Dedup). Keine Ticket-Messages/Konversation. |
QueueFailedJob/ExportJob |
prisma/schema/queue.prisma |
Block K Worker-DLQ + Block G Export. |
AppConfiguration, Log, MetricLog, Event |
prisma/schema/core.prisma |
Health/Config-Querschnitt Block K. |
app.prisma (leerer Platzhalter) |
prisma/schema/app.prisma |
Vorgesehener Ort für neue HiveGrid-Admin-Modelle (AdminRole, AdminDeal, AdminTenantNote, Subprocessor, AvvVersion …). |
Lücken / komplett neu zu bauen
- Eigenständige
AdminUser-Identität (Block A/B, M034) — separate Tabelle mitemail/passwordHash/totpSecret/totpEnabled/status/disabledReason/createdByAdminUserIdgetrennt vonUser. Heute existiert nur dieUser-Extension. Architektur-Entscheidung blockierend (siehe Kernbefund). AdminSession(Block C, M034) — server-side Postgres-Sessions mit 8h-Cap, 1h-Inaktivität, IP-Bindung (Re-Auth bei IP-Wechsel), Revoke-Liste. Boilerplate nutzt Cookie-Sessions (session.server.ts), kein DB-Session-Modell mit IP-Bindung → neu (Vorlage:packages/shared-storecreateSessionStore).AdminRole/AdminPermission/AdminRolePermission/AdminUserRole(Block D) — eigene parallele RBAC mit Bundle-Stufen (none/read/write/admin,viewer/operator/owner). Struktur auspermissions.prismaableitbar, aber neue Tabellen + Default-Rollen-Seed (super_admin/support/sales/engineering/viewer) + Self-Permission-Schutz (letzter super_admin).AdminDeal+ Deal-Pipeline/Kanban (Block M) — kein Deal-Modell, kein Kanban-View, kein Drag&Drop.expected_arr_eur,seats,stage. Komplett neu (Lead-Teil ist abgedeckt).AdminTenantNote(Block B/G) — pro-Tenant Markdown-Notizen mit Kategorie/Pin/Archiv. Kein Modell vorhanden. Neu.- Subprozessoren- + AVV-Verwaltung (Block P, M041) —
Subprocessor,AvvVersion(+ Re-Akzeptanz-Tracking) existieren nicht im Schema. Komplett neu (Querschnitt Modul 35). - KWP-Sync-Health (Block K, M041) — kein KWP/Heartbeat-Modell/Code in der Boilerplate (HiveGrid-spezifische Integration, M089-M095). Komplett neu.
- Support-Ticket-Konversation (Block O) —
AdminSupportTicketMessage(Reply-Thread, Internal-Notes, Attachments, Reaktions-Templates) fehlt; nur Single-Message-SupportTicketvorhanden. Erweitern. - Worker-Queue-Steuerungs-UI (Block K) — Retry/Pause/Resume/Remove pro BullMQ-Queue: Worker-Runtime + DLQ-Modell existieren, aber keine Admin-UI/Steuerungs-API. Neu (auf
apps/worker/admin-server.tsaufsetzen). - Manuelle Tenant-Anlage mit Sonder-Konditionen (Block N, M036) — Slug-Live-Validation, Initial-Admin-Anlage, Trial-Dauer, Demo-Daten-Provisionierung, Reason-Pflicht. Tenant-Anlage existiert im Onboarding-/Registrierungs-Pfad, aber kein Admin-„Tenant-anlegen-im-Auftrag"-Flow. Neu (Teile aus Registration/Onboarding wiederverwenden).
- Dashboard-Erweiterung (Block F) — Churn/Expansion-MRR, ARPU, System-Health-Kacheln, Sales-Pipeline-Volumen, Support-Kennzahlen, Redis-Cache (TTL 5 Min) + Hourly-Aggregat-Job. Basis (Tenant/MRR/Active-Users) vorhanden, Rest neu.
packages/ui-hivegridAdmin-Shell + Subframe-Adapter — der HiveGrid-eigene UI-Layer (ui-hivegrid,ui-subframe-Adapter) existiert in der Boilerplate nicht (nur Stubsui-app/ui-subframe). Admin-Sidebar muss inpackages/ui-hivegrid/src/app-shell/sidebar.config.tsregistriert werden (INTEGRATION-CONTRACTS §2). Querschnitt für alle M034-M041.- Impersonate 30-Min-Hard-Cap + Reason-Pflicht-Gate — State + Banner vorhanden, aber Spec verlangt harte 30-Min-Session-Begrenzung und Reason-Pflicht-Block (Block Q). Erweitern.
Empfohlenes Vorgehen (Reihenfolge)
Phase 0 — Architektur-Entscheidung (blockierend, vor M034):
- Klären: getrennte
AdminUser-Identität (Spec-treu, empfohlen) vs. Boilerplate-User-Extension (Change-Request gegen Pflichtenheft 37.1). Bis dahin keine Auth-Implementierung beginnen.
M034 (Auth) — zuerst aus BP übernehmen, Rest neu:
- Übernehmen (als-vorlage):
auth.admin.login/logout-Flow,admin-mfa-gate.server.ts(Step-up-Gate inkl. fertiger Sensitive-Action-Taxonomie),login-rate-limit.ts,session-cookie.ts(Domain-Lock),adminPath-Routing. - Neu:
AdminUser/AdminSession-Modelle (inapp.prisma), Email+Passwort+TOTP-Pflicht (Magic-Link verbieten), IP-Bindung, 8h/1h-Timeouts, 5/15 + 10/24h-Lockout, Bootstrap-Seed-Script.
M039 (Konten & Rollen) — RBAC:
3. AdminRole/AdminPermission/AdminUserRole als parallele RBAC (Struktur aus permissions.prisma, UI-Editor aus roles-and-permissions.tsx), Bundle-Stufen + Default-Rollen-Seed + Self-Permission-Schutz.
M035 (Dashboard) + M036 (Mandanten):
4. Dashboard: adminDashboardService + kpi.server als Basis, Health-/Pipeline-/Support-Kacheln + Redis-Cache + Hourly-Job ergänzen.
5. Mandanten: admin/accounts Liste/Detail erweitern (Status-Wechsel mit Reason → AuditEvent/ImpersonationAudit, AdminTenantNote neu, Trial-Verlängerung, Bulk-Email via notify/Campaign, Daten-Export via packages/exports). Manuelle Tenant-Anlage (Block N) neu.
M037 (Stellvertreter-Anmeldung):
6. Vorhandene Impersonation (Session-State + Banner + ImpersonationAudit) übernehmen; 30-Min-Hard-Cap + Reason-Pflicht-Gate (requireAdminMfaForAction("impersonation.start") ist schon da) + Banner-Text mit Ticket-Bezug ergänzen.
M038 (Mitarbeiter-Verwaltung):
7. admin/accounts/users als Vorlage; 2FA-Reset, Session-Revoke, Memberships, Deaktivieren auf neue Permission-Bundles umstellen; AdminUser-Verwaltung (Anlegen via Setup-Mail) neu.
M040 (Lead-Pipeline):
8. Lead/LeadEvent/marketing-leads-Routen + Convert-to-Tenant fast 1:1 übernehmen; AdminDeal-Modell + Kanban-Pipeline-UI + assigned_to/industry/company_size neu.
M041 (Health/Audit/Support/Legal):
9. Audit: audit-trails.tsx + logs.server + audit-pdf übernehmen (Volltext-Suche + JSON-Export ergänzen) — AuditEvent deckt AdminAuditLog.
10. Subscriptions-Admin: subscriptions/$id + Stripe erweitern (Override/Force-Sync/Refund/Diff-View).
11. Support: helpDesk + SupportTicket übernehmen; AdminSupportTicketMessage-Thread + Templates + Internal-Notes neu.
12. Health: metrics-Routen + Worker-Admin/Health-Server + Grafana als Basis; BullMQ-Queue-Steuerungs-UI (auf QueueFailedJob) + KWP-Sync (HiveGrid-spezifisch) neu.
13. Legal: Subprocessor/AvvVersion-Modelle + Re-Akzeptanz-Trigger (via notify/Campaign) komplett neu (Modul 35).
Querschnitt (durchgängig): withTenantScope/withPermission, auditEvent, notify, CatalogError, VMState, i18n-Keys + Sidebar-Registrierung in packages/ui-hivegrid (das zuerst angelegt werden muss) — gemäss AGENTS.md DoD + INTEGRATION-CONTRACTS.
Realitäts-Vorbehalte (keine Erfindungen)
packages/ui-hivegridund ein gebauterui-subframe-Adapter existieren nicht in der Boilerplate (nur Stubs). Die in AGENTS.md/INTEGRATION-CONTRACTS referenzierten Pfade sind HiveGrid-Soll, nicht Boilerplate-Ist.- Boilerplate-
AdminUserist kein parallel-Identity-Modell — das ist der zentrale Spec-Konflikt. - Kein
AdminRole/AdminPermission/AdminSession/AdminAuditLog/AdminLead/AdminDeal/AdminLeadActivity/AdminSupportTicket/AdminTenantNote/Subprocessor/AvvVersion-Modell existiert (verifiziert via Grep überprisma/schema/). - Kein KWP-/Heartbeat-Code existiert (HiveGrid-spezifisch).
SupportTicketist single-message (kein Konversations-Thread).- Admin-Login läuft heute via Authentik-OIDC, nicht Email+Passwort+TOTP wie Spec C verlangt.