@memberjunction/core-entities-server 6.1.0-edge.5 → 6.1.0-edge.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/custom/MJAIBridgeProviderEntityServer.server.d.ts +1 -5
  2. package/dist/custom/MJAIBridgeProviderEntityServer.server.d.ts.map +1 -1
  3. package/dist/custom/MJAIBridgeProviderEntityServer.server.js +13 -12
  4. package/dist/custom/MJAIBridgeProviderEntityServer.server.js.map +1 -1
  5. package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.d.ts +2 -6
  6. package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.d.ts.map +1 -1
  7. package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.js +16 -14
  8. package/dist/custom/MJAIRemoteBrowserProviderEntityServer.server.js.map +1 -1
  9. package/dist/custom/MJActionEntityServer.server.d.ts +2 -2
  10. package/dist/custom/MJActionEntityServer.server.d.ts.map +1 -1
  11. package/dist/custom/MJActionEntityServer.server.js +8 -6
  12. package/dist/custom/MJActionEntityServer.server.js.map +1 -1
  13. package/dist/custom/MJApplicationEntityServer.server.d.ts +2 -2
  14. package/dist/custom/MJApplicationEntityServer.server.d.ts.map +1 -1
  15. package/dist/custom/MJApplicationEntityServer.server.js +4 -4
  16. package/dist/custom/MJApplicationEntityServer.server.js.map +1 -1
  17. package/dist/custom/MJEntityEntityServer.server.d.ts +36 -0
  18. package/dist/custom/MJEntityEntityServer.server.d.ts.map +1 -0
  19. package/dist/custom/MJEntityEntityServer.server.js +77 -0
  20. package/dist/custom/MJEntityEntityServer.server.js.map +1 -0
  21. package/dist/custom/MJEntityFieldPermissionEntityServer.server.d.ts +167 -0
  22. package/dist/custom/MJEntityFieldPermissionEntityServer.server.d.ts.map +1 -0
  23. package/dist/custom/MJEntityFieldPermissionEntityServer.server.js +347 -0
  24. package/dist/custom/MJEntityFieldPermissionEntityServer.server.js.map +1 -0
  25. package/dist/custom/MJRemoteOperationEntityServer.server.d.ts +2 -2
  26. package/dist/custom/MJRemoteOperationEntityServer.server.d.ts.map +1 -1
  27. package/dist/custom/MJRemoteOperationEntityServer.server.js +6 -4
  28. package/dist/custom/MJRemoteOperationEntityServer.server.js.map +1 -1
  29. package/dist/custom/MJRoleEntityServer.server.d.ts +78 -0
  30. package/dist/custom/MJRoleEntityServer.server.d.ts.map +1 -0
  31. package/dist/custom/MJRoleEntityServer.server.js +131 -0
  32. package/dist/custom/MJRoleEntityServer.server.js.map +1 -0
  33. package/dist/custom/MJUserEntityServer.server.d.ts +243 -0
  34. package/dist/custom/MJUserEntityServer.server.d.ts.map +1 -0
  35. package/dist/custom/MJUserEntityServer.server.js +335 -0
  36. package/dist/custom/MJUserEntityServer.server.js.map +1 -0
  37. package/dist/custom/MJUserRoleEntityServer.server.d.ts +243 -0
  38. package/dist/custom/MJUserRoleEntityServer.server.d.ts.map +1 -0
  39. package/dist/custom/MJUserRoleEntityServer.server.js +376 -0
  40. package/dist/custom/MJUserRoleEntityServer.server.js.map +1 -0
  41. package/dist/custom/MJUserViewEntityServer.server.d.ts +12 -1
  42. package/dist/custom/MJUserViewEntityServer.server.d.ts.map +1 -1
  43. package/dist/custom/MJUserViewEntityServer.server.js +33 -1
  44. package/dist/custom/MJUserViewEntityServer.server.js.map +1 -1
  45. package/dist/custom/fieldPermissionDelta.d.ts +57 -0
  46. package/dist/custom/fieldPermissionDelta.d.ts.map +1 -0
  47. package/dist/custom/fieldPermissionDelta.js +196 -0
  48. package/dist/custom/fieldPermissionDelta.js.map +1 -0
  49. package/dist/custom/fieldPermissionReconciler.d.ts +34 -0
  50. package/dist/custom/fieldPermissionReconciler.d.ts.map +1 -0
  51. package/dist/custom/fieldPermissionReconciler.js +96 -0
  52. package/dist/custom/fieldPermissionReconciler.js.map +1 -0
  53. package/dist/index.d.ts +7 -0
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +7 -0
  56. package/dist/index.js.map +1 -1
  57. package/package.json +28 -28
