Permissions matrix
The permission catalogue (module × action), what each of the eight roles holds, sensitive-field and data-scope rules, guard rails, auditing of permission changes and the test checklist for ACL-05.
Cahier §3 defines eight profiles and §3.1 the principles: least privilege, permissions per action (voir, créer, modifier, supprimer, valider, exporter, administrer), delegates limited to their zone and resellers to their own data, every critical action historised. This page is the reference the server's guard, the back-office menus and the permission tests are built from (ACL-01 to ACL-05, ADM-02). The default role sets below are seeded by the database migration; HOCO can adjust staff roles in BO-62 within the guard rails.
How a request is authorised
Every API operation runs the same checks, in this order; the first failure stops the request.
| Step | Check | Failure |
|---|---|---|
| 1 | Session valid (bearer token or cookie). Operations marked optional also accept no session: the caller is a guest | 401 UNAUTHENTICATED |
| 2 | Account status gate (AccountStatus) | 403 ACCOUNT_SUSPENDED · 403 ACCOUNT_PENDING_VALIDATION on ordering |
| 3 | Audience gate: reseller sessions only reach reseller operations, delegate sessions delegate operations, staff sessions admin.* operations | 403 PERMISSION_DENIED |
| 4 | Permission key of the operation (perm in endpoints.json); for CRUD bundles module.* the HTTP method picks the action: GET view, POST create, PATCH/PUT update, DELETE delete. perm: any = any staff session, results filtered by the caller's permissions | 403 PERMISSION_DENIED |
| 5 | Data scope filter (self, delegate) on reads and writes | 404 NOT_FOUND on reads (existence never leaked) · 403 OUT_OF_SCOPE on a write to a known out-of-scope record |
| 6 | Field rules: sensitive fields removed from responses, refused on writes | Field absent · 403 FIELD_NOT_EDITABLE |
| 7 | Conditional rules on the operation (overrides, credit-only validation, re-authentication) | 403 PERMISSION_DENIED · 403 REAUTH_REQUIRED |
| 8 | Audit: sensitive actions, overrides and denials of sensitive operations are logged | — |
The interface hides what the user cannot do (menus of BO-03, buttons, allowedActions returned by detail endpoints), but hiding is never the protection: the server enforces every rule (ACL-05).
Permission catalogue
Keys are <module>.<action>. Only the combinations below exist (75 keys, 68 in V1); the catalogue is served by admin.permissions.list.
| Module | Covers | view | create | update | delete | validate | export | administer |
|---|---|---|---|---|---|---|---|---|
dashboard | Dashboards BO-04 to BO-08 | KPI cards and charts (each card also needs its domain's view) | — | — | — | — | — | — |
crm | Resellers, prospects, timeline, tasks, segments, validation | Lists, map, 360°, timeline, documents, phone check, tasks | Create reseller or prospect | Edit, status, assignment, customer type (audited), notes, tasks, documents, bulk | — (records are deactivated, never deleted) | Approve or reject accounts and new points of sale, confirm the customer type | Export lists | Segments |
catalog | Brands, categories, products, variants, attributes, visibility | Products, brands, categories, attributes | Create products, brands, categories, attributes | Edit, publish, hide, archive, visibility, variants (except price fields) | Delete drafts; archive referenced brands and categories | — | Export the catalog | Product import |
pricing | The three prices per variant, price history; V2 agreements | Prices and price history | V2: create agreements | Write the three prices, recommended price, VAT rate | V2: delete agreements | — | — | — |
stock | Warehouses, levels, movements, inventories, return reception | Levels, movements, alerts | Goods receipts | Adjustments, thresholds, inventory counts, return reception | — | Validate inventories, stock override at order confirmation | Export stock lists | Warehouses |
orders | Orders, confirmation queue, assisted orders, returns | Orders, history, returns | Orders on behalf of resellers | Modify lines, transitions, cancel, documents | — | Confirm or refuse orders, confirm assisted orders, approve or reject returns | Export order lists | — |
delivery | Delivery board, carriers, proofs | Board, picking lists | — | Carrier, driver, tracking, fulfilment transitions, proof of delivery | — | — | — | Carriers |
finance | Invoices, payments, credit, credit notes | Invoices, payments, credit accounts, purchase prices and margins | Record payments | Allocations | — | Verify declarations, cancel invoices, credit override | Accounting export (V2) | Credit limits, terms, blocking |
promotions | Promotions, daily offers, banners; V2 packs | List, preview | Create | Edit | Delete drafts and banners | Schedule, activate, pause, archive | — | — |
loyalty (V2) | Points, levels, rewards, redemptions | Accounts and ledgers | Rewards | Adjustments, fulfil redemptions | Rewards | — | — | Earning rules, levels |
field | Delegates, zones, objectives, tours, visits, invitations funnel | Delegates, tours, visits, live map, trails, funnel | Delegates, tours, objectives | Edit tours and stops, cancel tours, recommend order, edit delegates | Objectives | Reopen tours, unlock visits | Export field lists | Zones, visit types, checklists |
support | Tickets, after-sales claims | Queue, tickets and claims | — | Reply, internal notes, assign, status | — | Resolve after-sales claims | — | — |
communication | Announcements, notification templates and rules | Delivery log | Announcements | Edit announcements | Delete announcements | Send announcements | — | Templates, event → channel rules |
reports | Report library, export centre | Run reports | — | — | — | — | Export reports and lists through the export centre | — |
users | Staff and delegate accounts | Users | Create users | Edit, roles, status, reset password | — | — | — | Other users' sessions |
roles | Roles and permissions | Roles, catalogue | — | — | — | — | — | Change a role's permission set |
audit | Activity journal | Journal | — | — | — | — | Export the journal | — |
settings | Business settings, reference lists, support regions, app versions, imports, dashboard layouts | Settings | — | — | — | — | — | Change them |
Roles
| Value | UI label (fr) | Meaning |
|---|---|---|
SUPER_ADMIN | Super administrateur | Full access, global settings, roles, permissions and audit. |
COMMERCIAL_ADMIN | Administrateur commercial | CRM, resellers, delegates, tours, promotions, orders and reporting. |
STOCK_MANAGER | Responsable stock | Products, categories, variants, quantities, inventories and alerts. |
FINANCE_MANAGER | Responsable finance | Payments, credit, invoices, due dates and financial reports. |
DELIVERY_MANAGER | Responsable livraison | Preparation, shipping, carrier assignment and proof of delivery. |
DIRECTION | Direction | Read-only dashboards, KPIs and consolidated reports. |
DELEGATE | Délégué commercial | Tours, visits, sign-ups, invitations, assisted orders and reports — own scope only. |
RESELLER | Revendeur | Catalog, cart, orders, invoices, promotions, loyalty and support — own data only. |
A user has one UserType. Staff users can hold several staff roles (their permissions add up); DELEGATE and RESELLER are system roles held alone. SUPER_ADMIN, DELEGATE and RESELLER cannot be edited; the five other staff roles can.
Role × permission matrix
Columns: SA super admin · CA commercial admin · SM stock manager · FM finance manager · DM delivery manager · DIR direction · DEL delegate · RES reseller.
Values: ✓ granted · — not granted · read-only the role only views this module · own the user's own records only · zone records in the delegate's scope (scope rules) · credit only see conditional rules. Delegates and resellers hold no back-office permission key: their cells describe what their own scoped operations (delegate.*, reseller self operations) give them.
| Permission | SA | CA | SM | FM | DM | DIR | DEL | RES |
|---|---|---|---|---|---|---|---|---|
dashboard.view | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | own (home, performance) | — |
crm.view | ✓ | ✓ | — | read-only | read-only | read-only | zone | own (store profile) |
crm.create | ✓ | ✓ | — | — | — | — | zone (field sign-up) | — |
crm.update | ✓ | ✓ | — | — | — | — | zone (qualification statuses, notes, photos) | own (editable profile fields, addresses, documents) |
crm.validate | ✓ | ✓ | — | — | — | — | — | — |
crm.export | ✓ | ✓ | — | — | — | — | — | — |
crm.administer | ✓ | ✓ | — | — | — | — | — | — |
catalog.view | ✓ | read-only | ✓ | read-only | read-only | read-only | ✓ | ✓ (visible products; guests too) |
catalog.create | ✓ | — | ✓ | — | — | — | — | — |
catalog.update | ✓ | — | ✓ | — | — | — | — | — |
catalog.delete | ✓ | — | ✓ | — | — | — | — | — |
catalog.export | ✓ | — | ✓ | — | — | — | — | — |
catalog.administer | ✓ | — | ✓ | — | — | — | — | — |
pricing.view | ✓ | ✓ | read-only | read-only | — | read-only | zone (price of the reseller assisted) | own (price of their type) |
pricing.update | ✓ | ✓ | — | — | — | — | — | — |
pricing.create (V2) | ✓ | ✓ | — | — | — | — | — | — |
pricing.delete (V2) | ✓ | ✓ | — | — | — | — | — | — |
stock.view | ✓ | read-only | ✓ | read-only | read-only | read-only | — (availability only) | — (availability only) |
stock.create | ✓ | — | ✓ | — | — | — | — | — |
stock.update | ✓ | — | ✓ | — | — | — | — | — |
stock.validate | ✓ | — | ✓ | — | — | — | — | — |
stock.export | ✓ | — | ✓ | — | — | — | — | — |
stock.administer | ✓ | — | ✓ | — | — | — | — | — |
orders.view | ✓ | ✓ | read-only | read-only | ✓ | read-only | zone (read-only) | own |
orders.create | ✓ | ✓ | — | — | — | — | zone (assisted drafts) | own (checkout, return requests) |
orders.update | ✓ | ✓ | — | — | — | — | own (own drafts) | own (cancel before confirmation, confirm or reject assisted, reorder) |
orders.validate | ✓ | ✓ | — | credit only | — | — | — | — |
orders.export | ✓ | ✓ | — | — | — | — | — | — |
delivery.view | ✓ | read-only | read-only | — | ✓ | read-only | — | own (tracking) |
delivery.update | ✓ | — | — | — | ✓ | — | — | — |
delivery.administer | ✓ | — | — | — | ✓ | — | — | — |
finance.view | ✓ | — | — | ✓ | — | read-only | — | own (balance, invoices, payments, credit) |
finance.create | ✓ | — | — | ✓ | — | — | — | own (payment declarations) |
finance.update | ✓ | — | — | ✓ | — | — | — | — |
finance.validate | ✓ | — | — | ✓ | — | — | — | — |
finance.export (V2) | ✓ | — | — | ✓ | — | — | — | — |
finance.administer | ✓ | — | — | ✓ | — | — | — | — |
promotions.view | ✓ | ✓ | read-only | — | — | read-only | ✓ (to present them) | own (targeted; guests see them without prices) |
promotions.create | ✓ | ✓ | — | — | — | — | — | — |
promotions.update | ✓ | ✓ | — | — | — | — | — | — |
promotions.delete | ✓ | ✓ | — | — | — | — | — | — |
promotions.validate | ✓ | ✓ | — | — | — | — | — | — |
loyalty.view (V2) | ✓ | ✓ | — | read-only | — | read-only | — | own |
loyalty.create (V2) | ✓ | ✓ | — | — | — | — | — | — |
loyalty.update (V2) | ✓ | ✓ | — | — | — | — | — | own (redeem) |
loyalty.delete (V2) | ✓ | ✓ | — | — | — | — | — | — |
loyalty.administer (V2) | ✓ | — | — | — | — | — | — | — |
field.view | ✓ | ✓ | — | — | — | read-only | own (tours, visits, objectives, invitations) | — |
field.create | ✓ | ✓ | — | — | — | — | own (unplanned visits) | — |
field.update | ✓ | ✓ | — | — | — | — | own (start, suspend, close own tours; own visits) | — |
field.delete | ✓ | ✓ | — | — | — | — | — | — |
field.validate | ✓ | ✓ | — | — | — | — | — | — |
field.export | ✓ | ✓ | — | — | — | — | — | — |
field.administer | ✓ | ✓ | — | — | — | — | — | — |
support.view | ✓ | ✓ | — | ✓ | ✓ | read-only | — | own (tickets and claims) |
support.update | ✓ | ✓ | — | ✓ | ✓ | — | — | own (open, reply, close) |
support.validate | ✓ | ✓ | — | — | — | — | — | — |
communication.view | ✓ | ✓ | — | — | — | — | own (announcements received) | own (announcements received) |
communication.create | ✓ | ✓ | — | — | — | — | — | — |
communication.update | ✓ | ✓ | — | — | — | — | — | — |
communication.delete | ✓ | ✓ | — | — | — | — | — | — |
communication.validate | ✓ | ✓ | — | — | — | — | — | — |
communication.administer | ✓ | — | — | — | — | — | — | — |
reports.view | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | own (reports history) | — |
reports.export | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
users.view | ✓ | — | — | — | — | — | — | — |
users.create | ✓ | — | — | — | — | — | — | — |
users.update | ✓ | — | — | — | — | — | — | — |
users.administer | ✓ | — | — | — | — | — | own (own sessions) | own (own sessions) |
roles.view | ✓ | — | — | — | — | — | — | — |
roles.administer | ✓ | — | — | — | — | — | — | — |
audit.view | ✓ | — | — | — | — | — | — | — |
audit.export | ✓ | — | — | — | — | — | — | — |
settings.view | ✓ | read-only | read-only | read-only | read-only | read-only | — | — |
settings.administer | ✓ | — | — | — | — | — | — | — |
Reports are filtered too: each report declares the view permissions it needs (a margin report needs finance.view), so a role only sees the reports of its modules in admin.reports.list.
Conditional rules
Some operations need more than their permission key:
| Operation | Rule |
|---|---|
admin.orders.validate | Needs orders.validate. A stock override additionally needs stock.validate; a credit override needs finance.validate. A FINANCE_MANAGER (holding finance.validate, not orders.validate) may confirm an order only when its sole pending reason is CREDIT_OVER_LIMIT — the "credit only" cell. |
admin.orders.toConfirm | orders.validate (or finance.validate, showing only the orders pending for credit). |
admin.orders.transition | orders.update; the fulfilment targets PREPARING, READY, SHIPPED and RETURNED from SHIPPED also accept delivery.update, so the delivery manager works from BO-42. Putting an order on hold (PENDING) needs orders.validate. |
admin.returns.decide | orders.validate to approve or reject; recording the collection of an approved return (IN_TRANSIT) also accepts delivery.update. |
admin.returns.receive | stock.update — the reception posts the RETURN movements and issues the credit note; finance is notified of every credit note. |
admin.tickets.resolve | support.validate. A REPLACEMENT creates a free order that still needs orders.validate to be confirmed (two people may be involved, by design). |
admin.resellers.validate | crm.validate; confirming or changing the declared customer type is part of the decision. |
admin.resellers.update | crm.update; changing customerType requires a reason and writes an AuditLog entry with before and after values. |
admin.variants.crud | catalog.update; the three prices, recommended price and VAT rate need pricing.update; the purchase price needs pricing.update and finance.view. |
admin.credit.update · admin.roles.updatePermissions · admin.users.setStatus · admin.settings.update | Recent authentication: a back-office session older than auth.reauthMinutes (default 15) must re-enter its password (403 REAUTH_REQUIRED). |
Sensitive fields
| Data | Visible to | Hidden from | Enforcement |
|---|---|---|---|
| Purchase price, margins, cost-based reports | Holders of finance.view (SA, FM, DIR) | Everyone else, including CA, SM, DM, delegates, resellers | Field removed by the serializer of admin.products.get and every list; column removed from exports; margin reports hidden from the report catalogue |
| The three selling prices | Back-office pricing.view; a reseller sees only the price of their own type; a delegate sees the type price of the reseller they assist | Guests (price: null); resellers never see the other two prices | Reseller and delegate operations return a single resolved price |
| Stock quantities, thresholds, reserved quantities | Back-office stock.view | Resellers, guests, delegates: availability IN_STOCK or OUT_OF_STOCK only | No quantity field exists in reseller or delegate schemas |
| Credit limit, exposure, terms, credit history | finance.view; the reseller sees their own limit and available credit; order validators see the control result (« dans la limite » or « dépassement de X ») | Delegates (they only see whether credit is possible for the assisted order) and other roles | Masked in admin.resellers.get for roles without finance.view |
| Other resellers' data | — | Every reseller | self scope |
| Delegates' live positions and trails | field.view (SA, CA, DIR); only during tours and within the retention period | Other roles; delegates (they see their own kilometres, not their trail) | admin.live.delegates, admin.tours.track |
Store identity documents (ID_CARD) | crm.validate holders and the reseller | Other crm.view holders | Document list filtered by type |
| Customer type | Back-office crm.view; the reseller sees their own type | Other resellers | Changes only through crm.validate (at validation) or crm.update (audited) |
| Audit journal | audit.view (SA) | Everyone else | — |
Data scope rules
Scopes are query filters added by the server to every read and write; a client parameter can never widen them.
Reseller — self
Order,Invoice,Payment,CreditNote,ReturnRequest,Ticket,ResellerAddress,ResellerDocument,Favorite,Cart, V2LoyaltyAccount:resellerId = :myResellerId.- Catalog: visible products per BR-60; prices of
:myCustomerType; promotions and banners whose audience matches the reseller; announcements targeted to them. - Live events (
events.stream): only events about their own orders, returns, tickets and notifications.
Guest — no session
- Only
auth: optionaland public operations: catalog, facets, promotions, banners, support WhatsApp number. Products withoutALLOWrules;price: null; no cart, no favorites, no orders.
Delegate — delegate (decided rule)
-- Resellers and prospects a delegate may read and act on
reseller.assignedDelegateId = :me
OR (
reseller.assignedDelegateId IS NULL
AND reseller.zoneId IN (:myZoneIds)
AND reseller.status IN ('NEW_PROSPECT','TO_CONTACT','INTERESTED','NOT_INTERESTED','TO_FOLLOW_UP')
)
OR reseller.id IN (
SELECT v.resellerId FROM Visit v JOIN Tour t ON t.id = v.tourId
WHERE t.delegateId = :me AND t.date BETWEEN :today - 7 AND :today + 30
)- Assigned resellers: full field access (sheet, history, notes, photos, tasks, invitations, assisted orders, read-only orders).
- Unassigned prospects of the delegate's zones: visible so that delegates pick them up instead of creating duplicates; a prospect assigned to another delegate of the same zone is not visible.
- Tour-derived access: a reseller planned in one of the delegate's tours (from 7 days back to 30 days ahead) is accessible even if assigned to someone else — this covers replacements during leave.
- Orders:
order.resellerIdin the assigned resellers ororder.sourceDelegateId = :me; read-only, without purchase prices. - Tours and visits:
tour.delegateId = :me. Tasks:assigneeId = :me. Invitations, objectives, performance, reports, location pings:delegateId = :me. - Duplicate phone check (
delegate.resellers.checkPhone) searches every record but returns details only for records in scope (BR-04).
Staff — no row filter in V1: a staff role sees every record of the modules it holds. A regional restriction of staff (for example a manager limited to the West) would be a change request.
Guard rails
- The last active
SUPER_ADMINcannot be removed, suspended, disabled or lose the role (409 LAST_SUPER_ADMIN). - No escalation: a user can only grant a role, or add a permission to a role, if they hold every permission concerned themselves (
403 PERMISSION_ESCALATION). Onlyroles.administerholders edit roles. - No self-service: nobody edits their own roles or permissions, or suspends or disables themselves (
403 SELF_MODIFICATION_FORBIDDEN). - Fixed roles:
SUPER_ADMINalways holds every permission;DELEGATEandRESELLERhave no back-office permission and cannot be combined with staff roles. - Delegates never see purchase prices, margins, other prices than the assisted reseller's, stock quantities, credit limits or other delegates' positions.
- Resellers never see another reseller's data, another customer type's price or a stock quantity; their customer type is never editable by them.
- Financial actions are separated: confirming an order (
orders.validate) does not give the right to override credit (finance.validate) or stock (stock.validate); every override is a separateAuditLogentry with its reason. - Exports respect scope and masking: an export never contains a field or a record its requester cannot see on screen.
- Real-time streams are filtered:
admin.events.streamsends an event only to sessions holding the view permission of its module (orders events needorders.view, positions needfield.view);events.streamonly carries the caller's own events. - Changes apply at once: each session carries a permission version; after a role change, the next request reloads the permission set and open streams are re-authorised — no sign-out needed.
- Sensitive operations need recent authentication in the back-office (see conditional rules).
Audit of permission changes
| Event | Recorded in AuditLog | Notified |
|---|---|---|
| Role permission set replaced | PERMISSION_CHANGE: role, permissions added and removed, actor, reason, IP, requestId | Every active SUPER_ADMIN (in-app and email) |
| Roles granted or removed on a user | PERMISSION_CHANGE: user, roles before and after | The user concerned (in-app) and the super admins |
| User suspended, reactivated, disabled | STATUS_CHANGE with reason | The super admins |
| Customer type changed | UPDATE on the reseller: before, after, reason | The reseller (TRANSACTIONAL) |
| Overrides (stock, credit), visit unlocks, tour reopenings | OVERRIDE or STATUS_CHANGE with reason | Module managers |
| Refused attempts on sensitive operations | Security log (rate-limited), shown in BO-63 as PERMISSION_DENIED entries | — |
Entries are immutable and readable in BO-63 with audit.view; they follow the audit retention of BR-84.
Test checklist for ACL-05
The permission tests of SRV-28 are generated from endpoints.json and this page, and run in CI on every pull request.
- Every staff operation × every role: call each
admin.*operation with a user of each seeded role; expect2xxwhen the matrix grants the permission,403 PERMISSION_DENIEDotherwise (one test per cell, generated). - Audience gates: a reseller token on a
delegate.*oradmin.*operation, a delegate token on a reseller oradmin.*operation →403. - Reseller scope: reseller B reads, cancels, returns or claims on reseller A's order, invoice, ticket, address or document id →
404 NOT_FOUND;files.downloadUrlon A's private file →404. - Delegate scope: a reseller assigned to the delegate →
200; an unassigned prospect of their zone →200; a prospect of their zone assigned to another delegate →404; a reseller in one of their tours of next week →200; the same reseller 31 days ahead →404. - Guests: catalog and facets answer without a session with
price: nulland no price facet; cart, favorites, orders →401. - Sensitive fields:
admin.products.getas CA, SM and DM has nopurchasePrice; as FM it has. Reseller and delegate responses contain no quantity, no other customer-type price, no purchase price, no credit limit (delegate) — checked by schema snapshot. - Account gates: a suspended user gets
403 ACCOUNT_SUSPENDEDon every operation exceptappConfig.getand public ones; a pending reseller gets403 ACCOUNT_PENDING_VALIDATIONoncart.checkout,orders.confirmAssistedandreturns.create, and prices according tocatalog.pricesVisibleBeforeValidation. - Conditional rules: CA cannot validate with a stock override; FM can validate an order pending only for
CREDIT_OVER_LIMITand nothing else; DM can move an order toPREPARINGbut cannot cancel it; SM can receive a return but cannot approve it; aREPLACEMENTorder created byadmin.tickets.resolvestaysSUBMITTEDuntilorders.validate. - Customer type:
profile.updatewithcustomerType→403 FIELD_NOT_EDITABLE;admin.resellers.updatewithout a reason →422 REASON_REQUIRED; with a reason → anAuditLogentry with before and after. - Guard rails: removing the last super admin →
409; granting a permission one does not hold →403 PERMISSION_ESCALATION; editing one's own roles →403. - Immediate effect: remove
orders.validatefrom CA; the next CA request toadmin.orders.validate→403without signing out; the open SSE stream stops carrying order events. - Streams and search:
admin.events.streamfor SM carriesstock.alertbut nodelegate.position;admin.searchfor DM returns no invoices;events.streamfor reseller A never carries reseller B's events. - Exports: a CA export of the catalog has no purchase-price column; a delegate cannot reach any export.
- Interface: for each role, a snapshot of the back-office menu (BO-03) matches the matrix; hidden buttons correspond to refused operations.
- Audit: every permission change, override, customer-type change and user status change produces exactly one
AuditLogentry with actor, before and after.
Business rules
Numbered rules BR-01 to BR-86 grouped like cahier §8, with source, tuning setting, default and worked examples, followed by the catalogue of every business setting.
Notifications matrix
Every event that notifies someone — trigger, recipients, channels, preference category and group, deep link, template and release, smart notifications included — with the French copy, quiet hours, grouping, retries, preferences, channel costs and the checklist for adding an event.