This commit is contained in:
phaichayon
2026-06-22 13:36:00 +07:00
parent 827fd13fa7
commit 42f403a7ed
23 changed files with 1425 additions and 68 deletions

View File

@@ -0,0 +1,37 @@
# Task L.3: Full CRM Scope Enforcement
## Summary
Task L.3 starts hardening CRM security around resolved CRM access instead of relying on route-level permissions or legacy role strings alone.
## Implemented in this pass
- Added centralized CRM security helpers in `src/features/crm/security/server/service.ts`
- Added `canViewQuotationPricing()` guard for pricing-bearing quotation outputs
- Applied pricing enforcement to:
- document data
- document preview
- PDF preview
- PDF download
- approved PDF
- approved quotation artifact download
- Switched dashboard revenue visibility to pricing visibility instead of broad quotation-read semantics
- Blocked revenue dashboard export when pricing visibility is missing
- Updated quotation list/detail/create/update/delete routes to pass resolved CRM scope context
- Added quotation service-side branch/product/own-scope enforcement
- Updated approval step actor validation to use resolved CRM role unions
- Added `crm_security_access` audit events for pricing-denied flows
- Added security inventory and boundary documentation
- Added `scripts/security/verify-crm-access.ts`
## Remaining gaps
- Customers and contacts still need full ownership-scope enforcement
- Approval list/detail visibility is still broader than the final resolved-quotation scope
- Team-scope semantics remain approximate until the domain has a first-class team/subordinate model
- Runtime seeded scenario verification is still needed beyond the current static regression script
## Verification
- `npx tsc --noEmit`
- `node --experimental-strip-types scripts/security/verify-crm-access.ts`

View File

@@ -398,3 +398,35 @@ Future:
- decide which non-revenue widgets should fully honor project-party role filtering
- add a unified filtered reporting projection if cross-widget party-role filtering becomes a hard requirement
## After Task L.3
### Customer and contact ownership enforcement
Current:
Customer/contact modules are still more organization-wide than quotation security and need the same resolved ownership model applied consistently.
Future:
- add resolved CRM access context to every customer/contact service path
- enforce shared-contact visibility without falling back to blanket organization reads
### Approval visibility scoping
Current:
Approval step handling now resolves multi-role actors correctly, but approval list/detail visibility is still broader than final quotation-scoped security.
Future:
- scope approval requests by accessible entity visibility, not organization membership alone
- align dashboard approval widgets and approval detail pages with the same rule
### Team-scope fidelity
Current:
Resolved CRM access supports `team`, but the domain still lacks a first-class subordinate/team graph for precise enforcement.
Future:
- define the canonical team relationship source
- replace current coarse team handling with explicit team membership or manager hierarchy logic

View File

