HOCO B2B
SRS

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.

StepCheckFailure
1Session valid (bearer token or cookie). Operations marked optional also accept no session: the caller is a guest401 UNAUTHENTICATED
2Account status gate (AccountStatus)403 ACCOUNT_SUSPENDED · 403 ACCOUNT_PENDING_VALIDATION on ordering
3Audience gate: reseller sessions only reach reseller operations, delegate sessions delegate operations, staff sessions admin.* operations403 PERMISSION_DENIED
4Permission 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 permissions403 PERMISSION_DENIED
5Data scope filter (self, delegate) on reads and writes404 NOT_FOUND on reads (existence never leaked) · 403 OUT_OF_SCOPE on a write to a known out-of-scope record
6Field rules: sensitive fields removed from responses, refused on writesField absent · 403 FIELD_NOT_EDITABLE
7Conditional rules on the operation (overrides, credit-only validation, re-authentication)403 PERMISSION_DENIED · 403 REAUTH_REQUIRED
8Audit: 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.

ModuleCoversviewcreateupdatedeletevalidateexportadminister
dashboardDashboards BO-04 to BO-08KPI cards and charts (each card also needs its domain's view)——————
crmResellers, prospects, timeline, tasks, segments, validationLists, map, 360°, timeline, documents, phone check, tasksCreate reseller or prospectEdit, 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 typeExport listsSegments
catalogBrands, categories, products, variants, attributes, visibilityProducts, brands, categories, attributesCreate products, brands, categories, attributesEdit, publish, hide, archive, visibility, variants (except price fields)Delete drafts; archive referenced brands and categories—Export the catalogProduct import
pricingThe three prices per variant, price history; V2 agreementsPrices and price historyV2: create agreementsWrite the three prices, recommended price, VAT rateV2: delete agreements———
stockWarehouses, levels, movements, inventories, return receptionLevels, movements, alertsGoods receiptsAdjustments, thresholds, inventory counts, return reception—Validate inventories, stock override at order confirmationExport stock listsWarehouses
ordersOrders, confirmation queue, assisted orders, returnsOrders, history, returnsOrders on behalf of resellersModify lines, transitions, cancel, documents—Confirm or refuse orders, confirm assisted orders, approve or reject returnsExport order lists—
deliveryDelivery board, carriers, proofsBoard, picking lists—Carrier, driver, tracking, fulfilment transitions, proof of delivery———Carriers
financeInvoices, payments, credit, credit notesInvoices, payments, credit accounts, purchase prices and marginsRecord paymentsAllocations—Verify declarations, cancel invoices, credit overrideAccounting export (V2)Credit limits, terms, blocking
promotionsPromotions, daily offers, banners; V2 packsList, previewCreateEditDelete drafts and bannersSchedule, activate, pause, archive——
loyalty (V2)Points, levels, rewards, redemptionsAccounts and ledgersRewardsAdjustments, fulfil redemptionsRewards——Earning rules, levels
fieldDelegates, zones, objectives, tours, visits, invitations funnelDelegates, tours, visits, live map, trails, funnelDelegates, tours, objectivesEdit tours and stops, cancel tours, recommend order, edit delegatesObjectivesReopen tours, unlock visitsExport field listsZones, visit types, checklists
supportTickets, after-sales claimsQueue, tickets and claims—Reply, internal notes, assign, status—Resolve after-sales claims——
communicationAnnouncements, notification templates and rulesDelivery logAnnouncementsEdit announcementsDelete announcementsSend announcements—Templates, event → channel rules
reportsReport library, export centreRun reports————Export reports and lists through the export centre—
usersStaff and delegate accountsUsersCreate usersEdit, roles, status, reset password———Other users' sessions
rolesRoles and permissionsRoles, catalogue—————Change a role's permission set
auditActivity journalJournal————Export the journal—
settingsBusiness settings, reference lists, support regions, app versions, imports, dashboard layoutsSettings—————Change them

Roles

ValueUI label (fr)Meaning
SUPER_ADMINSuper administrateurFull access, global settings, roles, permissions and audit.
COMMERCIAL_ADMINAdministrateur commercialCRM, resellers, delegates, tours, promotions, orders and reporting.
STOCK_MANAGERResponsable stockProducts, categories, variants, quantities, inventories and alerts.
FINANCE_MANAGERResponsable financePayments, credit, invoices, due dates and financial reports.
DELIVERY_MANAGERResponsable livraisonPreparation, shipping, carrier assignment and proof of delivery.
DIRECTIONDirectionRead-only dashboards, KPIs and consolidated reports.
DELEGATEDélégué commercialTours, visits, sign-ups, invitations, assisted orders and reports — own scope only.
RESELLERRevendeurCatalog, 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.

PermissionSACASMFMDMDIRDELRES
dashboard.view✓✓✓✓✓✓own (home, performance)—
crm.view✓✓—read-onlyread-onlyread-onlyzoneown (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-onlyread-onlyread-only✓✓ (visible products; guests too)
catalog.create✓—✓—————
catalog.update✓—✓—————
catalog.delete✓—✓—————
catalog.export✓—✓—————
catalog.administer✓—✓—————
pricing.view✓✓read-onlyread-only—read-onlyzone (price of the reseller assisted)own (price of their type)
pricing.update✓✓——————
pricing.create (V2)✓✓——————
pricing.delete (V2)✓✓——————
stock.view✓read-only✓read-onlyread-onlyread-only— (availability only)— (availability only)
stock.create✓—✓—————
stock.update✓—✓—————
stock.validate✓—✓—————
stock.export✓—✓—————
stock.administer✓—✓—————
orders.view✓✓read-onlyread-only✓read-onlyzone (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-onlyread-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-onlyown (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-onlyread-onlyread-onlyread-onlyread-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:

OperationRule
admin.orders.validateNeeds 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.toConfirmorders.validate (or finance.validate, showing only the orders pending for credit).
admin.orders.transitionorders.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.decideorders.validate to approve or reject; recording the collection of an approved return (IN_TRANSIT) also accepts delivery.update.
admin.returns.receivestock.update — the reception posts the RETURN movements and issues the credit note; finance is notified of every credit note.
admin.tickets.resolvesupport.validate. A REPLACEMENT creates a free order that still needs orders.validate to be confirmed (two people may be involved, by design).
admin.resellers.validatecrm.validate; confirming or changing the declared customer type is part of the decision.
admin.resellers.updatecrm.update; changing customerType requires a reason and writes an AuditLog entry with before and after values.
admin.variants.crudcatalog.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.updateRecent authentication: a back-office session older than auth.reauthMinutes (default 15) must re-enter its password (403 REAUTH_REQUIRED).

Sensitive fields

DataVisible toHidden fromEnforcement
Purchase price, margins, cost-based reportsHolders of finance.view (SA, FM, DIR)Everyone else, including CA, SM, DM, delegates, resellersField 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 pricesBack-office pricing.view; a reseller sees only the price of their own type; a delegate sees the type price of the reseller they assistGuests (price: null); resellers never see the other two pricesReseller and delegate operations return a single resolved price
Stock quantities, thresholds, reserved quantitiesBack-office stock.viewResellers, guests, delegates: availability IN_STOCK or OUT_OF_STOCK onlyNo quantity field exists in reseller or delegate schemas
Credit limit, exposure, terms, credit historyfinance.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 rolesMasked in admin.resellers.get for roles without finance.view
Other resellers' data—Every resellerself scope
Delegates' live positions and trailsfield.view (SA, CA, DIR); only during tours and within the retention periodOther 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 resellerOther crm.view holdersDocument list filtered by type
Customer typeBack-office crm.view; the reseller sees their own typeOther resellersChanges only through crm.validate (at validation) or crm.update (audited)
Audit journalaudit.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, V2 LoyaltyAccount: 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: optional and public operations: catalog, facets, promotions, banners, support WhatsApp number. Products without ALLOW rules; 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.resellerId in the assigned resellers or order.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

  1. The last active SUPER_ADMIN cannot be removed, suspended, disabled or lose the role (409 LAST_SUPER_ADMIN).
  2. 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). Only roles.administer holders edit roles.
  3. No self-service: nobody edits their own roles or permissions, or suspends or disables themselves (403 SELF_MODIFICATION_FORBIDDEN).
  4. Fixed roles: SUPER_ADMIN always holds every permission; DELEGATE and RESELLER have no back-office permission and cannot be combined with staff roles.
  5. Delegates never see purchase prices, margins, other prices than the assisted reseller's, stock quantities, credit limits or other delegates' positions.
  6. Resellers never see another reseller's data, another customer type's price or a stock quantity; their customer type is never editable by them.
  7. 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 separate AuditLog entry with its reason.
  8. Exports respect scope and masking: an export never contains a field or a record its requester cannot see on screen.
  9. Real-time streams are filtered: admin.events.stream sends an event only to sessions holding the view permission of its module (orders events need orders.view, positions need field.view); events.stream only carries the caller's own events.
  10. 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.
  11. Sensitive operations need recent authentication in the back-office (see conditional rules).

Audit of permission changes

EventRecorded in AuditLogNotified
Role permission set replacedPERMISSION_CHANGE: role, permissions added and removed, actor, reason, IP, requestIdEvery active SUPER_ADMIN (in-app and email)
Roles granted or removed on a userPERMISSION_CHANGE: user, roles before and afterThe user concerned (in-app) and the super admins
User suspended, reactivated, disabledSTATUS_CHANGE with reasonThe super admins
Customer type changedUPDATE on the reseller: before, after, reasonThe reseller (TRANSACTIONAL)
Overrides (stock, credit), visit unlocks, tour reopeningsOVERRIDE or STATUS_CHANGE with reasonModule managers
Refused attempts on sensitive operationsSecurity 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.

  1. Every staff operation × every role: call each admin.* operation with a user of each seeded role; expect 2xx when the matrix grants the permission, 403 PERMISSION_DENIED otherwise (one test per cell, generated).
  2. Audience gates: a reseller token on a delegate.* or admin.* operation, a delegate token on a reseller or admin.* operation → 403.
  3. Reseller scope: reseller B reads, cancels, returns or claims on reseller A's order, invoice, ticket, address or document id → 404 NOT_FOUND; files.downloadUrl on A's private file → 404.
  4. 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.
  5. Guests: catalog and facets answer without a session with price: null and no price facet; cart, favorites, orders → 401.
  6. Sensitive fields: admin.products.get as CA, SM and DM has no purchasePrice; 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.
  7. Account gates: a suspended user gets 403 ACCOUNT_SUSPENDED on every operation except appConfig.get and public ones; a pending reseller gets 403 ACCOUNT_PENDING_VALIDATION on cart.checkout, orders.confirmAssisted and returns.create, and prices according to catalog.pricesVisibleBeforeValidation.
  8. Conditional rules: CA cannot validate with a stock override; FM can validate an order pending only for CREDIT_OVER_LIMIT and nothing else; DM can move an order to PREPARING but cannot cancel it; SM can receive a return but cannot approve it; a REPLACEMENT order created by admin.tickets.resolve stays SUBMITTED until orders.validate.
  9. Customer type: profile.update with customerType → 403 FIELD_NOT_EDITABLE; admin.resellers.update without a reason → 422 REASON_REQUIRED; with a reason → an AuditLog entry with before and after.
  10. Guard rails: removing the last super admin → 409; granting a permission one does not hold → 403 PERMISSION_ESCALATION; editing one's own roles → 403.
  11. Immediate effect: remove orders.validate from CA; the next CA request to admin.orders.validate → 403 without signing out; the open SSE stream stops carrying order events.
  12. Streams and search: admin.events.stream for SM carries stock.alert but no delegate.position; admin.search for DM returns no invoices; events.stream for reseller A never carries reseller B's events.
  13. Exports: a CA export of the catalog has no purchase-price column; a delegate cannot reach any export.
  14. Interface: for each role, a snapshot of the back-office menu (BO-03) matches the matrix; hidden buttons correspond to refused operations.
  15. Audit: every permission change, override, customer-type change and user status change produces exactly one AuditLog entry with actor, before and after.

On this page