@@ -0,0 +1,376 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ var MJUserRoleEntityServer_1;
8
+ import { BaseEntity, BaseEntityResult, IsRestrictingFieldRule, Metadata, ValidationErrorInfo, ValidationErrorType, } from '@memberjunction/core';
9
+ import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
10
+ import { MJUserRoleEntity } from '@memberjunction/core-entities';
11
+ import { FindSystemUserFieldAccessViolations, UserCache } from '@memberjunction/generic-database-provider';
12
+ /**
13
+ * Server-side `MJ: User Roles` entity. It carries TWO independent guards that happen to protect the
14
+ * same table from opposite directions. They share no state and no helpers, and every write path
15
+ * must satisfy both:
16
+ *
17
+ * A. **Role elevation** (issue #4282) — bounds what the CALLER may do, by their own roles.
18
+ * B. **System-user field access** — bounds what may be done TO the system user's role set.
19
+ *
20
+ * Guard A and guard B are orthogonal: A asks "is this caller entitled to make this change", B asks
21
+ * "does this change leave the system user field-restricted". A change must clear both, so the
22
+ * checks compose as a conjunction and are evaluated A-then-B (A is an in-memory role-list test; B
23
+ * walks cached metadata and only ever runs when the system user is the target).
24
+ *
25
+ * ---------------------------------------------------------------------------------------------
26
+ * GUARD A — ROLE ELEVATION (issue #4282)
27
+ * ---------------------------------------------------------------------------------------------
28
+ *
29
+ * WHY THIS EXISTS AT ALL. Issue #4260 (`MJUserEntityServer`) closed the `User.Type` route to
30
+ * elevated capability. Role assignment is the platform's OTHER authority mechanism and was
31
+ * unguarded: verified against a live database, the `Developer` and `Integration` roles hold
32
+ * unfiltered `CanCreate`/`CanUpdate`/`CanDelete` on `MJ: User Roles`, `AllowCreateAPI` /
33
+ * `AllowUpdateAPI` / `AllowDeleteAPI` are all true on the entity, and no server-side subclass
34
+ * existed — `ClassFactory` resolved the generated `MJUserRoleEntity`, whose `Validate()` knows
35
+ * nothing about who is calling. Reproduced end to end before this class was written: a caller whose
36
+ * `Type` is `'User'`, holding only `Developer` and `UI`, inserted a row granting itself
37
+ * `Integration`; `Validate()` returned `Success=true` and `Save()` returned `true`.
38
+ *
39
+ * THE INVARIANT: **a non-Owner may only grant, move or revoke a role they themselves hold.**
40
+ *
41
+ * Why that rule and not a stricter one. Unlike `User.Type` — two values, no legitimate non-Owner
42
+ * write — role assignment has real non-Owner use cases the platform ships with: delegated
43
+ * administration, IdP/group sync, onboarding automation. The subset rule is the weakest rule that
44
+ * still closes elevation completely, because it is a CEILING: whatever a non-Owner does through
45
+ * this entity, the authority they can hand out is authority they already had, so no sequence of
46
+ * calls lets any caller exceed their own grant. Issue #4282 also floated adding "and may not grant
47
+ * a role that confers write access to `MJ: Roles`/`MJ: User Roles`". That was considered and
48
+ * declined: it does not close any elevation the subset rule leaves open (granting a peer a role you
49
+ * hold is delegation, not escape), it breaks the peer-onboarding case above, and it would require
50
+ * this guard to read `EntityInfo.Permissions` — trading the purity below for protection that
51
+ * `MJ: Entity Permissions` (which carries the SAME unfiltered `Developer`/`Integration` grant, and
52
+ * is a separate, still-open route) would undercut anyway.
53
+ *
54
+ * WHY HERE RATHER THAN IN A RESOLVER, and why `Save()`/`Delete()` are overridden alongside
55
+ * `Validate()`: identical reasoning to `MJUserEntityServer`, and see that file for the full
56
+ * argument. In short — `Validate()` runs inside `BaseEntity.Save()`, so the rule holds on every
57
+ * write path (GraphQL resolvers, Remote Operations, the Create/Update/Delete Record actions,
58
+ * metadata sync, one-off scripts) and for any role a deployment invents; but `BaseEntity.Save()`
59
+ * force-passes validation WITHOUT calling `Validate()` under `EntitySaveOptions.ReplayOnly`
60
+ * (`baseEntity.ts:3725`) while still performing the write, and `Delete()` never consults
61
+ * `Validate()` at all. Overriding all three is what makes "every write path" literal.
62
+ *
63
+ * THE INVARIANTS, for a caller whose `Type` is not `'Owner'`:
64
+ *
65
+ * 1. **Create** — `RoleID` must name a role the caller holds. This is the #4282 escalation
66
+ * itself: the self-grant of an unheld role.
67
+ * 2. **Update** — the NEW `RoleID` must be held (invariant 1, reached through UPDATE instead of
68
+ * INSERT), and so must the PRE-SAVE `RoleID`. The second half is not redundant: moving an
69
+ * existing grant off a role you do not hold is a revocation you were not entitled to make,
70
+ * and without it a non-Owner could strip an Owner of any role by repointing the row.
71
+ * `UserID` is deliberately NOT frozen — moving a grant between users stays within the
72
+ * caller's ceiling, which is the whole point of the rule.
73
+ * 3. **Delete** — the row's `RoleID` must be held by the caller, for the same reason as the
74
+ * pre-save half of invariant 2. Revocation is bounded by the same ceiling as granting.
75
+ * 4. **A `ReplayOnly` save is refused outright**, because `ReplayOnly` is the one option that
76
+ * skips `Validate()` and therefore invariants 1-2. Refusing rather than re-running them keeps
77
+ * the decision in ONE place; `ReplayOnly` is a replication facility for trusted sync paths and
78
+ * a non-Owner has no legitimate reason to replay writes onto the role-assignment table.
79
+ *
80
+ * A caller holding NO roles can therefore grant nothing — correct, and it is also why this guard
81
+ * needs no special case for an unpopulated `UserInfo.UserRoles`: the subset test fails closed on
82
+ * an empty list on its own. (Server-side that list is populated — `MJServer`'s `context.ts` resolves
83
+ * the request principal from `UserCache` precisely "to ensure UserRoles is properly populated", and
84
+ * `CloneUserForSessionContext` carries it onto the per-session clone.)
85
+ *
86
+ * NOT COVERED, deliberately: a magic-link session's SYNTHESIZED roles (`context.ts`
87
+ * `buildMagicLinkSessionUser`) count as roles held, so a scope-limited principal is measured by the
88
+ * same rule as any other caller. Narrowing scope-limited principals further is a magic-link concern,
89
+ * not a role-elevation one — `IsScopeLimitedPrincipal` lives in MJServer, which this package cannot
90
+ * import, and duplicating its predicate here would put one decision in two places. Such a session
91
+ * still needs `CanCreate` on this entity to reach the guard at all, which the `Magic Link Baseline`
92
+ * role does not grant.
93
+ *
94
+ * Owner-type callers are exempt from all four, so admin role management, and the
95
+ * `SyncRolesAndUsers` / `SyncUsers` mutations (both `@RequireSystemUser()`, whose `getSystemUser()`
96
+ * resolves the seeded `Type='Owner'` system user), are unaffected on a default install. A
97
+ * deployment whose system user is NOT an Owner will see those sync paths fail closed — loudly,
98
+ * at the save — exactly as #4260 documented for `MJ: Users`.
99
+ *
100
+ * Guard A is pure: it reads only this record's own field state and the caller's cached roles. No
101
+ * `RunView`, no provider, no engine, no I/O — so it costs nothing per save/delete and is
102
+ * unit-testable without a database. (Guard B is NOT pure in that sense; see below.)
103
+ *
104
+ * ---------------------------------------------------------------------------------------------
105
+ * GUARD B — SYSTEM-USER FIELD ACCESS
106
+ * ---------------------------------------------------------------------------------------------
107
+ *
108
+ * This is the other half of the system-user guard for field-level security.
109
+ * `MJEntityFieldPermissionEntityServer` guards the RULES; this guards the account's ROLE SET, from
110
+ * both directions. Without both halves an administrator reaches the forbidden state simply by
111
+ * doing the steps in a different order.
112
+ *
113
+ * - **Assignment** ({@link SystemUserRejectionReason}) — refuses giving the system user a role that
114
+ * already denies a field. Only DENYING rules count here, because adding a role can only add rules
115
+ * to the aggregate: its `Allow` rows grant, its `No Access` rows are inert, and only a `Deny`
116
+ * can take something away. A guard that counted every rule would refuse to reassemble the
117
+ * account's own role set the moment field security was enabled anywhere.
118
+ * - **Removal** ({@link Delete}) — refuses taking a role away when that would leave the account
119
+ * short of its entity-level access. Removal is the opposite shape: it drops rules OUT of the
120
+ * aggregate, so what matters is not what the departing role said but whether an `Allow` survives
121
+ * without it.
122
+ *
123
+ * Why the system user must stay unrestricted: the server runs background work as that account.
124
+ * It pre-warms the shared engine caches at startup, and in task mode — which job and agent
125
+ * runners use — engines instead load on first touch, so whichever caller gets there first
126
+ * configures the engine for the entire process. Engine caches are process-wide and shared
127
+ * across users. A restricted system user could therefore leave partially loaded records in a
128
+ * cache that everyone reads afterward, with nothing at the point of failure pointing back at
129
+ * the role assignment that caused it.
130
+ *
131
+ * Unlike guard A, guard B reads cached metadata (`Metadata.Entities`, the provider, `UserCache`).
132
+ * It still performs no database I/O, but it is not state-free, and it needs those caches populated
133
+ * to have an opinion — a cold `UserCache` skips the check rather than blocking an administrator.
134
+ *
135
+ * This restricts CONFIGURATION only. There is no user who is exempt from a Deny at runtime —
136
+ * not even the system user, whose access comes from the same rows as everyone else's.
137
+ */
138
+ let MJUserRoleEntityServer = MJUserRoleEntityServer_1 = class MJUserRoleEntityServer extends MJUserRoleEntity {
139
+ Validate() {
140
+ const result = super.Validate();
141
+ // Guard A — the caller may only assign/move a role they hold themselves.
142
+ if (!this.callerIsOwner()) {
143
+ this.validateRoleHeld(result, this.RoleID, 'You may only assign a role that you hold yourself. Assigning a role you do not hold would ' +
144
+ 'grant authority you were not given. Ask an Owner to make this assignment.');
145
+ if (this.IsSaved) {
146
+ this.validatePriorRoleHeld(result);
147
+ }
148
+ }
149
+ // Guard B — the system user may not be given a role that denies it a field. Applies to every
150
+ // caller, Owner included: an Owner is entitled to make the assignment but the resulting state
151
+ // is one the server cannot run in, so this is not an authority question.
152
+ const rejection = MJUserRoleEntityServer_1.SystemUserRejectionReason(this.UserID, this.RoleID, this.ProviderToUse);
153
+ if (rejection) {
154
+ result.Errors.push(new ValidationErrorInfo('RoleID', rejection, this.RoleID, ValidationErrorType.Failure));
155
+ }
156
+ result.Success = result.Success && result.Errors.length === 0;
157
+ return result;
158
+ }
159
+ /**
160
+ * Refuses a `ReplayOnly` save by a non-Owner caller — invariant 4. `ReplayOnly` force-passes
161
+ * validation without calling `Validate()` (`baseEntity.ts:3725`) yet does not suppress the
162
+ * write, so without this override invariants 1-2 were one caller-supplied option away from off
163
+ * while invariant 3 stayed enforced (an override cannot be switched off). Mirrors
164
+ * `MJUserEntityServer.Save()`, including its reasoning for refusing rather than re-validating.
165
+ */
166
+ async Save(options) {
167
+ if (options?.ReplayOnly && !this.callerIsOwner()) {
168
+ return this.refuse(this.IsSaved ? 'update' : 'create', 'Only an Owner may perform a ReplayOnly save on a user-role assignment. ReplayOnly bypasses ' +
169
+ 'validation, which is where this entity\'s role-elevation invariants are enforced.');
170
+ }
171
+ return super.Save(options);
172
+ }
173
+ /**
174
+ * Enforces both guards on the delete path.
175
+ *
176
+ * Guard A, invariant 3 — a non-Owner may only revoke a role they hold themselves.
177
+ *
178
+ * Guard B — refuses REMOVING a role from the system user when that would cost the account its
179
+ * field access. The mirror of the assignment guard, and needed for the same reason the
180
+ * field-permission subclass guards its own delete path: the system user's access is ordinary
181
+ * `Allow` rows, and taking a role away drops that role's rows out of the aggregate. If the
182
+ * remaining roles have `No Access` on a field, the last removal denies it — with no `Deny`
183
+ * written anywhere and no field-permission row touched. It permits the removal when it also
184
+ * costs the account its entity-level read, since it is then denied one level up and field rules
185
+ * decide nothing — which is why the projection re-evaluates the entity ceiling too, rather than
186
+ * only the field rules.
187
+ *
188
+ * Reports refusals the way `MJUserEntityServer.Delete` does — a `BaseEntityResult` on the
189
+ * result history, so `LatestResult.CompleteMessage` carries the reason, `Delete()` returning
190
+ * `false` on a logical rejection rather than throwing (the CLAUDE.md Save/Delete error-handling
191
+ * contract). Note that `SyncRolesUsersResolver.SyncUserRoles` treats a `false` from this method
192
+ * as a hard error and throws, rolling its transaction back; that mutation runs as the system
193
+ * Owner, so it is exempt from guard A and never sees that refusal on a default install.
194
+ */
195
+ async Delete(options) {
196
+ if (!this.callerIsOwner() && !this.callerHoldsRole(this.RoleID)) {
197
+ return this.refuse('delete', 'You may only revoke a role that you hold yourself. Revoking a role you do not hold would let you ' +
198
+ 'strip authority you were never granted. Ask an Owner to make this change.');
199
+ }
200
+ const rejection = this.systemUserRoleRemovalReason();
201
+ if (rejection) {
202
+ return this.refuse('delete', rejection);
203
+ }
204
+ return super.Delete(options);
205
+ }
206
+ /**
207
+ * Invariants 1 and 2's forward half — the role being assigned must be one the caller holds.
208
+ * Attributed to `RoleID` because that IS the field whose value is refused, so a form highlights
209
+ * the control the user must change.
210
+ */
211
+ validateRoleHeld(result, roleId, message) {
212
+ if (this.callerHoldsRole(roleId)) {
213
+ return;
214
+ }
215
+ result.Errors.push(new ValidationErrorInfo('RoleID', message, roleId ?? null, ValidationErrorType.Failure));
216
+ }
217
+ /**
218
+ * Invariant 2's second half — the role this assignment granted BEFORE the edit must also be one
219
+ * the caller holds, compared against the pre-save value rather than whatever the in-memory
220
+ * object arrived holding. `MJ: User Roles` has `TrackRecordChanges=1`, which forces
221
+ * `ResolverBase.UpdateRecord` to genuinely load the row from the database before applying edits,
222
+ * so that pre-save value is the row's real prior state on every wire path — but that is an
223
+ * unrelated entity flag, not a guarantee this class should assume will always hold, so a
224
+ * pre-save value that cannot be established fails CLOSED with its own message rather than
225
+ * falling through to "you do not hold role null".
226
+ */
227
+ validatePriorRoleHeld(result) {
228
+ const priorRoleId = this.GetFieldByName('RoleID')?.OldValue;
229
+ if (!priorRoleId) {
230
+ result.Errors.push(new ValidationErrorInfo('RoleID', 'Could not determine which role this assignment previously granted, so it cannot be checked ' +
231
+ 'against your own roles. Refusing to save rather than risk changing a grant you were not ' +
232
+ 'entitled to change.', priorRoleId ?? null, ValidationErrorType.Failure));
233
+ return;
234
+ }
235
+ this.validateRoleHeld(result, priorRoleId, 'You may only change an assignment of a role that you hold yourself. This assignment currently grants ' +
236
+ 'a role you do not hold, so changing it would revoke authority you were never granted.');
237
+ }
238
+ /** Records a logical refusal on the result history so `LatestResult.CompleteMessage` carries the reason. */
239
+ refuse(type, message) {
240
+ const result = new BaseEntityResult();
241
+ result.Success = false;
242
+ result.Type = type;
243
+ result.Message = message;
244
+ result.StartedAt = new Date();
245
+ result.EndedAt = new Date();
246
+ this.RegisterResultHistoryEntry(result);
247
+ return false;
248
+ }
249
+ /**
250
+ * True when the caller's own role assignments include `roleId`.
251
+ *
252
+ * `UUIDsEqual` rather than `===` because role IDs reach this class from two different sources
253
+ * with two different casings — the cached `UserInfo.UserRoles` and the entity field the client
254
+ * sent (see `guides/UUID_COMPARISON_GUIDE.md`). A falsy `roleId`, or a caller with no roles,
255
+ * yields `false`: both are the fail-closed answer, and neither needs a branch of its own.
256
+ */
257
+ callerHoldsRole(roleId) {
258
+ if (!roleId) {
259
+ return false;
260
+ }
261
+ const caller = this.ActiveUser;
262
+ return (caller?.UserRoles ?? []).some((userRole) => UUIDsEqual(userRole.RoleID, roleId));
263
+ }
264
+ /**
265
+ * True when the caller is an Owner (or when there is no caller to evaluate — the guard has
266
+ * nothing to compare against). `Type` is an `NCHAR` column, so it arrives space-padded; casing
267
+ * is normalized for the same reason `MJServer`'s `principals.ts` does.
268
+ *
269
+ * The "no caller ⇒ exempt" default has the same asymmetric reachability documented on
270
+ * `MJUserEntityServer.callerIsOwner()`: `BaseEntity.CheckPermissions` throws on a falsy
271
+ * `ActiveUser` BEFORE `Validate()` runs inside `Save()`, so the default is decorative there;
272
+ * in `Delete()` it is load-bearing, letting a caller-less delete proceed into `super.Delete()`
273
+ * where `CheckPermissions` is what actually refuses it.
274
+ *
275
+ * Reads `ActiveUser` rather than `ContextCurrentUser` directly so a per-request provider's
276
+ * `CurrentUser` is honored on multi-provider servers.
277
+ *
278
+ * Applies to guard A only. Guard B has no Owner exemption — see `Validate()`.
279
+ */
280
+ callerIsOwner() {
281
+ const caller = this.ActiveUser;
282
+ if (!caller) {
283
+ return true;
284
+ }
285
+ return caller.Type?.trim().toLowerCase() === 'owner';
286
+ }
287
+ /**
288
+ * Why this role may not be taken off this user, or null when it may.
289
+ * Only ever rejects for the system user; every other user is unaffected.
290
+ */
291
+ systemUserRoleRemovalReason() {
292
+ const systemUser = UserCache.Instance?.GetSystemUser?.();
293
+ if (!systemUser || !this.UserID || !this.RoleID || !UUIDsEqual(systemUser.ID, this.UserID)) {
294
+ return null; // not the system user — nothing to guard
295
+ }
296
+ const provider = this.ProviderToUse;
297
+ const lost = FindSystemUserFieldAccessViolations(provider, systemUser, { WithoutRoleID: this.RoleID });
298
+ if (lost.length === 0) {
299
+ return null;
300
+ }
301
+ const shown = lost.slice(0, 3).map(v => `${v.EntityName}.${v.FieldName}`).join(', ');
302
+ const more = lost.length > 3 ? `, and ${lost.length - 3} more` : '';
303
+ return (`Removing this role from the MJ system user would leave it unable to use ${lost.length} field(s) ` +
304
+ `(${shown}${more}). The server runs background work as that account and shares one engine cache across ` +
305
+ `all users, so restricting it would let partially loaded records reach everyone. Field security has no ` +
306
+ `exempt user — grant those fields to another role the system user holds first.`);
307
+ }
308
+ /**
309
+ * Why this role may not be given to this user, or null when it may.
310
+ * Only ever rejects for the system user; every other user is unaffected.
311
+ */
312
+ static SystemUserRejectionReason(userID, roleID, provider) {
313
+ if (!userID || !roleID) {
314
+ return null;
315
+ }
316
+ // UserCache is populated on the server; a cold cache skips the check rather than
317
+ // blocking an administrator on missing state.
318
+ const systemUser = UserCache.Instance?.GetSystemUser?.();
319
+ if (!systemUser || !UUIDsEqual(systemUser.ID, userID)) {
320
+ return null; // not the system user — nothing to guard
321
+ }
322
+ const restricted = MJUserRoleEntityServer_1.EntitiesWithRestrictingFieldRulesForRole(roleID, provider);
323
+ if (restricted.length === 0) {
324
+ return null;
325
+ }
326
+ const shown = restricted.slice(0, 3).join(', ');
327
+ const more = restricted.length > 3 ? `, and ${restricted.length - 3} more` : '';
328
+ return (`This role denies access to at least one field (on ${shown}${more}), so it cannot be assigned to the MJ system user. ` +
329
+ `The server runs background work as that account and shares one engine cache across all users — ` +
330
+ `restricting it would let partially loaded records reach everyone. ` +
331
+ `Assign this role to a regular user, or remove its field permissions first.`);
332
+ }
333
+ /**
334
+ * Names of entities where this role carries at least one RESTRICTING field rule — a `Deny`
335
+ * on any verb. Walks cached metadata only — no database access. Runs only when the system
336
+ * user is the save target, which is rare.
337
+ *
338
+ * Grants and neutrals are ignored, and must be: the system user holds the standard roles
339
+ * (UI, Developer, Integration), snapshot initialization writes those roles `Allow` rows on
340
+ * every entity they can read, and field security has no runtime exemption to fall back on.
341
+ * A guard that counted any rule at all would refuse to reassemble the system user's own role
342
+ * set the moment field security was enabled anywhere.
343
+ *
344
+ * Deliberately does NOT gate on {@link EntityInfo.EnableFieldLevelSecurity}, even though
345
+ * rules on a disabled entity are inactive and gating would be the cheaper walk. Gating
346
+ * would leave the two halves of this guard unable to compose, and the gap is reachable in
347
+ * three ordinary steps: disable field security on an entity, assign the role (now carrying
348
+ * no active rules) to the system user, re-enable. Each step is permitted and the end state
349
+ * is the one both guards exist to prevent. Disabling preserves rules so re-enabling does
350
+ * not lose them, so a rule on a disabled entity is dormant rather than gone.
351
+ */
352
+ static EntitiesWithRestrictingFieldRulesForRole(roleID, provider) {
353
+ const md = provider ?? new Metadata();
354
+ const names = [];
355
+ for (const entity of md.Entities) {
356
+ const hit = entity.Fields.some(f => f.HasFieldPermissions &&
357
+ f.FieldPermissions.some(fp => UUIDsEqual(fp.RoleID, roleID) && IsRestrictingFieldRule(fp)));
358
+ if (hit) {
359
+ names.push(entity.Name);
360
+ }
361
+ }
362
+ return names;
363
+ }
364
+ };
365
+ MJUserRoleEntityServer = MJUserRoleEntityServer_1 = __decorate([
366
+ RegisterClass(BaseEntity, 'MJ: User Roles')
367
+ ], MJUserRoleEntityServer);
368
+ export { MJUserRoleEntityServer };
369
+ /**
370
+ * Loader stub — prevents the class from being tree-shaken out of the bundle. Mirrors the
371
+ * pattern used by the other server-side entity subclasses in this package.
372
+ */
373
+ export function LoadMJUserRoleEntityServer() {
374
+ // no-op
375
+ }
376
+ //# sourceMappingURL=MJUserRoleEntityServer.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJUserRoleEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJUserRoleEntityServer.server.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { EntityInfo, IMetadataProvider } from "@memberjunction/core";
1
+ import { EntityInfo, IMetadataProvider, ValidationResult } from "@memberjunction/core";
2
2
  import { MJUserViewEntityExtended } from '@memberjunction/core-entities';
