07 — Roles & Permissions (RBAC)

Project: crmoffice Implementation: spatie/laravel-permission Last updated: 2026-05-30

Implementation note (Phase 1–6): 7 roles seeded (owner, admin, sales, pm, support, accountant, staff) with Spatie Permission — each role has granular resource-level permissions matching the navigation structure.


1. Roles (Seeded)

Role Key Display Description
owner Owner Single owner (or co-owners). Full control + cannot be revoked by Admin. Mandatory 2FA.
admin Admin Full operational access. Cannot manage owner role.
sales Sales Leads, clients, estimates, proposals. No financial settings, limited invoice rights.
pm Project Manager Projects, tasks, milestones. View own clients/projects.
support Support Agent Tickets, KB authoring. Limited to support-related entities.
accountant Accountant All financial: invoices, payments, expenses, credit notes. No project/task editing.
staff Staff Generic team member. Has tasks assigned. Logs time.
customer Customer Portal access only. Different guard. Not in users table — uses contacts table.

Multi-role allowed: a user may have pm + sales simultaneously. Permissions union.

Customer (contacts) uses separate guard (portal guard in auth.php). No spatie role assignment — portal access controlled by contacts.portal_access + per-contact email preferences.


2. Permission Naming Convention

Format: {action}.{resource} lower-snake.

Actions:

  • view — read single
  • view_any — list / index
  • create
  • update
  • delete — soft delete (or void for financial)
  • force_delete — hard delete (restricted)
  • restore
  • export
  • import
  • manage — admin-level configuration of the resource

Some custom actions for specific resources:

  • send.invoice — email send
  • mark_paid.invoice — manual payment
  • void.invoice
  • refund.payment
  • convert.lead — lead → client
  • convert.estimate — estimate → invoice
  • impersonate.user — login as
  • assign.ticket
  • escalate.ticket
  • reveal_secret.provider — see plaintext API key
  • test.provider — ping/probe credentials

3. Permission Catalog (Full List, Seeded)

3.1 CRM

  • view.client, view_any.client, create.client, update.client, delete.client, restore.client, force_delete.client, export.client, import.client
  • view.contact, view_any.contact, create.contact, update.contact, delete.contact
  • view.lead, view_any.lead, create.lead, update.lead, delete.lead, convert.lead, export.lead, import.lead
  • view.activity, create.activity, update.activity, delete.activity

3.2 Sales

  • view.estimate, view_any.estimate, create.estimate, update.estimate, delete.estimate, send.estimate, convert.estimate
  • view.proposal, view_any.proposal, create.proposal, update.proposal, delete.proposal, send.proposal
  • view.contract, view_any.contract, create.contract, update.contract, delete.contract, send.contract
  • view.invoice, view_any.invoice, create.invoice, update.invoice, delete.invoice, send.invoice, mark_paid.invoice, void.invoice, apply_credit_note.invoice
  • view.payment, view_any.payment, create.payment, refund.payment, delete.payment
  • view.credit_note, view_any.credit_note, create.credit_note, update.credit_note, delete.credit_note
  • view.item, manage.item
  • view.tax_rate, manage.tax_rate
  • view.currency, manage.currency
  • view.expense, view_any.expense, create.expense, update.expense, delete.expense

3.3 Projects & Tasks

  • view.project, view_any.project, create.project, update.project, delete.project, manage_members.project
  • view.milestone, create.milestone, update.milestone, delete.milestone
  • view.task, view_any.task, create.task, update.task, delete.task, assign.task
  • view.time_entry, view_any.time_entry, create.time_entry, update.time_entry, delete.time_entry, view_others.time_entry
  • view.discussion, create.discussion, update.discussion, delete.discussion

3.4 Support

  • view.ticket, view_any.ticket, create.ticket, update.ticket, delete.ticket, assign.ticket, escalate.ticket, reply.ticket, internal_note.ticket
  • view.department, manage.department
  • view.ticket_priority, manage.ticket_priority
  • view.ticket_status, manage.ticket_status
  • view.sla_policy, manage.sla_policy
  • view.kb_category, manage.kb_category
  • view.kb_article, view_any.kb_article, create.kb_article, update.kb_article, delete.kb_article, publish.kb_article

3.5 Cross-Cutting

  • view.calendar, manage_own.calendar
  • view.notification, manage_own.notification
  • view.goal, view_any.goal, create.goal, update.goal, delete.goal, view_others.goal
  • view.survey, view_any.survey, create.survey, update.survey, delete.survey
  • view.announcement, manage.announcement
  • view.report.sales, view.report.leads, view.report.project_profitability, view.report.time, view.report.tickets, view.report.expenses

