[Security] Gate the remaining Mark Wilson CRM controllers to staff/admin (#688 follow-up) #689

Closed
opened 2026-07-17 22:42:10 +00:00 by spikerj · 1 comment
Owner

Follow-up to spikersoft-issues#688 (customer-PII controller, fixed in PR #385). The sibling generator-service CRM controllers share the identical gap: bare [Authorize] = any authenticated user (incl. student/consumer accounts on the shared API realm), with GET-list endpoints returning ALL records (staff views, not user-scoped).

Verified staff-only (this PR gates them): ServiceCallsController (service-call records incl. customer service history), LaborsController, PartsController, MilagesController, ServiceCallReasonsController. Each has a class-level [Authorize], standard CRUD, and no [AllowAnonymous] override / public endpoint.

Deliberately EXCLUDED: GeneratorBrandsController — it has no class-level [Authorize] and only gates writes; its GETs are intentionally public reference/lookup data (dropdown of brand names). Blanket-gating it would break legitimate anonymous reads. Its write-endpoints being only [Authorize] (any user) could be tightened separately if desired, but that's a distinct decision.

Fix: [Authorize(Roles = "Admin,admin,Staff,staff")] on the five controllers — the same established pattern (27 peers, and now Customers via #688). Added a parameterized authorization test covering all five.

Follow-up to spikersoft-issues#688 (customer-PII controller, fixed in PR #385). The sibling generator-service CRM controllers share the identical gap: bare `[Authorize]` = any authenticated user (incl. student/consumer accounts on the shared API realm), with `GET`-list endpoints returning ALL records (staff views, not user-scoped). **Verified staff-only (this PR gates them):** `ServiceCallsController` (service-call records incl. customer service history), `LaborsController`, `PartsController`, `MilagesController`, `ServiceCallReasonsController`. Each has a class-level `[Authorize]`, standard CRUD, and no `[AllowAnonymous]` override / public endpoint. **Deliberately EXCLUDED:** `GeneratorBrandsController` — it has no class-level `[Authorize]` and only gates writes; its `GET`s are intentionally public reference/lookup data (dropdown of brand names). Blanket-gating it would break legitimate anonymous reads. Its write-endpoints being only `[Authorize]` (any user) could be tightened separately if desired, but that's a distinct decision. **Fix:** `[Authorize(Roles = "Admin,admin,Staff,staff")]` on the five controllers — the same established pattern (27 peers, and now Customers via #688). Added a parameterized authorization test covering all five.
Author
Owner

Resolved in spikersoft-backend PR #386 (merged to master). Gated the sibling CRM controllers (ServiceCalls/Labors/Parts/Milages/ServiceCallReasons) to [Authorize(Roles = "Admin,admin,Staff,staff")]; GeneratorBrands excluded (public reference reads). 10 authorization tests pass. Closing.

Resolved in spikersoft-backend PR #386 (merged to `master`). Gated the sibling CRM controllers (ServiceCalls/Labors/Parts/Milages/ServiceCallReasons) to `[Authorize(Roles = "Admin,admin,Staff,staff")]`; GeneratorBrands excluded (public reference reads). 10 authorization tests pass. Closing.
Sign in to join this conversation.