3
3
  /**
4
4
  * Template data passed to the Smart Filter Generation Nunjucks prompt.
@@ -18,6 +18,17 @@ export declare class MJUserViewEntityServer extends MJUserViewEntityExtended {
18
18
  * This property is hard-coded to true in this class because we DO support smart filters in this class. If you want to disable smart filters for a specific view you can override this property in your subclass and set it to false.
19
19
  */
20
20
  protected get SmartFilterImplemented(): boolean;
21
+ Validate(): ValidationResult;
22
+ /**
23
+ * SECURITY gate: `CustomWhereClause = 1` tells the platform to fold the stored
24
+ * `WhereClause` into view SQL verbatim — it bypasses both the auto-generation in
25
+ * `UpdateWhereClause()` and the render-time `ValidateUserProvidedSQLClause` screen in
26
+ * the database provider (custom clauses may legitimately contain constructs that screen
27
+ * blocks). That makes the CustomWhereClause/WhereClause pair an arbitrary-SQL channel,
28
+ * so creating or modifying a view with `CustomWhereClause` enabled is restricted to
29
+ * admin (Owner-type) users. Ordinary views (CustomWhereClause off) are unaffected.
30
+ */
31
+ private validateCustomWhereClauseAuthorization;
21
32
  /**
22
33
  * This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
23
34
  * to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
@@ -1 +1 @@
1
- {"version":3,"file":"MJUserViewEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJUserViewEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,UAAU,EAAY,iBAAiB,EAAY,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAcxE;;GAEG;AACH,UAAU,uBAAwB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,qBACa,sBAAuB,SAAQ,wBAAwB;IAChE;;OAEG;IACH,cAAuB,sBAAsB,IAAI,OAAO,CAEvD;IAED;;;;OAIG;IACU,8BAA8B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF5I;;;OAGG;IACH,SAAS,CAAC,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,uBAAuB;IAwC5E;;OAEG;IACH,SAAS,CAAC,4BAA4B,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM;CAoC1H"}
1
+ {"version":3,"file":"MJUserViewEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJUserViewEntityServer.server.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,UAAU,EAAY,iBAAiB,EAAsD,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACjK,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAcxE;;GAEG;AACH,UAAU,uBAAwB,SAAQ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAC7D,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC7B;AAED,qBACa,sBAAuB,SAAQ,wBAAwB;IAChE;;OAEG;IACH,cAAuB,sBAAsB,IAAI,OAAO,CAEvD;IAEe,QAAQ,IAAI,gBAAgB;IAO5C;;;;;;;;OAQG;IACH,OAAO,CAAC,sCAAsC;IAwB9C;;;;OAIG;IACU,8BAA8B,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,MAAM,CAAA;KAAC,CAAC;IAkF5I;;;OAGG;IACH,SAAS,CAAC,iBAAiB,CAAC,UAAU,EAAE,UAAU,GAAG,uBAAuB;IAwC5E;;OAEG;IACH,SAAS,CAAC,4BAA4B,CAAC,UAAU,EAAE,UAAU,EAAE,EAAE,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,MAAM;CAoC1H"}
@@ -5,7 +5,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  };
7
7
  import { RegisterClass, CleanAndParseJSON } from "@memberjunction/global";
8
- import { BaseEntity, LogError } from "@memberjunction/core";
8
+ import { BaseEntity, LogError, ValidationErrorInfo, ValidationErrorType } from "@memberjunction/core";
9
9
  import { MJUserViewEntityExtended } from '@memberjunction/core-entities';
10
10
  import { AIPromptParams } from "@memberjunction/ai-core-plus";
11
11
  import { AIEngine } from "@memberjunction/aiengine";
@@ -17,6 +17,38 @@ let MJUserViewEntityServer = class MJUserViewEntityServer extends MJUserViewEnti
17
17
  get SmartFilterImplemented() {
18
18
  return true;
19
19
  }
20
+ Validate() {
21
+ const result = super.Validate();
22
+ this.validateCustomWhereClauseAuthorization(result);
23
+ result.Success = result.Success && result.Errors.length === 0;
24
+ return result;
25
+ }
26
+ /**
27
+ * SECURITY gate: `CustomWhereClause = 1` tells the platform to fold the stored
28
+ * `WhereClause` into view SQL verbatim — it bypasses both the auto-generation in
29
+ * `UpdateWhereClause()` and the render-time `ValidateUserProvidedSQLClause` screen in
30
+ * the database provider (custom clauses may legitimately contain constructs that screen
31
+ * blocks). That makes the CustomWhereClause/WhereClause pair an arbitrary-SQL channel,
32
+ * so creating or modifying a view with `CustomWhereClause` enabled is restricted to
33
+ * admin (Owner-type) users. Ordinary views (CustomWhereClause off) are unaffected.
34
+ */
35
+ validateCustomWhereClauseAuthorization(result) {
36
+ const isCustom = !!this.CustomWhereClause; // truthy — the DB may hand back true or 1
37
+ if (!isCustom) {
38
+ return;
39
+ }
40
+ const customDirty = this.GetFieldByName('CustomWhereClause')?.Dirty === true;
41
+ const whereDirty = this.GetFieldByName('WhereClause')?.Dirty === true;
42
+ if (!customDirty && !whereDirty) {
43
+ // Neither sensitive field changed on this save — nothing new to authorize.
44
+ return;
45
+ }
46
+ const user = this.ContextCurrentUser
47
+ ?? this.ProviderToUse.CurrentUser;
48
+ if (!user || user.Type?.trim().toLowerCase() !== 'owner') {
49
+ result.Errors.push(new ValidationErrorInfo('CustomWhereClause', 'Only an administrator (Owner-type user) may create or modify a view with CustomWhereClause enabled — the custom WhereClause is executed as raw SQL.', this.CustomWhereClause, ValidationErrorType.Failure));
50
+ }
51
+ }
20
52
  /**
21
53
  * This method will use AI to return a valid WHERE clause based on the provided prompt. This is automatically called at the right time if the view has SmartFilterEnabled turned on and the SmartFilterPrompt is set. If you want
22
54
  * to call this directly to get back a WHERE clause for other purposes you can call this method directly and provide both a prompt and the entity that the view is based on.
@@ -1 +1 @@
1
- {"version":3,"file":"MJUserViewEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJUserViewEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAc,QAAQ,EAA+B,MAAM,sBAAsB,CAAC;AACrG,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AA0BrD,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,wBAAwB;IAChE;;OAEG;IACH,IAAuB,sBAAsB;QACzC,OAAO,IAAI,CAAC;IAChB,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,8BAA8B,CAAC,MAAc,EAAE,UAAsB;QAC9E,IAAI,CAAC;YACD,gCAAgC;YAChC,MAAM,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAE/D,0CAA0C;YAC1C,MAAM,iBAAiB,GAAG,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CACpD,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,yBAAyB,IAAI,CAAC,CAAC,QAAQ,KAAK,YAAY,CAC3E,CAAC;YAEF,IAAI,CAAC,iBAAiB,EAAE,CAAC;gBACrB,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;YAC9G,CAAC;YAED,qCAAqC;YACrC,MAAM,YAAY,GAAG,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,CAAC;YAExD,8BAA8B;YAC9B,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;YACpC,MAAM,CAAC,MAAM,GAAG,iBAAiB,CAAC;YAClC,MAAM,CAAC,WAAW,GAAG,IAAI,CAAC,kBAAkB,CAAC;YAC7C,MAAM,CAAC,IAAI,GAAG,YAAY,CAAC;YAC3B,MAAM,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAC,gCAAgC;YAEjE,0DAA0D;YAC1D,MAAM,CAAC,oBAAoB,GAAG;gBAC1B;oBACI,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,MAAM;iBAClB;aACJ,CAAC;YAEF,qBAAqB;YACrB,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;YACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,aAAa,CAAsB,MAAM,CAAC,CAAC;YAEvE,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;YAC1E,CAAC;YAED,uEAAuE;YACvE,0EAA0E;YAC1E,4CAA4C;YAC5C,IAAI,WAAuC,CAAC;YAC5C,IAAI,MAAM,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,aAAa,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBACvF,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC;YAChC,CAAC;iBAAM,CAAC;gBACJ,WAAW,GAAG,iBAAiB,CAAsB,MAAM,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC;YAChH,CAAC;YACD,IAAI,CAAC,WAAW,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;YAC/E,CAAC;YAED,6DAA6D;YAC7D,IAAI,WAAW,CAAC,WAAW,IAAI,WAAW,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAChE,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;gBAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;oBAC1D,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;oBACtB,CAAC,CAAC,WAAW,CAAC,WAAW,CAAC;gBAE9B,OAAO;oBACH,WAAW;oBACX,eAAe,EAAE,WAAW,CAAC,sBAAsB;iBACtD,CAAC;YACN,CAAC;iBACI,IAAI,WAAW,CAAC,WAAW,KAAK,SAAS,IAAI,WAAW,CAAC,WAAW,KAAK,IAAI,EAAE,CAAC;gBACjF,gDAAgD;gBAChD,OAAO;oBACH,WAAW,EAAE,EAAE;oBACf,eAAe,EAAE,WAAW,CAAC,sBAAsB;iBACtD,CAAC;YACN,CAAC;iBACI,CAAC;gBACF,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;YAC/E,CAAC;QACL,CAAC;QACD,OAAO,CAAC,EAAE,CAAC;YACP,QAAQ,CAAC,CAAC,CAAC,CAAC;YACZ,MAAM,CAAC,CAAC;QACZ,CAAC;IACL,CAAC;IAED;;;OAGG;IACO,iBAAiB,CAAC,UAAsB;QAC9C,MAAM,cAAc,GAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;QACvD,MAAM,EAAE,GAAsB,IAAI,CAAC,aAA6C,CAAC;QACjF,MAAM,WAAW,GAAG,EAAE,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QACjD,MAAM,iBAAiB,GAAG,EAAE,CAAC,YAAY,CAAC,kBAAkB,CAAC,CAAC;QAE9D,2BAA2B;QAC3B,MAAM,iBAAiB,GAAG,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAChD,IAAI,GAAG,GAAW,GAAG,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC;YAC1C,IAAI,CAAC,CAAC,aAAa,EAAE,CAAC;gBAClB,GAAG,IAAI,aAAa,CAAC,CAAC,qBAAqB,GAAG,CAAC;YACnD,CAAC;YACD,OAAO,GAAG,CAAC;QACf,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAEb,kCAAkC;QAClC,MAAM,uBAAuB,GAAG,IAAI,CAAC,4BAA4B,CAAC,UAAU,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;QAElG,qBAAqB;QACrB,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YACzD,OAAO,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC;QAC/C,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAEnB,4BAA4B;QAC5B,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,CAAC,CAAC,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAC3E,OAAO,CAAC,CAAC,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC,WAAW,GAAG,GAAG,CAAC;QAC/C,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAEnB,OAAO;YACH,UAAU,EAAE,UAAU,CAAC,IAAI;YAC3B,QAAQ,EAAE,UAAU,CAAC,EAAE;YACvB,QAAQ,EAAE,UAAU,CAAC,QAAQ;YAC7B,iBAAiB;YACjB,uBAAuB,EAAE,cAAc,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAuB,CAAC,CAAC,CAAC,EAAE;YACjF,WAAW,EAAE,WAAW,EAAE,UAAU,IAAI,MAAM;YAC9C,WAAW;YACX,iBAAiB;SACpB,CAAC;IACN,CAAC;IAED;;OAEG;IACO,4BAA4B,CAAC,UAAsB,EAAE,EAAqB,EAAE,cAAwB;QAC1G,MAAM,UAAU,GAAG,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QAChG,MAAM,qBAAqB,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QAEnH,+BAA+B;QAC/B,MAAM,aAAa,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAChD,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACnC,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC;gBAClD,IAAI,CAAC,EAAE,CAAC;oBACJ,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;oBACvB,OAAO,KAAK,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;wBACzD,OAAO,EAAE,CAAC,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC,IAAI,GAAG,GAAG,CAAC;oBAC1C,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAE,EAAE,CAAC;gBACpB,CAAC;YACL,CAAC;YACD,OAAO,EAAE,CAAC;QACd,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,2CAA2C;QAC3C,MAAM,mBAAmB,GAAG,UAAU,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE;YAC3D,MAAM,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,aAAa,CAAC,CAAC;YAC5D,IAAI,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjD,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;gBAChC,OAAO,KAAK,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE;oBACzD,IAAI,GAAG,GAAW,GAAG,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,GAAG,CAAC;oBAC5C,IAAI,EAAE,CAAC,aAAa,EAAE,CAAC;wBACnB,GAAG,IAAI,aAAa,EAAE,CAAC,qBAAqB,GAAG,CAAC;oBACpD,CAAC;oBACD,OAAO,GAAG,CAAC;gBACf,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAE,EAAE,CAAC;YACpB,CAAC;YACD,OAAO,EAAE,CAAC;QACd,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAExC,OAAO,CAAC,aAAa,EAAE,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrF,CAAC;CACJ,CAAA;AAlLY,sBAAsB;IADlC,aAAa,CAAC,UAAU,EAAE,gBAAgB,CAAC;GAC/B,sBAAsB,CAkLlC"}
1
+ {"version":3,"file":"MJUserViewEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJUserViewEntityServer.server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { EntityInfo, FieldPermissionAccess } from '@memberjunction/core';
2
+ /**
3
+ * A permission row that should exist but does not, at its snapshot defaults.
4
+ */
5
+ export type FieldPermissionSnapshotRow = {
6
+ EntityFieldID: string;
7
+ RoleID: string;
8
+ ReadAccess: FieldPermissionAccess;
9
+ UpdateAccess: FieldPermissionAccess;
10
+ CreateAccess: FieldPermissionAccess;
11
+ };
12
+ /**
13
+ * What reconciliation must write to bring an entity's field-permission rows back in line with
14
+ * its fields and its entity-level permissions.
15
+ *
16
+ * There is no `ToUpdate`. An existing row is never touched, because an administrator's
17
+ * tightening has to survive every reconciliation — including disable → schema change →
18
+ * re-enable. That single rule is what makes "keep the rows, functionally inactive" on disable
19
+ * and "graceful reconciliation" on re-enable the same code path.
20
+ */
21
+ export type FieldPermissionDelta = {
22
+ /** (field, role) pairs that should have a row and do not. */
23
+ ToInsert: FieldPermissionSnapshotRow[];
24
+ /** IDs of rows that should no longer exist. */
25
+ ToDelete: string[];
26
+ };
27
+ /**
28
+ * Computes the field-permission rows an entity is missing and the ones it should no longer
29
+ * have. Pure — no I/O, no entity objects, no provider. Everything comes off `EntityInfo`.
30
+ *
31
+ * **Snapshot defaults mirror entity-level permissions**, so enabling field security on an
32
+ * entity changes no behavior until an administrator tightens a specific field. A role that can
33
+ * read the entity gets `ReadAccess = 'Allow'`; its Update and Create follow the entity-level
34
+ * grants, defaulting to `No Access`.
35
+ *
36
+ * **A role without entity-level read gets no rows at all.** The entity-level gate already
37
+ * excludes it, so rows would be noise — and a row granting Update without Read would violate
38
+ * the Read-required CHECK constraint anyway.
39
+ *
40
+ * **Unrestrictable fields (primary keys, `__mj_` columns) get no rows.** They are forced open
41
+ * in the aggregation regardless, so rows for them are clutter that would also trip the
42
+ * save-time guard.
43
+ *
44
+ * **No role is excluded — including the ones the MJ system user holds.** Those rows are what
45
+ * keeps the system user working now that the aggregation has no bypass for it: it holds the
46
+ * standard roles (UI, Developer, Integration), which carry entity read on essentially
47
+ * everything, so the snapshot grants them `Allow` here and the save-time guards refuse any
48
+ * later `Deny` aimed at them. Access the server relies on is therefore visible in the data
49
+ * rather than implied by an exemption in code.
50
+ */
51
+ export declare function ComputeFieldPermissionDelta(entity: EntityInfo): FieldPermissionDelta;
52
+ /**
53
+ * True when the delta would change nothing, so callers can skip opening a transaction at all.
54
+ * Reconciliation runs on ordinary saves, and the overwhelmingly common answer is "nothing to do".
55
+ */
56
+ export declare function IsEmptyFieldPermissionDelta(delta: FieldPermissionDelta): boolean;
57
+ //# sourceMappingURL=fieldPermissionDelta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fieldPermissionDelta.d.ts","sourceRoot":"","sources":["../../src/custom/fieldPermissionDelta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,UAAU,EAAwB,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAEhH;;GAEG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACrC,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,qBAAqB,CAAC;IAClC,YAAY,EAAE,qBAAqB,CAAC;IACpC,YAAY,EAAE,qBAAqB,CAAC;CACvC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,oBAAoB,GAAG;IAC/B,6DAA6D;IAC7D,QAAQ,EAAE,0BAA0B,EAAE,CAAC;IACvC,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB,CAAC;AAqBF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,UAAU,GAAG,oBAAoB,CAcpF;AAoKD;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAEhF"}