# 04 — Authorization Matrix

## 1. Layers of authorization (all enforced server-side)

1. **Platform layer (master):** `users.is_super_admin`. Super Admins manage the platform but have **no implicit access to tenant data**. Entering a tenant requires a time-boxed `support_access_grant` approved by the tenant owner (or a break-glass action with a mandatory reason). Every access is written to `admin_audit_logs` and to the tenant's `tenant_audit_logs`.
2. **Tenant membership (master):** `tenant_users.role` ∈ `owner, admin, manager, creator, approver, client, viewer` with `status = active`. The tenant is resolved from the host and the membership from master. The request body is never trusted.
3. **Client scope (tenant):** for roles other than `owner`/`admin`, every client-owned resource requires a `client_users` row for (client, user). Its `team_role_id` can narrow, never widen, the agency role. **Clients are hidden** (404, not 403) when the user is not assigned to them.
4. **Resource policy:** a Laravel Policy per model (`ClientPolicy`, `SocialAccountPolicy`, `PostPolicy`, `ApprovalPolicy`, `MediaAssetPolicy`, `ReportPolicy`, `ConversationPolicy`, …) calls `Gate::check(permission, $client)`.
5. **Plan entitlement:** `EntitlementService` (e.g. client count, inbox feature, white-label).
6. **Provider capability:** the `CapabilityResolver` decides whether an action is possible on that social account.
7. **Background jobs** re-check 2–6 at execution time (the user may have lost access between scheduling and publishing). The job runs as a system actor but records `requested_by`.

Livewire: every component action calls `$this->authorize()`. Locked public properties (`#[Locked]`) protect IDs from tampering, and model IDs are re-resolved through scoped queries (`Client::visibleTo($user)`).

## 2. Permission catalogue (`config/rbac.php`)

| Group | Permissions |
|-------|-------------|
| Agency | `agency.view`, `agency.settings.manage`, `agency.branding.manage`, `agency.domains.manage`, `agency.security.manage` (MFA policy, sessions), `agency.delete` |
| Team | `team.view`, `team.invite`, `team.roles.manage`, `team.remove` |
| Billing | `billing.view`, `billing.manage` |
| Clients | `clients.view`, `clients.create`, `clients.update`, `clients.archive`, `clients.assign_users` |
| Social accounts | `accounts.view`, `accounts.connect`, `accounts.disconnect`, `accounts.reconnect` |
| Posts | `posts.view`, `posts.create`, `posts.update`, `posts.delete`, `posts.submit_for_review`, `posts.schedule`, `posts.publish_now`, `posts.cancel`, `posts.retry` |
| Approvals | `approvals.view`, `approvals.decide_internal`, `approvals.decide_client`, `approvals.delegate`, `approvals.workflows.manage` |
| Calendar/campaigns | `calendar.view`, `calendar.reschedule`, `campaigns.manage` |
| Media | `media.view`, `media.upload`, `media.manage` |
| Inbox | `inbox.view`, `inbox.reply`, `inbox.assign`, `inbox.moderate` |
| Analytics | `analytics.view`, `reports.manage`, `reports.export` |
| AI | `ai.use`, `ai.settings.manage` |
| Audit | `audit.view` |

## 3. Role × permission matrix

Legend: ✅ allowed (agency-wide) · 🅲 allowed only on assigned clients · ◐ read-only · — denied

