diff --git a/repos/INSIGHT-Platform/BRIEFING.md b/repos/INSIGHT-Platform/BRIEFING.md index d009452..b4e381f 100644 --- a/repos/INSIGHT-Platform/BRIEFING.md +++ b/repos/INSIGHT-Platform/BRIEFING.md @@ -1,43 +1,438 @@ # INSIGHT-Platform — Developer Briefing -> Dieses Dokument ist der Einstiegspunkt für jede neue Claude Code Session in diesem Repo. -> Lies es vollständig bevor du Code schreibst. +> **Lies dieses Dokument vollständig bevor du Code schreibst.** +> Es ist dein vollständiger Einstieg in Phase 2. -## Was ist dieses Repo? +--- -Die **INSIGHT-Plattform** ist das Fundament aller INSIGHT-Dienste. -Sie stellt Authentifizierung, User-Verwaltung, Berechtigungssystem und die React-Shell bereit, -in die sich fachliche Apps (aus `INSIGHT-Apps`) einbinden. +## Deine Aufgabe -## Struktur +Du bist der **Platform-Entwickler** für INSIGHT. Dein Job in Phase 2: -``` -packages/ -├── core-service/ # NestJS: Auth, JWT, User, Settings, Expertenprofil, RBAC -└── frontend/ # React 18 + Vite: Shell, Login, Navigation, Admin-Bereich +> **NestJS Core-Service (Auth, User, RBAC, Settings) + React Shell (Login, Navigation, Admin) implementieren.** + +Die Infrastruktur läuft bereits (Phase 1 ✅). Du hast Zugriff auf: +- PostgreSQL 16 auf `insight-dbs01.xinion.lan:5432` (Datenbank: `insight_core`) +- PgBouncer auf `insight-dbs01.xinion.lan:6432` +- Redis 8.6.1 auf `172.20.10.20:6379` +- Docker auf `insight-aps01.xinion.lan` für Deployments +- Nginx auf `insight-web01.xinion.lan` für das Frontend + +--- + +## Was ist bereits fertig + +- Infra: PostgreSQL, PgBouncer, Redis, Docker, Nginx — alles läuft ✅ +- Datenbank `insight_core` existiert mit User `insight_app` ✅ +- Repo-Struktur (`packages/core-service/`, `packages/frontend/`) ✅ +- Deploy Key für dieses Repo: `.keys/deploy_platform_ed25519` ✅ +- MVP-Referenzimplementierung unter `git@git.xinion.lan:gitadmin/INSIGHT-MVP.git` ✅ + +--- + +## Wichtiger Hinweis: MVP als Referenz + +**Der MVP (`INSIGHT-MVP`) ist vollständig implementiert und dient als direkte Vorlage.** + +Folgende Module kannst du 1:1 oder minimal angepasst übernehmen: + +| MVP-Datei | Für INSIGHT nutzen | +|---|---| +| `packages/core-service/src/core/auth/auth.service.ts` | Auth-Logik (Login, TOTP, SSO, Refresh) | +| `packages/core-service/src/core/auth/auth.controller.ts` | Auth-Endpoints | +| `packages/core-service/src/core/auth/strategies/jwt.strategy.ts` | JWT RS256 Passport-Strategy | +| `packages/core-service/src/core/auth/totp.service.ts` | TOTP 2FA | +| `packages/core-service/src/core/auth/sso/entra-id.service.ts` | MS Entra ID SSO + M365 OAuth2 | +| `packages/core-service/src/core/auth/sso/sso.controller.ts` | SSO-Endpoints | +| `packages/core-service/src/core/users/users.service.ts` | User-CRUD | +| `packages/core-service/src/core/users/users.controller.ts` | User-Endpoints | +| `packages/core-service/src/core/settings/settings.controller.ts` | External Links, Branding, Company, SSL, AI-Config | +| `packages/core-service/src/redis/redis.service.ts` | Redis-Wrapper | +| `packages/core-service/src/common/guards/` | JwtAuthGuard, RolesGuard, Public-Decorator | +| `packages/frontend/src/auth/` | LoginPage, AuthContext, SsoCallbackPage | +| `packages/frontend/src/admin/AdminLayout.tsx` | Admin-Navigation-Shell | +| `packages/frontend/src/admin/Admin*.tsx` | Admin-Seiten (Users, SSO, Branding, etc.) | +| `packages/frontend/src/hooks/` | useAuth, TanStack Query Hooks | + +**Anpassungen notwendig:** +- **Kein Multi-Tenant**: INSIGHT ist single-tenant. Alle `tenant*`-Felder aus Prisma-Schema entfernen. +- **RBAC erweitern**: `moduleRoles` als JSON-Feld statt `tenantRole` (siehe Schema unten). +- **Neue Admin-Seiten**: User-Rollenmanagement (platformRole + moduleRoles), Modul-Registry. +- **Dashboard**: Modulare Widget-Area statt MVP-Dashboard (M365 Calendar/Email). + +--- + +## Prisma Schema (insight_core) + +**Datei:** `packages/core-service/prisma/schema.prisma` + +```prisma +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DATABASE_URL_DIRECT") +} + +// -------------------------------------------------------- +// User - Plattform-Benutzer +// -------------------------------------------------------- +model User { + id String @id @default(uuid()) @db.Uuid + email String @unique @db.VarChar(255) + firstName String @map("first_name") @db.VarChar(100) + lastName String @map("last_name") @db.VarChar(100) + avatar String? @db.Text + + // Kontaktdaten + phone String? @db.VarChar(30) + mobile String? @db.VarChar(30) + street String? @db.VarChar(200) + postalCode String? @map("postal_code") @db.VarChar(10) + city String? @db.VarChar(100) + + // Organisation + jobTitle String? @map("job_title") @db.VarChar(100) + department String? @db.VarChar(100) + companyName String? @map("company_name") @db.VarChar(200) + officeLocation String? @map("office_location") @db.VarChar(200) + + // Rollen + platformRole String @default("USER") @map("platform_role") @db.VarChar(50) + // "PLATFORM_ADMIN" oder "USER" + + // Modul-Rollen als JSON: { "crm": "MANAGER", "project": "VIEWER" } + moduleRoles Json @default("{}") @map("module_roles") + + isActive Boolean @default(true) @map("is_active") + + // 2FA (nur für lokale Auth) + twoFactorEnabled Boolean @default(false) @map("two_factor_enabled") + + // Login-Tracking + lastLogin DateTime? @map("last_login") + failedLoginAttempts Int @default(0) @map("failed_login_attempts") + lastFailedLogin DateTime? @map("last_failed_login") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + authProvider AuthProvider[] + integrations UserIntegration[] + auditLogs AuditLog[] + + @@map("users") +} + +// -------------------------------------------------------- +// AuthProvider - Lokale Auth, MS SSO +// -------------------------------------------------------- +model AuthProvider { + id String @id @default(uuid()) @db.Uuid + userId String @map("user_id") @db.Uuid + provider String @db.VarChar(50) // LOCAL, MS_SSO + providerId String? @map("provider_id") @db.VarChar(255) + passwordHash String? @map("password_hash") @db.VarChar(255) + totpSecret String? @map("totp_secret") @db.VarChar(255) + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, provider]) + @@map("auth_providers") +} + +// -------------------------------------------------------- +// UserIntegration - Microsoft 365 OAuth2 Tokens pro User +// -------------------------------------------------------- +model UserIntegration { + id String @id @default(uuid()) @db.Uuid + userId String @map("user_id") @db.Uuid + provider String @db.VarChar(50) // MICROSOFT_365 + accessToken String @map("access_token") @db.Text // AES-256-GCM verschlüsselt + refreshToken String @map("refresh_token") @db.Text + expiresAt DateTime @map("expires_at") + scopes String[] @default([]) + msTenantId String? @map("ms_tenant_id") @db.VarChar(100) + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([userId, provider]) + @@map("user_integrations") +} + +// -------------------------------------------------------- +// Module - Registrierte INSIGHT-Module +// -------------------------------------------------------- +model Module { + id String @id @default(uuid()) @db.Uuid + key String @unique @db.VarChar(50) // "crm", "project" + name String @db.VarChar(100) + description String? @db.Text + version String @default("1.0.0") @db.VarChar(20) + isActive Boolean @default(true) @map("is_active") + // Verfügbare Rollen für dieses Modul (JSON Array): ["ADMIN","MANAGER","USER","VIEWER"] + availableRoles Json @default("[]") @map("available_roles") + config Json @default("{}") + + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + + @@map("modules") +} + +// -------------------------------------------------------- +// AuditLog +// -------------------------------------------------------- +model AuditLog { + id String @id @default(uuid()) @db.Uuid + userId String? @map("user_id") @db.Uuid + action String @db.VarChar(100) + entity String @db.VarChar(100) + entityId String? @map("entity_id") @db.VarChar(255) + details Json? + ipAddress String? @map("ip_address") @db.VarChar(45) + + createdAt DateTime @default(now()) @map("created_at") + + user User? @relation(fields: [userId], references: [id], onDelete: SetNull) + + @@index([userId]) + @@index([action]) + @@index([createdAt]) + @@map("audit_logs") +} ``` -## Abhängigkeiten zu anderen Repos +--- -| Repo | Beziehung | -|------|-----------| -| `INSIGHT-Shared` | Lesen: module-interface.md, api-platform.md — Schreiben: api-platform.md nach Änderungen | -| `INSIGHT-Apps` | Apps registrieren sich via InsightModule-Contract in der Shell | -| `INSIGHT-Infra` | Stellt DBS01 (PostgreSQL + Redis) und APS01 (Docker) bereit | +## RBAC-Konzept -## Confluence Dokumentation +``` +User.platformRole: + PLATFORM_ADMIN → Zugang zum kompletten Admin-Bereich + USER → Normaler Nutzer -- **Phase 2 — Plattform:** https://xinion.atlassian.net/wiki/x/oYKSEw -- **Berechtigungskonzept:** https://xinion.atlassian.net/wiki/x/FQCVEw -- **Projekt-Übersicht:** https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview +User.moduleRoles (JSON): + { "crm": "ADMIN" } → Admin im CRM-Modul + { "crm": "MANAGER" } → Manager im CRM-Modul + { "crm": "USER" } → Normaler CRM-Nutzer + { "crm": "VIEWER" } → Nur lesen + {} → Kein Zugang zu CRM +``` -## Tech Stack +**Im JWT-Token** werden beide Felder mitgegeben: +```json +{ + "sub": "uuid", + "email": "user@firma.de", + "platformRole": "USER", + "moduleRoles": { "crm": "MANAGER" }, + "jti": "uuid", + "iat": 1234567890, + "exp": 1234568790 +} +``` -- **Backend:** NestJS 10, TypeScript strict, Prisma ORM -- **Frontend:** React 18, Vite, TanStack Query, React Router -- **Datenbank:** PostgreSQL 16 (auf DBS01, via PgBouncer) -- **Cache:** Redis 7 (auf DBS01) -- **Auth:** JWT RS256, Refresh Token (HttpOnly Cookie), TOTP 2FA +**Guards:** +- `JwtAuthGuard` — globaler Default (alle Routen geschützt) +- `@Public()` — explizit für Login, SSO-Callback, Branding +- `@Roles('PLATFORM_ADMIN')` + `RolesGuard` — für Admin-Endpoints +- Module-Rollen: im jeweiligen Modul-Service prüfen (z.B. `user.moduleRoles.crm`) + +--- + +## Implementierungsreihenfolge + +### Backend (core-service) + +**1. Projekt aufsetzen:** +```bash +cd packages/core-service +npm init -y +# NestJS CLI installieren und Projekt erstellen +npm install -g @nestjs/cli +nest new . --skip-git --package-manager npm +``` + +Abhängigkeiten (analog MVP): +```bash +npm install @nestjs/passport @nestjs/jwt passport passport-jwt +npm install @nestjs/config @nestjs/swagger +npm install @prisma/client prisma +npm install bcrypt uuid speakeasy qrcode +npm install @azure/msal-node +npm install ioredis cookie-parser +npm install class-validator class-transformer +npm install @types/bcrypt @types/uuid @types/passport-jwt @types/cookie-parser +``` + +**2. Prisma initialisieren:** +```bash +npx prisma init --datasource-provider postgresql +# schema.prisma aus diesem Briefing verwenden +npx prisma migrate dev --name init +``` + +**3. Implementierungsreihenfolge Module:** +1. `redis/` — RedisService (aus MVP kopieren) +2. `prisma/` — PrismaService (aus MVP kopieren) +3. `common/` — Guards, Decorators, Pipes (aus MVP kopieren, `tenantRole` → `platformRole`) +4. `core/auth/` — AuthService, AuthController, JwtStrategy, TotpService (aus MVP, tenant-Felder entfernen) +5. `core/auth/sso/` — EntraIdService, SsoController (aus MVP 1:1 übernehmen) +6. `core/users/` — UsersService, UsersController (aus MVP, `moduleRoles` statt `tenantRole`) +7. `core/settings/` — SettingsController (aus MVP, External Links + Branding + Company + SSL + AI-Config) +8. `core/modules/` — NEU: ModuleRegistryService (Module aus DB laden, User-Rollen-Zuweisung) + +**4. JWT RS256 Keys generieren:** +```bash +mkdir -p keys +openssl genrsa -out keys/jwt-private.pem 4096 +openssl rsa -in keys/jwt-private.pem -pubout -out keys/jwt-public.pem +``` +Keys werden als Docker-Volume gemountet (nicht in Git!). + +**5. Seed (erster Admin-User):** +```typescript +// prisma/seed.ts +const admin = await prisma.user.create({ + data: { + email: 'admin@insight.local', + firstName: 'Platform', + lastName: 'Admin', + platformRole: 'PLATFORM_ADMIN', + moduleRoles: {}, + isActive: true, + authProvider: { + create: { + provider: 'LOCAL', + passwordHash: await bcrypt.hash('ChangeMe123!', 12), + }, + }, + }, +}); +``` + +--- + +### Frontend (frontend) + +**1. Projekt aufsetzen:** +```bash +cd packages/frontend +npm create vite@latest . -- --template react-ts +npm install react-router-dom @tanstack/react-query axios +npm install react-hot-toast @heroicons/react +``` + +**2. Implementierungsreihenfolge:** +1. `auth/AuthContext.tsx` — AuthContext + useAuth Hook + Silent Refresh (aus MVP) +2. `auth/LoginPage.tsx` — Login-Formular (lokal + SSO-Button) (aus MVP) +3. `auth/SsoCallbackPage.tsx` — SSO-Callback (aus MVP) +4. `shell/App.tsx` — Routing-Struktur (ohne CRM-Routen — die kommen Phase 3) +5. `shell/AppLayout.tsx` — Haupt-Layout (Sidebar + Topbar + Outlet) +6. `shell/DashboardPage.tsx` — Leeres Dashboard mit Widget-Platzhaltern +7. `profile/ProfilePage.tsx` — Eigenes Profil (aus MVP) +8. `admin/AdminLayout.tsx` — Admin-Navigation (aus MVP, CRM-Settings entfernen) +9. `admin/AdminUsersPage.tsx` — User-Verwaltung + Rollen-Zuweisung (aus MVP erweitern) +10. `admin/AdminSsoPage.tsx` — SSO-Konfiguration (aus MVP 1:1) +11. `admin/AdminCustomizePage.tsx` — Branding (aus MVP 1:1) +12. `admin/AdminExternalLinksPage.tsx` — Externe Links (aus MVP 1:1) +13. `admin/AdminCompanyPage.tsx` — Firmendaten (aus MVP 1:1) +14. `admin/AdminSslPage.tsx` — SSL/Domain (aus MVP 1:1) +15. `admin/AdminAiSettingsPage.tsx` — KI-Einstellungen (aus MVP 1:1) +16. `admin/AdminModulesPage.tsx` — NEU: Modul-Registry (welche Module aktiv) + +**Wichtig für Admin-User-Management (NEU gegenüber MVP):** +- User-Liste zeigt: Name, E-Mail, platformRole, moduleRoles +- PLATFORM_ADMIN kann platformRole ändern +- PLATFORM_ADMIN kann moduleRoles pro Modul zuweisen (Dropdown: ADMIN/MANAGER/USER/VIEWER/Kein Zugang) + +--- + +## Deployment auf APS01 + +### Docker Compose (`docker-compose.yml` im Repo-Root) + +```yaml +services: + core-service: + build: ./packages/core-service + restart: unless-stopped + environment: + DATABASE_URL: postgresql://insight_app:${DB_PASSWORD}@insight-dbs01.xinion.lan:6432/insight_core + DATABASE_URL_DIRECT: postgresql://insight_app:${DB_PASSWORD}@insight-dbs01.xinion.lan:5432/insight_core + REDIS_HOST: 172.20.10.20 + REDIS_PORT: 6379 + REDIS_PASSWORD: ${REDIS_PASSWORD} + JWT_ISSUER: insight-platform + JWT_ACCESS_TOKEN_EXPIRY: 15m + JWT_REFRESH_TOKEN_EXPIRY: 7d + JWT_PRIVATE_KEY_PATH: /app/keys/jwt-private.pem + JWT_PUBLIC_KEY_PATH: /app/keys/jwt-public.pem + FRONTEND_URL: http://insight-web01.xinion.lan + NODE_ENV: production + volumes: + - ./keys:/app/keys:ro + - traefik_certs:/app/traefik-certs + - traefik_dynamic:/app/traefik-dynamic + labels: + - "traefik.enable=true" + - "traefik.http.routers.core-api.rule=PathPrefix(`/api`)" + - "traefik.http.services.core-api.loadbalancer.server.port=3000" + networks: + - insight + + frontend: + build: ./packages/frontend + restart: unless-stopped + labels: + - "traefik.enable=true" + - "traefik.http.routers.frontend.rule=PathPrefix(`/`)" + - "traefik.http.services.frontend.loadbalancer.server.port=80" + networks: + - insight + + traefik: + image: traefik:v3 + restart: unless-stopped + command: + - "--api.insecure=true" + - "--providers.docker=true" + - "--entrypoints.web.address=:80" + ports: + - "80:80" + - "8080:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - insight + +networks: + insight: + driver: bridge + +volumes: + traefik_certs: + traefik_dynamic: +``` + +### Deployment-Kommando (von Mac aus): +```bash +ssh -i .keys/deploy_platform_ed25519 deploy@172.20.10.21 \ + "cd ~/insight-platform && git pull origin develop && \ + docker compose build && docker compose up -d" +``` + +--- ## Sicherheitsregeln (nicht verhandelbar) @@ -45,30 +440,104 @@ packages/ - Kein `localStorage` für Tokens (Access: Memory, Refresh: HttpOnly Cookie) - Globaler `ValidationPipe` (whitelist + forbidNonWhitelisted) - `@Public()` explizit für öffentliche Routen -- Rollen immer via `@Roles()` + `RolesGuard` auf jedem Endpoint +- Rollen immer via `@Roles()` + `RolesGuard` - Kein raw SQL — ausschließlich Prisma +- BCRYPT_COST 12 +- JWT RS256 (nicht HS256!) +- Tokens in Redis blocklistbar (JTI-basiert) -## RBAC-Modell +--- -``` -User -├── platformRole: PLATFORM_ADMIN | USER -└── moduleRoles: { crm: ADMIN | MANAGER | VIEWER, ... } +## Umgebungsvariablen (.env auf APS01) + +```env +DATABASE_URL=postgresql://insight_app:PASSWORT@insight-dbs01.xinion.lan:6432/insight_core +DATABASE_URL_DIRECT=postgresql://insight_app:PASSWORT@insight-dbs01.xinion.lan:5432/insight_core +REDIS_HOST=172.20.10.20 +REDIS_PORT=6379 +REDIS_PASSWORD=PASSWORT +BCRYPT_COST=12 +JWT_ISSUER=insight-platform +JWT_ACCESS_TOKEN_EXPIRY=15m +JWT_REFRESH_TOKEN_EXPIRY=7d +NODE_ENV=production +FRONTEND_URL=http://insight-web01.xinion.lan ``` -Platform Admin: User-Verwaltung, System-Einstellungen, Modul-Zuweisung -Module Admin: Berechtigungen innerhalb seines Moduls +Passwörter: siehe Confluence → [SSH Keys & Zugangsverwaltung](https://xinion.atlassian.net/wiki/x/UACVEw) -## Deploy Key (dieses Repo) +--- + +## INSIGHT-Shared — Kommunikations-Repo + +**Wir nutzen `INSIGHT-Shared` als zentrales Kommunikations-Repo aller Entwickler.** + +Nach jedem relevanten Fortschritt trägst du deinen Stand in `dev-status/platform.md` ein. +Dort siehst du auch was Infra- und Apps-Entwickler gerade machen. ``` -ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK2deXkhf9Ut9728mvxwl+MyFtbjoPRXNbTR2XjmDJVs deploy@INSIGHT-Platform +git@git.xinion.lan:Projekt-INSIGHT/INSIGHT-Shared.git ``` -Privater Key: `.keys/deploy_platform_ed25519` +**Format:** +```markdown +## [Datum] — [Kurzbeschreibung] -## Aktueller Status +**Abgeschlossen:** +- Was ist fertig? -- **Phase:** 2 — Plattform -- **Stand:** Konzeption (Berechtigungskonzept in Klärung) -- **Nächster Schritt:** Berechtigungskonzept finalisieren → Prisma Schema → Auth-Service +**Nächste Schritte:** +- Was machst du als nächstes? + +**Blockiert / Brauche Input:** +- Was fehlt dir von anderen Streams? + +**Bereit für andere Streams:** +- Was kann ein anderer Entwickler jetzt nutzen? +``` + +**Wichtig für Apps-Entwickler (Phase 3):** +Sobald folgendes fertig ist, sofort in `dev-status/platform.md` dokumentieren: +- `InsightModule`-Interface stabil (aus `INSIGHT-Shared/contracts/module-interface.md`) +- Auth-Guards und RBAC-Mechanismus beschrieben +- `useAuth()` Hook API definiert +- React Shell kann Module registrieren und rendern + +--- + +## Confluence-Dokumentation + +| Seite | Link | +|---|---| +| Phase 2 — Plattform | https://xinion.atlassian.net/wiki/x/oYKSEw | +| Berechtigungskonzept | https://xinion.atlassian.net/wiki/x/FQCVEw | +| Phase 1 Status (Infra fertig) | https://xinion.atlassian.net/wiki/x/PACVEw | +| Netzwerk & IPs | https://xinion.atlassian.net/wiki/x/FYCVEw | +| Projekt-Übersicht | https://xinion.atlassian.net/wiki/spaces/ProjektINS/overview | + +--- + +## Branching + +``` +main — stabil, nur via PR +develop — Integration, hier arbeitest du +feature/* — für größere Änderungen +fix/* — Bugfixes +``` + +Commit-Format: `feat:`, `fix:`, `chore:`, `docs:`, `refactor:` + +--- + +## Server-Referenz + +| Server | IP | DNS | Funktion | +|---|---|---|---| +| DBS01 | 172.20.10.20 | insight-dbs01.xinion.lan | PostgreSQL:5432, PgBouncer:6432, Redis:6379 | +| APS01 | 172.20.10.21 | insight-aps01.xinion.lan | Docker (core-service läuft hier) | +| WEB01 | 172.20.10.22 | insight-web01.xinion.lan | Nginx (Frontend) | + +--- + +_Stand: 15.03.2026 | Phase 2 | Repo: git@git.xinion.lan:Projekt-INSIGHT/INSIGHT-Platform.git_