diff --git a/aidlc-docs/_shared/reverse-engineering/api-documentation.md b/aidlc-docs/_shared/reverse-engineering/api-documentation.md index d31f208..2372aac 100644 --- a/aidlc-docs/_shared/reverse-engineering/api-documentation.md +++ b/aidlc-docs/_shared/reverse-engineering/api-documentation.md @@ -10,15 +10,16 @@ - **Purpose**: Authenticate a user and receive JWT tokens - **Authorization**: Anonymous - **Request**: `{ "email": string, "password": string }` -- **Response**: `{ "accessToken": string, "refreshToken": string, "expiresAt": datetime, "user": { "id": guid, "email": string, "naam": string, "role": string } }` +- **Response**: `{ "accessToken": string, "expiresAt": datetime, "user": { "id": guid, "email": string, "name": string, "role": string, "isActive": bool } }` +- **Cookie set**: `refreshToken` (httpOnly, Secure, SameSite=Strict, Path=/api/v1/auth) #### POST /auth/refresh - **Method**: POST - **Path**: `/auth/refresh` -- **Purpose**: Refresh an access token using a valid refresh token +- **Purpose**: Refresh an access token using the httpOnly refresh token cookie - **Authorization**: Anonymous -- **Request**: `{ "accessToken": string, "refreshToken": string }` -- **Response**: Same as `/auth/login` +- **Request**: (empty body — refresh token read from cookie) +- **Response**: Same as `/auth/login` (new access token + new cookie) #### POST /auth/revoke - **Method**: POST @@ -110,10 +111,18 @@ - `expiresAt` — access token expiry datetime - `user` — authenticated user info +### Password Validation Rules (enforced by backend) +Configured in `ServiceCollectionExtensions.cs` via ASP.NET Core Identity `PasswordOptions`: +- `RequiredLength = 8` — minimum 8 characters +- `RequireUppercase = true` — at least 1 uppercase letter +- `RequireLowercase = true` — at least 1 lowercase letter +- `RequireDigit = true` — at least 1 digit +- `RequireNonAlphanumeric = true` — at least 1 non-alphanumeric character (e.g. `!@#$%^&*`) + ### ApplicationUser (returned in auth responses) - `id` — Guid - `email` — string -- `naam` — string (display name) +- `name` — string (display name) - `role` — string (Owner / Admin / User) - `isActive` — boolean diff --git a/aidlc-docs/features/cms-frontend/aidlc-state.md b/aidlc-docs/features/cms-frontend/aidlc-state.md index e407908..0a57b08 100644 --- a/aidlc-docs/features/cms-frontend/aidlc-state.md +++ b/aidlc-docs/features/cms-frontend/aidlc-state.md @@ -5,7 +5,7 @@ - **Feature Slug**: cms-frontend - **Project Type**: Brownfield - **Start Date**: 2026-06-16T20:27:00Z -- **Current Stage**: INCEPTION - User Stories (awaiting user approval) +- **Current Stage**: INCEPTION - Application Design (awaiting approval) - **Branch**: unknown ## Workspace State @@ -29,11 +29,31 @@ | Security Baseline | Yes | Requirements Analysis | | Property-Based Testing | No | Requirements Analysis | +## Execution Plan Summary +- **Total Units**: 6 +- **Stages to Execute**: Application Design, Units Generation, Functional Design (per unit), NFR Requirements (Unit 1), NFR Design (Unit 1), Code Generation (per unit), Build and Test +- **Stages to Skip**: Infrastructure Design (no cloud infra; static SPA) + ## Stage Progress + +### 🔵 INCEPTION PHASE - [x] Workspace Detection — Completed 2026-06-16T20:28:00Z -- [x] Reverse Engineering (shared) — Completed 2026-06-16T20:32:00Z — Artifacts: aidlc-docs/_shared/reverse-engineering/ -- [~] Requirements Analysis — In Progress (requirements.md generated; awaiting approval) -- [~] User Stories — In Progress (stories.md + personas.md generated; awaiting approval) -- [ ] Workflow Planning — Not Started -- [ ] Construction — Not Started +- [x] Reverse Engineering (shared) — Completed 2026-06-16T20:32:00Z +- [x] Requirements Analysis — Completed 2026-06-16T21:00:00Z +- [x] User Stories — Completed 2026-06-16T21:05:00Z +- [x] Workflow Planning — Completed 2026-06-16T21:15:00Z +- [~] Application Design — In Progress (all artifacts generated; awaiting approval) +- [ ] Units Generation — Not Started + +### 🟢 CONSTRUCTION PHASE (per unit) +- [ ] Unit 0 — Backend Prerequisites (CORS + httpOnly cookie) — blocking +- [ ] Unit 1 — Project Scaffold & Infrastructure +- [ ] Unit 2 — Authentication Pages +- [ ] Unit 3 — Layout & Navigation +- [ ] Unit 4 — Dashboard +- [ ] Unit 5 — User Management +- [ ] Unit 6 — Profile, Settings & CMS Placeholder - [ ] Build and Test — Not Started + +### 🟡 OPERATIONS PHASE +- [ ] Operations — Placeholder diff --git a/aidlc-docs/features/cms-frontend/audit.md b/aidlc-docs/features/cms-frontend/audit.md index 55f4b95..a59708f 100644 --- a/aidlc-docs/features/cms-frontend/audit.md +++ b/aidlc-docs/features/cms-frontend/audit.md @@ -12,6 +12,18 @@ --- +## User Stories — Approved + +**Timestamp**: 2026-06-16T21:10:00Z + +**User Input**: "approve & continue" + +**AI Response**: "User Stories approved. Proceeding to Workflow Planning." + +**Context**: User Stories stage completed and approved. Requirements also implicitly approved. Moving to Workflow Planning. + +--- + ## User Stories — Stage Added **Timestamp**: 2026-06-16T20:47:00Z diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/application-design.md b/aidlc-docs/features/cms-frontend/inception/application-design/application-design.md new file mode 100644 index 0000000..cde5ea7 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/application-design.md @@ -0,0 +1,151 @@ +# Application Design — CMS Frontend + +## Overview + +This document consolidates the complete application design for the CMS frontend feature. It covers both the backend prerequisite changes (Unit 0) and the full frontend React SPA (Units 1–6). + +**Tech decisions confirmed by user:** +- **State management**: React Context (auth) + TanStack Query (server state) +- **HTTP client**: TanStack Query (server state) using native fetch via `ApiClient` wrapper +- **Form validation**: react-hook-form + zod +- **Routing**: TanStack Router — file-based (`src/routes/`) +- **Auth token storage**: Access token in-memory (React context), refresh token in httpOnly cookie +- **Cookie**: `refreshToken`, path `/api/v1/auth`, HttpOnly, Secure, SameSite=Strict +- **CORS origins**: Configured via `appsettings.json → Cors:AllowedOrigins[]` (dotnet-appsettings pattern) +- **User data in context**: Full user object `{ id, email, name, role, isActive }` +- **Error boundaries**: Global + per-page + +--- + +## Architecture at a Glance + +```mermaid +graph LR + subgraph FE["Frontend (frontend/)"] + direction TB + Infra["Infrastructure\nApiClient · AuthContext · RouterConfig"] + Guards["Guards\nProtectedRoute · RoleGuard · InitGuard"] + Layout["Layout\nAppLayout · Sidebar · ThemeProvider"] + Hooks["TanStack Query Hooks\nuseAvailability · useUsers · useInvitation · useSetup"] + Pages["Pages\nLogin · Setup · InviteComplete\nDashboard · Users · Profile\nSettings · CMS · 403 · 404"] + end + + subgraph BE["Backend (src/)"] + direction TB + CORS["CORS Policy\n(ServiceCollectionExtensions)"] + AuthCtrl["AuthController\n(httpOnly cookie)"] + AuthSvc["AuthService"] + end + + Pages --> Hooks + Pages --> Infra + Layout --> Infra + Guards --> Infra + Hooks --> Infra + Infra -->|HTTP + cookie| AuthCtrl + AuthCtrl --> AuthSvc + + style Infra fill:#FFC107,stroke:#F57F17,color:#000 + style Guards fill:#FF5722,stroke:#BF360C,color:#fff + style Layout fill:#9C27B0,stroke:#4A148C,color:#fff + style Hooks fill:#009688,stroke:#004D40,color:#fff + style Pages fill:#2196F3,stroke:#0D47A1,color:#fff + style CORS fill:#4CAF50,stroke:#2E7D32,color:#fff + style AuthCtrl fill:#4CAF50,stroke:#2E7D32,color:#fff + style AuthSvc fill:#FFC107,stroke:#F57F17,color:#000 +``` + +--- + +## Unit Decomposition Summary + +| Unit | Name | Primary Components | +|------|------|--------------------| +| 0 | Backend Prerequisites | CorsPolicy, AuthController (cookie), appsettings updates | +| 1 | Project Scaffold | ApiClient, AuthContext, RouterConfig, ThemeProvider, ErrorBoundary, `.env` | +| 2 | Auth Pages | LoginPage, SetupPage, InviteCompletePage, ProtectedRoute, RoleGuard, InitGuard | +| 3 | Layout & Navigation | AppLayout, Sidebar | +| 4 | Dashboard | DashboardPage, AvailabilityStatusBadge, useAvailabilityStatus | +| 5 | User Management | UsersPage, InviteUserDialog, useUsers, useInviteUser, useValidateInvitation, useCompleteSetup | +| 6 | Remaining Pages | ProfilePage, SettingsPage, CmsPage, NotFoundPage, AccessDeniedPage, README.md | + +--- + +## Project Structure + +``` +frontend/ +├── src/ +│ ├── api/ +│ │ ├── client.ts # ApiClient (fetch wrapper, 401 interceptor) +│ │ ├── types.ts # Shared TypeScript interfaces +│ │ ├── useAvailability.ts # TanStack Query hooks +│ │ ├── useUsers.ts +│ │ ├── useInvitation.ts +│ │ └── useSetup.ts +│ ├── auth/ +│ │ ├── AuthContext.tsx # AuthContext + AuthProvider +│ │ ├── useAuth.ts # useAuth hook +│ │ ├── ProtectedRoute.tsx +│ │ ├── RoleGuard.tsx +│ │ └── InitGuard.tsx +│ ├── components/ +│ │ ├── layout/ +│ │ │ ├── AppLayout.tsx +│ │ │ ├── Sidebar.tsx +│ │ │ └── ThemeProvider.tsx +│ │ ├── users/ +│ │ │ └── InviteUserDialog.tsx +│ │ ├── shared/ +│ │ │ └── AvailabilityStatusBadge.tsx +│ │ └── error/ +│ │ └── ErrorBoundary.tsx +│ ├── routes/ # TanStack Router file-based routes +│ │ ├── __root.tsx # Root route (InitGuard, ErrorBoundary) +│ │ ├── index.tsx # / → DashboardPage +│ │ ├── login.tsx # /login +│ │ ├── setup.tsx # /setup +│ │ ├── invite.complete.tsx # /invite/complete +│ │ ├── users.tsx # /users (Owner/Admin) +│ │ ├── profile.tsx # /profile +│ │ ├── settings.tsx # /settings (Owner) +│ │ ├── cms.tsx # /cms (Owner) +│ │ ├── 403.tsx # /403 +│ │ └── $404.tsx # catch-all +│ ├── routeTree.gen.ts # Generated by TanStack Router CLI +│ ├── main.tsx # App entry point +│ └── app.tsx # QueryClientProvider + AuthProvider + RouterProvider +├── .env.example # Template for VITE_API_BASE_URL +├── .env # gitignored — developer local override +├── package.json +├── pnpm-lock.yaml +├── tsconfig.json +├── vite.config.ts +└── tailwind.config.ts +``` + +--- + +## Security Design Summary + +| Concern | Design Decision | +|---------|----------------| +| Access token storage | In-memory only (React state in `AuthContext`) | +| Refresh token storage | httpOnly cookie — set/cleared by backend | +| CORS | Named policy `"FrontendPolicy"` with specific origins + `AllowCredentials()` | +| Role enforcement | `RoleGuard` (routing) + `Sidebar` (UX) + backend (authoritative) | +| HTTP calls | Centralised through `ApiClient` only | +| Form validation | zod schemas matching backend password rules | +| Error handling | Global + per-page `ErrorBoundary`; generic user-facing error messages | + +--- + +## Artifacts Index + +| Artifact | Path | +|---------|------| +| Component definitions | `inception/application-design/components.md` | +| Method signatures | `inception/application-design/component-methods.md` | +| Service layer | `inception/application-design/services.md` | +| Dependency diagram | `inception/application-design/component-dependency.md` | +| This document | `inception/application-design/application-design.md` | diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/component-dependency.md b/aidlc-docs/features/cms-frontend/inception/application-design/component-dependency.md new file mode 100644 index 0000000..8fe19db --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/component-dependency.md @@ -0,0 +1,143 @@ +# Component Dependencies — CMS Frontend + +## Dependency Overview + +```mermaid +graph TD + subgraph Backend["Backend (Unit 0)"] + CorsPolicy["CorsPolicy\n(ServiceCollectionExtensions)"] + AuthCtrl["AuthController\n(updated)"] + AuthSvc["AuthService\n(no changes)"] + AuthCtrl --> AuthSvc + end + + subgraph Infrastructure["Frontend Infrastructure Layer"] + ApiClient["ApiClient\n(fetch wrapper)"] + AuthContext["AuthContext\n(session state)"] + Router["RouterConfig\n(TanStack Router)"] + AuthContext --> ApiClient + end + + subgraph Guards["Auth Guards"] + ProtectedRoute["ProtectedRoute"] + RoleGuard["RoleGuard"] + InitGuard["InitGuard"] + ProtectedRoute --> AuthContext + RoleGuard --> AuthContext + InitGuard --> ApiClient + end + + subgraph Layout["Layout Components"] + AppLayout["AppLayout"] + Sidebar["Sidebar"] + ThemeProvider["ThemeProvider"] + AppLayout --> Sidebar + Sidebar --> AuthContext + end + + subgraph QueryHooks["TanStack Query Hooks"] + useAvailability["useAvailabilityStatus"] + useUsers["useUsers + useInviteUser"] + useInvitation["useValidateInvitation\nuseCompleteSetup"] + useSetup["useSetupStatus\nuseCreateOwner"] + useAvailability --> ApiClient + useUsers --> ApiClient + useInvitation --> ApiClient + useSetup --> ApiClient + end + + subgraph Pages["Page Components"] + LoginPage["LoginPage"] + SetupPage["SetupPage"] + InviteCompletePage["InviteCompletePage"] + DashboardPage["DashboardPage"] + UsersPage["UsersPage"] + ProfilePage["ProfilePage"] + SettingsPage["SettingsPage"] + CmsPage["CmsPage"] + + LoginPage --> AuthContext + SetupPage --> useSetup + InviteCompletePage --> useInvitation + DashboardPage --> useAvailability + UsersPage --> useUsers + ProfilePage --> AuthContext + SettingsPage --> useAvailability + end + + Router --> Guards + Router --> AppLayout + AppLayout --> Pages + + style CorsPolicy fill:#4CAF50,stroke:#2E7D32,color:#fff + style AuthCtrl fill:#4CAF50,stroke:#2E7D32,color:#fff + style AuthSvc fill:#FFC107,stroke:#F57F17,color:#000 + style ApiClient fill:#FFC107,stroke:#F57F17,color:#000 + style AuthContext fill:#FFC107,stroke:#F57F17,color:#000 + style Router fill:#FFC107,stroke:#F57F17,color:#000 + style ProtectedRoute fill:#FF5722,stroke:#BF360C,color:#fff + style RoleGuard fill:#FF5722,stroke:#BF360C,color:#fff + style InitGuard fill:#FF5722,stroke:#BF360C,color:#fff + style AppLayout fill:#9C27B0,stroke:#4A148C,color:#fff + style Sidebar fill:#9C27B0,stroke:#4A148C,color:#fff + style ThemeProvider fill:#9C27B0,stroke:#4A148C,color:#fff + style useAvailability fill:#009688,stroke:#004D40,color:#fff + style useUsers fill:#009688,stroke:#004D40,color:#fff + style useInvitation fill:#009688,stroke:#004D40,color:#fff + style useSetup fill:#009688,stroke:#004D40,color:#fff + style LoginPage fill:#2196F3,stroke:#0D47A1,color:#fff + style SetupPage fill:#2196F3,stroke:#0D47A1,color:#fff + style InviteCompletePage fill:#2196F3,stroke:#0D47A1,color:#fff + style DashboardPage fill:#2196F3,stroke:#0D47A1,color:#fff + style UsersPage fill:#2196F3,stroke:#0D47A1,color:#fff + style ProfilePage fill:#2196F3,stroke:#0D47A1,color:#fff + style SettingsPage fill:#2196F3,stroke:#0D47A1,color:#fff + style CmsPage fill:#2196F3,stroke:#0D47A1,color:#fff +``` + +--- + +## Dependency Matrix + +| Component | Depends On | Used By | +|-----------|-----------|---------| +| **ApiClient** | `import.meta.env.VITE_API_BASE_URL`, `AuthContext.accessToken` | All TanStack Query hooks, `InitGuard` | +| **AuthContext** | `ApiClient` (for login/logout/refresh calls) | `ProtectedRoute`, `RoleGuard`, `Sidebar`, `ProfilePage`, `LoginPage` | +| **RouterConfig** | `ProtectedRoute`, `RoleGuard`, `InitGuard`, `AppLayout` | App root | +| **ProtectedRoute** | `AuthContext` | Router (wraps authenticated routes) | +| **RoleGuard** | `AuthContext` | Router (wraps role-restricted routes) | +| **InitGuard** | `useSetupStatus` → `ApiClient` | Router (root-level check) | +| **AppLayout** | `Sidebar`, `ThemeProvider` | Authenticated routes | +| **Sidebar** | `AuthContext` | `AppLayout` | +| **useAvailabilityStatus** | `ApiClient` | `DashboardPage`, `SettingsPage` | +| **useUsers / useInviteUser** | `ApiClient` | `UsersPage` | +| **useValidateInvitation / useCompleteSetup** | `ApiClient` | `InviteCompletePage` | +| **useSetupStatus / useCreateOwner** | `ApiClient` | `InitGuard`, `SetupPage` | +| **DashboardPage** | `useAvailabilityStatus`, `AuthContext` | Router | +| **UsersPage** | `useUsers`, `useInviteUser`, `InviteUserDialog` | Router | +| **ProfilePage** | `AuthContext` (user data from context — no API call) | Router | +| **SettingsPage** | `useAvailabilityStatus` | Router | +| **InviteCompletePage** | `useValidateInvitation`, `useCompleteSetup` | Router | +| **LoginPage** | `AuthContext.login()` | Router | +| **SetupPage** | `useCreateOwner` | Router | + +--- + +## Communication Patterns + +| Pattern | Where Used | +|---------|-----------| +| **React Context** | Auth state shared from `AuthContext` to guards, sidebar, profile, login page | +| **TanStack Query** | Server state (availability, users, invitation, setup) — cached, auto-refetch | +| **Cookie (httpOnly)** | Browser ↔ Backend: refresh token — set by backend, never read by JS | +| **In-memory state** | Access token held in `AuthContext` — cleared on page refresh (intentional) | +| **LocalStorage** | Theme preference only (`cms-theme`) — no auth data | + +--- + +## Key Dependency Rules (Security Baseline) + +- `ApiClient` is the **only** module that makes HTTP calls — no direct `fetch` in pages/components +- `accessToken` is accessed **only** through `AuthContext` — never passed as prop or stored elsewhere +- `refreshToken` is **never accessible to JavaScript** — httpOnly cookie only +- Role checks happen in `RoleGuard` (routing) and `Sidebar` (UX) — backend is authoritative diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/component-methods.md b/aidlc-docs/features/cms-frontend/inception/application-design/component-methods.md new file mode 100644 index 0000000..c725dca --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/component-methods.md @@ -0,0 +1,192 @@ +# Component Methods — CMS Frontend + +> **Note**: Detailed business rules and logic are defined per-unit in the Functional Design stage (CONSTRUCTION phase). This document captures method signatures, inputs, outputs, and high-level purpose only. + +--- + +## Backend — AuthController (updated) + +```csharp +// POST /api/v1/auth/login +// Body: LoginRequest { Email, Password } +// Sets: Set-Cookie: refreshToken=; HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth +// Returns: { accessToken: string, expiresAt: datetime, user: { id, email, name, role, isActive } } +Task Login(LoginRequest request) + +// POST /api/v1/auth/refresh +// Reads: Cookie: refreshToken +// Sets: Set-Cookie: refreshToken=; HttpOnly; Secure; SameSite=Strict; Path=/api/v1/auth +// Returns: { accessToken: string, expiresAt: datetime, user: { id, email, name, role, isActive } } +Task Refresh() // no body parameter — reads from cookie + +// POST /api/v1/auth/revoke +// Requires: Authorization: Bearer +// Reads: Cookie: refreshToken +// Clears: Set-Cookie: refreshToken=; Expires=epoch; Path=/api/v1/auth +// Returns: 204 No Content +Task Revoke() // no body parameter — reads from cookie +``` + +## Backend — ServiceCollectionExtensions (updated) + +```csharp +// Registers CORS policy "FrontendPolicy" using Cors:AllowedOrigins from configuration +// Called in AddCoreInfrastructure() +static void AddCorsFrontendPolicy(this IServiceCollection services, IConfiguration configuration) + +// Adds app.UseCors("FrontendPolicy") to the pipeline +// Called in Program.cs after UseExceptionHandler, before UseAuthentication +static void UseFrontendCors(this WebApplication app) +``` + +--- + +## Frontend — ApiClient (`frontend/src/api/client.ts`) + +```typescript +// Base configured fetch wrapper; sets Authorization header and credentials: 'include' +// Retries once on 401 after calling authContext.refresh() +function createApiClient(getAccessToken: () => string | null, refresh: () => Promise): ApiClient + +// Typed GET request +async function get(path: string, options?: RequestInit): Promise + +// Typed POST request +async function post(path: string, body: unknown, options?: RequestInit): Promise +``` + +## Frontend — AuthContext (`frontend/src/auth/AuthContext.tsx`) + +```typescript +interface AuthContextValue { + isAuthenticated: boolean + isLoading: boolean // true during initial session restore + user: User | null // { id, email, name, role, isActive } + accessToken: string | null // in-memory only, never in localStorage + + login(email: string, password: string): Promise + logout(): Promise + refresh(): Promise // called by ApiClient on 401; restores token from cookie +} + +// Hook +function useAuth(): AuthContextValue +``` + +## Frontend — Route Guards + +```typescript +// ProtectedRoute: renders children if authenticated, redirects to /login otherwise +// Used as a TanStack Router beforeLoad or as a wrapper component +function ProtectedRoute({ children }: { children: ReactNode }): JSX.Element + +// RoleGuard: renders children if user.role is in allowedRoles, redirects to /403 otherwise +function RoleGuard({ allowedRoles, children }: { allowedRoles: string[], children: ReactNode }): JSX.Element + +// InitGuard: checks GET /setup/status on app load; redirects to /setup if not initialized +function InitGuard({ children }: { children: ReactNode }): JSX.Element +``` + +## Frontend — TanStack Query hooks (`frontend/src/api/`) + +```typescript +// Fetch current availability status; staleTime: 30s +function useAvailabilityStatus(): UseQueryResult + +// Fetch user list; staleTime: 60s +function useUsers(): UseQueryResult + +// Invite a user; invalidates useUsers on success +function useInviteUser(): UseMutationResult + +// Complete invitation setup (public, no auth required) +function useCompleteSetup(): UseMutationResult + +// Validate invitation token (public) +function useValidateInvitation(token: string): UseQueryResult +``` + +## Frontend — Page component signatures + +```typescript +// All page components are default exports used as TanStack Router route components +export default function LoginPage(): JSX.Element +export default function SetupPage(): JSX.Element +export default function InviteCompletePage(): JSX.Element // reads token from search params +export default function DashboardPage(): JSX.Element +export default function UsersPage(): JSX.Element +export default function ProfilePage(): JSX.Element +export default function SettingsPage(): JSX.Element +export default function CmsPage(): JSX.Element +export default function NotFoundPage(): JSX.Element +export default function AccessDeniedPage(): JSX.Element +``` + +## Frontend — Shared components + +```typescript +// Availability badge with colour-coding +function AvailabilityStatusBadge({ status, message }: { + status: 'Available' | 'Maintenance' | 'Unavailable' + message?: string +}): JSX.Element + +// Two-step invite dialog +function InviteUserDialog({ open, onClose }: { + open: boolean + onClose: () => void +}): JSX.Element + +// Error boundary — class component wrapping children +class ErrorBoundary extends React.Component<{ + fallback?: ReactNode + children: ReactNode +}> +``` + +--- + +## TypeScript Type Definitions (`frontend/src/api/types.ts`) + +```typescript +interface User { + id: string + email: string + name: string + role: 'Owner' | 'Admin' | 'User' + isActive: boolean +} + +interface AuthResponse { + accessToken: string + expiresAt: string // ISO datetime + user: User +} + +interface AvailabilityStatusResponse { + status: 'Available' | 'Maintenance' | 'Unavailable' + checkedAt: string // ISO datetime + message?: string +} + +interface InviteUserRequest { + email: string + role: 'Admin' | 'User' +} + +interface InviteResponse { + inviteLink: string +} + +interface CompleteSetupRequest { + token: string + displayName: string + password: string +} + +interface InvitationValidationResponse { + valid: boolean + email: string + role: string +} +``` diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/components.md b/aidlc-docs/features/cms-frontend/inception/application-design/components.md new file mode 100644 index 0000000..0e85224 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/components.md @@ -0,0 +1,196 @@ +# Component Definitions — CMS Frontend + +## Backend Components (Unit 0) + +### CorsPolicy +- **Purpose**: Configure Cross-Origin Resource Sharing for the frontend SPA +- **Responsibilities**: + - Allow the configured frontend origin(s) from `appsettings.json → AllowedOrigins` + - Allow credentials (`AllowCredentials()`) so the browser sends the httpOnly cookie + - Allow required HTTP methods and headers +- **Location**: `src/SlpModularCms.Api/Extensions/ServiceCollectionExtensions.cs` (extended) +- **Configuration**: `appsettings.json → Cors:AllowedOrigins[]`, `appsettings.Development.json` (dev origins), `appsettings.local.json` (developer override) + +### AuthController (updated) +- **Purpose**: Authentication HTTP endpoints — updated to set/clear httpOnly cookie +- **Responsibilities**: + - `Login`: Call `IAuthService.AuthenticateAsync()`, set `refreshToken` httpOnly cookie, return access token + user info only in body + - `Refresh`: Read `refreshToken` from cookie (not request body), call `IAuthService.RefreshTokenAsync()`, set new cookie, return new access token + - `Revoke`: Call `IAuthService.RevokeTokenAsync()` with cookie value, clear the cookie +- **Location**: `src/SlpModularCms.Modules.Identity/Controllers/AuthController.cs` + +### AuthService (updated) +- **Purpose**: Core authentication business logic — updated for cookie-based refresh flow +- **Responsibilities**: + - `RefreshTokenAsync(accessToken, refreshToken)` — signature unchanged; cookie reading is the controller's responsibility + - No other changes to service layer + +--- + +## Frontend Components (Units 1–6) + +### Infrastructure Layer + +#### ApiClient +- **Purpose**: Centralised HTTP client — all API calls go through this module +- **Responsibilities**: + - Base URL from `import.meta.env.VITE_API_BASE_URL` + - Attach `Authorization: Bearer {accessToken}` header on authenticated requests + - Intercept 401 responses — trigger token refresh via `AuthContext.refresh()`, retry original request once + - Expose typed request functions used by TanStack Query + - Send requests with `credentials: 'include'` so the browser includes the httpOnly cookie +- **Location**: `frontend/src/api/client.ts` + +#### AuthContext +- **Purpose**: React Context providing authentication state to the entire app +- **Responsibilities**: + - Store in-memory `accessToken` (string | null) — never persisted to localStorage + - Store authenticated user: `{ id, email, name, role, isActive }` + - Expose `login(email, password)`, `logout()`, `refresh()` methods + - On app mount: call `/auth/refresh` via httpOnly cookie to restore session + - Provide `isAuthenticated`, `isLoading`, `user`, `role` derived state +- **Location**: `frontend/src/auth/AuthContext.tsx` + `frontend/src/auth/useAuth.ts` + +#### RouterConfig +- **Purpose**: TanStack Router file-based route tree with integrated guards +- **Responsibilities**: + - File-based route structure under `frontend/src/routes/` + - Root route checks `InitGuard` (setup status) before rendering any child + - Authenticated routes wrapped with `ProtectedRoute` check + - Role-specific routes wrapped with `RoleGuard` check + - Redirect to `/login` with `?redirect=` param on auth failure +- **Location**: `frontend/src/routes/` (file-based), `frontend/src/routeTree.gen.ts` (generated) + +--- + +### Auth & Guard Components + +#### ProtectedRoute +- **Purpose**: Redirect unauthenticated users to `/login` +- **Responsibilities**: + - Read `isAuthenticated` from `AuthContext` + - If not authenticated: redirect to `/login?redirect={currentPath}` + - If loading (session restore in progress): show loading spinner — no flash of content + - If authenticated: render child route +- **Location**: `frontend/src/auth/ProtectedRoute.tsx` + +#### RoleGuard +- **Purpose**: Restrict routes to specific roles +- **Responsibilities**: + - Accept `allowedRoles: string[]` prop + - Read `user.role` from `AuthContext` + - If role not in `allowedRoles`: redirect to `/403` + - If role allowed: render child route +- **Location**: `frontend/src/auth/RoleGuard.tsx` + +#### InitGuard +- **Purpose**: Redirect to `/setup` if the system is not initialized +- **Responsibilities**: + - On first app render: call `GET /setup/status` + - If `initialized: false`: redirect all routes to `/setup` + - If `initialized: true`: allow normal routing + - Show loading state during check + - Handle API failure with error page + retry +- **Location**: `frontend/src/auth/InitGuard.tsx` + +--- + +### Layout Components + +#### AppLayout +- **Purpose**: Authenticated shell layout wrapping all protected pages +- **Responsibilities**: + - Render `Sidebar` on the left + `` (page content) on the right + - Responsive: full sidebar on desktop, collapsed/hamburger on mobile +- **Location**: `frontend/src/components/layout/AppLayout.tsx` + +#### Sidebar +- **Purpose**: Navigation sidebar with role-filtered links +- **Responsibilities**: + - Read `user.role` from `AuthContext` to filter navigation items + - Navigation items: Dashboard (all), User Management (Owner/Admin), System Settings (Owner), CMS (Owner), Profile (all) + - Logout button: calls `AuthContext.logout()` + - Theme toggle: calls `ThemeProvider.toggle()` + - Highlight active route + - Collapsible on mobile +- **Location**: `frontend/src/components/layout/Sidebar.tsx` + +#### ThemeProvider +- **Purpose**: Dark/light theme management +- **Responsibilities**: + - Wrap app with `next-themes` provider + - Persist theme preference in `localStorage` under key `cms-theme` + - Default to system preference + - Expose `useTheme()` hook via next-themes +- **Location**: `frontend/src/components/layout/ThemeProvider.tsx` + +--- + +### Page Components + +#### LoginPage (`/login`) +- **Purpose**: Unauthenticated login form +- **Responsibilities**: Email + password form, react-hook-form + zod validation, calls `AuthContext.login()` +- **Location**: `frontend/src/routes/login.tsx` + +#### SetupPage (`/setup`) +- **Purpose**: First-time system initialization form +- **Responsibilities**: Email + password form (owner creation), calls `POST /setup/owner`, redirects to `/login` on success +- **Location**: `frontend/src/routes/setup.tsx` + +#### InviteCompletePage (`/invite/complete`) +- **Purpose**: Invitation token completion form +- **Responsibilities**: Validate token via `GET /users/validate-invitation`, show display name + password form, call `POST /users/complete-setup` +- **Location**: `frontend/src/routes/invite.complete.tsx` + +#### DashboardPage (`/`) +- **Purpose**: Authenticated home page +- **Responsibilities**: Welcome widget (user name + role), availability status widget (TanStack Query) +- **Location**: `frontend/src/routes/index.tsx` + +#### UsersPage (`/users`) +- **Purpose**: User management page (Owner/Admin only) +- **Responsibilities**: User list (TanStack Query), InviteUserDialog (invite form + share link step) +- **Location**: `frontend/src/routes/users.tsx` + +#### ProfilePage (`/profile`) +- **Purpose**: Read-only profile page +- **Responsibilities**: Show `user.name`, `user.email`, `user.role` from `AuthContext` +- **Location**: `frontend/src/routes/profile.tsx` + +#### SettingsPage (`/settings`) +- **Purpose**: System settings page (Owner only) +- **Responsibilities**: Show availability status (TanStack Query), show initialized state, placeholder sections +- **Location**: `frontend/src/routes/settings.tsx` + +#### CmsPage (`/cms`) +- **Purpose**: CMS management placeholder (Owner only) +- **Responsibilities**: Work-in-progress placeholder with description of future multi-CMS management +- **Location**: `frontend/src/routes/cms.tsx` + +#### NotFoundPage (`/404` or catch-all) +- **Purpose**: 404 error page +- **Location**: `frontend/src/routes/$404.tsx` + +#### AccessDeniedPage (`/403`) +- **Purpose**: 403 forbidden page shown when role guard fails +- **Location**: `frontend/src/routes/403.tsx` + +--- + +### Shared UI Components + +#### InviteUserDialog +- **Purpose**: Modal dialog for inviting a new user +- **Responsibilities**: Two-step: (1) invite form with email + role, (2) share invite link with copy button +- **Location**: `frontend/src/components/users/InviteUserDialog.tsx` + +#### AvailabilityStatusBadge +- **Purpose**: Colour-coded availability status indicator +- **Responsibilities**: Display Available (green) / Maintenance (yellow) / Unavailable (red) with reason +- **Location**: `frontend/src/components/shared/AvailabilityStatusBadge.tsx` + +#### ErrorBoundary (Global + Per-Page) +- **Purpose**: Catch unexpected React errors +- **Responsibilities**: Global boundary at app root; per-page boundaries for isolated page failures; show user-friendly fallback UI +- **Location**: `frontend/src/components/error/ErrorBoundary.tsx` diff --git a/aidlc-docs/features/cms-frontend/inception/application-design/services.md b/aidlc-docs/features/cms-frontend/inception/application-design/services.md new file mode 100644 index 0000000..77dbad9 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/application-design/services.md @@ -0,0 +1,112 @@ +# Services — CMS Frontend + +## Service Architecture Overview + +The frontend uses **TanStack Query** as the server-state service layer. All API interactions are encapsulated in typed query/mutation hooks. The `ApiClient` handles transport concerns (headers, 401 retry). `AuthContext` manages session state. There is no additional service abstraction layer needed. + +--- + +## Backend Services (Unit 0 changes) + +### CORS Service (new) +- **Service**: `ServiceCollectionExtensions.AddCorsFrontendPolicy()` +- **Purpose**: Register named CORS policy `"FrontendPolicy"` for frontend SPA +- **Configuration source**: `IConfiguration` → `Cors:AllowedOrigins` array +- **Key settings**: + - `WithOrigins(allowedOrigins)` — from config + - `AllowAnyHeader()` + `AllowAnyMethod()` + - `AllowCredentials()` — required for httpOnly cookie +- **dotnet-appsettings pattern**: `AllowedOrigins` stored in `appsettings.json` (production) and `appsettings.Development.json` (dev, e.g. `["http://localhost:5173"]`); `appsettings.local.json` for developer-specific overrides + +--- + +## Frontend Services (TanStack Query hooks) + +### Auth Service (`frontend/src/auth/`) + +**AuthContext** functions as the auth service — it is the single source of truth for authentication state. + +| Operation | Implementation | Notes | +|-----------|----------------|-------| +| Login | `POST /api/v1/auth/login` → stores `accessToken` in memory | httpOnly cookie set by server | +| Session restore | `POST /api/v1/auth/refresh` on app mount → stores new `accessToken` | Reads cookie automatically | +| Logout | `POST /api/v1/auth/revoke` → clears in-memory token | Server clears cookie | +| Token refresh (interceptor) | `POST /api/v1/auth/refresh` on 401 → retry original request | Centralised in ApiClient | + +### Availability Service (`frontend/src/api/useAvailability.ts`) + +| Hook | Query key | Endpoint | Stale time | +|------|-----------|----------|-----------| +| `useAvailabilityStatus()` | `['availability', 'status']` | `GET /api/v1/availability/status` | 30s | + +### Users Service (`frontend/src/api/useUsers.ts`) + +| Hook | Query key | Endpoint | Notes | +|------|-----------|----------|-------| +| `useUsers()` | `['users']` | `GET /api/v1/users` | Owner/Admin only | +| `useInviteUser()` | mutation | `POST /api/v1/users/invite` | Invalidates `['users']` on success | + +### Invitation Service (`frontend/src/api/useInvitation.ts`) + +| Hook | Query key | Endpoint | Notes | +|------|-----------|----------|-------| +| `useValidateInvitation(token)` | `['invitation', token]` | `GET /api/v1/users/validate-invitation?token=` | Public, no auth | +| `useCompleteSetup()` | mutation | `POST /api/v1/users/complete-setup` | Public, no auth | + +### Setup Service (`frontend/src/api/useSetup.ts`) + +| Hook | Query key | Endpoint | Notes | +|------|-----------|----------|-------| +| `useSetupStatus()` | `['setup', 'status']` | `GET /api/v1/setup/status` | Used by `InitGuard` | +| `useCreateOwner()` | mutation | `POST /api/v1/setup/owner` | Used on `/setup` page | + +--- + +## Service Interaction Diagram + +```mermaid +graph TD + App["App Entry\n(main.tsx)"] + InitGuard["InitGuard\n(useSetupStatus)"] + AuthCtx["AuthContext\n(session state)"] + ApiClient["ApiClient\n(fetch + 401 retry)"] + Router["TanStack Router\n(ProtectedRoute + RoleGuard)"] + + subgraph Pages["Page Components"] + Login["LoginPage"] + Dashboard["DashboardPage\n(useAvailabilityStatus)"] + Users["UsersPage\n(useUsers, useInviteUser)"] + Invite["InviteCompletePage\n(useValidateInvitation\nuseCompleteSetup)"] + Settings["SettingsPage\n(useAvailabilityStatus)"] + Profile["ProfilePage"] + end + + App --> InitGuard + App --> AuthCtx + InitGuard -->|initialized| Router + Router --> Login + Router --> Dashboard + Router --> Users + Router --> Invite + Router --> Settings + Router --> Profile + + AuthCtx -->|getAccessToken| ApiClient + AuthCtx -->|refresh| ApiClient + Dashboard --> ApiClient + Users --> ApiClient + Invite --> ApiClient + Settings --> ApiClient + + style App fill:#CE93D8,stroke:#6A1B9A,color:#000 + style InitGuard fill:#FFC107,stroke:#F57F17,color:#000 + style AuthCtx fill:#FFC107,stroke:#F57F17,color:#000 + style ApiClient fill:#FFC107,stroke:#F57F17,color:#000 + style Router fill:#4CAF50,stroke:#2E7D32,color:#fff + style Login fill:#2196F3,stroke:#0D47A1,color:#fff + style Dashboard fill:#2196F3,stroke:#0D47A1,color:#fff + style Users fill:#2196F3,stroke:#0D47A1,color:#fff + style Invite fill:#2196F3,stroke:#0D47A1,color:#fff + style Settings fill:#2196F3,stroke:#0D47A1,color:#fff + style Profile fill:#2196F3,stroke:#0D47A1,color:#fff +``` diff --git a/aidlc-docs/features/cms-frontend/inception/plans/application-design-plan.md b/aidlc-docs/features/cms-frontend/inception/plans/application-design-plan.md new file mode 100644 index 0000000..54faba9 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/plans/application-design-plan.md @@ -0,0 +1,155 @@ +# Application Design Plan — CMS Frontend + +## Design Scope +This plan covers the high-level component architecture for: +1. **Unit 0** — Backend prerequisites (CORS + httpOnly cookie — .NET changes to `SlpModularCms.Api`) +2. **Units 1–6** — React SPA frontend + +--- + +## Design Checklist +- [x] Context analyzed (requirements.md + stories.md + backend inspection) +- [x] Questions generated +- [x] Questions answered +- [x] components.md generated +- [x] component-methods.md generated +- [x] services.md generated +- [x] component-dependency.md generated +- [x] application-design.md (consolidated) generated + +--- + +## Identified Components (preliminary — to be validated by answers below) + +### Backend (Unit 0) +- `CorsConfiguration` — CORS policy setup in `ServiceCollectionExtensions` +- `AuthController` (update) — Add `Set-Cookie` on login/refresh/revoke +- `AuthService` (update) — Read refresh token from cookie in `RefreshTokenAsync` + +### Frontend (Units 1–6) +**Infrastructure layer** +- `ApiClient` — Centralised HTTP client with baseURL, auth headers, 401 interceptor +- `AuthContext` — React context holding in-memory access token + user info + session state +- `RouterConfig` — TanStack Router route tree with guards + +**Auth & Guard components** +- `ProtectedRoute` — Redirects unauthenticated users to `/login` +- `RoleGuard` — Redirects users to access-denied if role insufficient +- `InitGuard` — Checks `/setup/status` and redirects to `/setup` if uninitialized + +**Layout components** +- `AppLayout` — Authenticated shell with sidebar + main content area +- `Sidebar` — Role-filtered navigation links + logout + theme toggle +- `ThemeProvider` — next-themes wrapper for dark/light mode + +**Page components** +- `LoginPage`, `SetupPage`, `InviteCompletePage` — Public/unauthenticated pages +- `DashboardPage` — Availability widget + welcome +- `UsersPage` — User list + invite dialog + share link dialog +- `ProfilePage` — Read-only user info +- `SettingsPage` — Owner-only system info +- `CmsPage` — Owner-only placeholder +- `NotFoundPage`, `AccessDeniedPage` — Error pages + +--- + +## Clarification Questions + +Please answer the following questions by filling in the letter choice after the `[Answer]:` tag. + +--- + +### Question 1: State management approach +How should global application state (auth token, user info, availability status) be managed? + +A) React Context API only — simple, no extra dependencies, sufficient for this app size +B) React Context API + Zustand — Context for auth, Zustand for other shared state (e.g. availability) +C) React Query / TanStack Query for all server state + Context for auth only +D) React Context API + TanStack Query for server state (API calls with caching) +X) Other (please describe after [Answer]: tag below) + +[Answer]: D + +--- + +### Question 2: API client library +Which library should be used for HTTP calls to the backend? + +A) Native `fetch` API with a custom wrapper (no extra dependency) +B) Axios — popular HTTP client with interceptors, request cancellation +C) TanStack Query (React Query) — server state management + caching built-in +D) ky — modern fetch-based HTTP client, lightweight +X) Other (please describe after [Answer]: tag below) + +[Answer]: C + +--- + +### Question 3: Form validation library +Which library should handle form state and validation (already mentioned react-hook-form in NFR-01, but confirm)? + +A) react-hook-form with zod schema validation — standard modern choice +B) react-hook-form with yup schema validation +C) react-hook-form only (no schema library — manual validation) +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +--- + +### Question 4: TanStack Router approach +Which routing style should be used with TanStack Router? + +A) Code-based routing — define routes as objects in a central `routes.ts` file +B) File-based routing — file structure in `src/routes/` maps to URL structure (TanStack Router convention) +X) Other (please describe after [Answer]: tag below) + +[Answer]: B + +--- + +### Question 5: Backend — refresh token cookie name and path +What name and path should the httpOnly cookie use for the refresh token? + +A) `refreshToken` with path `/api/v1/auth` — scoped to the auth endpoints only (more secure) +B) `refreshToken` with path `/` — available for all paths +C) `cms_refresh_token` with path `/api/v1/auth` +X) Other (please describe after [Answer]: tag below) + +[Answer]: A + +--- + +### Question 6: CORS allowed origins configuration +How should the CORS allowed origins be configured in the backend? + +A) Via appsettings.json — e.g. `"AllowedOrigins": ["http://localhost:5173"]` configurable per environment +B) Hardcoded in `ServiceCollectionExtensions.cs` for development only +C) Via environment variable `CORS_ALLOWED_ORIGINS` read at startup +X) Other (please describe after [Answer]: tag below) + +[Answer]: A, let the dotnet-appsettings skill help you manage these settings if needed + +--- + +### Question 7: User data in auth context +What user data should be stored in the auth context after login? + +A) Only `userId`, `email`, `role` (minimum needed for routing/guards) +B) Full user object: `id`, `email`, `naam`, `role`, `isActive` +C) JWT claims only — extract `userId` and `role` directly from the decoded token +X) Other (please describe after [Answer]: tag below) + +[Answer]: B + +--- + +### Question 8: Error boundary scope +Where should React Error Boundaries be placed? + +A) One global error boundary at the app root only +B) Global error boundary + per-page boundaries for isolated page failures +C) Global error boundary + per-widget boundaries (e.g. availability widget on dashboard) +X) Other (please describe after [Answer]: tag below) + +[Answer]: B diff --git a/aidlc-docs/features/cms-frontend/inception/plans/execution-plan.md b/aidlc-docs/features/cms-frontend/inception/plans/execution-plan.md new file mode 100644 index 0000000..20b8af8 --- /dev/null +++ b/aidlc-docs/features/cms-frontend/inception/plans/execution-plan.md @@ -0,0 +1,145 @@ +# Execution Plan — CMS Frontend + +## Detailed Analysis Summary + +### Transformation Scope +- **Transformation Type**: New greenfield frontend project within a brownfield workspace +- **Primary Changes**: New React SPA (`frontend/`) with auth layer, routing, API client, 8+ pages, role-based access +- **Related Components**: SlpModularCms.Api (REST consumer), existing example app (design reference only) + +### Change Impact Assessment +- **User-facing changes**: Yes — entire new admin interface +- **Structural changes**: Yes — new `frontend/` project added to workspace root +- **Data model changes**: No — frontend consumes existing API contracts +- **API changes**: Yes — backend requires CORS configuration and httpOnly cookie support for refresh token (Unit 0, blocking) +- **NFR impact**: Yes — Security Baseline enabled; auth token strategy, input validation, HTTP headers all addressed + +### Risk Assessment +- **Risk Level**: High → Mitigated to Medium after backend fixes +- **Rollback Complexity**: Moderate — backend changes (CORS, httpOnly cookie) must be coordinated with frontend +- **Testing Complexity**: Moderate — auth flows, role guards, invitation token states require careful testing + +### Backend Blocking Issues Found (pre-flight inspection) +1. **🔴 No CORS configuration** — `Program.cs` has no `app.UseCors()` or `builder.Services.AddCors()`. The frontend SPA (different origin) cannot make any API calls without a CORS error. +2. **🟠 Refresh token via JSON body** — `AuthService` returns the refresh token in the response body and the `/auth/refresh` endpoint reads it from the request body. No httpOnly cookie support. This conflicts with NFR-04 (Security Baseline SECURITY-12). +3. **🟡 JWT expiry: 60 minutes** — Acceptable; no action needed. + +**Resolution**: Backend must be updated (Unit 0) before frontend development begins. + +--- + +## Workflow Visualization + +```mermaid +flowchart TD + Start(["User Request"]) + + subgraph INCEPTION["🔵 INCEPTION PHASE"] + WD["Workspace Detection\n✅ COMPLETED"] + RE["Reverse Engineering\n✅ COMPLETED"] + RA["Requirements Analysis\n✅ COMPLETED"] + US["User Stories\n✅ COMPLETED"] + WP["Workflow Planning\n⚙️ IN PROGRESS"] + AD["Application Design\n▶️ EXECUTE"] + UG["Units Generation\n▶️ EXECUTE"] + end + + subgraph CONSTRUCTION["🟢 CONSTRUCTION PHASE"] + FD["Functional Design\n▶️ EXECUTE (per unit)"] + NFRA["NFR Requirements\n▶️ EXECUTE (Unit 1 only)"] + NFRD["NFR Design\n▶️ EXECUTE (Unit 1 only)"] + ID["Infrastructure Design\n⏭️ SKIP"] + CG["Code Generation\n▶️ EXECUTE (per unit)"] + BT["Build and Test\n▶️ EXECUTE"] + end + + subgraph OPERATIONS["🟡 OPERATIONS PHASE"] + OPS["Operations\n⏸️ PLACEHOLDER"] + end + + Start --> WD --> RE --> RA --> US --> WP + WP --> AD --> UG + UG --> FD --> NFRA --> NFRD --> CG + NFRD -.->|skip infra| CG + ID -.->|skipped| CG + CG -->|next unit| FD + CG --> BT --> OPS --> End(["Complete"]) + + style WD fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style RE fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style RA fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style US fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style WP fill:#FFA726,stroke:#E65100,stroke-width:3px,color:#000 + style AD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray:5 5,color:#000 + style UG fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray:5 5,color:#000 + style FD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray:5 5,color:#000 + style NFRA fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray:5 5,color:#000 + style NFRD fill:#FFA726,stroke:#E65100,stroke-width:3px,stroke-dasharray:5 5,color:#000 + style ID fill:#BDBDBD,stroke:#424242,stroke-width:2px,stroke-dasharray:5 5,color:#000 + style CG fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style BT fill:#4CAF50,stroke:#1B5E20,stroke-width:3px,color:#fff + style OPS fill:#FFF9C4,stroke:#F57F17,stroke-width:2px,stroke-dasharray:5 5,color:#000 + style Start fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000 + style End fill:#CE93D8,stroke:#6A1B9A,stroke-width:3px,color:#000 +``` + +--- + +## Phases to Execute + +### 🔵 INCEPTION PHASE + +- [x] Workspace Detection — COMPLETED +- [x] Reverse Engineering (shared) — COMPLETED +- [x] Requirements Analysis — COMPLETED +- [x] User Stories — COMPLETED +- [~] Workflow Planning — IN PROGRESS +- [ ] **Application Design — EXECUTE** + - **Rationale**: New project with multiple layers (auth context, API client, router, role guards, pages). Component responsibilities and service boundaries need to be defined before code generation. +- [ ] **Units Generation — EXECUTE** + - **Rationale**: The frontend consists of 6 logical units (scaffold, auth pages, layout, dashboard, user management, remaining pages) that benefit from being designed and implemented one at a time. + +### 🟢 CONSTRUCTION PHASE (per unit) + +- [ ] **Functional Design — EXECUTE (for each unit)** + - **Rationale**: Each unit has business logic (auth flows, token refresh, role guards, invitation token validation) that needs to be designed before coding. +- [ ] **NFR Requirements — EXECUTE (Unit 1 — Project Scaffold only)** + - **Rationale**: Tech stack and security patterns are set in Unit 1. Subsequent units inherit these decisions; no need to re-evaluate NFRs per unit. +- [ ] **NFR Design — EXECUTE (Unit 1 only)** + - **Rationale**: Auth token storage pattern, API client interceptor, and error boundary design are foundational and should be explicitly designed once. +- [ ] **Infrastructure Design — SKIP** + - **Rationale**: No cloud infrastructure resources to define. The frontend is a static SPA served from a web server or CDN. Deployment instructions go in README.md per NFR-05. +- [ ] **Code Generation — EXECUTE (per unit, always)** + - **Rationale**: Implementation of each unit. +- [ ] **Build and Test — EXECUTE** + - **Rationale**: Build, type-check, lint, and verify all units work together. + +### 🟡 OPERATIONS PHASE +- [ ] Operations — PLACEHOLDER (future deployment/monitoring workflows) + +--- + +## Proposed Unit Decomposition + +| Unit | Name | Key Deliverables | +|------|------|-----------------| +| **Unit 0** | **Backend Prerequisites** | CORS policy (allow frontend origin, credentials), httpOnly cookie for refresh token (Set-Cookie on login/refresh, clear on revoke), update `/auth/refresh` to read token from cookie | +| Unit 1 | Project Scaffold & Infrastructure | Vite + React + TypeScript project init, TanStack Router setup, shadcn/ui + Tailwind v4, auth context (in-memory access token), API client with interceptor, `.env` config | +| Unit 2 | Authentication Pages | Login page, Setup (initialization) page, Invite Complete page, token refresh on 401 | +| Unit 3 | Layout & Navigation | Authenticated layout shell, role-based sidebar, theme toggle (dark/light), ProtectedRoute + RoleGuard | +| Unit 4 | Dashboard | Dashboard page with welcome widget and availability status indicator | +| Unit 5 | User Management | Users list page, Invite user dialog, share invite link step | +| Unit 6 | Profile, System Settings & CMS Placeholder | Profile page, System Settings page (Owner only), CMS placeholder page (Owner only), 404/403 pages, README.md frontend section | + +--- + +## Success Criteria +- **Primary Goal**: Fully functional CMS admin SPA that connects to the existing .NET API +- **Key Deliverables**: All 20 user stories implemented, INVEST-compliant acceptance criteria met +- **Quality Gates**: + - TypeScript compiles without errors + - All routes protected with correct role guards + - Auth flow (login → refresh → logout) works end-to-end + - Password validation matches backend rules + - Security baseline compliance maintained + - README.md frontend section complete diff --git a/aidlc-docs/features/cms-frontend/inception/requirements/requirements.md b/aidlc-docs/features/cms-frontend/inception/requirements/requirements.md index df0381d..9f5e610 100644 --- a/aidlc-docs/features/cms-frontend/inception/requirements/requirements.md +++ b/aidlc-docs/features/cms-frontend/inception/requirements/requirements.md @@ -34,6 +34,7 @@ - If the user is not authenticated and tries to access a protected route, redirect to `/login` - Role-based guards MUST be applied per page: - System Settings page: Owner only + - CMS Management page: Owner only - User Management page: Owner and Admin - Profile page: Any authenticated user - Dashboard: Any authenticated user @@ -75,6 +76,7 @@ - Dashboard: all authenticated users - User Management: Owner and Admin - System Settings: Owner only + - CMS Management: Owner only (multi-CMS management feature; restricted from the start even though it is a placeholder in v1) - Profile: all authenticated users - Logout button: all authenticated users - Mobile-responsive: sidebar collapses on small screens @@ -85,9 +87,10 @@ - Default: system preference ### FR-12: CMS Management (Placeholder) -- Page at `/cms` (structure ready, no real data) -- Shows navigation structure for future CMS content modules -- Clearly marked as "Work in Progress" with a placeholder message +- Page at `/cms` accessible to **Owner only** +- Non-Owner roles attempting to access `/cms` are redirected to an "Access Denied" page or back to `/` +- Shows a "Work in Progress" placeholder describing that this section will allow managing multiple client CMS instances +- CMS Management is NOT shown in the sidebar for Admin or User roles --- @@ -140,7 +143,14 @@ ### NFR-06: Input Validation (SECURITY-05) - All form inputs are validated client-side using react-hook-form before submission -- Validation includes: required fields, email format, password minimum length (8+ chars), max lengths +- Password fields enforce the **exact backend rules** (verified in `ServiceCollectionExtensions.cs`): + - Minimum length: **8 characters** + - At least **1 uppercase letter** (`RequireUppercase = true`) + - At least **1 lowercase letter** (`RequireLowercase = true`) + - At least **1 digit** (`RequireDigit = true`) + - At least **1 non-alphanumeric character** (`RequireNonAlphanumeric = true`, e.g. `!@#$%^&*`) +- Inline per-rule validation messages indicate which specific rule is not yet met +- Validation also includes: required fields, email format, max lengths - Server-side validation errors are displayed to the user without exposing internal details ### NFR-07: Access Control (SECURITY-08) diff --git a/aidlc-docs/features/cms-frontend/inception/user-stories/stories.md b/aidlc-docs/features/cms-frontend/inception/user-stories/stories.md index 08d036c..55d77c5 100644 --- a/aidlc-docs/features/cms-frontend/inception/user-stories/stories.md +++ b/aidlc-docs/features/cms-frontend/inception/user-stories/stories.md @@ -16,7 +16,7 @@ As an Owner/Admin/User, I want to log in with my email address and password, so - [ ] A `/login` page is displayed for unauthenticated users - [ ] The form contains an email field, a password field, and a submit button - [ ] Email field validates format before submission (invalid format shows inline error) -- [ ] Password field has a minimum length client-side hint (8 characters) +- [ ] Password field enforces backend rules client-side: minimum 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 digit, and 1 non-alphanumeric character (e.g. `!@#$%`) - [ ] On valid credentials: access token is stored in memory, refresh token is set as httpOnly cookie, user is redirected to `/` - [ ] On invalid credentials: a generic error message is shown ("Invalid email or password") — no distinction between wrong email and wrong password - [ ] On network error: a user-friendly error message is shown ("Unable to connect. Please try again.") @@ -205,7 +205,8 @@ As a person who received an invitation link, I want to complete my account setup **Acceptance Criteria**: - [ ] Navigating to `/invite/complete?token={token}` triggers `GET /users/validate-invitation?token={token}` - [ ] If the token is valid: a form is shown with a display name field and a password field (with confirmation) -- [ ] Password must meet minimum requirements (8+ characters); a strength indicator is shown +- [ ] Password must meet the backend requirements: minimum 8 characters, at least 1 uppercase letter, 1 lowercase letter, 1 digit, and 1 non-alphanumeric character — these rules are enforced client-side before submission and validated server-side +- [ ] Inline validation messages indicate which specific rule is not yet met (e.g. "Must contain at least 1 uppercase letter") - [ ] Password confirmation must match; mismatch shows an inline error - [ ] On submit: `POST /users/complete-setup` is called with the token, display name, and password - [ ] On success: a confirmation message is shown ("Your account is ready. You can now log in.") with a link to `/login` @@ -283,7 +284,7 @@ As an authenticated user, I want to see only the navigation items relevant to my - [ ] Dashboard: visible to all authenticated users - [ ] User Management: visible to Owner and Admin only - [ ] System Settings: visible to Owner only -- [ ] CMS Management: visible to all authenticated users +- [ ] CMS Management: visible to Owner only (feature is for managing other client CMS instances; out of scope for v1 but access is restricted from the start) - [ ] Profile: visible to all authenticated users - [ ] Logout: visible to all authenticated users - [ ] The sidebar is responsive: collapses to an icon-only view or a hamburger menu on small screens @@ -309,15 +310,18 @@ As an authenticated user, I want to switch between dark and light themes, so tha ## Epic: CMS Management (Placeholder) ### US-20: View CMS management placeholder page -**Persona**: Owner, Admin, User +**Persona**: Owner -As an authenticated user, I want to navigate to the CMS Management section, so that I know where CMS content features will be available in the future. +As an Owner, I want to navigate to the CMS Management section, so that I know where multi-CMS management features will be available in the future. **Acceptance Criteria**: -- [ ] The `/cms` route renders a CMS Management page for all authenticated users +- [ ] The `/cms` route renders a CMS Management page for Owner only +- [ ] Navigating to `/cms` as Admin or User redirects to an "Access Denied" page or back to `/` +- [ ] The CMS Management item is NOT shown in the sidebar for Admin or User roles - [ ] The page displays a clear "Work in Progress" or "Coming Soon" message -- [ ] A brief description explains that CMS content modules will appear here +- [ ] A brief description explains that this section will allow managing multiple client CMS instances - [ ] The page uses the same layout as other pages (sidebar, header) +- [ ] **Security**: The access restriction is enforced client-side as defence-in-depth; the backend also enforces authorisation ---