3.6 Platform / Admin

  • view.user, view_any.user, create.user, update.user, delete.user, impersonate.user, disable.user
  • view.role, manage.role
  • view.permission
  • view.custom_field, manage.custom_field
  • view.provider, manage.provider, reveal_secret.provider, test.provider
  • view.setting, manage.setting
  • view.audit_log
  • view.webhook, manage.webhook, replay.webhook_delivery
  • view.file, delete.file, delete_others.file

4. Role-Permission Matrix

✅ = granted · ⛔ = not granted · 🔸 = scoped (own/assigned only)

4.1 CRM

Permission Owner Admin Sales PM Support Accountant Staff
*.client ✅ ✅ ✅ 🔸 own client 🔸 own ticket's client ✅ 🔸 view only
*.contact ✅ ✅ ✅ 🔸 🔸 ✅ 🔸 view
*.lead ✅ ✅ ✅ ⛔ ⛔ ⛔ ⛔
*.activity ✅ ✅ ✅ 🔸 own 🔸 own 🔸 own 🔸 own

4.2 Sales

Permission Owner Admin Sales PM Support Accountant Staff
*.estimate ✅ ✅ ✅ 🔸 view ⛔ ✅ ⛔
*.proposal ✅ ✅ ✅ 🔸 view ⛔ 🔸 view ⛔
*.contract ✅ ✅ ✅ 🔸 view ⛔ ✅ ⛔
view.invoice ✅ ✅ 🔸 created_by_self 🔸 own project ⛔ ✅ ⛔
create.invoice ✅ ✅ ✅ ✅ project-billing ⛔ ✅ ⛔
update.invoice ✅ ✅ 🔸 status=draft 🔸 own project ⛔ ✅ ⛔
mark_paid.invoice ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
void.invoice ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
*.payment ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
*.credit_note ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
*.expense ✅ ✅ 🔸 own 🔸 own project ⛔ ✅ 🔸 own
manage.item ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
manage.tax_rate ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔
manage.currency ✅ ✅ ⛔ ⛔ ⛔ ✅ ⛔

4.3 Projects & Tasks

Permission Owner Admin Sales PM Support Accountant Staff
view.project ✅ ✅ 🔸 own client ✅ 🔸 linked 🔸 invoicing 🔸 member
create.project ✅ ✅ ⛔ ✅ ⛔ ⛔ ⛔
update.project ✅ ✅ ⛔ 🔸 own ⛔ ⛔ ⛔
manage_members.project ✅ ✅ ⛔ 🔸 own ⛔ ⛔ ⛔
*.milestone ✅ ✅ ⛔ 🔸 own project ⛔ ⛔ ⛔
view.task ✅ ✅ ⛔ 🔸 project ⛔ ⛔ 🔸 assigned
create.task ✅ ✅ ⛔ ✅ ⛔ ⛔ ⛔
update.task ✅ ✅ ⛔ 🔸 project ⛔ ⛔ 🔸 assigned
assign.task ✅ ✅ ⛔ 🔸 project ⛔ ⛔ ⛔
delete.task ✅ ✅ ⛔ 🔸 project ⛔ ⛔ ⛔
create.time_entry ✅ ✅ ⛔ ✅ ⛔ ⛔ ✅
view_others.time_entry ✅ ✅ ⛔ 🔸 project ⛔ ✅ ⛔
update.time_entry ✅ ✅ ⛔ 🔸 own project ⛔ ⛔ 🔸 own
*.discussion ✅ ✅ ⛔ 🔸 own project 🔸 linked ⛔ 🔸 member

4.4 Support

Permission Owner Admin Sales PM Support Accountant Staff
view.ticket ✅ ✅ 🔸 own client 🔸 own project ✅ ⛔ 🔸 assigned
create.ticket ✅ ✅ ✅ ✅ ✅ ⛔ ✅
update.ticket ✅ ✅ ⛔ ⛔ ✅ ⛔ 🔸 assigned
assign.ticket ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔
escalate.ticket ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔
internal_note.ticket ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔
delete.ticket ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
manage.department ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
manage.sla_policy ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
*.kb_article ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔
publish.kb_article ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔

4.5 Cross-Cutting