@@ -0,0 +1,41 @@
# CRM Access Enforcement Inventory
## Status Key
- `Protected`: route already passes through resolved CRM access or explicit permission/scope enforcement
- `Partially Protected`: route requires auth/permission but still has legacy or coarse organization-wide behavior
- `Legacy Protected`: route depends on role string or legacy membership behavior instead of resolved union access
- `Not Protected`: no meaningful CRM scope enforcement beyond organization lookup
## Inventory
| Area | Route / Module | Status | Notes |
| --- | --- | --- | --- |
| Customers | `/api/crm/customers` | `Partially Protected` | Permission-gated, but service is still mostly organization-wide and not yet ownership-scoped |
| Contacts | `/api/crm/customers/[id]/contacts` | `Partially Protected` | Bound to customer org, but no dedicated resolved ownership/contact-sharing policy yet |
| Leads / Enquiries | `/api/crm/enquiries` and child routes | `Partially Protected` | Uses branch/product/own-scope logic already, but still passes legacy `businessRole` fields through route/service seams |
| Quotations | `/api/crm/quotations` and `[id]` | `Protected` | List/detail/create/update/delete now pass resolved CRM scope context and enforce branch/product/own visibility |
| Quotation pricing surfaces | document data, preview, PDF preview/download, approved PDF | `Protected` | Pricing-bearing outputs now require pricing visibility and emit `crm_security_access` audit events when denied |
| Document artifacts | `/api/crm/document-artifacts/[id]/download` | `Protected` | Approved quotation artifacts now require artifact permission plus pricing visibility guard |
| Approvals | `/api/crm/approvals`, approve/reject/return | `Legacy Protected` | Permission-gated; step actor resolution now uses resolved CRM access union, but request listing is still organization-wide |
| Dashboard | `/api/crm/dashboard` | `Protected` | Route uses resolved context; revenue widgets are hidden unless quotation pricing is visible |
| Dashboard export | `/api/crm/dashboard/export` | `Protected` | Revenue export explicitly blocked without pricing visibility |
| CRM settings: roles | `/api/crm/settings/roles*` | `Partially Protected` | Permission-gated, but inventory still needs full pass for every mutation route |
| CRM settings: user assignments | `/api/crm/settings/user-role-assignments*` | `Protected` | Explicit permission-gated through CRM role-assignment permissions |
| CRM settings: approval workflows | `/api/crm/settings/approval-workflows*` | `Partially Protected` | Permission-gated at route layer; deeper union-based workflow visibility still pending |
| CRM settings: document templates / sequences | `/api/crm/settings/document-templates*`, `/document-sequences*` | `Partially Protected` | Permission-gated, but not yet audited against the full L.3 export/artifact matrix |
| Master options | `/api/foundation/master-options` | `Partially Protected` | Admin-oriented and permission-gated, but outside resolved CRM scope model |
## High-Risk Findings
1. Customer/contact services still need full ownership-scope enforcement rather than organization-only reads.
2. Approval request listing and detail visibility are still broader than the final resolved-ownership model.
3. Enquiry services already enforce meaningful scope, but several route files still pass legacy role-shaped context instead of a dedicated security context object.
4. Team-scope semantics are still coarse because the current model does not yet carry a first-class subordinate/team graph.
## Follow-up Focus
- Finish customer/contact ownership enforcement.
- Convert approval list/detail visibility from organization scope to resolved quotation visibility.
- Normalize remaining enquiry routes onto the same security-context builder used by quotations and dashboard.
- Add runtime scenario tests once seeded multi-role fixtures are available.

View File

@@ -0,0 +1,84 @@
# CRM Authorization Boundaries
## Source of Truth
Resolved CRM access is the source of truth for CRM authorization.
The effective model is:
1. Authenticated user
2. Active organization membership
3. CRM role assignments
4. Resolved permission union
5. Resolved branch scope
6. Resolved product scope
7. Resolved ownership scope
8. Resolved approval authority
`memberships.businessRole` remains a compatibility fallback only inside the resolver path.
## Enforcement Layers
### Route permission gates
Every CRM route must first require organization access plus at least one explicit permission.
### Service-level scope enforcement
Sensitive list/detail/mutation services must additionally enforce:
- branch scope
- product scope
- ownership visibility
This is required because UI filtering is not a security boundary.
### Pricing visibility
Pricing-bearing outputs require quotation pricing visibility:
- quotation PDF preview/download
- approved PDF
- document preview/data
- revenue dashboard/export
- approved artifact download
Denied pricing access is logged as `crm_security_access` with action `pricing_hidden`.
### Approval authority
Approval step handling must use resolved CRM role unions instead of a single legacy business-role string.
## Current Ownership Model
- `own`: creator or assigned owner/salesman
- `team`: currently treated as broader-than-own, but still requires future team graph formalization
- `organization`: organization-wide inside active tenant
- `monitor`: read-oriented role with limited commercial visibility
## Artifact Boundary
Artifact permission alone is not enough for approved quotation PDFs.
The caller must satisfy:
1. artifact download permission
2. quotation pricing visibility when the artifact is an approved quotation PDF
## Dashboard Boundary
Dashboard access is split:
- base dashboard visibility: `crm.dashboard.read`
- revenue/commercial visibility: quotation pricing visibility
- approval widgets: approval read permission
- export visibility: same scope as dashboard, with extra revenue export restriction
## Audit Events
Security-sensitive denials are recorded under entity type `crm_security_access` with actions:
- `access_denied`
- `scope_violation`
- `permission_violation`
- `pricing_hidden`