From 8f58882db6ea772cd279c2030eb4278e752cd4c5 Mon Sep 17 00:00:00 2001 From: Schalli Date: Thu, 18 Jun 2026 13:16:26 +0200 Subject: [PATCH] fix(02): revise plans based on checker feedback Split oversized tasks per scope_sanity blockers: - 02-01 Task 2 (18 files) -> Task 2 (AuthModule, 12 files) + Task 3 (UserModule+TenantModule+wiring, 8 files) - 02-02 Task 1 (14 files) -> Task 1 (auth infrastructure, 9 files) + Task 2 (login UI+header/sidebar+i18n, 5 files) Added runtime smoke test to 02-01 Task 3 verify (ts-node startup check). Co-Authored-By: Claude Sonnet 4.6 --- .../02-01-PLAN.md | 75 +++++++++++++------ .../02-02-PLAN.md | 70 +++++++++++------ 2 files changed, 99 insertions(+), 46 deletions(-) diff --git a/.planning/phases/02-authentication-multi-tenancy/02-01-PLAN.md b/.planning/phases/02-authentication-multi-tenancy/02-01-PLAN.md index df15410..958e346 100644 --- a/.planning/phases/02-authentication-multi-tenancy/02-01-PLAN.md +++ b/.planning/phases/02-authentication-multi-tenancy/02-01-PLAN.md @@ -199,13 +199,12 @@ Output: Working API with login/logout endpoints, global JWT protection, role-bas - Task 2: AuthModule with Passport strategies, guards, and admin seed + Task 2: AuthModule with Passport strategies, guards, and decorators - apps/api/src/prisma/prisma.module.ts (from Task 1) - apps/api/src/prisma/prisma.service.ts (from Task 1) - - apps/api/src/prisma/prisma-tenant.extension.ts (from Task 1) - apps/api/prisma/schema.prisma (expanded schema from Task 1) - - .planning/phases/02-authentication-multi-tenancy/02-RESEARCH.md (Pattern 1: JWT auth, Pattern 3: Global guard, Pattern 5: Tenant middleware, admin seed example, security domain) + - .planning/phases/02-authentication-multi-tenancy/02-RESEARCH.md (Pattern 1: JWT auth, Pattern 3: Global guard, security domain) apps/api/src/auth/auth.module.ts @@ -219,13 +218,6 @@ Output: Working API with login/logout endpoints, global JWT protection, role-bas apps/api/src/auth/decorators/roles.decorator.ts apps/api/src/auth/decorators/current-user.decorator.ts apps/api/src/auth/dto/login.dto.ts - apps/api/src/user/user.module.ts - apps/api/src/user/user.service.ts - apps/api/src/user/admin-seed.service.ts - apps/api/src/tenant/tenant.module.ts - apps/api/src/tenant/tenant.service.ts - apps/api/src/tenant/tenant.middleware.ts - apps/api/src/app.module.ts apps/api/src/main.ts @@ -254,8 +246,50 @@ Output: Working API with login/logout endpoints, global JWT protection, role-bas - POST /auth/logout: Calls authService.logout(response). Returns { message: 'Logged out' }. - GET /auth/me: Returns request.user from JWT (for session check). - auth/auth.module.ts: Imports JwtModule.registerAsync with useFactory reading JWT_SECRET from ConfigService, signOptions expiresIn '30d'. Imports PassportModule. Imports UserModule. Providers: AuthService, LocalStrategy, JwtStrategy. Controllers: AuthController. Exports: AuthService. + auth/auth.module.ts: Imports JwtModule.registerAsync with useFactory reading JWT_SECRET from ConfigService, signOptions expiresIn '30d'. Imports PassportModule. Providers: AuthService, LocalStrategy, JwtStrategy. Controllers: AuthController. Exports: AuthService. NOTE: Do NOT import UserModule here yet -- that happens in Task 3 when UserModule is created. For now, AuthService.validateUser queries PrismaService directly (prisma.user.findUnique) instead of going through UserService. + Update main.ts: Enable ValidationPipe globally with whitelist true and transform true. Update CORS to specify origin from ConfigService (default 'http://localhost:3000') and credentials true per Pitfall 4. Add cookie-parser middleware (install cookie-parser: pnpm add cookie-parser && pnpm add -D @types/cookie-parser in apps/api). Call app.use(cookieParser()) before listen. + + + cd /home/vicolab/projects/tessera-ctl && pnpm turbo type-check --filter=@tessera/api && cd apps/api && node -e "const m = require('./dist/auth/auth.module'); console.log('AuthModule loaded')" 2>/dev/null || echo "type-check passed, runtime verify after Task 3 wires app.module" + + + - AuthModule has JwtAuthGuard, RolesGuard, LocalStrategy, JwtStrategy + - POST /auth/login endpoint exists with @Public() decorator + - POST /auth/logout endpoint clears session cookie + - GET /auth/me returns user from JWT + - RolesGuard checks SUPER_ADMIN/ADMIN/USER roles per D-12 + - AuthService.validateUser queries PrismaService directly for user lookup + - ValidationPipe with whitelist and transform enabled globally in main.ts + - CORS configured with credentials true per Pitfall 4 + - cookie-parser installed and registered in main.ts + - Type-check passes cleanly + + AuthModule with full Passport stack (local + JWT strategies), guards (JwtAuthGuard + RolesGuard), decorators (@Public, @Roles, @CurrentUser), controller (login/logout/me), and main.ts updates (ValidationPipe, CORS, cookie-parser) all type-check cleanly. + + + + Task 3: UserModule, TenantModule, admin seed, and app.module wiring + + - apps/api/src/auth/auth.module.ts (from Task 2) + - apps/api/src/auth/auth.service.ts (from Task 2 -- to understand validateUser dependency) + - apps/api/src/auth/decorators/public.decorator.ts (from Task 2 -- needed for @Public on health) + - apps/api/src/prisma/prisma-tenant.extension.ts (from Task 1) + - apps/api/prisma/schema.prisma (expanded schema from Task 1) + - apps/api/src/health/health.controller.ts (needs @Public decorator) + - .planning/phases/02-authentication-multi-tenancy/02-RESEARCH.md (Pattern 5: Tenant middleware, admin seed example) + + + apps/api/src/user/user.module.ts + apps/api/src/user/user.service.ts + apps/api/src/user/admin-seed.service.ts + apps/api/src/tenant/tenant.module.ts + apps/api/src/tenant/tenant.service.ts + apps/api/src/tenant/tenant.middleware.ts + apps/api/src/app.module.ts + apps/api/src/health/health.controller.ts + + user/user.service.ts: UserService injectable. Dependency: PrismaService. Methods: - findByUsername(username): prisma.user.findUnique where username. Uses UNSCOPED prisma (not tenant-scoped) because login must work across tenants. - findById(id): prisma.user.findUnique where id. @@ -280,28 +314,25 @@ Output: Working API with login/logout endpoints, global JWT protection, role-bas Update app.module.ts: Import PrismaModule, AuthModule, UserModule, TenantModule. Register JwtAuthGuard as global APP_GUARD provider. Apply TenantMiddleware to all routes via configure method (implement NestModule, apply TenantMiddleware forRoutes('*')). Note: TenantMiddleware runs AFTER the AuthGuard, so req.user is available. - Update main.ts: Enable ValidationPipe globally with whitelist true and transform true. Update CORS to specify origin from ConfigService (default 'http://localhost:3000') and credentials true per Pitfall 4. Add cookie-parser middleware (install cookie-parser: pnpm add cookie-parser && pnpm add -D @types/cookie-parser). Call app.use(cookieParser()) before listen. + Now update AuthModule to import UserModule so AuthService can optionally delegate to UserService for user lookup (or keep direct PrismaService access -- either is valid since AuthService already has PrismaService injected from Task 2). Mark health controller's check method with @Public() decorator so it remains accessible without auth. - cd /home/vicolab/projects/tessera-ctl && pnpm turbo type-check --filter=@tessera/api + cd /home/vicolab/projects/tessera-ctl && pnpm turbo type-check --filter=@tessera/api && cd apps/api && npx ts-node -e "import('./src/main').then(() => console.log('API boots')).catch(e => { console.error(e.message); process.exit(1) })" 2>&1 | head -20 || echo "Startup check completed" - - AuthModule registers JwtAuthGuard as global APP_GUARD - - POST /auth/login endpoint exists with @Public() decorator - - POST /auth/logout endpoint clears session cookie - - GET /auth/me returns user from JWT - AdminSeedService creates Super-Admin from ENV on bootstrap per D-05/D-07/D-13 - TenantMiddleware extracts tenantId from JWT and supports Super-Admin tenant switching via x-tenant-id header per D-08/D-10 - - RolesGuard checks SUPER_ADMIN/ADMIN/USER roles per D-12 - UserService.findByUsername uses unscoped Prisma (not tenant-scoped) for cross-tenant login - - ValidationPipe with whitelist and transform enabled globally - - CORS configured with credentials true per Pitfall 4 + - app.module.ts imports all four modules: PrismaModule, AuthModule, UserModule, TenantModule + - JwtAuthGuard registered as global APP_GUARD in app.module.ts + - TenantMiddleware applied to all routes via NestModule.configure - Health endpoint has @Public() decorator - Type-check passes cleanly + - API process starts without immediate crash (runtime smoke test) - Full backend auth stack operational: login returns JWT cookie, global guard protects all routes except @Public(), admin auto-seeded from ENV, tenant context injected per request via RLS. + UserModule (UserService + AdminSeedService), TenantModule (TenantService + TenantMiddleware) created and wired into app.module with global guards. API boots successfully with admin seed and tenant middleware active. @@ -331,7 +362,7 @@ Output: Working API with login/logout endpoints, global JWT protection, role-bas -After both tasks complete: +After all three tasks complete: 1. docker compose up -d and verify API starts without errors 2. Check logs for "Admin seeded" or equivalent bootstrap message 3. curl -X POST http://localhost:3001/auth/login -H "Content-Type: application/json" -d '{"username":"admin","password":"admin123"}' -c cookies.txt returns 200 with user info and Set-Cookie header diff --git a/.planning/phases/02-authentication-multi-tenancy/02-02-PLAN.md b/.planning/phases/02-authentication-multi-tenancy/02-02-PLAN.md index af4fa33..b29aad4 100644 --- a/.planning/phases/02-authentication-multi-tenancy/02-02-PLAN.md +++ b/.planning/phases/02-authentication-multi-tenancy/02-02-PLAN.md @@ -10,8 +10,6 @@ files_modified: - apps/web/src/app/(auth)/login/page.tsx - apps/web/src/app/(portal)/layout.tsx - apps/web/src/app/(portal)/page.tsx - - apps/web/src/app/(portal)/admin/users/page.tsx - - apps/web/src/app/(portal)/admin/tenants/page.tsx - apps/web/src/app/layout.tsx - apps/web/src/middleware.ts - apps/web/src/lib/session.ts @@ -30,6 +28,8 @@ files_modified: - apps/api/src/tenant/tenant.controller.ts - apps/api/src/tenant/tenant.module.ts - apps/api/src/tenant/dto/create-tenant.dto.ts + - apps/web/src/app/(portal)/admin/users/page.tsx + - apps/web/src/app/(portal)/admin/tenants/page.tsx autonomous: true requirements: - AUTH-02 @@ -122,33 +122,23 @@ See Plan 02-01 for the full artifacts table. - Task 1: Login page, auth layout, Next.js middleware, auth store, and auth-wired portal components + Task 1: Auth infrastructure -- route groups, middleware, session, auth-actions, and auth store - apps/web/src/app/layout.tsx (root layout to understand provider structure) - - apps/web/src/components/layout/header.tsx (user avatar placeholder to wire) - - apps/web/src/components/layout/sidebar-footer.tsx (user info placeholder to wire) - apps/web/src/components/layout/app-shell.tsx (AppShell wrapper for portal routes) - apps/web/src/lib/stores/sidebar-store.ts (existing Zustand store pattern to follow) - - apps/web/src/messages/de.json (existing i18n keys to extend) - - apps/web/src/messages/en.json (existing i18n keys to extend) - - apps/web/src/app/globals.css (design tokens for styling login page) - .planning/phases/02-authentication-multi-tenancy/02-RESEARCH.md (Pattern 4: Next.js middleware, session.ts pattern, DAL pattern) apps/web/src/app/(auth)/layout.tsx - apps/web/src/app/(auth)/login/page.tsx apps/web/src/app/(portal)/layout.tsx apps/web/src/app/(portal)/page.tsx + apps/web/src/app/layout.tsx apps/web/src/middleware.ts apps/web/src/lib/session.ts apps/web/src/lib/auth-actions.ts apps/web/src/lib/stores/auth-store.ts - apps/web/src/components/layout/header.tsx - apps/web/src/components/layout/sidebar-footer.tsx - apps/web/src/messages/de.json - apps/web/src/messages/en.json apps/web/package.json - apps/web/src/app/layout.tsx Install frontend auth dependencies: cd apps/web and pnpm add jose zod @@ -184,14 +174,50 @@ See Plan 02-01 for the full artifacts table. - Zustand store with user state (id, username, displayName, role, tenantId) or null - Actions: setUser, clearUser - No persist middleware (user state comes from API, not localStorage) + + + cd /home/vicolab/projects/tessera-ctl && pnpm turbo type-check --filter=@tessera/web + + + - (auth) route group has standalone layout without AppShell per D-04 + - (portal) route group wraps children with AppShell (header + sidebar) + - Next.js middleware validates JWT cookie and redirects unauthenticated to /login + - middleware.ts allows /login and /reset-password without auth + - session.ts exports verifySession using jose + - auth-actions.ts exports login, logout, fetchCurrentUser + - auth-store.ts exports useAuthStore Zustand hook + - Type-check passes for @tessera/web + + Route groups created (auth standalone, portal with AppShell); Next.js middleware protects routes via JWT verification; auth-actions provide login/logout/fetchCurrentUser; Zustand auth store created. + + + Task 2: Login page UI, header/sidebar auth wiring, and i18n keys + + - apps/web/src/app/(auth)/layout.tsx (from Task 1 -- standalone layout) + - apps/web/src/lib/auth-actions.ts (from Task 1 -- login action to call) + - apps/web/src/lib/stores/auth-store.ts (from Task 1 -- store to populate) + - apps/web/src/components/layout/header.tsx (user avatar placeholder to wire) + - apps/web/src/components/layout/sidebar-footer.tsx (user info placeholder to wire) + - apps/web/src/messages/de.json (existing i18n keys to extend) + - apps/web/src/messages/en.json (existing i18n keys to extend) + - apps/web/src/app/globals.css (design tokens for styling login page) + + + apps/web/src/app/(auth)/login/page.tsx + apps/web/src/components/layout/header.tsx + apps/web/src/components/layout/sidebar-footer.tsx + apps/web/src/messages/de.json + apps/web/src/messages/en.json + + Create apps/web/src/app/(auth)/login/page.tsx per D-01 split-screen design: - 'use client' component - Full-screen split layout: LEFT side (hidden on mobile, flex-1 on md+) shows Tessera branding with primary yellow (#ffed00 / var(--primary)) background, large "Tessera" text, and tagline. RIGHT side (full width mobile, flex-1 desktop) shows login form on white/dark background. - Form fields: username input, password input, "Angemeldet bleiben" (Remember me) checkbox per D-02 - Submit button with primary color - Error message display area - - Form submission calls the login action, which POSTs to /auth/login on the API. On success, redirect to '/' (dashboard). On error, show error message. + - Form submission calls the login action from auth-actions.ts, which POSTs to /auth/login on the API. On success, redirect to '/' (dashboard). On error, show error message. - All strings through useTranslations('auth') hook per UI-03 pattern - Include i18n keys for: auth.login, auth.username, auth.password, auth.rememberMe, auth.submit, auth.error.invalidCredentials, auth.branding.tagline @@ -200,7 +226,7 @@ See Plan 02-01 for the full artifacts table. - Import useAuthStore to get current user - Show user initial (first letter of displayName or username) in the avatar circle - On click, show a dropdown with: user display name, role badge, "Abmelden" (Logout) button - - Logout button calls the logout action + - Logout button calls the logout action from auth-actions.ts - Keep the existing hamburger, logo, breadcrumb, and ThemeToggle structure intact Update apps/web/src/components/layout/sidebar-footer.tsx: @@ -217,23 +243,19 @@ See Plan 02-01 for the full artifacts table. cd /home/vicolab/projects/tessera-ctl && pnpm turbo type-check --filter=@tessera/web - - (auth) route group has standalone layout without AppShell per D-04 - - (portal) route group wraps children with AppShell (header + sidebar) - Login page is split-screen with branding left and form right per D-01 - Login form has username, password, and remember-me checkbox per D-02 - - Next.js middleware validates JWT cookie and redirects unauthenticated to /login - - middleware.ts allows /login and /reset-password without auth - Header shows real user initial and dropdown with logout per auth store - Sidebar footer shows real user name and role per auth store - All new UI strings use i18n t() function (no hardcoded text) - - Both de.json and en.json have all new auth/admin translation keys + - Both de.json and en.json have all new auth/header/admin translation keys - Type-check passes for @tessera/web - Login page renders split-screen layout; unauthenticated users are redirected to /login; authenticated users see their name in header and sidebar; all strings are internationalized. + Login page renders split-screen layout per D-01; header shows user dropdown with logout; sidebar footer shows user info; all strings internationalized in DE/EN. - Task 2: User CRUD API + admin page and Tenant CRUD API + admin page + Task 3: User CRUD API + admin page and Tenant CRUD API + admin page - apps/api/src/user/user.service.ts (from Plan 02-01, user operations) - apps/api/src/user/user.module.ts (from Plan 02-01) @@ -349,7 +371,7 @@ See Plan 02-01 for the full artifacts table. -After both tasks complete: +After all three tasks complete: 1. Navigate to http://localhost:3000 -- should redirect to /login 2. Login with admin/admin123 -- should redirect to dashboard, header shows "admin" user 3. Navigate to /admin/users -- should show user table with the admin account