# INSIGHT-Platform — Developer Briefing > **Lies dieses Dokument vollständig bevor du Code schreibst.** > Es ist dein vollständiger Einstieg in Phase 2. --- ## Deine Aufgabe Du bist der **Platform-Entwickler** für INSIGHT. Dein Job in Phase 2: > **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") } ``` --- ## RBAC-Konzept ``` User.platformRole: PLATFORM_ADMIN → Zugang zum kompletten Admin-Bereich USER → Normaler Nutzer 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 ``` **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 } ``` **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) - Kein `any` in TypeScript — strict mode immer - 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` - Kein raw SQL — ausschließlich Prisma - BCRYPT_COST 12 - JWT RS256 (nicht HS256!) - Tokens in Redis blocklistbar (JTI-basiert) --- ## 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 ``` Passwörter: siehe Confluence → [SSH Keys & Zugangsverwaltung](https://xinion.atlassian.net/wiki/x/UACVEw) --- ## 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. ``` git@git.xinion.lan:Projekt-INSIGHT/INSIGHT-Shared.git ``` **Format:** ```markdown ## [Datum] — [Kurzbeschreibung] **Abgeschlossen:** - Was ist fertig? **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_