mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-22 08:48:34 +00:00
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>
|
||
|---|---|---|
| .. | ||
| hubspot | ||
| bulkApprove.test.ts | ||
| bulkApprove.ts | ||
| bulkInstructDeals.test.ts | ||
| bulkInstructDeals.ts | ||
| createPibiRequests.test.ts | ||
| createPibiRequests.ts | ||
| dealPropertyUpdate.test.ts | ||
| dealPropertyUpdate.ts | ||
| deletePibiRequest.test.ts | ||
| deletePibiRequest.ts | ||
| email.test.ts | ||
| email.ts | ||
| instructMeasure.test.ts | ||
| instructMeasure.ts | ||
| measureDocumentRequirements.test.ts | ||
| measureDocumentRequirements.ts | ||
| parseMeasures.test.ts | ||
| parseMeasures.ts | ||
| pibiSectionHelpers.test.ts | ||
| pibiSectionHelpers.ts | ||
| portfolioAdmin.test.ts | ||
| portfolioAdmin.ts | ||
| portfolioInvitations.test.ts | ||
| portfolioInvitations.ts | ||
| resolvePortfolioPrivilege.ts | ||
| selectPibiMeasures.test.ts | ||
| selectPibiMeasures.ts | ||
| softWarnings.test.ts | ||
| softWarnings.ts | ||
| updatePibiRequest.test.ts | ||
| updatePibiRequest.ts | ||
| verificationCode.test.ts | ||
| verificationCode.ts | ||