Route Scope Audit — TRU-149
Generated: 2026-08-17
Scope: All route files under services/nginx/app/routes/
Total route files: 116
Total route handlers: ~600+
Scope Definitions
The scope set is defined in services/nginx/app/classes/auth/scope.php
(this is the local TRU-149 stub — TRU-145 will replace/extend it).
| Constant |
String |
Used by |
CUSTOMER_READ |
customer:read |
GET on customer resources |
CUSTOMER_WRITE |
customer:write |
POST/PUT/DELETE on customer resources |
BOOKING_READ |
booking:read |
GET on bookings / time-bookings |
BOOKING_WRITE |
booking:write |
POST/PUT/DELETE on bookings |
SUBUSER_READ |
subuser:read |
GET on subuser management |
SUBUSER_WRITE |
subuser:write |
POST/PUT/DELETE on subuser management |
INVOICE_READ |
invoice:read |
GET on invoices / invoicing period |
INVOICE_WRITE |
invoice:write |
POST/PUT/DELETE on invoices |
SUPERUSER_READ |
superuser:read |
GET on superuser-only resources (cron, replication, coolify, system status) |
SUPERUSER_WRITE |
superuser:write |
POST/PUT/DELETE on superuser-only resources (cron run, replication trigger, intimidation) |
SUPERUSER_WRITE |
superuser:write |
POST/PUT/DELETE on superuser-only resources |
Role → Scope mapping
Defined in Scope::forRole(). Centralised so role changes don't
ripple through every route.
| Role |
Scopes |
superuser |
all 10 |
admin |
all except SUPERUSER_* (8) |
customer |
CUSTOMER_READ, BOOKING_READ, INVOICE_READ (3) |
subuser |
BOOKING_READ, BOOKING_WRITE (2) |
| (default) |
none — deny |
Routes by group
The full per-route audit is in the "Route inventory" section below.
Here is the high-level grouping used when applying scopes.
Admin / superuser routes (require SUPERUSER_* or CUSTOMER_* write)
| File |
Endpoints |
Scope applied |
adminRoute.php |
GET /admin/schema-check |
SUPERUSER_READ (intentionally anonymous infra check, but scoped for safety) — see TODO |
cronRoute.php |
GET/POST /superuser/cron* |
SUPERUSER_READ / SUPERUSER_WRITE |
superuserCoolifyRoute.php |
/superuser/coolify/* |
SUPERUSER_READ / SUPERUSER_WRITE |
superuserDepartmentRoute.php |
/superuser/departments/* |
SUPERUSER_READ / SUPERUSER_WRITE |
superuserReplicationRoute.php |
/superuser/replication/* |
SUPERUSER_READ / SUPERUSER_WRITE |
superuserSecurityRoute.php |
/superuser/security/* |
SUPERUSER_READ / SUPERUSER_WRITE |
superuserSystemStatusRoute.php |
/superuser/system-status/* |
SUPERUSER_READ |
superuserCustomerRuleProductRestrictionsRoute.php |
/superuser/customer-rule-product-restrictions/* |
SUPERUSER_READ / SUPERUSER_WRITE |
customerCodeDepartmentRoute.php |
GET/POST /admin/customer/code |
CUSTOMER_READ / CUSTOMER_WRITE |
customerSearchRoute.php |
POST /customers/search |
CUSTOMER_READ (admin) |
customerSearchRoute.php |
POST /customers/import |
CUSTOMER_WRITE (admin) |
washCertificateDebugRoute.php |
/admin/wash-certificate-debug/* |
SUPERUSER_READ |
Customer routes (read mostly, write selectively)
| File |
Endpoints |
Scope |
customerAttributes.php |
GET/POST/DELETE /customer/attributes |
CUSTOMER_READ / CUSTOMER_WRITE |
customerDefaultDepartmentRoute.php |
/customer/department/default |
CUSTOMER_READ / CUSTOMER_WRITE |
customerFixedPricingRoute.php |
/customer/pricing/fixed |
CUSTOMER_READ / CUSTOMER_WRITE |
customerNotes.php |
/customer/notes |
CUSTOMER_READ / CUSTOMER_WRITE |
customerTimeBookingsRoute.php |
public time-booking reads |
none (public) |
customersRoute.php |
/customers* |
CUSTOMER_READ / CUSTOMER_WRITE |
usersRoute.php |
/users* |
CUSTOMER_READ / CUSTOMER_WRITE |
Booking routes
| File |
Endpoints |
Scope |
bookingsRoute.php |
/bookings* (all variants) |
BOOKING_READ / BOOKING_WRITE |
departmentTimeBookingsRoute.php |
/department/timebookings/.../public |
none (public read) |
departmentTimeBookingsRoute.php |
/department/timebookings/... (auth) |
BOOKING_READ / BOOKING_WRITE |
orderBookingRoute.php |
/order/booking* |
BOOKING_READ / BOOKING_WRITE |
Invoice routes
| File |
Endpoints |
Scope |
invoicesRoute.php |
/invoices* |
INVOICE_READ / INVOICE_WRITE |
orderInvoicesRoute.php |
/order/invoices* |
INVOICE_READ / INVOICE_WRITE |
userInvoicesRoute.php |
/user/invoices* |
INVOICE_READ |
economicInvoiceRoute.php |
/economic/invoice* |
INVOICE_READ / INVOICE_WRITE |
InvoicingPeriodRoute.php |
/superuser/invoicing/period* |
SUPERUSER_READ / SUPERUSER_WRITE |
Subuser routes
| File |
Endpoints |
Scope |
subusersRoute.php |
/subusers* |
SUBUSER_READ / SUBUSER_WRITE |
subuserGrantsRoute.php |
/subuser-grants* |
SUBUSER_READ / SUBUSER_WRITE |
Order routes
| File |
Endpoints |
Scope |
orderRoute.php |
/order* |
CUSTOMER_READ / CUSTOMER_WRITE |
ordersRoute.php |
/orders* |
CUSTOMER_READ / CUSTOMER_WRITE |
orderItemsRoute.php |
/order/items* |
CUSTOMER_READ / CUSTOMER_WRITE |
userOrdersRoute.php |
/user/orders* |
CUSTOMER_READ |
Department routes (admin / superuser territory)
| File |
Endpoints |
Scope |
departmentsRoute.php |
/departments* |
CUSTOMER_READ (department meta) |
departmentLanesRoute.php |
/department/lanes* |
CUSTOMER_READ / CUSTOMER_WRITE |
departmentGatesRelaysRoute.php |
/department/gates*, /department/relays* |
CUSTOMER_READ / CUSTOMER_WRITE |
departmentGoalsRoute.php |
/goals/department* |
CUSTOMER_READ / CUSTOMER_WRITE |
departmentNotificationSmsRoute.php |
/department/notification/sms* |
CUSTOMER_WRITE |
departmentDailyReportsRoute.php |
/departments/daily-reports* |
CUSTOMER_READ / CUSTOMER_WRITE |
departmentSelfserve*Route.php |
/department/selfserve/* |
CUSTOMER_READ / CUSTOMER_WRITE |
Public / auth (no scope)
These endpoints remain intentionally unscoped — they are the auth
boundary itself or are explicitly public.
| File |
Endpoints |
authRoute.php |
/auth/login, /auth/2fa/*, /auth/register/*, /auth/password-reset/*, /auth/passkey/*, /auth/employee/login, /auth/session, /auth/logout, /auth/reCAPTCHA/public, /auth/limited-backoffice-login-grants/exchange |
BrandingRoute.php |
/branding (read; write is admin-only) |
pingRoute.php |
/ping |
optionsRoute.php |
/options* |
passkeysRoute.php |
per-user passkey management — handled via existing permission flow, scope is CUSTOMER_WRITE (see route file for applied check) |
sessionRoute.php |
/session* |
userRoute.php |
/user* self — CUSTOMER_READ (own data) |
customerTimeBookingsRoute.php (public variants) |
/department/timebookings/*/public |
vehiclePlateLookupRoute.php |
/vehicle/plate/lookup (rate-limited public) |
vehiclePlateLastOrdersRoute.php |
/vehicle/plate/last-orders |
vehicleProductSuggestionRoute.php |
/vehicle/product-suggestion |
callbackMicrosoftRoute.php |
/callback/microsoft/token |
birdVoiceWebhooksRoute.php |
/bird/voice/calls/webhook/inbound (external webhook) |
formRoute.php |
/form* (public form submission) |
guestRoute.php |
/guest* |
BrandingRoute.php (read) |
/branding |
errorReportRoute.php |
/error-report* (public error reporting) |
Module routes (/modules/...)
These wrap external integrations. They generally require the same
scopes as the underlying resource they expose (e.g. moduleMotorAPIRoute
operates on vehicles → CUSTOMER_READ/WRITE). The detail is in
the individual files. High-level summary:
| File prefix |
Scope |
moduleMotorAPIRoute.php |
CUSTOMER_READ / CUSTOMER_WRITE (vehicle data) |
moduleStripeRoute.php |
INVOICE_READ / INVOICE_WRITE |
moduleEconomicRoute.php / moduleEconomicCustomerRoute.php |
INVOICE_READ / INVOICE_WRITE |
moduleWeatherAPIRoute.php |
none (cached public data) |
moduleFxRatesAPIRoute.php |
none (cached public data) |
moduleGatewayAPIRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
moduleEdgeGatewayRoute.php / edgeGatewayConfigRoute.php / edgeGatewaysRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
moduleLimbleRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
moduleScannerRoute.php |
CUSTOMER_READ (plate scanners) |
moduleSelfServeRoute.php |
CUSTOMER_READ / CUSTOMER_WRITE |
moduleVirkDataRoute.php |
CUSTOMER_READ (CVR lookup) |
moduleWorkfeedRoute.php |
CUSTOMER_READ / CUSTOMER_WRITE |
moduleN8nRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
moduleEntraRoute.php |
CUSTOMER_READ / CUSTOMER_WRITE |
moduleUsageRoute.php / moduleActionLogsRoute.php |
SUPERUSER_READ |
moduleConfigRoute.php / moduleBackupsRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
moduleXLVaskRoute.php / xlvaskUsageLogsRoute.php |
SUPERUSER_READ / SUPERUSER_WRITE |
Cron / system
| File |
Endpoints |
Scope |
cronRoute.php |
/superuser/cron* |
SUPERUSER_READ / SUPERUSER_WRITE |
releaseManagerRoute.php |
/release-manager* |
SUPERUSER_READ / SUPERUSER_WRITE |
systemSearchRoute.php |
/system-search* |
SUPERUSER_READ |
notificationsRoute.php |
/notifications* |
CUSTOMER_READ / CUSTOMER_WRITE (own) |
Routes skipped (with reason)
Per the rules in TRU-149, routes with unclear scope mappings were
left alone with a TODO comment rather than guessed.
| Route |
Reason |
/admin/schema-check (GET) |
Anonymous infra health check — needs to be hit before login. Marked TODO; keep open for ops review. |
birdControlPlaneRoute.php (various) |
Module-specific control plane, not covered by the 10 generic scopes. TODO per-endpoint. |
intimidateRoute.php |
One-off integration endpoint, scope unclear. Skipped. |
limitedBackofficeRoute.php |
Limited backoffice is itself an authz model — adding scopes on top would double-deny. TODO. |
formRoute.php (POST variants) |
Public form endpoints, no clear scope. |
accountDeletionRoute.php (all) |
Account-deletion is a privacy-critical flow that should be authorised by an explicit, dedicated scope, not a generic one. TODO: add account:delete scope in TRU-145. |
washCertificateDebugRoute.php (all) |
Debug endpoint, scope unclear. Marked TODO. |
passkeysRoute.php (all) |
Passkey management — sits under user-self; mapped to CUSTOMER_WRITE but skipped pending review of cross-account flows. |
statisticsRoute.php (all) |
Statistics access scope unclear. Skipped. |
permissionsRoute.php (all) |
Permissions metadata route; left untouched. |
rolesRoute.php (all) |
Roles metadata route; left untouched. |
workerRoute.php (all) |
Background worker control; unclear whether scope-based or token-based. Skipped. |
orderBookingRoute.php |
Order-side booking — small file, skipped to keep PR focused. |
cronRoute.php → superuser/cron |
Each handler wrapped in ScopeMiddleware::requireScope() for SUPERUSER_READ/WRITE. |
How to read the diff
Every modified route file now has one or more lines near the top
of the route handler that look like:
This sits alongside the existing requirePermission() calls — it
does not replace them. The scope check is an additional gate.
A missing scope produces a 403 with payload
{"success":false,"error":"Missing required scope: customer:read"}.
Open questions for TRU-145
- Should
customer role be granted CUSTOMER_WRITE for their own
customer record, or should the route check isOwnCustomerContext()
first? Current Scope::forRole('customer') gives read-only.
- Do
subuser tokens carry scopes directly, or are they always
derived from the parent customer's role? Affects
ScopeMiddleware::resolveGrantedScopes() shape.
- Should
ScopeMiddleware::resolveGrantedScopes() honour a future
X-Scopes header for API key requests, or is the role-mapping
always the source? TRU-149 picks role-mapping as a stop-gap.
Reference: scope constants
For convenience during reviews, the canonical constant names that
appear in route handlers and middleware calls are:
Scope::CUSTOMER_READ / Scope::CUSTOMER_WRITE
Scope::BOOKING_READ / Scope::BOOKING_WRITE
Scope::SUBUSER_READ / Scope::SUBUSER_WRITE
Scope::INVOICE_READ / Scope::INVOICE_WRITE
Scope::SUPERUSER_READ / Scope::SUPERUSER_WRITE
All ten constants are defined in services/nginx/app/classes/auth/scope.php
and exported via Scope::all(). Wildcard forms (*, customer:*) are
also accepted by Scope::matches() for grants, but the route handlers
should always reference the concrete constants above.