keycloak init
This commit is contained in:
@@ -1,160 +1,236 @@
|
||||
# Post-Generation Validation
|
||||
|
||||
After generating the backend or fullstack application, run these checks to ensure the project will run without runtime errors.
|
||||
After generating the backend or fullstack application, run these checks to ensure the project will start cleanly and that the default auth model has been generated correctly.
|
||||
|
||||
---
|
||||
|
||||
# Validation Checklist
|
||||
|
||||
## 1. Environment file
|
||||
## 1. Frontend and backend env files
|
||||
|
||||
- [ ] **`.env` exists** in the backend root (e.g. `server/.env`).
|
||||
- [ ] **`.env.example` exists** with at least `DATABASE_URL` and a placeholder value.
|
||||
- [ ] **`DATABASE_URL`** is present in `.env` (or documented in `.env.example` so the user can copy and set it).
|
||||
- [ ] `server/.env.example` exists and documents:
|
||||
- `PORT`
|
||||
- `DATABASE_URL`
|
||||
- `CORS_ALLOWED_ORIGINS`
|
||||
- `KEYCLOAK_ISSUER_URL`
|
||||
- `KEYCLOAK_AUDIENCE`
|
||||
- optional `KEYCLOAK_JWKS_URL`
|
||||
- [ ] `client/.env.example` exists and documents:
|
||||
- `VITE_API_URL`
|
||||
- `VITE_KEYCLOAK_URL`
|
||||
- `VITE_KEYCLOAK_REALM`
|
||||
- `VITE_KEYCLOAK_CLIENT_ID`
|
||||
- [ ] Runtime code fails fast when required auth or database env vars are missing.
|
||||
- [ ] Runtime code does not silently fall back to production auth settings.
|
||||
|
||||
**Failure symptom:** `Environment variable not found: DATABASE_URL` at startup.
|
||||
**Failure symptoms:** startup succeeds with undefined auth config, or fails later with opaque auth/runtime errors.
|
||||
|
||||
---
|
||||
|
||||
## 2. PrismaService implementation
|
||||
## 2. Keycloak realm artifact
|
||||
|
||||
- [ ] A **PrismaService** (or equivalent) class exists and extends `PrismaClient`.
|
||||
- [ ] It implements **`OnModuleInit`** and calls **`await this.$connect()`** in `onModuleInit()`.
|
||||
- [ ] It does **not** use **`this.$on('beforeExit', ...)`** or any `beforeExit` hook.
|
||||
- [ ] A root-level generated Keycloak realm import artifact exists.
|
||||
- [ ] If the repository default filename `toir-realm.json` is not used, the project-specific equivalent is documented consistently across bootstrap and workflow docs.
|
||||
- [ ] The generated realm artifact is self-contained and reproducible.
|
||||
- [ ] The generated realm artifact parameterizes realm name, frontend client ID, backend audience/client ID, production URLs, and artifact filename consistently with the generated project auth config.
|
||||
- [ ] It defines realm roles:
|
||||
- `admin`
|
||||
- `editor`
|
||||
- `viewer`
|
||||
- [ ] It defines a frontend SPA client consistent with the generated frontend Keycloak client ID.
|
||||
- [ ] It defines a backend audience/resource client consistent with the generated backend audience/client ID.
|
||||
- [ ] It defines explicit audience delivery, such as an `api-audience` client scope.
|
||||
- [ ] It does not rely on undeclared built-in client scopes being present after import.
|
||||
- [ ] It explicitly addresses delivery of:
|
||||
- `sub`
|
||||
- `aud`
|
||||
- `realm_access.roles`
|
||||
|
||||
**Failure symptom:** Deprecation/runtime errors in Prisma 5 when using `beforeExit`.
|
||||
**Failure symptoms:** access tokens are missing required claims, or realm import succeeds but generated apps still cannot authenticate/authorize reliably.
|
||||
|
||||
---
|
||||
|
||||
## 3. Frontend auth files and behavior
|
||||
|
||||
- [ ] Generated frontend includes:
|
||||
- `client/src/config/env.ts`
|
||||
- `client/src/auth/keycloak.ts`
|
||||
- `client/src/auth/authProvider.ts`
|
||||
- `client/src/main.tsx`
|
||||
- `client/src/App.tsx`
|
||||
- `client/src/dataProvider.ts`
|
||||
- [ ] `keycloak-js` is installed.
|
||||
- [ ] Keycloak is initialized before the SPA renders.
|
||||
- [ ] Login is redirect-based only.
|
||||
- [ ] No custom in-app username/password login form is generated.
|
||||
- [ ] `Authorization Code + PKCE (S256)` is encoded in the frontend auth flow.
|
||||
- [ ] `client/src/dataProvider.ts` or the documented shared request seam injects `Authorization: Bearer <access_token>` into all API requests.
|
||||
- [ ] Token refresh is concurrency-safe:
|
||||
- one shared in-flight refresh operation
|
||||
- no parallel refresh stampede
|
||||
- [ ] Generated auth code does not persist access tokens or refresh tokens in `localStorage` or `sessionStorage`.
|
||||
- [ ] React Admin auth semantics distinguish:
|
||||
- `401` -> force logout / re-authentication
|
||||
- `403` -> do not re-authenticate; surface access denied / permission error
|
||||
|
||||
**Failure symptoms:** app renders before auth is ready, reference calls miss auth headers, refresh storms occur, or `403` incorrectly forces logout.
|
||||
|
||||
---
|
||||
|
||||
## 4. Backend auth files and behavior
|
||||
|
||||
- [ ] Generated backend includes:
|
||||
- `server/src/auth/auth.module.ts`
|
||||
- JWT guard
|
||||
- roles guard
|
||||
- `@Public()` decorator
|
||||
- `@Roles()` decorator
|
||||
- typed authenticated principal interface
|
||||
- typed config validation in `server/src/config/`
|
||||
- [ ] `jose` is installed.
|
||||
- [ ] JWT verification uses issuer + audience + JWKS.
|
||||
- [ ] JWKS resolution follows this exact priority:
|
||||
1. `KEYCLOAK_JWKS_URL`
|
||||
2. OIDC discovery
|
||||
3. `${issuer}/protocol/openid-connect/certs`
|
||||
- [ ] Authorization roles are extracted only from `realm_access.roles`.
|
||||
- [ ] Deprecated Keycloak-specific Node adapters are not used.
|
||||
|
||||
**Failure symptoms:** invalid tokens are accepted, valid tokens are rejected due to bad JWKS resolution, or RBAC depends on unstable/non-standard claims.
|
||||
|
||||
---
|
||||
|
||||
## 5. CRUD protection and RBAC defaults
|
||||
|
||||
- [ ] `/health` is public.
|
||||
- [ ] Each generated CRUD controller method other than explicit public routes is protected by the generated auth/RBAC infrastructure.
|
||||
- [ ] CRUD RBAC defaults are present:
|
||||
- `GET` -> `viewer | editor | admin`
|
||||
- `POST`, `PATCH`, `PUT` -> `editor | admin`
|
||||
- `DELETE` -> `admin`
|
||||
- [ ] Unauthenticated request to a protected route returns `401`.
|
||||
- [ ] Authenticated user with insufficient role receives `403`.
|
||||
|
||||
**Failure symptoms:** anonymous CRUD access remains open, or insufficient-role users are not denied properly.
|
||||
|
||||
---
|
||||
|
||||
## 6. PrismaService implementation
|
||||
|
||||
- [ ] A `PrismaService` (or equivalent) class exists and extends `PrismaClient`.
|
||||
- [ ] It implements `OnModuleInit` and calls `await this.$connect()` in `onModuleInit()`.
|
||||
- [ ] It does not use `this.$on('beforeExit', ...)` or any `beforeExit` hook.
|
||||
|
||||
**Failure symptom:** deprecation/runtime errors in Prisma 5 when using `beforeExit`.
|
||||
|
||||
**Reference:** `backend/prisma-service.md`
|
||||
|
||||
---
|
||||
|
||||
## 3. Prisma client lifecycle
|
||||
## 7. Prisma client lifecycle
|
||||
|
||||
- [ ] **`package.json`** includes a script that runs Prisma client generation:
|
||||
- Either **`"postinstall": "prisma generate"`** (or `npx prisma generate`),
|
||||
- Or clear documentation to run **`npx prisma generate`** after install.
|
||||
- [ ] After schema generation or change, **`npx prisma generate`** has been run (or will run via postinstall).
|
||||
- [ ] `package.json` includes a script that runs Prisma client generation:
|
||||
- either `"postinstall": "prisma generate"` (or `npx prisma generate`)
|
||||
- or clear documentation to run `npx prisma generate` after install
|
||||
- [ ] After schema generation or change, `npx prisma generate` has been run or will run via `postinstall`.
|
||||
|
||||
**Failure symptom:** `Cannot find module '@prisma/client'` or missing types at build/run.
|
||||
**Failure symptom:** `Cannot find module '@prisma/client'` or missing generated types.
|
||||
|
||||
---
|
||||
|
||||
## 4. Database migration
|
||||
## 8. Database migration
|
||||
|
||||
- [ ] **Migration workflow** is documented (e.g. in README or generation docs).
|
||||
- [ ] Instruction to run **`npx prisma migrate dev`** (or `prisma migrate deploy` for production) after first generation or schema change.
|
||||
- [ ] Generation pipeline (see `generation/backend-generation.md`) includes or documents the migration step.
|
||||
- [ ] Migration workflow is documented.
|
||||
- [ ] Instruction to run `npx prisma migrate dev` exists after first generation or schema change.
|
||||
- [ ] Generation pipeline includes or documents the migration step.
|
||||
|
||||
**Failure symptom:** Tables do not exist; Prisma errors on first query.
|
||||
**Failure symptom:** tables do not exist; Prisma errors on first query.
|
||||
|
||||
---
|
||||
|
||||
## 5. REST route parameters
|
||||
## 9. REST route parameters
|
||||
|
||||
- [ ] For each entity, path parameters use the **correct primary key name** from the DSL.
|
||||
- [ ] **Entity with PK `id` (uuid):** routes use **`/:id`** (e.g. `GET /equipment/:id`, `PATCH /equipment/:id`).
|
||||
- [ ] **Entity with non-`id` primary key (e.g. `code`):** routes use **`/:code`** (or the actual PK attribute name), e.g. `GET /equipment-types/:code`, **not** `GET /equipment-types/:id`.
|
||||
- [ ] For each entity, path parameters use the correct primary key name from the DSL.
|
||||
- [ ] Entity with PK `id` uses `/:id`.
|
||||
- [ ] Entity with non-`id` primary key (for example `code`) uses the actual PK name such as `/:code`, not `/:id`.
|
||||
|
||||
**Example:**
|
||||
**Failure symptom:** controller expects one param name while the route defines another, leading to broken CRUD behavior.
|
||||
|
||||
| Entity | Primary key | Correct path | Incorrect path |
|
||||
| ------------- | ----------- | ---------------------- | -------------------- |
|
||||
| Equipment | id | /equipment/:id | — |
|
||||
| EquipmentType | code | /equipment-types/:code | /equipment-types/:id |
|
||||
| RepairOrder | id | /repair-orders/:id | — |
|
||||
|
||||
**Failure symptom:** Controller expects `params.id` but route is defined with `:code`; or vice versa; 404 or wrong resource updated.
|
||||
|
||||
**Reference:** `backend/architecture.md` — API path rules for non-id primary keys.
|
||||
**Reference:** `backend/architecture.md`
|
||||
|
||||
---
|
||||
|
||||
## 6. DTO type mapping (serialization)
|
||||
## 10. DTO type mapping and React Admin ID compatibility
|
||||
|
||||
- [ ] **DSL `decimal`** → In DTO/API response, use **`string`** (or a type that serializes to string), not Prisma `Decimal`, to avoid JSON serialization issues.
|
||||
- [ ] **DSL `date`** → In DTO/API response, use **`string`** (ISO 8601) or ensure DateTime is serialized to string, so React Admin and JSON consumers receive a string.
|
||||
- [ ] DSL `decimal` maps to DTO/API `string`.
|
||||
- [ ] DSL `date` maps to DTO/API `string` (ISO) or equivalent string serialization.
|
||||
- [ ] Every API response object contains a field named `id`.
|
||||
- [ ] If the entity primary key is not named `id`, the response maps the primary key to `id`.
|
||||
|
||||
**Reference:** `backend/prisma-rules.md` — DTO type mapping table.
|
||||
**Failure symptoms:** serialization issues for decimals/dates, or React Admin cannot identify records.
|
||||
|
||||
---
|
||||
|
||||
## 7. React Admin ID field in API responses
|
||||
## 11. Update payload sanitization
|
||||
|
||||
- [ ] **Every API response object** (list items and single-resource GET) contains a field named **`id`**.
|
||||
- [ ] If the entity primary key is **not** named `id`, the response must **map** the primary key to `id`: e.g. `id: record.code` for EquipmentType, so the payload includes both `id` and `code` (or at least `id` with the PK value).
|
||||
- [ ] The `id` field value must be the **primary key value** (string or uuid as appropriate).
|
||||
- [ ] Update endpoints do not pass `id` or the primary key in Prisma `data`.
|
||||
- [ ] Generated update methods remove `id`, the entity primary key, and readonly attributes before calling `prisma.*.update()`.
|
||||
|
||||
**Failure symptom:** React Admin fails to identify records, breaks cache/references, or throws when expecting `record.id`.
|
||||
|
||||
**Reference:** `frontend/react-admin-rules.md` — React Admin ID Field Requirement.
|
||||
**Failure symptom:** Prisma throws because immutable or invalid fields are passed in update `data`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Update payload sanitization (service layer)
|
||||
## 12. Database runtime
|
||||
|
||||
- [ ] **Update endpoint must not pass `id` (or primary key) in Prisma `data`.** The service must sanitize the incoming DTO before calling `prisma.*.update({ where, data })`: remove `id`, remove the entity primary key field (e.g. `code`), and remove any readonly attributes. Only updatable fields should be passed as `data`.
|
||||
- [ ] Generated update methods follow the pattern from **backend/service-rules.md** (e.g. `const { id, code, ...data } = dto` then pass `data` to Prisma).
|
||||
- [ ] `docker-compose.yml` exists at the project root.
|
||||
- [ ] It defines a PostgreSQL service with image `postgres:16`, port `5432`, and credentials matching `DATABASE_URL`.
|
||||
- [ ] `docker compose up -d` starts the database successfully.
|
||||
- [ ] `docker-compose.yml` does not add Keycloak in this repository.
|
||||
|
||||
**Failure symptom:** Prisma throws when `data` contains `id` or another field that is not on the model or not writable (e.g. entity with PK `code` receives body with `id` from React Admin).
|
||||
|
||||
**Reference:** `backend/service-rules.md`
|
||||
**Failure symptom:** Prisma cannot reach the development database or repo topology drifts from the documented external-Keycloak model.
|
||||
|
||||
---
|
||||
|
||||
## 9. Database runtime (docker-compose)
|
||||
## 13. Migrations, seed, and health endpoint
|
||||
|
||||
- [ ] **`docker-compose.yml` exists** at the project root (or documented location).
|
||||
- [ ] It defines a **PostgreSQL** service with image (e.g. `postgres:16`), port `5432`, and credentials/DB name matching `DATABASE_URL` in `server/.env`.
|
||||
- [ ] **Database container starts:** `docker compose up -d` runs without error and the container is reachable on the configured port.
|
||||
- [ ] `npx prisma migrate dev` runs successfully from `server/`.
|
||||
- [ ] Seed script exists at `server/prisma/seed.ts` (or equivalent).
|
||||
- [ ] `npx prisma db seed` runs without error.
|
||||
- [ ] Backend exposes `GET /health`.
|
||||
- [ ] `GET /health` returns HTTP 200 with a status payload.
|
||||
|
||||
**Failure symptom:** `PrismaClientInitializationError P1001: Can't reach database server at localhost:5432`.
|
||||
|
||||
**Reference:** `backend/database-runtime.md`
|
||||
|
||||
---
|
||||
|
||||
## 10. Migrations and seed
|
||||
|
||||
- [ ] **`npx prisma migrate dev`** runs successfully from `server/` when the database is up (schema is applied or created).
|
||||
- [ ] **Seed script** exists at `server/prisma/seed.ts` (or equivalent) and creates minimal sample data (e.g. one EquipmentType, one Equipment, one RepairOrder).
|
||||
- [ ] **`npx prisma db seed`** runs without error (package.json has `prisma.seed` configured and seed runner installed).
|
||||
|
||||
**Reference:** `backend/seed-rules.md`, `generation/runtime-bootstrap.md`
|
||||
|
||||
---
|
||||
|
||||
## 11. Health endpoint
|
||||
|
||||
- [ ] Backend exposes **GET /health** (or equivalent health route).
|
||||
- [ ] **API responds to /health:** With backend running, `GET http://localhost:<port>/health` returns HTTP 200 and a body such as `{ "status": "ok" }`.
|
||||
|
||||
**Reference:** `backend/architecture.md` — Health Endpoint
|
||||
**Failure symptom:** development bootstrap cannot complete end-to-end.
|
||||
|
||||
---
|
||||
|
||||
# Summary Table
|
||||
|
||||
| Check | Required artifact / rule |
|
||||
| ------------------- | ---------------------------------------------- |
|
||||
| .env | File exists with DATABASE_URL |
|
||||
| DATABASE_URL | Present in .env or .env.example |
|
||||
| PrismaService | OnModuleInit + $connect(); no beforeExit |
|
||||
| prisma generate | postinstall script or documented step |
|
||||
| Migration | Documented step: prisma migrate dev |
|
||||
| REST path params | Use entity PK name (:id or :code, etc.) |
|
||||
| Decimal/Date in DTO | Map to string for serialization |
|
||||
| API response `id` | Every record has `id`; if PK ≠ id, map PK → id |
|
||||
| Update payload | Service strips `id`, PK, readonly from data before Prisma |
|
||||
| docker-compose.yml | Exists at project root; PostgreSQL service |
|
||||
| Database container | Starts with `docker compose up -d` |
|
||||
| prisma migrate dev | Runs successfully from server/ |
|
||||
| Seed script | Exists; prisma db seed runs |
|
||||
| GET /health | Backend responds with 200 and status payload |
|
||||
| Check | Required artifact / rule |
|
||||
| --- | --- |
|
||||
| Frontend env | `client/.env.example` with required Vite auth vars |
|
||||
| Backend env | `server/.env.example` with DB, CORS, and Keycloak vars |
|
||||
| Fail-fast config | Startup fails when required auth env is missing |
|
||||
| Realm artifact | Root generated realm import artifact with self-contained auth setup |
|
||||
| Frontend auth | `keycloak.ts`, `authProvider.ts`, authenticated `dataProvider.ts` |
|
||||
| Backend auth | `AuthModule`, guards, decorators, typed principal |
|
||||
| JWKS strategy | explicit URL -> discovery -> certs fallback |
|
||||
| Role source | `realm_access.roles` only |
|
||||
| CRUD RBAC | GET viewer/editor/admin; write editor/admin; delete admin |
|
||||
| `/health` | Public and returns 200 |
|
||||
| Protected route unauthenticated | Returns `401` |
|
||||
| Protected route insufficient role | Returns `403` |
|
||||
| Token storage | No `localStorage` / `sessionStorage` persistence |
|
||||
| Token refresh | Concurrency-safe single in-flight refresh |
|
||||
| Prisma lifecycle | `OnModuleInit` + `$connect()`, no `beforeExit` |
|
||||
| Update sanitization | Strip `id` / PK / readonly before Prisma update |
|
||||
| React Admin `id` | Every record includes `id` |
|
||||
| Database runtime | PostgreSQL compose exists and starts |
|
||||
|
||||
---
|
||||
|
||||
# Integration with generation pipeline
|
||||
|
||||
1. **Backend generation** (see `generation/backend-generation.md`) should produce artifacts that satisfy the above by default.
|
||||
2. After generation, run this checklist manually or via a script that parses generated code and config.
|
||||
3. If any check fails, the AI context or generator should be updated so that future runs pass.
|
||||
1. Backend and frontend generation must produce artifacts that satisfy the above by default.
|
||||
2. Runtime bootstrap must include Keycloak realm import/verification before app startup.
|
||||
3. After generation, run this checklist manually or via an automated script.
|
||||
4. If any check fails, update the generator context so future runs pass without manual repair.
|
||||
|
||||
Reference in New Issue
Block a user