Permission Owner Admin Sales PM Support Accountant Staff
manage_own.calendar ✅ ✅ ✅ ✅ ✅ ✅ ✅
view.goal ✅ ✅ 🔸 own 🔸 own 🔸 own 🔸 own 🔸 own
view_others.goal ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
create.goal ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
*.survey ✅ ✅ 🔸 view 🔸 view 🔸 view ⛔ ⛔
manage.announcement ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
view.report.sales ✅ ✅ 🔸 own deals ⛔ ⛔ ✅ ⛔
view.report.leads ✅ ✅ ✅ ⛔ ⛔ ⛔ ⛔
view.report.project_profitability ✅ ✅ ⛔ 🔸 own ⛔ ✅ ⛔
view.report.time ✅ ✅ ⛔ 🔸 project ⛔ ✅ 🔸 own
view.report.tickets ✅ ✅ ⛔ ⛔ ✅ ⛔ ⛔
view.report.expenses ✅ ✅ ⛔ 🔸 project ⛔ ✅ ⛔

4.6 Platform / Admin

Permission Owner Admin Sales PM Support Accountant Staff
*.user ✅ ✅ (no owner) ⛔ ⛔ ⛔ ⛔ ⛔
impersonate.user ✅ ⛔ ⛔ ⛔ ⛔ ⛔ ⛔
manage.role ✅ ⛔ ⛔ ⛔ ⛔ ⛔ ⛔
manage.custom_field ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
manage.provider ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
reveal_secret.provider ✅ ⛔ ⛔ ⛔ ⛔ ⛔ ⛔
test.provider ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
manage.setting ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
view.audit_log ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
manage.webhook ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔
replay.webhook_delivery ✅ ✅ ⛔ ⛔ ⛔ ⛔ ⛔

5. Scoped Access Rules ("🔸 own")

Permission alone isn't enough — Laravel Policies enforce ownership/scope:

ProjectPolicy

  • view allowed if: user->can('view.project') AND (user->id == project.project_manager_id OR project.members->contains(user) OR user->hasAnyRole(['owner','admin']) OR project.client.account_manager_id == user->id)

TaskPolicy

  • view/update allowed if: assigned to user OR can manage project

InvoicePolicy

  • view: account_manager of client OR billing role (accountant, owner, admin)
  • update: only if status = draft for sales role

ClientPolicy

  • view: account_manager_id == user->id OR user has full role

LeadPolicy

  • view/update: assigned_to == user->id OR user has full role

TimeEntryPolicy

  • view_others: only if view_others.time_entry permission

ProviderPolicy

  • Reading credentials masked unless reveal_secret.provider AND owner role

6. Implementation Notes

Default Permission Cache

  • spatie/laravel-permission caches via Redis (configured in config/permission.php)
  • Cache cleared on permission/role change

Adding a New Role

  1. Create role in admin UI → POST /api/v1/roles
  2. Assign permission subset
  3. Use Policies to scope further

Adding a New Permission

  1. Add to PermissionSeeder array (code-defined; not user-creatable arbitrary)
  2. Run php artisan db:seed --class=PermissionSeeder
  3. Update Policies/Filament resource visibility

Filament Resource Visibility

  • Each Resource has public static function canViewAny() { return auth()->user()->can('view_any.{resource}'); }
  • Forms/actions guarded similarly

Customer Portal Authorization

  • Customer guard = portal
  • No spatie role on contacts
  • Per-action access derived from:
    • contacts.portal_access = true
    • contacts.receives_*_emails for notification preferences
    • Direct ownership: a contact can see invoices of client_id = self.client_id, tickets they opened, projects of their client (if project.is_visible_to_customer), discussions marked is_visible_to_customer
  • Customer cannot see internal notes (ticket_replies.is_internal = true)

7. Two-Factor Authentication Policy

Role 2FA Requirement
Owner Mandatory (cannot be disabled)
Admin Mandatory after first login (force setup)
Sales / PM / Support / Accountant Optional, encouraged
Staff Optional
Customer Optional (per contact preference)

Enforcement via RequiresTwoFactor middleware on /admin routes when user role demands it. Recovery codes (10 single-use) generated at setup.


8. Audit Log Triggers (Mandatory)

Every action logs to audit_log:

  • All login/logout (success + failure)
  • Role assignment / removal
  • Permission grant / revoke
  • Provider credential change (value masked)
  • Setting change
  • Invoice: create, send, mark_paid, void
  • Payment: record, refund
  • Credit Note: create, apply, refund
  • User: create, disable, delete, impersonate
  • Webhook: create, delete, replay
  • Data export (CSV/PDF of any entity list)
  • Custom field create/delete

Implementation: domain event subscribers append to audit_log. Cannot be edited from UI (only view.audit_log permission).


9. Owner Role Special Rules

  • At least 1 user with owner role must exist at all times
  • An owner cannot delete themselves if they are the last owner
  • An admin cannot manage owner role (assign, revoke, delete owner-tagged user)
  • Owner role configurable to require quorum (e.g., 2 owners must approve provider credential change) — Phase 4 feature