Compare commits

...

4 Commits

Author SHA1 Message Date
Dmitri
376776620e fix: make admin-user seeder idempotent
Skip inserting users that already exist in the database to prevent
duplicate key constraint violations on restarts.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 20:50:11 +02:00
Dmitri
3725d0603c fixed .env 2026-06-10 20:45:52 +02:00
Dmitri
97bffdc201 fix: rename login.tsx to Login.tsx for case-sensitive filesystems
Linux is case-sensitive, macOS is not. The import used @/pages/Login
but the file was login.tsx (lowercase).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-06-10 20:28:05 +02:00
Dmitri
6501275715 roles 2026-06-10 18:59:43 +02:00
4 changed files with 385 additions and 340 deletions

View File

@ -76,7 +76,17 @@ export default {
},
];
await queryInterface.bulkInsert('users', rows);
// Check which users already exist to make seeder idempotent
const existing = await queryInterface.sequelize.query<{ id: string }>(
`SELECT id FROM users WHERE id IN (:ids)`,
{ replacements: { ids }, type: 'SELECT' },
);
const existingIds = new Set(existing.map((row) => row.id));
const toInsert = rows.filter((row) => !existingIds.has(row.id as string));
if (toInsert.length > 0) {
await queryInterface.bulkInsert('users', toInsert);
}
},
down: async (queryInterface: QueryInterface) => {

View File

@ -2,7 +2,8 @@ import fs from 'fs';
import path from 'path';
const __dirname = import.meta.dirname;
const ENV_FILE = path.resolve(__dirname, '..', '..', '.env');
// Path: shared/config -> shared -> src -> backend/.env (3 levels up)
const ENV_FILE = path.resolve(__dirname, '..', '..', '..', '.env');
interface EnvEntry {
key: string;

View File

@ -57,7 +57,11 @@ Backend current baseline:
## Active Workstreams
### 1. Backend Migration And Runtime Verification
The workstreams are numbered in dependency/execution order and grouped into five phases. Earlier phases unblock later ones; within a phase, items may run in parallel unless a dependency is stated. Renumbered from the previous flat backlog: the former Role Model Decision, Product Onboarding Contract, and Permission-Based Frontend Authorization workstreams are now merged into Workstream 3 (RBAC); the former standalone seed-verification step is merged into Workstream 4.
**Phase 1 — Foundation (everything else depends on a backend that boots, migrates, and serves routes).**
### 1. Backend Migration And Runtime Boot
Status: open.
@ -68,7 +72,7 @@ Backend product modules exist, but the configured local database migration/seed
Required work:
1. Run `npm run db:migrate` against the configured local database.
2. Run `npm run db:seed`.
2. Run `npm run db:seed` (current seed set; the final RBAC seed content is delivered in Workstream 4, which re-verifies this step).
3. Start backend with the documented env file.
4. Verify public content catalog routes, auth routes, and product module routes respond.
5. Record exact failing migration/seed/runtime errors if any.
@ -81,9 +85,11 @@ Acceptance criteria:
- `GET /api/public/content-catalog/:contentType` works for the required seeded content catalog types.
- Any remaining backend runtime blocker is captured as a specific follow-up with file/error references.
**Phase 2 — Identity, Tenancy & Access Core (the security spine: tenant isolation, the role/permission model, the seeded fixtures, and the finalized protected API surface). Workstreams 57 finalize what is public, what exists, and what is permission-gated, so they belong with the access model rather than after it.**
### 2. Tenant Boundary Audit And Tests
Status: open and high priority.
Status: open and high priority. Co-designed with Workstream 3: the campus/organization scoping here and the `campusId`-on-`users` model in §3.1 are the same scoping layer.
Problem:
@ -103,223 +109,135 @@ Acceptance criteria:
- A non-global user cannot read or mutate another tenant's data.
- Campus-scoped users cannot mutate another campus unless their backend permission explicitly allows it.
### 3. Role Model Decision
### 3. RBAC: Role Hierarchy, Scoped Provisioning, And Guards
Status: open.
Status: open and high priority. Umbrella workstream for the platform's user/role model. **Merged here:** the former Role Model Decision (role-model choice), the former Product Onboarding Contract (provisioning, §3.4), and the former Permission-Based Frontend Authorization (frontend guards, §3.6). Consumes the existing permission engine in `middlewares/check-permissions.ts`.
Problem:
Decisions recorded (owner-approved):
Backend currently maps generated roles to product roles. It is not yet a final product decision whether this mapping is the supported model or product roles should become first-class persisted backend roles/permissions.
- Role model: **first-class persisted roles** in the `roles` table, each carrying a `scope`, with preset permission sets and code-enforced relational constraints. The `GENERATED_ROLE_TO_PRODUCT_ROLE` / `STAFF_TYPE_TO_PRODUCT_ROLE` mappings and the 5-value `productRole` are retired.
- Frontend: **permission-based guards plus an authentication guard**; forbidden direct-URL access redirects to the 404 page; menu items the user cannot access are hidden.
Required decision:
#### 3.0 Current state (analysis, verified)
- Option A: keep generated-role to product-role mapping as the supported model.
- Option B: create first-class persisted product roles: `Teacher`, `Para`, `Office Manager`, `Director`, `Superintendent`.
- Roles today (`backend/src/db/seeders/20200430130760-user-roles.ts`, `backend/src/shared/constants/roles.ts`): 7 generated roles (`Super Administrator`, `Administrator`, `Platform Owner`, `Tenant Director`, `Campus Manager`, `Academic Coordinator`, `Finance Officer`) + `Public` + a `User` default-role name referenced in code. `globalAccess: true` is set only on `Super Administrator` and `Administrator`. These map down to 5 `productRole` values (`teacher | para | office | director | superintendent`).
- Authorization engine: `middlewares/check-permissions.ts` grants a request when (1) `currentUser.id === req.params.id || req.body?.id` (self-access), (2) a `custom_permissions` row matches, or (3) the effective role (assigned `app_role`, else cached `Public`) has the `${METHOD}_${ENTITY}` permission. CRUD routers wire `checkCrudPermissions(entity)`. This engine is sound and is kept.
- Permission catalog: CRUD × 27 entities + `READ_API_DOCS` + `CREATE_SEARCH`. **Product modules are not represented** (no permission exists for the FRAME, walkthrough, quizzes, director-dashboard, attendance-fill, ESA/vocational/community pages, classrooms, etc.).
- Multi-tenancy: `organizationId` on most models (unified, per Workstream 2). Campus link for a user is only indirect, via the `staff` row's `campusId`; `staff_type` is `teacher | admin | support`. There is **no `campusId` on `users`** and no first-class concept of director/owner/student/guardian.
- Seeders: **no organization row is seeded at all**, and the seeded campuses (`shared/constants/campuses.ts`, 6 rows) have **no `organizationId`**. `20200430130760` assigns roles by raw SQL: `super_admin@flatlogic.com`→SuperAdmin, `admin@flatlogic.com`→Administrator, `client@hello.com`→PlatformOwner, `john@doe.com`→TenantDirector. **This contradicts the product spec**, which states `admin@flatlogic.com` (`SEED_ADMIN_EMAIL`) is the super admin. No staff profiles are seeded.
- Frontend: `ModuleRouteGuard` + sidebar filter gate purely on the 5-value `productRole` (`shared/constants/appData.ts` `MODULES[].roles`, `shared/constants/moduleRoutes.ts`). `CurrentUser.permissions` exists but is unused. There is **no authentication guard** (an unauthenticated user reaching the shell is not redirected to `/login`), and `canUserRoleAccessModuleRoute` returns `true` for unknown paths (forbidden routes fall through to the catch-all rather than failing at the guard). Only `/login` and `*` (404) are public; there is no signup route.
Required work after decision:
#### 3.1 Target role hierarchy and scope
1. Document the chosen role model.
2. Align `/api/auth/me` permissions and product role payload with the chosen model.
3. Add backend role/permission tests for staff, director, and superintendent paths.
4. Keep frontend navigation driven by backend role/permissions.
Eleven roles across five scopes (guest is the unauthenticated case, not a stored user):
Acceptance criteria:
- Role model is documented in backend docs.
- Backend tests prove role access for product modules.
- No frontend role grants capability that backend does not enforce.
### 4. Product Onboarding Contract
Status: blocked by customer decision.
Problem:
Generated auth signup/profile endpoints exist, but they do not define the product workflow for company creation, campus creation, user creation, staff profile creation, role assignment, campus assignment, or profile updates.
No-go rules:
- Do not implement frontend registration.
- Do not implement profile creation/editing UI.
- Do not treat generated signup/profile endpoints as the product onboarding contract.
- Do not add temporary compatibility paths.
Required customer decisions:
1. Who creates a company.
2. Who creates campuses.
3. Who invites or creates users.
4. How staff profiles are created and linked to users.
5. How roles and campuses are assigned.
6. Which profile fields users may update themselves.
7. Which profile fields require director/superintendent/admin permissions.
Required work after decision:
1. Add backend onboarding contract and docs.
2. Add tenant-scoped backend workflows.
3. Add frontend typed API modules.
4. Add frontend business hooks.
5. Add UI only after backend contract exists.
6. Add authenticated Playwright workflows using backend-seeded fixtures.
Acceptance criteria:
- Onboarding workflow is customer-approved and documented.
- Backend owns all creation, assignment, and permission checks.
- Frontend only exposes flows backed by typed backend contracts.
### 5. Refresh Token Maintenance
Status: open.
Problem:
Cookie auth and refresh rotation exist, but scheduled cleanup for expired/revoked refresh-token rows remains unresolved.
1. `super_admin` — scope `system`. Unlimited across the whole platform.
2. `system_admin` — scope `system`. Unlimited platform-wide **except** creating/deleting `super_admin` or other `system_admin`.
3. `owner` — scope `organization`. Unlimited inside its own company.
4. `superintendent` — scope `organization`. Unlimited inside the company **except** creating/deleting `owner` or other `superintendent`, and **except** deleting the company profile.
5. `director` — scope `campus`. Unlimited inside its assigned campus; creates campus users and classrooms; grants extra per-user permissions.
6. `office_manager` — scope `campus`. Reads all campus pages **except** the director dashboard; takes quizzes; leaves read receipts; may fill the attendance page.
7. `teacher` — scope `campus`. Reads all campus pages except the director dashboard.
8. `support_staff` — scope `campus`. Reads all campus pages except the director dashboard. (Replaces the current `para` value.)
9. `student` — scope `external`. Reads only `/community-partnerships`, `/vocational-opportunities`, `/esa-funding`.
10. `guardian` — scope `external`. Same external pages as `student`.
11. `guest` — unauthenticated. Public routes only (`/login`, `/signup`, error page); any other URL redirects to 404.
Required work:
1. Define refresh-token retention period.
2. Add cleanup job or operational command.
3. Ensure cleanup is observable.
4. Document operational usage.
1. Add a `scope` column to the `roles` model/migration: enum `system | organization | campus | external | guest`. Keep `globalAccess` for the two system roles (it already short-circuits tenant filtering in the CRUD layer).
2. Replace the seeded role set with these 11 (plus keep `Public` as the unauthenticated fallback mapped to `guest`). Write a data migration mapping legacy roles → new roles (`Super Administrator``super_admin`, `Administrator``system_admin`, `Platform Owner``owner`, `Tenant Director``superintendent`, `Campus Manager``director`, `Academic Coordinator``teacher`, `Finance Officer``office_manager`; `para`/`support` HR type → `support_staff`).
3. Retire `GENERATED_ROLE_TO_PRODUCT_ROLE`, `STAFF_TYPE_TO_PRODUCT_ROLE`, and `PRODUCT_ROLE_VALUES`; the role name + scope is now the source of truth. Replace `productRole` in the `/api/auth/me` DTO with `role: { name, scope }` (keep a stable string the frontend can switch on).
4. Add a campus link for campus-scoped users. Recommended: a nullable `campusId` on `users` (authorization scope), independent of the HR `staff` row. `organizationId` remains the tenant key; `campusId` is required for `director`/`office_manager`/`teacher`/`support_staff` and optional for `student`/`guardian`; null for `system`/`organization` scopes. (Same scoping layer as Workstream 2.)
#### 3.2 Permission catalog expansion
The frontend can only gate on permissions the backend actually checks, so every guarded page/action needs a permission name.
1. Keep the CRUD × entity permissions; add `classrooms` (alias of `classes` if the same table, otherwise a new entity) to CRUD.
2. Add product-feature permissions for the module routes and special actions, named in the existing `${VERB}_${THING}` style — e.g. `READ_DIRECTOR_DASHBOARD`, `FILL_ATTENDANCE`, `TAKE_QUIZ`, `ACK_READ_RECEIPT`, `READ_COMMUNITY_PARTNERSHIPS`, `READ_VOCATIONAL_OPPORTUNITIES`, `READ_ESA_FUNDING`, plus one per remaining campus module page.
3. Define the **role → permission preset matrix** (the "predefined permissions per role" from the spec) and seed it into `rolesPermissionsPermissions`. Directors additionally hand out per-user `custom_permissions`.
4. Document the matrix in `backend/docs/auth-profile.md` (or a new `backend/docs/rbac.md`).
#### 3.3 Relational role constraints (the "except …" rules)
The constraints are about *who the target is*, not just *what verb on what entity*, so a flat `${METHOD}_${ENTITY}` permission cannot express them. Implement a policy layer enforced in the **service layer** (authoritative), with a middleware in front for early rejection:
1. On user create/update/delete, compare actor scope+role against the target's role: `system_admin` cannot create/delete `super_admin` or `system_admin`; `superintendent` cannot create/delete `owner` or `superintendent`; campus roles cannot escalate above their scope; a director may only create/manage users within its own `campusId`.
2. On organization delete, require `super_admin`/`system_admin`/`owner` (a `superintendent` is blocked).
3. Cross-scope tenancy: every actor may only act within its `organizationId` (and `campusId` for campus scope) unless `globalAccess`.
4. Fix the self-access bypass security hole: `currentUser.id === req.body?.id` (`middlewares/check-permissions.ts`, the self-access branch) lets any user pass a guard by putting their own id in the request body; restrict self-access to read/update of the caller's own profile only.
#### 3.4 Scoped provisioning / onboarding contract (former Product Onboarding Contract)
Auto-provisioning chain, all backend-owned, each step sending an invitation email with a login link (`services/email/list/invitation.ts` already exists). No-go rules carried over from the former onboarding workstream: do not implement frontend self-registration; do not build profile creation/editing UI ahead of the backend contract; do not treat the generated `signup`/`profile` endpoints as the product contract; no temporary compatibility paths.
1. `super_admin`/`system_admin` create an `owner` user → **auto-create the company (organization)** and link the owner; both start minimal (owner has only email+password; company has only the owner link).
2. `owner`/`superintendent` create superintendents, other org users, and campuses; assign a `director` to a campus.
3. `director` creates campus users (`teacher`, `support_staff`, `office_manager`, `student`, `guardian`) and classrooms, and grants extra `custom_permissions`.
4. Every created user receives a login-link email; on first login they land on their own editable profile.
5. Define which profile fields each role may self-edit vs. which require a higher role.
#### 3.5 Backend guard coverage audit
1. Confirm **every** route mounts authentication + `checkCrudPermissions`/`checkPermissions` (cross-reference Workstream 5 public-route audit so only intended routes are public).
2. Add the §3.3 relational policy to the user, organization, campus, staff, and classroom write paths.
3. Add the product-feature permission checks (§3.2) to the feature routes (FRAME, walkthrough, quizzes, attendance, communications, content-catalog, etc.).
4. Ensure list/count/autocomplete are tenant- and campus-scoped (ties to Workstream 2).
#### 3.6 Frontend guards (former Permission-Based Frontend Authorization)
Context: the backend already authorizes every request by **permission**, not role; the user DTO already carries `permissions: string[]`, but the frontend ignores it and gates on the 5-value `productRole`. The goal is to gate UI affordances (routes/menu items/buttons/request triggers) by **permission** — using the same `${METHOD}_${ENTITY}` names the backend checks — while the backend stays the sole source of truth (frontend gating is UX-only and must never be the only enforcement).
1. **AuthGuard.** Wrap the shell: unauthenticated → redirect to `/login`; only `/login`, `/signup`, and the error page are reachable without a session.
2. **Expose permissions in the auth contract.** Confirm `GET /api/auth/me` and the sign-in response include the resolved effective `permissions: string[]` (role permissions `custom_permissions`); merge `custom_permissions` if not already; document the field in `backend/docs/auth-profile.md`.
3. **Shared permission vocabulary.** Add a typed catalog on the frontend (`frontend/src/shared/auth/permissions.ts`) mirroring backend `${METHOD}_${ENTITY}` names; keep it as `shared/constants`-style UI config; do not import backend code.
4. **Permission selector/hook.** Implement `hasPermission/hasAnyPermission/hasAllPermissions` in `frontend/src/business/auth/` (pure functions over `CurrentUser.permissions`) plus a `usePermissions()` hook.
5. **Gate routes by permission.** Replace `productRole`-based `canUserRoleAccessModuleRoute`/`getAccessibleModules` with permission checks; each `MODULES[]` entry declares the permission(s) it requires. Keep a role concept only where genuinely role-specific (e.g. dashboards), not for resource access.
6. **Forbidden direct-URL access redirects to the 404 page** (change `canUserRoleAccessModuleRoute`'s unknown-path `return true` and the guard's redirect target); inaccessible sidebar items are hidden.
7. **Encode the role-specific page sets.** `office_manager` sees all campus pages except the director dashboard (plus quiz/read-receipt/attendance-fill affordances); `teacher`/`support_staff` see all campus pages except the director dashboard; `student`/`guardian` see only `/community-partnerships`, `/vocational-opportunities`, `/esa-funding`; `director`/`superintendent`/`owner` see their full scope; `guest` sees only public routes.
8. **Gate affordances + handle 403.** Hide/disable create/edit/delete triggers by the matching permission (e.g. hide "Add campus" without `CREATE_CAMPUSES`); surface backend `forbidden` responses through one handler (toast + no crash) so UI/permission drift degrades gracefully.
9. **Roles/permissions admin UI.** Let an admin/director create a role, attach/detach permissions, and assign `custom_permissions` to a user — backed by the existing `roles`/`permissions`/`users` endpoints.
10. **Docs.** Update `frontend/docs/frontend-architecture.md` and `backend/docs/auth-profile.md` to describe the permission-based frontend model and the `${METHOD}_${ENTITY}` contract.
#### 3.7 Tests
1. Backend: relational-constraint tests (`system_admin` cannot delete `super_admin`; `superintendent` cannot delete `owner`/`superintendent` or the company; director scoped to its campus), tenant/campus isolation, and the provisioning chain (owner-create auto-creates company).
2. Frontend: per-role route-guard tests (each role can/cannot open the right pages), sidebar-visibility tests, `hasPermission`/selector unit tests, unauthenticated→`/login`, and forbidden-URL→404.
3. Backend-seeded authenticated e2e using the Workstream 4 fixtures (feeds Workstream 8) proving UI affordances match backend enforcement for at least one CRUD entity.
Acceptance criteria:
- Expired and revoked refresh-token rows are cleaned after the approved retention window.
- Cleanup failures are visible and not silent.
- Auth behavior remains unchanged for valid sessions.
- The 11 roles exist as first-class scoped roles; the legacy mapping and 5-value `productRole` are gone.
- Backend is the sole authority: every route enforces authentication + permission + (where relational) the §3.3 policy; removing a frontend check never grants real access (backend still returns 403).
- The "except …" constraints hold in tests for `system_admin`, `superintendent`, and `director`.
- Frontend route/menu/affordance visibility is permission-driven; unauthenticated users are redirected to `/login`; forbidden direct URLs land on 404; guest sees only public routes.
- An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
- Owner-creation auto-creates the company; each provisioning step emails a login link.
- Backend and frontend `typecheck`/`lint`/`test` pass; the Workstream 4 seed runs clean and backs an authenticated e2e.
### 6. API Documentation Hardening
### 4. RBAC Seed Data And Fixtures
Status: open.
Problem:
Markdown docs exist for migrated modules, but Swagger/OpenAPI coverage for product-specific and cookie-session endpoints is incomplete.
Status: open — depends on Workstream 3 (run after the role/permission/model changes land). Implements the spec's seed requirement: a preset company with one campus and staff covering every role, plus one test user per role. Also re-verifies the `npm run db:seed` gate from Workstream 1 against the final seed content.
Required work:
1. Document `/api/auth/signin/local`.
2. Document `/api/auth/refresh`.
3. Document `/api/auth/signout`.
4. Document `/api/auth/me`.
5. Document product module endpoints that are not covered by generated Swagger output.
6. Document response and error shapes per endpoint.
1. **Reconcile the admin identity with the spec.** Make `admin@flatlogic.com` (`SEED_ADMIN_EMAIL`) the **`super_admin`** (the spec's superadmin), not `Administrator`. Add a distinct `system_admin` seed user. Repurpose or delete the leftover template users (`john@doe.com`, `client@hello.com`, `super_admin@flatlogic.com`) so the seed set is exactly the role fixtures below.
2. **Seed one organization (company)** and **one campus** linked to it (give the existing `campuses` seed rows an `organizationId`, or seed a dedicated test campus).
3. **Seed 10 users, one per stored role**`super_admin`, `system_admin`, `owner`, `superintendent`, `director`, `office_manager`, `teacher`, `support_staff`, `student`, `guardian` — each with `app_roleId` set, `organizationId` set (except the two `system` users), and `campusId` set for the campus-scoped and external users as applicable. (`guest` = no user.) Passwords come from `SEED_ADMIN_PASSWORD`/`SEED_USER_PASSWORD` (no plaintext in the repo); test credentials live only in ignored local env or a dedicated test-seed config (per Workstream 8).
4. **Seed staff profiles** for the campus staff roles (`director`, `office_manager`, `teacher`, `support_staff`) so the campus has staff covering every staff role, each linked to the org, the campus, and its user.
5. Make the seed **idempotent** and reversible (clean `down`), and ordered after the role/permission seeder.
6. Re-verify the seeded data: one company, one campus under it, staff with all campus roles, and exactly one loginable user per role. Add a seed/integration check.
Acceptance criteria:
- API docs match actual route payloads and response shapes.
- Cookie auth behavior is explicit.
- Frontend API contract tests remain aligned with docs.
- `npm run db:seed` produces exactly one company, one campus under that company, staff covering every campus role, and one user per role with correct role/org/campus links.
- `admin@flatlogic.com` is the `super_admin` and can log in; each seeded role can log in and lands on the access surface defined in §3.6.
- No template leftover users remain; no plaintext credentials are committed.
### 7. Policy And Safety Acknowledgment Persistence
### 5. Public Backend Route Audit
Status: open pending product contract.
Problem:
Policy content is document-backed, but policy/protocol acknowledgments are not yet persisted.
Required decisions:
1. Which policies/protocols require acknowledgment.
2. Which roles must acknowledge.
3. Whether acknowledgments are per document version.
4. Who can report acknowledgment status.
Required work after decision:
1. Add acknowledgment backend model/migration/service/route.
2. Enforce tenant and role scope.
3. Add frontend typed API and business workflow.
4. Add report views only if required.
Acceptance criteria:
- Acknowledgments survive reload.
- Acknowledgment status is tenant-scoped.
- Unauthorized roles cannot view individual acknowledgment records.
### 8. Attendance Source Contracts
Status: partially open.
Current state:
- Campus attendance daily aggregate summaries are implemented for the current UI.
- Staff attendance snapshot/reporting is read-only.
- Student/class attendance source-of-truth workflow is not defined.
Open decisions:
1. Whether campus attendance aggregates are manually entered, imported, or derived from student attendance records.
2. Which external or internal source owns staff attendance writes/imports.
3. Whether student-level attendance UI is required.
Required work after decision:
1. Add write/import endpoints only after source contract exists.
2. Keep derived summaries server-side if summaries are derived.
3. Add backend tests for source-of-truth calculations.
4. Add frontend workflows only after backend contracts exist.
Acceptance criteria:
- Attendance source of truth is documented.
- UI values can be traced to backend records or server-side derivation.
- No frontend-only attendance source remains in persisted workflows.
### 9. Generated Audio Provider Contract
Status: open pending provider decision.
Problem:
Classroom timer uses built-in Web Audio sounds. AI-generated audio UI is intentionally not exposed because no backend audio provider contract exists.
No-go rules:
- Do not add generated audio UI.
- Do not call external audio providers from frontend runtime.
- Do not add frontend API keys or provider secrets.
Required work after decision:
1. Define backend provider contract.
2. Keep provider secrets in backend env.
3. Add backend service with native provider errors where required.
4. Add typed frontend API and explicit loading/error states.
Acceptance criteria:
- Generated audio is backend-mediated.
- No provider secret reaches the browser.
- Provider failures remain visible.
### 10. File Upload And Download Permissions
Status: open.
Problem:
Backend file and document routes exist. Product file workflows need explicit permission verification before new upload/download UI is added.
Required work:
1. Audit upload permissions.
2. Audit download permissions.
3. Verify tenant and document ownership checks.
4. Add typed frontend upload client only for approved workflows.
Acceptance criteria:
- Unauthorized users cannot access another tenant's files.
- Upload/download behavior is documented and tested before new UI is added.
### 11. Public Backend Route Audit
Status: open.
Status: open. Feeds Workstream 3 §3.5 (guard coverage): the guard audit can only assert "every non-public route is authenticated" once the public set is fixed.
Problem:
@ -337,128 +255,9 @@ Acceptance criteria:
- Every unauthenticated backend route is documented.
- No tenant-owned data is exposed through accidental public routes.
### 12. Backend-Seeded Authenticated E2E
### 6. API Surface Coverage And Dead-Endpoint Decision
Status: blocked by onboarding/profile fixtures.
Problem:
Current Playwright coverage includes backend-free smoke tests and backend-seeded content catalog tests. Authenticated persisted workflows need backend-seeded users, roles, campuses, staff profiles, and known credentials.
Required prerequisites:
1. Product onboarding/profile contract.
2. Backend-seeded auth fixtures.
3. Tenant-scoped campus/staff fixtures.
4. Stable test credentials stored only in ignored local env or dedicated test seed config.
Required workflows after prerequisites:
1. Login to dashboard.
2. Director creates or edits a FRAME entry and sees it after reload.
3. Staff completes QBS quiz and director/superintendent sees compliance.
4. Office enters campus attendance and superintendent sees aggregate.
5. Director submits walkthrough and sees summary update.
6. Staff marks a sign learned and progress persists after reload.
Acceptance criteria:
- Authenticated e2e tests use backend seeds, not frontend mock data.
- Tests do not require production secrets.
- Tests are documented and repeatable.
### 13. Accessibility Test Coverage
Status: open.
Required work:
1. Add axe/Playwright accessibility checks for login.
2. Add axe/Playwright checks for dashboard.
3. Add checks for sidebar navigation.
4. Add checks for modal dialogs.
5. Add checks for forms with validation.
6. Add checks for tables/reports.
Acceptance criteria:
- Accessibility tests run in a documented command.
- Critical violations block completion of the refactor.
### 14. `ref-frontend/` Removal
Status: open.
Required work:
1. Confirm no active docs require `ref-frontend/` for endpoint contract reference.
2. Confirm no scripts import or run `ref-frontend/`.
3. Delete `ref-frontend/`.
4. Update root docs and any setup docs.
Acceptance criteria:
- Only `frontend/` and `backend/` remain as active application code.
- No docs describe `ref-frontend/` as needed for normal development.
### 15. OAuth Provider Strategy Modernization
Status: open. Deferred until after the backend TypeScript/ESM migration (now complete), to keep auth-flow changes separate from the language/module migration.
Problem:
Social login uses `passport-google-oauth2` (0.2.0, last published 2022) and `passport-microsoft` (2.1.0). The Google strategy is low-maintenance and should be modernized, ideally consolidating both providers on a single maintained OAuth/OIDC library.
Required work:
1. Choose target: minimal swap to `passport-google-oauth20`, or consolidate both providers on `openid-client`.
2. Replace the Google (and optionally Microsoft) passport strategy in `backend/src/auth/auth.ts`.
3. Keep the existing cookie-based session and JWT issuance unchanged.
4. Verify callback URLs, scopes, and the social-signup user flow end to end.
5. Update auth docs (`backend/docs/auth-profile.md`, `cookie-auth.md`).
Acceptance criteria:
- Google and Microsoft sign-in work end to end with the chosen library.
- No deprecated/unmaintained OAuth strategy remains in dependencies.
- Cookie/JWT auth behavior is unchanged for existing sessions.
- This change is isolated from the language/module migration (separate PR/task).
### 16. Permission-Based Frontend Authorization
Status: open.
Problem:
The backend already authorizes every request by **permission**, not by role. `backend/src/middlewares/check-permissions.ts` exposes `checkCrudPermissions(entity)`, which derives a permission name `${METHOD}_${ENTITY}` (e.g. `READ_CAMPUSES`, `CREATE_USERS`, `DELETE_ROLES` via `METHOD_MAP`) and grants access when any of the following holds, in order: (1) self-access — `currentUser.id === req.params.id`/`req.body.id`; (2) the user has a direct `custom_permissions` entry with that name; (3) the user's `app_role` (or the `Public` role for unauthenticated/no-role requests) has that permission via `role.getPermissions()`. An admin can therefore create roles and attach permissions (`Roles ↔ Permissions` many-to-many through `RolesDBApi.setPermissions`), plus assign per-user `custom_permissions`.
The frontend, however, does **not** gate on permissions. Module/page access is currently decided by `productRole` (`teacher | para | office | director | superintendent`) in `frontend/src/business/app-shell/selectors.ts` (`canAccessModule(modules, moduleId, userRole)`), and per the architecture contract frontend route hiding is **UX-only**. The user DTO already carries a `permissions: string[]` array (built by `getPermissionNames` in `services/auth.ts`), but the frontend ignores it. This makes the frontend authorization model inconsistent with the backend: a role with customized permissions is correctly enforced by the backend but not reflected in what the UI shows or allows.
Goal: make the frontend gate UI affordances (routes/menu items/buttons/request triggers) by **permission** — using the same `${METHOD}_${ENTITY}` permission names the backend checks — while keeping the backend as the sole source of truth (frontend gating stays UX-only; it must never be the only enforcement).
Required work:
1. **Expose permissions reliably in the auth contract.** Confirm `GET /api/auth/me` and the sign-in response include the resolved `permissions: string[]` (effective permissions = role permissions `custom_permissions`). Add `custom_permissions` to the resolution if not already merged. Document the exact field in `backend/docs/auth-profile.md`.
2. **Define a shared permission vocabulary.** Add a typed catalog of permission names on the frontend (`frontend/src/shared/auth/permissions.ts`) mirroring backend naming `${METHOD}_${ENTITY}` (READ/CREATE/UPDATE/DELETE × entity). Keep it in `shared/constants`-style UI config; do not import backend code.
3. **Add a permission selector/hook.** Implement `hasPermission(user, permissionName)` and `hasAnyPermission/hasAllPermissions` in `frontend/src/business/auth/` (pure functions over `CurrentUser.permissions`), plus a `usePermissions()` hook for components. Include the self-access nuance only where relevant (the backend allows self-access regardless of permission).
4. **Gate routes by permission.** Replace/augment role-based `canAccessModule` with permission-based checks. Each route/module declares the permission(s) it requires; the router redirects/hides when the user lacks them. Keep `productRole` only where a true role concept is still needed (e.g. dashboards), not for resource access.
5. **Gate UI affordances.** Hide or disable create/edit/delete buttons and other action triggers based on the matching permission (e.g. hide "Add campus" without `CREATE_CAMPUSES`). Avoid firing requests the backend will reject with 403.
6. **Handle 403 consistently.** Ensure the API layer surfaces backend `forbidden` responses to a single handler (toast + no crash), so permission drift between UI and backend degrades gracefully.
7. **Admin UI for roles/permissions.** Verify the roles management screens let an admin create a role, attach/detach permissions, and assign `custom_permissions` to a user — backed by the existing `roles`/`permissions`/`users` endpoints.
8. **Tests.** Unit-test `hasPermission`/selectors with permission fixtures; add route-guard tests proving a user without `READ_X` cannot open module X; add a backend-seeded e2e proving UI affordances match backend enforcement for at least one CRUD entity.
9. **Docs.** Update `frontend/docs/frontend-architecture.md` and `backend/docs/auth-profile.md` to describe the permission-based frontend model and the `${METHOD}_${ENTITY}` contract.
Acceptance criteria:
- Frontend route/menu/affordance visibility is driven by the user's effective permissions, using the same names the backend enforces.
- The backend remains the sole authority: removing a frontend check never grants real access (backend still returns 403).
- An admin can create a role, assign permissions, and the change is reflected in both backend enforcement and frontend UI for affected users.
- `productRole`-based gating is removed for resource access (kept only for genuine role-specific UI), with no remaining role↔permission inconsistency.
- Frontend `typecheck`, `lint`, `test` pass; a seeded e2e proves UI/permission alignment for at least one entity.
### 17. API Surface Coverage And Dead-Endpoint Decision
Status: open (analysis complete; decision pending).
Status: open (analysis complete; decision pending). Belongs with the access core: the permission catalog (§3.2) and guard audit (§3.5) must cover exactly the endpoints that survive this classification.
Problem:
@ -526,19 +325,18 @@ Required work:
them (e.g. real `students`/`staff`/`guardians`/`invoices` management) and bring
each group up to the target backend architecture. Do **not** prune them.
2. `auth` extras: keep `signup`/password-reset/verify-email only if onboarding/recovery is
in scope (see Workstream 4); otherwise prune. OAuth callbacks tie to Workstream 15.
3. `file`: keep only if document/avatar upload is on the roadmap (ties to Workstream 10);
in scope (see Workstream 3 §3.4 provisioning); otherwise prune. OAuth callbacks tie to Workstream 15.
3. `file`: keep only if document/avatar upload is on the roadmap (ties to Workstream 7);
otherwise prune.
4. `search`: prune unless a search UI is planned.
5. After any prune, re-run backend `typecheck`/`lint`, and regenerate
`database-schema.md` only if models change.
Cross-references: Workstream 10 (file upload), 11 (public route audit), 15 (OAuth), 16
(permission-based authorization).
Cross-references: Workstream 7 (file upload), 5 (public route audit), 15 (OAuth), 3 (RBAC / permission-based authorization).
Acceptance criteria:
- Every backend endpoint is classified as **used**, **prune**, or **wire (planned)**, with no “unclassified” remainder.
- Every backend endpoint is classified as **used**, **prune**, or **wire (planned)**, with no "unclassified" remainder.
- Pruned routes are removed cleanly (route + service + db/api + permission wiring) with `typecheck`/`lint` green and no dangling imports.
- The frontend remains fully wired (0 orphan calls) after changes.
- This document reflects the final surface.
@ -552,23 +350,256 @@ Performance hardening applied (owner chose **wire**, not prune, for the generic
- The per-request `UsersDBApi.findBy` (passport JWT strategy → `req.currentUser`, read by every guarded route) was collapsed from `findOne` + 4 parallel association getters + a `getPermissions()` into a **single** eager-loaded query (`findOne` + `include` of `app_role`+`permissions`, `staff_user`, `custom_permissions`, `organizations`). Its returned `app_role` now carries `permissions`, and `middlewares/check-permissions.ts` was reordered to read that eager-loaded `permissions` array before falling back to `getPermissions()` — removing the extra per-request permissions query. Same returned shape/fields as before (`AuthenticatedUser`); ~67 queries per request → 1. The cached `Public` role still works (its record carries `permissions`).
- `AuthService.currentUserProfile` (the `GET /me`, signin and refresh responses) now uses a dedicated `UsersDBApi.findProfileById` — a single eager-loaded query (`findByPk` + scoped `include`/`attributes`) returning only the columns and relations the profile DTO reads — instead of the heavy generic `findBy` (1 `findOne` + parallel association getters + `getPermissions`) plus a separate `getCampus`. Required idiomatic `NonAttribute` association declarations on the `Users`/`Roles`/`Staff` models so the include is type-safe without casts. The per-request passport `findBy` that populates `req.currentUser` is unchanged (it is the auth gate read by every guard); `/me` still performs that auth fetch plus this one lean profile query.
## Strict Implementation Sequence
### 7. File Upload And Download Permissions
Use this order unless the user explicitly reprioritizes:
Status: open. Permission-gated surface; aligns with Workstream 3 §3.2/§3.5 and Workstream 6 (`file` extras).
1. Backend migration and seed verification.
2. Tenant boundary audit and tests.
3. Role model decision and enforcement tests.
4. Public route audit.
5. API documentation hardening.
6. Product onboarding after customer decision.
7. Authenticated backend-seeded e2e after onboarding/profile fixtures exist.
8. Remaining optional product contracts: acknowledgments, attendance imports, generated audio, file upload/download UI.
9. Accessibility test coverage.
10. Remove `ref-frontend/`.
11. OAuth provider strategy modernization.
12. Permission-based frontend authorization (align frontend gating with backend permission checks).
13. API surface coverage decision: classify every endpoint and prune or wire the unused backend layer (Workstream 17).
Problem:
Backend file and document routes exist. Product file workflows need explicit permission verification before new upload/download UI is added.
Required work:
1. Audit upload permissions.
2. Audit download permissions.
3. Verify tenant and document ownership checks.
4. Add typed frontend upload client only for approved workflows.
Acceptance criteria:
- Unauthorized users cannot access another tenant's files.
- Upload/download behavior is documented and tested before new UI is added.
**Phase 3 — Verification & Quality (locks in correctness once the access model and fixtures exist).**
### 8. Backend-Seeded Authenticated E2E
Status: depends on Workstream 4 fixtures and Workstream 3 guards.
Problem:
Current Playwright coverage includes backend-free smoke tests and backend-seeded content catalog tests. Authenticated persisted workflows need backend-seeded users, roles, campuses, staff profiles, and known credentials.
Required prerequisites:
1. Product onboarding/profile contract (Workstream 3 §3.4).
2. Backend-seeded auth fixtures (Workstream 4).
3. Tenant-scoped campus/staff fixtures (Workstream 4).
4. Stable test credentials stored only in ignored local env or dedicated test seed config.
Required workflows after prerequisites:
1. Login to dashboard.
2. Director creates or edits a FRAME entry and sees it after reload.
3. Staff completes QBS quiz and director/superintendent sees compliance.
4. Office enters campus attendance and superintendent sees aggregate.
5. Director submits walkthrough and sees summary update.
6. Staff marks a sign learned and progress persists after reload.
Acceptance criteria:
- Authenticated e2e tests use backend seeds, not frontend mock data.
- Tests do not require production secrets.
- Tests are documented and repeatable.
### 9. API Documentation Hardening
Status: open. Run after the route surface is finalized (Workstreams 3, 5, 6) so the docs describe the real endpoints.
Problem:
Markdown docs exist for migrated modules, but Swagger/OpenAPI coverage for product-specific and cookie-session endpoints is incomplete.
Required work:
1. Document `/api/auth/signin/local`.
2. Document `/api/auth/refresh`.
3. Document `/api/auth/signout`.
4. Document `/api/auth/me`.
5. Document product module endpoints that are not covered by generated Swagger output.
6. Document response and error shapes per endpoint.
Acceptance criteria:
- API docs match actual route payloads and response shapes.
- Cookie auth behavior is explicit.
- Frontend API contract tests remain aligned with docs.
### 10. Accessibility Test Coverage
Status: open.
Required work:
1. Add axe/Playwright accessibility checks for login.
2. Add axe/Playwright checks for dashboard.
3. Add checks for sidebar navigation.
4. Add checks for modal dialogs.
5. Add checks for forms with validation.
6. Add checks for tables/reports.
Acceptance criteria:
- Accessibility tests run in a documented command.
- Critical violations block completion of the refactor.
**Phase 4 — Product Contracts (each gated by a customer/provider decision; independent of one another and parallelizable once the access core is in place).**
### 11. Policy And Safety Acknowledgment Persistence
Status: open pending product contract.
Problem:
Policy content is document-backed, but policy/protocol acknowledgments are not yet persisted.
Required decisions:
1. Which policies/protocols require acknowledgment.
2. Which roles must acknowledge.
3. Whether acknowledgments are per document version.
4. Who can report acknowledgment status.
Required work after decision:
1. Add acknowledgment backend model/migration/service/route.
2. Enforce tenant and role scope.
3. Add frontend typed API and business workflow.
4. Add report views only if required.
Acceptance criteria:
- Acknowledgments survive reload.
- Acknowledgment status is tenant-scoped.
- Unauthorized roles cannot view individual acknowledgment records.
### 12. Attendance Source Contracts
Status: partially open.
Current state:
- Campus attendance daily aggregate summaries are implemented for the current UI.
- Staff attendance snapshot/reporting is read-only.
- Student/class attendance source-of-truth workflow is not defined.
Open decisions:
1. Whether campus attendance aggregates are manually entered, imported, or derived from student attendance records.
2. Which external or internal source owns staff attendance writes/imports.
3. Whether student-level attendance UI is required.
Required work after decision:
1. Add write/import endpoints only after source contract exists.
2. Keep derived summaries server-side if summaries are derived.
3. Add backend tests for source-of-truth calculations.
4. Add frontend workflows only after backend contracts exist.
Acceptance criteria:
- Attendance source of truth is documented.
- UI values can be traced to backend records or server-side derivation.
- No frontend-only attendance source remains in persisted workflows.
### 13. Generated Audio Provider Contract
Status: open pending provider decision.
Problem:
Classroom timer uses built-in Web Audio sounds. AI-generated audio UI is intentionally not exposed because no backend audio provider contract exists.
No-go rules:
- Do not add generated audio UI.
- Do not call external audio providers from frontend runtime.
- Do not add frontend API keys or provider secrets.
Required work after decision:
1. Define backend provider contract.
2. Keep provider secrets in backend env.
3. Add backend service with native provider errors where required.
4. Add typed frontend API and explicit loading/error states.
Acceptance criteria:
- Generated audio is backend-mediated.
- No provider secret reaches the browser.
- Provider failures remain visible.
**Phase 5 — Operations, Modernization & Cleanup (low coupling; can run any time after Phase 2, scheduled last to avoid mixing with the access-model changes).**
### 14. Refresh Token Maintenance
Status: open.
Problem:
Cookie auth and refresh rotation exist, but scheduled cleanup for expired/revoked refresh-token rows remains unresolved.
Required work:
1. Define refresh-token retention period.
2. Add cleanup job or operational command.
3. Ensure cleanup is observable.
4. Document operational usage.
Acceptance criteria:
- Expired and revoked refresh-token rows are cleaned after the approved retention window.
- Cleanup failures are visible and not silent.
- Auth behavior remains unchanged for valid sessions.
### 15. OAuth Provider Strategy Modernization
Status: open. Deferred until after the backend TypeScript/ESM migration (now complete), to keep auth-flow changes separate from the language/module migration.
Problem:
Social login uses `passport-google-oauth2` (0.2.0, last published 2022) and `passport-microsoft` (2.1.0). The Google strategy is low-maintenance and should be modernized, ideally consolidating both providers on a single maintained OAuth/OIDC library.
Required work:
1. Choose target: minimal swap to `passport-google-oauth20`, or consolidate both providers on `openid-client`.
2. Replace the Google (and optionally Microsoft) passport strategy in `backend/src/auth/auth.ts`.
3. Keep the existing cookie-based session and JWT issuance unchanged.
4. Verify callback URLs, scopes, and the social-signup user flow end to end.
5. Update auth docs (`backend/docs/auth-profile.md`, `cookie-auth.md`).
Acceptance criteria:
- Google and Microsoft sign-in work end to end with the chosen library.
- No deprecated/unmaintained OAuth strategy remains in dependencies.
- Cookie/JWT auth behavior is unchanged for existing sessions.
- This change is isolated from the language/module migration (separate PR/task).
### 16. `ref-frontend/` Removal
Status: open.
Required work:
1. Confirm no active docs require `ref-frontend/` for endpoint contract reference.
2. Confirm no scripts import or run `ref-frontend/`.
3. Delete `ref-frontend/`.
4. Update root docs and any setup docs.
Acceptance criteria:
- Only `frontend/` and `backend/` remain as active application code.
- No docs describe `ref-frontend/` as needed for normal development.
## Phased Execution Order
Run in workstream-number order unless the user explicitly reprioritizes. The phases are the dependency gates; within a phase, items may proceed in parallel.
1. **Phase 1 — Foundation:** Workstream 1 (backend migrates, boots, serves routes).
2. **Phase 2 — Identity, Tenancy & Access Core:** Workstream 2 (tenant isolation) → Workstream 3 (RBAC: roles, permissions, constraints, provisioning, guards) → Workstream 4 (RBAC seed/fixtures), with Workstream 5 (public route audit), Workstream 6 (API surface classify/wire), and Workstream 7 (file permissions) finalizing the protected surface alongside Workstream 3.
3. **Phase 3 — Verification & Quality:** Workstream 8 (authenticated seeded e2e) → Workstream 9 (API docs) → Workstream 10 (accessibility).
4. **Phase 4 — Product Contracts (customer-gated, parallel):** Workstreams 11 (acknowledgments), 12 (attendance source), 13 (generated audio).
5. **Phase 5 — Operations, Modernization & Cleanup:** Workstreams 14 (refresh-token cleanup), 15 (OAuth modernization), 16 (`ref-frontend/` removal).
## Definition Of Done
@ -577,9 +608,12 @@ The integration refactor is complete only when all of the following are true:
- Backend migrations and seeders pass on the configured local database.
- Backend lint passes without broad ignores.
- Backend tenant isolation tests prove cross-tenant data is not visible or mutable.
- The 11-role scoped hierarchy (Workstream 3) is enforced backend-side, including the relational "except …" constraints for `system_admin`, `superintendent`, and `director`; the legacy generated-role mapping and 5-value `productRole` are removed.
- Backend role/permission tests cover product staff, director, and superintendent paths.
- Frontend gating is permission-driven with an authentication guard; unauthenticated users redirect to `/login`, forbidden direct URLs land on 404, and guest sees only public routes.
- The RBAC seed (Workstream 4) provisions one company, one campus with staff covering every campus role, and one loginable user per role, with `admin@flatlogic.com` as the super admin.
- Every unauthenticated backend route is documented as public-by-design.
- Product onboarding contract is customer-approved or explicitly excluded from release scope.
- Product onboarding contract is implemented per Workstream 3 §3.4 or explicitly excluded from release scope.
- Every visible frontend workflow is backed by a typed backend API contract.
- No frontend runtime workflow depends on mock, sample, seed, or fallback records for persisted behavior.
- Frontend `typecheck`, `lint`, `test`, `build`, `test:e2e`, and documented seeded e2e suites pass in their required environments.