| Permission | Super Admin* | Owner | Admin | Social Media Manager | Creator / Designer | Approver | Client User | Viewer |
|---|---|---|---|---|---|---|---|---|
| agency.view | via grant | ✅ | ✅ | ✅ | ✅ | ✅ | — | ✅ |
| agency.settings / branding / domains | — | ✅ | ✅ | — | — | — | — | — |
| agency.security.manage | — | ✅ | ✅ | — | — | — | — | — |
| agency.delete | — | ✅ | — | — | — | — | — | — |
| team.view | via grant | ✅ | ✅ | ◐ | ◐ | ◐ | — | ◐ |
| team.invite / remove / roles.manage | — | ✅ | ✅ (not owner) | — | — | — | — | — |
| billing.view / manage | platform billing admin | ✅ / ✅ | ✅ / — | — | — | — | — | — |
| clients.view | via grant | ✅ | ✅ | 🅲 | 🅲 | 🅲 | own client | 🅲 |
| clients.create / archive | — | ✅ | ✅ | — | — | — | — | — |
| clients.update | — | ✅ | ✅ | 🅲 | — | — | — | — |
| clients.assign_users | — | ✅ | ✅ | — | — | — | — | — |
| accounts.view | via grant | ✅ | ✅ | 🅲 | 🅲 (name/status only) | 🅲 | own (status only) | 🅲 |
| accounts.connect / reconnect | — | ✅ | ✅ | 🅲 | — | — | own client** | — |
| accounts.disconnect | — | ✅ | ✅ | 🅲 | — | — | own client** | — |
| posts.view | via grant | ✅ | ✅ | 🅲 | 🅲 | 🅲 | own (submitted for review / scheduled / published) | 🅲 |
| posts.create / update | — | ✅ | ✅ | 🅲 | 🅲 | — | — | — |
| posts.submit_for_review | — | ✅ | ✅ | 🅲 | 🅲 | — | — | — |
| posts.schedule / publish_now / cancel / retry | — | ✅ | ✅ | 🅲 | — | — | — | — |
| approvals.decide_internal | — | ✅ | ✅ | 🅲 (if a workflow approver) | — | 🅲 | — | — |
| approvals.decide_client | — | — | — | — | — | 🅲 (client-side approver) | own | — |
| approvals.delegate | — | ✅ | ✅ | 🅲 | — | 🅲 | — | — |
| approvals.workflows.manage | — | ✅ | ✅ | — | — | — | — | — |
| calendar.view | via grant | ✅ | ✅ | 🅲 | 🅲 | 🅲 | own | 🅲 |
| calendar.reschedule | — | ✅ | ✅ | 🅲 | — | — | — | — |
| campaigns.manage | — | ✅ | ✅ | 🅲 | — | — | — | — |
| media.view / upload | via grant | ✅ | ✅ | 🅲 | 🅲 | ◐ 🅲 | own (view; upload if enabled) | ◐ 🅲 |
| media.manage (delete, folders) | — | ✅ | ✅ | 🅲 | 🅲 (own uploads) | — | — | — |
| inbox.view / reply / assign | — | ✅ | ✅ | 🅲 | — | — | — | ◐ 🅲 |
| inbox.moderate (hide/delete) | — | ✅ | ✅ | 🅲 | — | — | — | — |
| analytics.view | via grant | ✅ | ✅ | 🅲 | ◐ 🅲 | ◐ 🅲 | own | 🅲 |
| reports.manage / export | — | ✅ | ✅ | 🅲 | — | — | own (export) | — |
| ai.use | — | ✅ | ✅ | 🅲 | 🅲 | — | — | — |
| ai.settings.manage | — | ✅ | ✅ | — | — | — | — | — |
| audit.view | platform audit | ✅ | ✅ | — | — | — | — | — |

\* Super Admin actions in the platform area (`/admin`): tenants, provisioning, plans, payments, integrations, system health, incidents, support audit. Tenant data is reachable only via a support grant, which is **read-only by default**. Write access requires an explicit grant flag.
\** Client users can connect their own client's accounts when the agency enables "client-side connection" in client settings. This is useful when the client owns the social assets.

### Client-side approvals (M4)

A client approval stage can only be decided by members who hold `approvals.decide_client` **and not** `approvals.decide_internal` for that client: client users, or approvers whose client-level role on that client is "Client User". Internal approvers cannot sign off on a client's behalf. Client users use the portal (`/portal`) and are redirected away from agency pages.

## 4. MFA & session rules

- TOTP 2FA is **mandatory** for Super Admin, Owner, and Admin (enforced by the `RequireTwoFactor` middleware). Other roles can opt in, and the agency can mandate it for all roles.
- Re-authentication (password confirm, within 15 min) is required for: disconnecting accounts, deleting clients, changing billing, managing domains, exporting data, deleting the tenant, and super-admin impersonation.
- Session idle timeout is configurable per tenant (default 8 h; 1 h in the admin area). Sessions are revoked on role change or membership revocation.
- Login rate limit: 5/min per email+IP. Password reset: 3/hour per email.

## 5. Test obligations

Every permission row has a dataset-driven Pest test (`tests/Feature/Authorization/MatrixTest.php`) that iterates role × permission × (assigned / unassigned client) and asserts allow/deny. The matrix above and `config/rbac.php` must stay in sync, and a test diffs them.
