| 8 | import type { ResolvedFeatures } from "./feature-resolution.types"; |
| 9 | |
| 10 | export function buildAbility( |
| 11 | membership: IMembership, |
| 12 | features: ResolvedFeatures |
| 13 | ): AppAbility { |
| 14 | const { can, cannot, build } = new AbilityBuilder<AppAbility>( |
| 15 | createMongoAbility |
| 16 | ); |
| 17 | const { accountId } = membership; |
| 18 | |
| 19 | switch (membership.role) { |
| 20 | case ROLE.owner: |
| 21 | can("manage", "TeamMember", { accountId }); |
| 22 | can("manage", "Site", { accountId }); |
| 23 | can("manage", "Account", { id: accountId }); |
| 24 | break; |
| 25 | case ROLE.admin: |
| 26 | /* |
| 27 | * Admin manages content + members + settings, but NOT the account |
| 28 | * itself: deleting the account, transferring ownership, and |
| 29 | * changing account-level billing stay with the owner. Billing |
| 30 | * routes also enforce owner-only at the route layer. |
| 31 | */ |
| 32 | can("manage", "TeamMember", { accountId }); |
| 33 | can("manage", "Site", { accountId }); |
| 34 | can("read", "Account", { id: accountId }); |
| 35 | break; |
| 36 | case ROLE.member: |
| 37 | can("read", "TeamMember", { accountId }); |
| 38 | can("read", "Account", { id: accountId }); |
| 39 | can("create", "Site", { accountId }); |
| 40 | can("read", "Site", { accountId }); |
| 41 | can("update", "Site", { accountId }); |
| 42 | can("delete", "Site", { accountId }); |
| 43 | break; |
| 44 | case ROLE.viewer: |
| 45 | can("read", "TeamMember", { accountId }); |
| 46 | can("read", "Site", { accountId }); |
| 47 | can("read", "Account", { id: accountId }); |
| 48 | break; |
| 49 | } |
| 50 | |
| 51 | /* |
| 52 | * Feature gates: a missing feature forbids the action regardless of |
| 53 | * role. Per-account `admin` does NOT bypass plan checks; only |
| 54 | * `users.is_platform_admin = true` bypasses, and that bypass is |
| 55 | * applied above this layer with an audit row. |
| 56 | */ |
| 57 | if (!features.can_export) { |
| 58 | cannot("export", "Site"); |
| 59 | } |
| 60 | |
| 61 | if (!features.can_invite_team) { |
| 62 | cannot("invite", "TeamMember"); |
| 63 | } |
| 64 | |
| 65 | return build(); |
| 66 | } |
| 67 | |