You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This commit was created on GitHub.com and signed with GitHub’s verified signature.
feat: model events are standardized, so storage backend no longer decides what can be live. The ORM announced changes for models it managed; everything else was silent, which meant LDAP groups and users, Redis-backed notifications and PATs could not update a page no matter what the view did. utils/model_events gives them the identical contract — model:<Name>:<action> with {model, action, pk, data} — so a subscriber cannot tell which backend a model uses. data goes through toJSON() (stripping isPrivate fields), and a delete never carries a body, enforced in the emitter rather than trusted to each call site. ORM and bespoke models share one filtered bus, so "does this model have a read gate?" is answered in exactly one place.
feat: LDAP groups and users announce their changes.Group covers add, remove and member/owner changes; User covers add, update and remove. The users and profile views consume them, so a user added or a group membership changed by another admin now shows up without a refresh — including on someone's own profile, where a stale page is most misleading since it is showing them their own access.
security: read gates for every model whose data the UI renders — ResourceEdge, Group, User, Notification, ApiToken, MeshClient, MeshSite, MeshExitGrant, AccessRequest, Agent. Several are row-level, not merely model-level: a user receives their own User record, notifications, PATs and mesh clients and nobody else's, while a directory admin receives all of them except PATs — which have no admin path because the REST route has none either.
security: User payloads strip userPassword explicitly. It is present on a record read with attributes: ['*','+'] as the admin bind, and stayed off the wire only because user_parse() sets it to undefined inside an if branch — an incidental protection in an unrelated function, not somewhere to hang a credential.
note: the Group gate mirrors its currently ungated REST route (routes/group.js:8) rather than being tightened past it. A socket stricter than the endpoint feeding the same page shows a list that silently stops updating; tighten the route and the gate follows automatically.