assessment-model/src/app/lib
Khalim Conn-Kowlessar 616302a5c7 Gate user-access page behind admin privilege; allow admin role assignment
Adds a portfolio-privilege concept (creator > admin > domna employee >
write > read > none) and gates all user-access mutations + the pending-
invitations view behind it. Plus opens the role dropdown to include
"admin" so creators/admins/Domna can promote and demote.

Privilege model:
  - Portfolio creator: full admin powers (cannot be removed/demoted)
  - Portfolio admin: full admin powers via explicit membership role
  - Domna employee (email ends @domna.homes, case-insensitive):
    implicit admin across all portfolios, even if not a member —
    intended for customer-support / internal-tooling needs
  - Anyone else (read/write/none): no admin powers

Backend:
  - New pure-function helpers in src/app/lib/portfolioAdmin.ts —
    isDomnaEmail() and canAdminister(privilege), with 6 tests covering
    case-insensitivity and look-alike domain rejection
  - New server helper resolvePortfolioPrivilege() that reads
    portfolioUsers + checks the email, returning the highest privilege
  - New denyIfNotAdmin(portfolioId, session) one-liner that returns a
    401/403 NextResponse or null; used at the top of every mutating
    route handler to keep the guard out of the way
  - POST/PUT/DELETE on /colloborators and GET/DELETE on /invitations
    are now all gated. Non-admin callers get 403.
  - GET /colloborators now requires auth and returns
    `{ users, currentUser: { privilege } }` so the UI knows which
    actions to expose without an extra round-trip

Frontend:
  - ROLE_OPTIONS extended to ["read", "write", "admin"]. RoleDropdown
    takes allowAdminPromotion?: boolean to keep the basic dropdown
    unchanged where promotion isn't allowed.
  - UsersPermissionsCard derives isAdmin = canAdminister(privilege)
    from the API response. Invite section, role-change dropdown,
    Remove button, and the entire Pending Invitations section are now
    rendered only when isAdmin. Non-admins see a read-only members
    table.
  - The invitations useQuery is disabled when !isAdmin, avoiding
    guaranteed-403 network calls.

Defensive note: the UI gating is for UX; the backend guard is the
security boundary. A non-admin who hand-crafts a POST still gets 403.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 16:40:34 +00:00
..
hubspot Adding bulk approve and instruct 2026-05-09 08:39:20 +00:00
bulkApprove.test.ts Adding bulk approve and instruct 2026-05-09 08:39:20 +00:00
bulkApprove.ts Adding bulk approve and instruct 2026-05-09 08:39:20 +00:00
bulkInstructDeals.test.ts Adding bulk approve and instruct 2026-05-09 08:39:20 +00:00
bulkInstructDeals.ts Adding bulk approve and instruct 2026-05-09 08:39:20 +00:00
createPibiRequests.test.ts working on pibi ui 2026-05-06 23:04:45 +00:00
createPibiRequests.ts working on pibi ui 2026-05-06 23:04:45 +00:00
dealPropertyUpdate.test.ts Updated survey request UI for Devon County Council 2026-05-06 20:37:30 +00:00
dealPropertyUpdate.ts Updated survey request UI for Devon County Council 2026-05-06 20:37:30 +00:00
deletePibiRequest.test.ts working on pibi ui 2026-05-06 23:04:45 +00:00
deletePibiRequest.ts working on pibi ui 2026-05-06 23:04:45 +00:00
email.test.ts added missing email and email test files 2026-05-27 16:19:14 +00:00
email.ts added missing email and email test files 2026-05-27 16:19:14 +00:00
instructMeasure.test.ts added instruct measures approval 2026-05-06 18:00:06 +00:00
instructMeasure.ts added instruct measures approval 2026-05-06 18:00:06 +00:00
measureDocumentRequirements.test.ts add vitest harness with parser and getRequiredDocs unit tests 2026-05-05 11:26:02 +00:00
measureDocumentRequirements.ts extract MEASURE_NAMES catalogue and MeasureName type 2026-05-05 11:25:45 +00:00
parseMeasures.test.ts add vitest harness with parser and getRequiredDocs unit tests 2026-05-05 11:26:02 +00:00
parseMeasures.ts share parseMeasures helper, accept ; and , separators 2026-05-05 11:25:54 +00:00
pibiSectionHelpers.test.ts working on pibi ui 2026-05-06 23:04:45 +00:00
pibiSectionHelpers.ts working on pibi ui 2026-05-06 23:04:45 +00:00
portfolioAdmin.test.ts Gate user-access page behind admin privilege; allow admin role assignment 2026-05-27 16:40:34 +00:00
portfolioAdmin.ts Gate user-access page behind admin privilege; allow admin role assignment 2026-05-27 16:40:34 +00:00
portfolioInvitations.test.ts initial implementation for portfolio invitations. A user can send an invitation to a user and they will receive an invitation email 2026-05-27 16:18:21 +00:00
portfolioInvitations.ts initial implementation for portfolio invitations. A user can send an invitation to a user and they will receive an invitation email 2026-05-27 16:18:21 +00:00
resolvePortfolioPrivilege.ts Gate user-access page behind admin privilege; allow admin role assignment 2026-05-27 16:40:34 +00:00
selectPibiMeasures.test.ts add vitest unit tests for pibi-selection service 2026-05-05 19:15:05 +00:00
selectPibiMeasures.ts add pibi-selection service 2026-05-05 19:14:12 +00:00
softWarnings.test.ts add instruct-measure service, route and soft-warning helper 2026-05-05 18:56:48 +00:00
softWarnings.ts add instruct-measure service, route and soft-warning helper 2026-05-05 18:56:48 +00:00
updatePibiRequest.test.ts working on pibi ui 2026-05-06 23:04:45 +00:00
updatePibiRequest.ts working on pibi ui 2026-05-06 23:04:45 +00:00
verificationCode.test.ts Add 6-digit code sign-in as primary, magic link as fast-path fallback 2026-05-27 14:16:47 +00:00
verificationCode.ts Add 6-digit code sign-in as primary, magic link as fast-path fallback 2026-05-27 14:16:47 +00:00