A token carries the user's roles and account, so a demoted admin kept admin
claims until the token expired. The team member update and the admin user
edit now rotate the security stamp and evict the cached value when the role,
account or user name changes. Permission overrides are read per request and
are not in the token.
Tokens now carry a keyed hash of the account's security stamp, and every
authenticated request compares it with the stored stamp (cached for 60 s,
evicted in-process on change). A password reset or change, a deactivation
and a deletion all rotate or remove the stamp, so tokens issued before them
get 401. Tokens without the claim get 401 too.
User creation, contact, calendar event and site-contact create/modify/delete stamps used local server time. Validation rules comparing user-entered dates and the JWT expiry are unchanged.
Align DeleteUser with AddUser/EditUser: Admin role at controller and
service entry, Forbidden for non-Admin, and regression coverage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(api): enforce service and data-service boundaries
* refactor(api): complete feature service boundaries
* refactor(identity): enforce service and data boundaries
* refactor(vendors): enforce service and data boundaries
* refactor(workorders): enforce service and data boundaries
* refactor(backend): enforce architecture and optimize dispatch
* style(backend): format changed architecture files
* fix(architecture): address backend review follow-ups
* fix(backend): sanitize exception disclosure in changed API endpoints
Replace raw exception-message disclosure (ex.Message) returned to API
callers with a stable sanitized public message plus correlated structured
internal logging, across the endpoints changed in this PR.
- Add SanitizedErrors helper: logs the original exception at Error with a
generated correlation id and returns a stable public message referencing
it so support can trace without exposing internals.
- Inject ILogger<T> into the 14 changed controllers and route every
ex.Message/dbex.Message disclosure through the helper, preserving status
codes, response shapes, and business data (e.g. OpenWorkOrders).
- Leave FluentValidation (vex.Errors) and existing fixed-message catches
untouched; out-of-scope controllers (Account/Contact/Employee/Asset/
PMSchedule) are unchanged.
- Add focused tests proving internal exception text is not returned and
that Error logging carrying the original exception is invoked.
* fix(architecture): abstract job run state access
* style: format board update service
* test: use collection assertion idiom