@memberjunction/core-entities-server 6.1.0-edge.4 → 6.1.0-edge.6

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 (41) hide show
  1. package/dist/custom/MJAIPromptRunEntityServer.server.d.ts +76 -0
  2. package/dist/custom/MJAIPromptRunEntityServer.server.d.ts.map +1 -1
  3. package/dist/custom/MJAIPromptRunEntityServer.server.js +164 -28
  4. package/dist/custom/MJAIPromptRunEntityServer.server.js.map +1 -1
  5. package/dist/custom/MJActionEntityServer.server.d.ts +2 -2
  6. package/dist/custom/MJActionEntityServer.server.d.ts.map +1 -1
  7. package/dist/custom/MJActionEntityServer.server.js +8 -6
  8. package/dist/custom/MJActionEntityServer.server.js.map +1 -1
  9. package/dist/custom/MJApplicationEntityServer.server.d.ts +2 -2
  10. package/dist/custom/MJApplicationEntityServer.server.d.ts.map +1 -1
  11. package/dist/custom/MJApplicationEntityServer.server.js +4 -4
  12. package/dist/custom/MJApplicationEntityServer.server.js.map +1 -1
  13. package/dist/custom/MJConversationDetailEntityServer.server.d.ts +14 -2
  14. package/dist/custom/MJConversationDetailEntityServer.server.d.ts.map +1 -1
  15. package/dist/custom/MJConversationDetailEntityServer.server.js +14 -2
  16. package/dist/custom/MJConversationDetailEntityServer.server.js.map +1 -1
  17. package/dist/custom/MJRemoteOperationEntityServer.server.d.ts +2 -2
  18. package/dist/custom/MJRemoteOperationEntityServer.server.d.ts.map +1 -1
  19. package/dist/custom/MJRemoteOperationEntityServer.server.js +6 -4
  20. package/dist/custom/MJRemoteOperationEntityServer.server.js.map +1 -1
  21. package/dist/custom/MJRoleEntityServer.server.d.ts +78 -0
  22. package/dist/custom/MJRoleEntityServer.server.d.ts.map +1 -0
  23. package/dist/custom/MJRoleEntityServer.server.js +131 -0
  24. package/dist/custom/MJRoleEntityServer.server.js.map +1 -0
  25. package/dist/custom/MJUserEntityServer.server.d.ts +243 -0
  26. package/dist/custom/MJUserEntityServer.server.d.ts.map +1 -0
  27. package/dist/custom/MJUserEntityServer.server.js +335 -0
  28. package/dist/custom/MJUserEntityServer.server.js.map +1 -0
  29. package/dist/custom/MJUserRoleEntityServer.server.d.ts +146 -0
  30. package/dist/custom/MJUserRoleEntityServer.server.d.ts.map +1 -0
  31. package/dist/custom/MJUserRoleEntityServer.server.js +216 -0
  32. package/dist/custom/MJUserRoleEntityServer.server.js.map +1 -0
  33. package/dist/custom/MJUserViewEntityServer.server.d.ts +12 -1
  34. package/dist/custom/MJUserViewEntityServer.server.d.ts.map +1 -1
  35. package/dist/custom/MJUserViewEntityServer.server.js +33 -1
  36. package/dist/custom/MJUserViewEntityServer.server.js.map +1 -1
  37. package/dist/index.d.ts +3 -0
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +3 -0
  40. package/dist/index.js.map +1 -1
  41. package/package.json +28 -28
@@ -1 +1 @@
1
- {"version":3,"file":"MJConversationDetailEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AACH,qBACa,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACY,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlE;;;;;;;;;;;OAWG;IACI,gCAAgC,IAAI,OAAO;CAiBrD"}
1
+ {"version":3,"file":"MJConversationDetailEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAErE,OAAO,EAAE,kCAAkC,EAAE,MAAM,+BAA+B,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBACa,gCAAiC,SAAQ,kCAAkC;IACpF;;;OAGG;IACY,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAQlE;;;;;;;;;;;OAWG;IACI,gCAAgC,IAAI,OAAO;CAiBrD"}
@@ -6,7 +6,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
6
6
  };
7
7
  import { BaseEntity } from "@memberjunction/core";
8
8
  import { RegisterClass } from "@memberjunction/global";
9
- import { MJConversationDetailEntity } from "@memberjunction/core-entities";
9
+ import { MJConversationDetailEntityExtended } from "@memberjunction/core-entities";
10
10
  /**
11
11
  * Server-side subclass of MJConversationDetailEntity that automatically tracks
12
12
  * when the original message content has been modified.
@@ -19,8 +19,20 @@ import { MJConversationDetailEntity } from "@memberjunction/core-entities";
19
19
  * UI can derive "edited after summarization" from the flag plus the row's Sequence
20
20
  * being below the current summary boundary — no summary regeneration required (the
21
21
  * summary is explicitly lossy; agents page in exact rows via the retrieval tools).
22
+ *
23
+ * Extends {@link MJConversationDetailEntityExtended} rather than the generated entity
24
+ * directly. Both classes register for `BaseEntity` under the key
25
+ * `'MJ: Conversation Details'`, and `@RegisterClass` passes `priority = 0`, which routes
26
+ * to the auto-increment branch — so whichever registers LAST wins outright. On the server
27
+ * this package loads after `@memberjunction/core-entities`, which meant this class replaced
28
+ * the Extended one and its `Save`/`Delete` permission gate never ran. That gate is the check
29
+ * that only a conversation's owner may set `UserRating` / `UserFeedback`, and that a
30
+ * non-owner without a resource grant cannot write at all — and it is explicitly designed to
31
+ * run server-side (`ProviderType === 'Database'`), which is exactly where it was being
32
+ * shadowed out. Inheriting composes the two behaviors instead: this class flags the edit,
33
+ * then delegates to the permission gate via `super.Save`.
22
34
  */
23
- let MJConversationDetailEntityServer = class MJConversationDetailEntityServer extends MJConversationDetailEntity {
35
+ let MJConversationDetailEntityServer = class MJConversationDetailEntityServer extends MJConversationDetailEntityExtended {
24
36
  /**
25
37
  * Override Save to detect message changes and set the OriginalMessageChanged flag.
26
38
  * This is done as pre-processing before calling super.Save() to ensure it's a single DB round trip.
@@ -1 +1 @@
1
- {"version":3,"file":"MJConversationDetailEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAE3E;;;;;;;;;;;;GAYG;AAEI,IAAM,gCAAgC,GAAtC,MAAM,gCAAiC,SAAQ,0BAA0B;IAC5E;;;OAGG;IACM,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,IAAI,CAAC,gCAAgC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;QACvC,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;;;;;OAWG;IACI,gCAAgC;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC9E,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,WAAW,EAAE,KAAK,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA1CY,gCAAgC;IAD5C,aAAa,CAAC,UAAU,EAAE,0BAA0B,CAAC;GACzC,gCAAgC,CA0C5C"}
1
+ {"version":3,"file":"MJConversationDetailEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJConversationDetailEntityServer.server.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAE,UAAU,EAAqB,MAAM,sBAAsB,CAAC;AACrE,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,kCAAkC,EAAE,MAAM,+BAA+B,CAAC;AAEnF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEI,IAAM,gCAAgC,GAAtC,MAAM,gCAAiC,SAAQ,kCAAkC;IACpF;;;OAGG;IACM,KAAK,CAAC,IAAI,CAAC,OAA2B;QAC3C,IAAI,IAAI,CAAC,gCAAgC,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC,sBAAsB,GAAG,IAAI,CAAC;QACvC,CAAC;QAED,OAAO,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/B,CAAC;IAED;;;;;;;;;;;OAWG;IACI,gCAAgC;QACnC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAChB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,KAAK,IAAI,YAAY,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC9E,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,KAAK,aAAa,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,MAAM,WAAW,GAAG,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,WAAW,EAAE,KAAK,EAAE,CAAC;YACrB,OAAO,KAAK,CAAC;QACjB,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ,CAAA;AA1CY,gCAAgC;IAD5C,aAAa,CAAC,UAAU,EAAE,0BAA0B,CAAC;GACzC,gCAAgC,CA0C5C"}
@@ -1,4 +1,4 @@
1
- import { EntityInfo, EntitySaveOptions } from "@memberjunction/core";
1
+ import { EntityInfo, EntitySaveOptions, IEntityDataProvider } from "@memberjunction/core";
2
2
  import { MJRemoteOperationEntity, MJRemoteOperationEntity_RemoteOperationLibrary } from "@memberjunction/core-entities";
3
3
  /** The shape `GenerateCode()` returns — the AI-authored body + its explanation + the libraries it declared. */
4
4
  interface RemoteOperationGeneratedCode {
@@ -25,7 +25,7 @@ export declare class MJRemoteOperationEntityServer extends MJRemoteOperationEnti
25
25
  * is unchanged. Reset to false after every save, exactly like Action.ForceCodeGeneration.
26
26
  */
27
27
  ForceCodeGeneration: boolean;
28
- constructor(Entity: EntityInfo);
28
+ constructor(Entity: EntityInfo, Provider?: IEntityDataProvider | null);
29
29
  /**
30
30
  * Save override: (re)generates the AI body when needed, within a transaction so a generation/save failure
31
31
  * leaves no partial state.
@@ -1 +1 @@
1
- {"version":3,"file":"MJRemoteOperationEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJRemoteOperationEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,UAAU,EAAE,iBAAiB,EAAsB,MAAM,sBAAsB,CAAC;AAC3H,OAAO,EAAE,uBAAuB,EAAE,8CAA8C,EAAE,MAAM,+BAA+B,CAAC;AAOxH,+GAA+G;AAC/G,UAAU,4BAA4B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,8CAA8C,EAAE,CAAC;CAC/D;AAED;;;;;;;;;;;GAWG;AACH,qBACa,6BAA8B,SAAQ,uBAAuB;IACtE;;;OAGG;IACI,mBAAmB,UAAS;gBAEvB,MAAM,EAAE,UAAU;IAS9B;;;OAGG;IACmB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCzE,kIAAkI;IAClI,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAQrC;;;;OAIG;IACU,YAAY,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAqClE,yFAAyF;IACzF,SAAS,CAAC,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IActD,2HAA2H;IAC3H,SAAS,CAAC,wBAAwB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CAOtI"}
1
+ {"version":3,"file":"MJRemoteOperationEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJRemoteOperationEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoC,UAAU,EAAE,iBAAiB,EAAE,mBAAmB,EAAsB,MAAM,sBAAsB,CAAC;AAChJ,OAAO,EAAE,uBAAuB,EAAE,8CAA8C,EAAE,MAAM,+BAA+B,CAAC;AAOxH,+GAA+G;AAC/G,UAAU,4BAA4B;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,8CAA8C,EAAE,CAAC;CAC/D;AAED;;;;;;;;;;;GAWG;AACH,qBACa,6BAA8B,SAAQ,uBAAuB;IACtE;;;OAGG;IACI,mBAAmB,UAAS;gBAEvB,MAAM,EAAE,UAAU,EAAE,QAAQ,GAAE,mBAAmB,GAAG,IAAW;IAW3E;;;OAGG;IACmB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiCzE,kIAAkI;IAClI,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAQrC;;;;OAIG;IACU,YAAY,IAAI,OAAO,CAAC,4BAA4B,CAAC;IAqClE,yFAAyF;IACzF,SAAS,CAAC,iBAAiB,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IActD,2HAA2H;IAC3H,SAAS,CAAC,wBAAwB,IAAI,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC;CAOtI"}
@@ -27,14 +27,16 @@ import { DocumentationEngine } from "@memberjunction/doc-utils";
27
27
  * there is no junction CRUD — `this.LibrariesObject = ...` is the whole "manage libraries" step.
28
28
  */
29
29
  let MJRemoteOperationEntityServer = class MJRemoteOperationEntityServer extends MJRemoteOperationEntity {
30
- constructor(Entity) {
31
- super(Entity);
30
+ constructor(Entity, Provider = null) {
31
+ super(Entity, Provider);
32
32
  /**
33
33
  * Transient (never persisted) flag — set it before `Save()` to force regeneration even when `Description`
34
34
  * is unchanged. Reset to false after every save, exactly like Action.ForceCodeGeneration.
35
35
  */
36
36
  this.ForceCodeGeneration = false;
37
- // Constructor runs before the entity provider is wired, so the global Metadata is the only check available.
37
+ // ProviderType is process-wide; this only asserts the server subclass is not
38
+ // loaded in a client bundle. GetEntityObject rebinds the instance provider
39
+ // after construct even if a future edit drops the second argument again.
38
40
  const md = new Metadata(); // global-provider-ok: constructor-time provider-type check
39
41
  if (md.ProviderType !== 'Database') {
40
42
  throw new Error('MJRemoteOperationEntityServer is server/database-only — remove @memberjunction/core-entities-server from client applications.');
@@ -142,7 +144,7 @@ let MJRemoteOperationEntityServer = class MJRemoteOperationEntityServer extends
142
144
  MJRemoteOperationEntityServer = __decorate([
143
145
  RegisterClass(BaseEntity, 'MJ: Remote Operations') // high priority — ensure this server subclass wins over the generated entity
144
146
  ,
145
- __metadata("design:paramtypes", [EntityInfo])
147
+ __metadata("design:paramtypes", [EntityInfo, Object])
146
148
  ], MJRemoteOperationEntityServer);
147
149
  export { MJRemoteOperationEntityServer };
148
150
  //# sourceMappingURL=MJRemoteOperationEntityServer.server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MJRemoteOperationEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJRemoteOperationEntityServer.server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MJRemoteOperationEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJRemoteOperationEntityServer.server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ import { EntityDeleteOptions, EntitySaveOptions, ValidationResult } from '@memberjunction/core';
2
+ import { MJRoleEntity } from '@memberjunction/core-entities';
3
+ /**
4
+ * Server-side `MJ: Roles` entity enforcing MJ's role-elevation invariant (issue #4282).
5
+ *
6
+ * WHY THIS EXISTS AT ALL. Roles are the containers the platform's authority is expressed in, and
7
+ * writing one was unguarded: verified against a live database, the `Developer` and `Integration`
8
+ * roles hold unfiltered `CanCreate`/`CanUpdate`/`CanDelete` on `MJ: Roles`, all three `Allow*API`
9
+ * flags are true on the entity, and no server-side subclass existed. Reproduced end to end before
10
+ * this class was written: a caller whose `Type` is `'User'` created a brand-new role and `Save()`
11
+ * returned `true`. Its sibling `MJUserRoleEntityServer` guards who may be GIVEN a role; this class
12
+ * guards what the roles themselves are.
13
+ *
14
+ * THE INVARIANT: **a non-Owner may not create, change or delete a role.** All three, not a subset
15
+ * of fields — because every field on this entity is authority-bearing:
16
+ *
17
+ * - `Name` is what `SyncRolesUsersResolver` matches roles by (case-insensitively, in
18
+ * `DoSyncRoles` / `SyncUserRoles`) and what Explorer's `resolveRoleByIDOrName` resolves against,
19
+ * so renaming a role redirects which role a sync or a deep link lands on.
20
+ * - `DirectoryID` is the role's identifier in the external directory used for authentication —
21
+ * the mapping from an IdP group to platform authority.
22
+ * - `SQLName` is what CodeGen emits `GRANT` statements against, so it decides which DATABASE role
23
+ * receives object-level rights on every regenerated view and procedure.
24
+ * - Creation matters even though a new role confers nothing on its own: it is step one of
25
+ * "mint a role, grant it to myself, then give it permissions", and refusing it removes the
26
+ * step this entity owns.
27
+ * - Deletion destroys every assignment under the role at once, which is authority removal for
28
+ * everyone who held it, not just the caller.
29
+ *
30
+ * Freezing only the authority-bearing fields was considered and declined: it would leave the list
31
+ * above to be re-derived every time a field is added to this entity, and `Description` — the only
32
+ * field it would keep writable — is not worth a rule with that failure mode. This shape also
33
+ * matches what issue #4260 chose for `MJ: Users` create/delete: refuse the operation, not a column.
34
+ *
35
+ * WHY HERE RATHER THAN IN A RESOLVER, and why `Save()` and `Delete()` are overridden alongside
36
+ * `Validate()`: see `MJUserEntityServer` for the full argument. `Validate()` runs inside
37
+ * `BaseEntity.Save()`, so the rule holds on every write path; `EntitySaveOptions.ReplayOnly`
38
+ * force-passes validation without calling `Validate()` (`baseEntity.ts:3725`) while still writing,
39
+ * and `Delete()` never consults `Validate()` at all.
40
+ *
41
+ * WHAT THIS BREAKS, deliberately. Explorer's role-management screen (`role-management.component.ts`
42
+ * and its `role-dialog`) has no Owner gate of its own, so a non-Owner administrator who reaches it
43
+ * today can create, rename and delete roles; after this change those saves are refused with the
44
+ * messages below. That is the same trade-off issue #4260 accepted for the user-management screen,
45
+ * and it is called out in this branch's changeset as an upgrade note. `SyncRoles` and
46
+ * `SyncRolesAndUsers` are unaffected on a default install: both carry `@RequireSystemUser()`, whose
47
+ * `getSystemUser()` resolves the seeded `Type='Owner'` system user. A deployment whose system user
48
+ * is NOT an Owner will see them fail closed — loudly, at the save.
49
+ *
50
+ * Pure: reads only the caller. No `RunView`, no provider, no engine, no I/O.
51
+ */
52
+ export declare class MJRoleEntityServer extends MJRoleEntity {
53
+ private static readonly CREATE_REFUSED;
54
+ private static readonly UPDATE_REFUSED;
55
+ private static readonly DELETE_REFUSED;
56
+ Validate(): ValidationResult;
57
+ /**
58
+ * Closes the `ReplayOnly` bypass. Without this, a non-Owner could write this entity through the
59
+ * one option that skips `Validate()` — see the class docstring and `MJUserEntityServer.Save()`
60
+ * for why refusing outright beats re-running the rule in a second place.
61
+ */
62
+ Save(options?: EntitySaveOptions): Promise<boolean>;
63
+ /**
64
+ * Reports the refusal the way `MJUserEntityServer.Delete` does — a `BaseEntityResult` on the
65
+ * result history so `LatestResult.CompleteMessage` carries the reason, `Delete()` returning
66
+ * `false` on a logical rejection rather than throwing (the CLAUDE.md Save/Delete contract).
67
+ */
68
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
69
+ /** Records a logical refusal on the result history so `LatestResult.CompleteMessage` carries the reason. */
70
+ private refuse;
71
+ /**
72
+ * True when the caller is an Owner (or when there is no caller to evaluate). Identical contract,
73
+ * normalization and `Save()`-vs-`Delete()` reachability asymmetry as
74
+ * `MJUserRoleEntityServer.callerIsOwner()` — see that method's docstring.
75
+ */
76
+ private callerIsOwner;
77
+ }
78
+ //# sourceMappingURL=MJRoleEntityServer.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJRoleEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJRoleEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,mBAAmB,EAAE,iBAAiB,EAAsD,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAElL,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBACa,kBAAmB,SAAQ,YAAY;IAChD,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAE4B;IAElE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CAG8C;IAEpF,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,cAAc,CACuD;IAE7E,QAAQ,IAAI,gBAAgB;IAkB5C;;;;OAIG;IACmB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAWzE;;;;OAIG;IACmB,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAO7E,4GAA4G;IAC5G,OAAO,CAAC,MAAM;IAWd;;;;OAIG;IACH,OAAO,CAAC,aAAa;CAOxB"}
@@ -0,0 +1,131 @@
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 MJRoleEntityServer_1;
8
+ import { BaseEntity, BaseEntityResult, ValidationErrorInfo, ValidationErrorType } from '@memberjunction/core';
9
+ import { RegisterClass } from '@memberjunction/global';
10
+ import { MJRoleEntity } from '@memberjunction/core-entities';
11
+ /**
12
+ * Server-side `MJ: Roles` entity enforcing MJ's role-elevation invariant (issue #4282).
13
+ *
14
+ * WHY THIS EXISTS AT ALL. Roles are the containers the platform's authority is expressed in, and
15
+ * writing one was unguarded: verified against a live database, the `Developer` and `Integration`
16
+ * roles hold unfiltered `CanCreate`/`CanUpdate`/`CanDelete` on `MJ: Roles`, all three `Allow*API`
17
+ * flags are true on the entity, and no server-side subclass existed. Reproduced end to end before
18
+ * this class was written: a caller whose `Type` is `'User'` created a brand-new role and `Save()`
19
+ * returned `true`. Its sibling `MJUserRoleEntityServer` guards who may be GIVEN a role; this class
20
+ * guards what the roles themselves are.
21
+ *
22
+ * THE INVARIANT: **a non-Owner may not create, change or delete a role.** All three, not a subset
23
+ * of fields — because every field on this entity is authority-bearing:
24
+ *
25
+ * - `Name` is what `SyncRolesUsersResolver` matches roles by (case-insensitively, in
26
+ * `DoSyncRoles` / `SyncUserRoles`) and what Explorer's `resolveRoleByIDOrName` resolves against,
27
+ * so renaming a role redirects which role a sync or a deep link lands on.
28
+ * - `DirectoryID` is the role's identifier in the external directory used for authentication —
29
+ * the mapping from an IdP group to platform authority.
30
+ * - `SQLName` is what CodeGen emits `GRANT` statements against, so it decides which DATABASE role
31
+ * receives object-level rights on every regenerated view and procedure.
32
+ * - Creation matters even though a new role confers nothing on its own: it is step one of
33
+ * "mint a role, grant it to myself, then give it permissions", and refusing it removes the
34
+ * step this entity owns.
35
+ * - Deletion destroys every assignment under the role at once, which is authority removal for
36
+ * everyone who held it, not just the caller.
37
+ *
38
+ * Freezing only the authority-bearing fields was considered and declined: it would leave the list
39
+ * above to be re-derived every time a field is added to this entity, and `Description` — the only
40
+ * field it would keep writable — is not worth a rule with that failure mode. This shape also
41
+ * matches what issue #4260 chose for `MJ: Users` create/delete: refuse the operation, not a column.
42
+ *
43
+ * WHY HERE RATHER THAN IN A RESOLVER, and why `Save()` and `Delete()` are overridden alongside
44
+ * `Validate()`: see `MJUserEntityServer` for the full argument. `Validate()` runs inside
45
+ * `BaseEntity.Save()`, so the rule holds on every write path; `EntitySaveOptions.ReplayOnly`
46
+ * force-passes validation without calling `Validate()` (`baseEntity.ts:3725`) while still writing,
47
+ * and `Delete()` never consults `Validate()` at all.
48
+ *
49
+ * WHAT THIS BREAKS, deliberately. Explorer's role-management screen (`role-management.component.ts`
50
+ * and its `role-dialog`) has no Owner gate of its own, so a non-Owner administrator who reaches it
51
+ * today can create, rename and delete roles; after this change those saves are refused with the
52
+ * messages below. That is the same trade-off issue #4260 accepted for the user-management screen,
53
+ * and it is called out in this branch's changeset as an upgrade note. `SyncRoles` and
54
+ * `SyncRolesAndUsers` are unaffected on a default install: both carry `@RequireSystemUser()`, whose
55
+ * `getSystemUser()` resolves the seeded `Type='Owner'` system user. A deployment whose system user
56
+ * is NOT an Owner will see them fail closed — loudly, at the save.
57
+ *
58
+ * Pure: reads only the caller. No `RunView`, no provider, no engine, no I/O.
59
+ */
60
+ let MJRoleEntityServer = class MJRoleEntityServer extends MJRoleEntity {
61
+ static { MJRoleEntityServer_1 = this; }
62
+ static { this.CREATE_REFUSED = 'Only an Owner may create a role. Roles are the containers MJ expresses authority in, so creating one ' +
63
+ 'is the first step in granting authority that was not given.'; }
64
+ static { this.UPDATE_REFUSED = 'Only an Owner may change a role. A role\'s Name is what user/role synchronization matches on, its ' +
65
+ 'DirectoryID maps an external directory group to this role, and its SQLName decides which database role ' +
66
+ 'CodeGen grants object rights to — so editing any of them redirects authority.'; }
67
+ static { this.DELETE_REFUSED = 'Only an Owner may delete a role. Deleting one removes it from every user who holds it.'; }
68
+ Validate() {
69
+ const result = super.Validate();
70
+ if (!this.callerIsOwner()) {
71
+ // Attributed to 'ID' rather than to a value-bearing field: this is a whole-record
72
+ // refusal, not a complaint about any one value, and 'ID' is the field that identifies
73
+ // WHICH record is being refused. Same convention and reasoning as
74
+ // `MJUserEntityServer.validateCreateRefused`.
75
+ result.Errors.push(new ValidationErrorInfo('ID', this.IsSaved ? MJRoleEntityServer_1.UPDATE_REFUSED : MJRoleEntityServer_1.CREATE_REFUSED, this.ID, ValidationErrorType.Failure));
76
+ }
77
+ result.Success = result.Success && result.Errors.length === 0;
78
+ return result;
79
+ }
80
+ /**
81
+ * Closes the `ReplayOnly` bypass. Without this, a non-Owner could write this entity through the
82
+ * one option that skips `Validate()` — see the class docstring and `MJUserEntityServer.Save()`
83
+ * for why refusing outright beats re-running the rule in a second place.
84
+ */
85
+ async Save(options) {
86
+ if (options?.ReplayOnly && !this.callerIsOwner()) {
87
+ return this.refuse(this.IsSaved ? 'update' : 'create', 'Only an Owner may perform a ReplayOnly save on a role. ReplayOnly bypasses validation, which is ' +
88
+ 'where this entity\'s role-elevation invariants are enforced.');
89
+ }
90
+ return super.Save(options);
91
+ }
92
+ /**
93
+ * Reports the refusal the way `MJUserEntityServer.Delete` does — a `BaseEntityResult` on the
94
+ * result history so `LatestResult.CompleteMessage` carries the reason, `Delete()` returning
95
+ * `false` on a logical rejection rather than throwing (the CLAUDE.md Save/Delete contract).
96
+ */
97
+ async Delete(options) {
98
+ if (!this.callerIsOwner()) {
99
+ return this.refuse('delete', MJRoleEntityServer_1.DELETE_REFUSED);
100
+ }
101
+ return super.Delete(options);
102
+ }
103
+ /** Records a logical refusal on the result history so `LatestResult.CompleteMessage` carries the reason. */
104
+ refuse(type, message) {
105
+ const result = new BaseEntityResult();
106
+ result.Success = false;
107
+ result.Type = type;
108
+ result.Message = message;
109
+ result.StartedAt = new Date();
110
+ result.EndedAt = new Date();
111
+ this.RegisterResultHistoryEntry(result);
112
+ return false;
113
+ }
114
+ /**
115
+ * True when the caller is an Owner (or when there is no caller to evaluate). Identical contract,
116
+ * normalization and `Save()`-vs-`Delete()` reachability asymmetry as
117
+ * `MJUserRoleEntityServer.callerIsOwner()` — see that method's docstring.
118
+ */
119
+ callerIsOwner() {
120
+ const caller = this.ActiveUser;
121
+ if (!caller) {
122
+ return true;
123
+ }
124
+ return caller.Type?.trim().toLowerCase() === 'owner';
125
+ }
126
+ };
127
+ MJRoleEntityServer = MJRoleEntityServer_1 = __decorate([
128
+ RegisterClass(BaseEntity, 'MJ: Roles')
129
+ ], MJRoleEntityServer);
130
+ export { MJRoleEntityServer };
131
+ //# sourceMappingURL=MJRoleEntityServer.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJRoleEntityServer.server.js","sourceRoot":"","sources":["../../src/custom/MJRoleEntityServer.server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,243 @@
1
+ import { EntityDeleteOptions, EntitySaveOptions, ValidationResult } from '@memberjunction/core';
2
+ import { MJUserEntity } from '@memberjunction/core-entities';
3
+ /**
4
+ * Server-side `MJ: Users` entity enforcing MJ's privilege-elevation invariant (issue #4260).
5
+ *
6
+ * WHY THIS EXISTS AT ALL. `User.Type` is the column every Owner check in the platform reads —
7
+ * `SqlLoggingConfigResolver`, magic-link `canIssueInvites`, the backup-system-user fallback in
8
+ * MJServer's bootstrap. Writing it is therefore equivalent to granting yourself the platform's
9
+ * superuser level. Nothing below this class stops that: on the baseline seed the `Developer` and
10
+ * `Integration` roles hold unfiltered `CanCreate`/`CanUpdate`/`CanDelete` on `MJ: Users` (verified
11
+ * against a live database, not just the seed), `AllowUpdateAPI`/`AllowDeleteAPI` are true on the
12
+ * entity, `AllowUpdateAPI` is also true on both the `Type` and `Name` fields, `Name` has no unique
13
+ * index, and CodeGen has already issued `GRANT EXECUTE` on the relevant stored procedures to those
14
+ * roles' DB roles.
15
+ *
16
+ * WHY NOT ROW-LEVEL SECURITY. MJ's only field-adjacent access control is `RowLevelSecurityFilter`,
17
+ * and an "own row only" update filter does NOT close this: scoping the update to the caller's own
18
+ * row still permits setting one's OWN `Type` to `'Owner'`. There is no per-role FIELD permission in
19
+ * MJ, and setting `EntityField.AllowUpdateAPI = 0` on `Type` would block Owners too, breaking every
20
+ * legitimate admin path. An invariant in the save/delete path is the only mechanism that expresses
21
+ * the actual rule.
22
+ *
23
+ * WHY HERE RATHER THAN IN A RESOLVER. `Validate()` runs inside `BaseEntity.Save()` and this class
24
+ * also overrides `Save()` and `Delete()`, so all five hold on every write path — GraphQL resolvers,
25
+ * Remote Operations, the Create/Update/Delete Record actions, metadata sync, one-off scripts — and
26
+ * for any role a deployment invents, not just the two seeded ones. A resolver-level check would
27
+ * cover one door in a building with several.
28
+ *
29
+ * The `Save()` override is what makes that sentence literally true rather than nearly true.
30
+ * `Validate()` alone does NOT cover every write path: `BaseEntity.Save()` force-passes validation
31
+ * without calling `Validate()` when `EntitySaveOptions.ReplayOnly` is set (`baseEntity.ts:3725`),
32
+ * and `ReplayOnly` does not suppress the write. Invariants 1-4 were therefore skippable by an
33
+ * option, while invariant 5 was not — `Delete()` being an override. See `Save()` below for the
34
+ * reachability analysis and why the fix refuses rather than re-validates.
35
+ *
36
+ * THE INVARIANTS, for a caller whose `Type` is not `'Owner'`:
37
+ *
38
+ * 1. **Creating a `MJ: Users` row at all is refused.** This is stricter than "may not create an
39
+ * Owner": there are FOUR automated creators of this entity — `NewUserBase.createNewUser`
40
+ * (`MJServer/src/auth/newUsers.ts`), `MagicLinkService`'s provisioning path
41
+ * (`MJServer/src/auth/magicLink/MagicLinkService.ts`), and `CreateNewUserBase.createNewUser`
42
+ * (`CodeGenLib/src/Misc/createNewUser.ts:33`, a CLI provisioning tool that sets `Type='Owner'`
43
+ * unconditionally at `:39` — already refused for a non-Owner caller before this round,
44
+ * regardless of the analysis below). The first two run as `ResolveConfiguredPrincipal(...)`,
45
+ * whose ladder is: rung 1 matches the configured string against `User.Name`, rung 2 against
46
+ * `User.Email` — NEITHER rung filters by `Type` (`MJServer/src/auth/principals.ts:134,141`) —
47
+ * and only rungs 3-4 (System-by-ID, then lowest-ID-among-ACTIVE-Owners) guarantee an Owner.
48
+ * So "no legitimate path creates a user row as a non-Owner" is NOT unconditional: it holds
49
+ * only while a deployment's `contextUserForNewUserCreation` / `contextUserForProvisioning`
50
+ * names an Owner-type user's `Name` or `Email` (the shipped default, `not.set@nowhere.com`,
51
+ * resolves by Email to the seeded Owner, so default installs are unaffected). A deployment
52
+ * that instead points either setting at a non-Owner user will have JWT auto-provisioning and
53
+ * magic-link provisioning fail CLOSED at `Save()` after this change — loudly, not silently —
54
+ * rather than continuing to create rows as that non-Owner; see the changeset for the upgrade
55
+ * note. Separately, Explorer's user-management UI has NO Owner gate today (no such guard
56
+ * exists in `user-management.component.ts` or its module), so a Developer-role non-Owner
57
+ * reaches it in practice and will now receive this same create/delete refusal there — a real
58
+ * consequence for such deployments, not a hypothetical one.
59
+ * A FOURTH creator exists and is not config-driven: `SyncRolesUsersResolver.AddNewUsers`
60
+ * (`MJServer/src/resolvers/SyncRolesUsersResolver.ts:343`), whose sibling `UpdateExistingUsers`
61
+ * also sets `Name` AND `Type` unconditionally on every synced row and whose `DeleteSingleUser`
62
+ * deletes. All three carry `@RequireSystemUser()`, and `getSystemUser()` resolves the seeded
63
+ * `Type='Owner'` system user, so the guard exempts them on a default install — but a deployment
64
+ * whose system user is NOT an Owner will see that sync path fail closed too, for the same
65
+ * reason as the config-driven pair above. Note also that `DeleteSingleUser` reads a `false`
66
+ * from `Delete()` as an FK-constraint condition and downgrades to a soft delete; invariant 5
67
+ * gives that same `false` a second meaning (non-Owner caller), which that call site does not
68
+ * distinguish.
69
+ * Refusing only `Type='Owner'` on create is NOT enough on its own: `Name` has no unique index
70
+ * and both seeded non-Owner roles hold `CanCreate`, so a non-Owner could otherwise repeatedly
71
+ * `Create` rows named to match the configured principal string until one sorts below the real
72
+ * system user by ID — the exact principal-redirection invariant 4 (below) exists to prevent,
73
+ * just reached through INSERT instead of UPDATE.
74
+ * 2. `Type` may not be changed on an EXISTING row. It is a two-value CHECK column
75
+ * (`'User' | 'Owner'`), so any change by a non-Owner is either self-promotion or demoting
76
+ * somebody else. (Creation is already covered by invariant 1, so this only needs to consider
77
+ * updates.)
78
+ * 3. The row must be the caller's own, compared against the PRE-SAVE `ID`. `ID` is a primary-key
79
+ * field — `EntityFieldInfo.ReadOnly` is `true` for `IsPrimaryKey`, and `EntityField.Value`'s
80
+ * setter silently ignores writes to a `ReadOnly` field after the record's initial hydration —
81
+ * so on a genuinely LOADED row `this.ID` is not reassignable through ordinary means. The
82
+ * pre-save value still matters for a narrower reason: it ties this guard to the row identity
83
+ * established the last time the object was actually loaded/hydrated from the database, which
84
+ * is what `ResolverBase.UpdateRecord` does first for any entity with `TrackRecordChanges=1`
85
+ * (as `MJ: Users` has) — rather than trusting whatever the in-memory object merely arrived
86
+ * holding. If that pre-save identity cannot be established at all, the guard fails CLOSED
87
+ * (refuses the save) instead of silently permitting it — see `validateOwnRowOnly`.
88
+ * 4. `Name` may not be changed on an existing row. Invariants 2-3 do NOT cover this — renaming
89
+ * yourself is editing your own row. `resolvePrincipalFrom` (MJServer `auth/principals.ts`)
90
+ * resolves `contextUserForNewUserCreation` / `contextUserForProvisioning` /
91
+ * `contextUserForLookup` against `User.Name` FIRST, breaking ties by lowest ID, so a user
92
+ * who can rename themselves to the configured string and who sorts below the real system
93
+ * user becomes the principal the server acts as. That ordering is deliberate (backward
94
+ * compatibility) and is only sound while `Name` is not writable by untrusted parties; this
95
+ * invariant is what makes that true. `Name` is the login identifier — auto-provisioning sets
96
+ * `Name = email` — while `FirstName` / `LastName` / `Title` are the display fields and stay
97
+ * freely editable.
98
+ * `Email` is the ladder's OTHER rung (`principals.ts:141`) with the IDENTICAL lack of a `Type`
99
+ * filter, and is deliberately left mutable here — not overlooked. Two things narrow it: (a)
100
+ * `MJ: Users.Email` carries the database's `UQ_User_Email` unique constraint, so redirecting
101
+ * the Email rung to your own row requires the configured candidate to match NO active user at
102
+ * all — already the misconfiguration case this file's docs (and `resolvePrincipalFrom`'s own
103
+ * per-redeem logging) already surface loudly, not a quiet success path; and (b) even granting
104
+ * that misconfiguration, invariants 1 and 3 mean the payoff is no longer elevation: whatever
105
+ * row a provisioning path resolves its principal to, THIS guard still checks that resolved
106
+ * principal's actual `Type` before permitting the write it is attempting, so a non-Owner who
107
+ * gets themselves matched by the Email rung still cannot create or promote anything through
108
+ * it — the provisioning operation that would have run as them instead fails CLOSED. Freezing
109
+ * `Email` too would add friction to an already-narrow, already-loud misconfiguration path
110
+ * without closing any route that is still open.
111
+ * 5. **Deleting a `MJ: Users` row at all is refused** (see the `Delete()` override below). MJ
112
+ * deactivates users via `IsActive`; it does not delete them. An unguarded delete would let a
113
+ * non-Owner remove ANY account — Owners included, which destroys the very accounts every
114
+ * exemption above depends on.
115
+ *
116
+ * Owner-type callers are exempt from all five — CONDITIONALLY on the deployment's own configuration
117
+ * keeping `contextUserForNewUserCreation` / `contextUserForProvisioning` pointed at an Owner (see
118
+ * invariant 1 above for why that is not automatic). Provided it is, this keeps admin user management
119
+ * working, and it keeps auto-provisioning working — `NewUserBase.createNewUser` runs as
120
+ * `contextUserForNewUserCreation`, which resolves to the seeded system user (`Type='Owner'`) under
121
+ * the shipped default.
122
+ * A caller-less save (no `ActiveUser` at all — e.g. a system/CLI path running under a bound provider
123
+ * default) is likewise treated as exempt, though for `Save()` this is effectively decorative:
124
+ * `BaseEntity.CheckPermissions` already throws on a falsy `ActiveUser` and runs BEFORE `Validate()`
125
+ * inside `Save()`, so in production a caller-less `Save()` never reaches this guard's `Validate()`
126
+ * body at all — see the "no caller" test for the exact call ordering. `Delete()`'s sequencing is the
127
+ * OPPOSITE and the "no caller ⇒ exempt" default IS load-bearing there — see `callerIsOwner()`'s
128
+ * docstring.
129
+ *
130
+ * Pure: reads only this record's own field state and the caller. No `RunView`, no provider, no
131
+ * engine, no I/O — so it costs nothing per save/delete and is unit-testable without a database.
132
+ */
133
+ export declare class MJUserEntityServer extends MJUserEntity {
134
+ Validate(): ValidationResult;
135
+ /**
136
+ * Refuses a `ReplayOnly` save by a non-Owner caller.
137
+ *
138
+ * WHY THIS OVERRIDE EXISTS. Invariants 1-4 are enforced in `Validate()`, and `Validate()` is the
139
+ * one enforcement point `BaseEntity.Save()` can be told to skip: under `EntitySaveOptions.ReplayOnly`
140
+ * it force-passes validation WITHOUT calling `Validate()` at all (`baseEntity.ts:3725`), and
141
+ * `ReplayOnly` does NOT suppress the write — the provider only uses it to bypass the
142
+ * `AllowUpdateAPI`/`AllowCreateAPI` gates before building and executing the SQL
143
+ * (`databaseProviderBase.ts:1436-1443`). So a `ReplayOnly` save skipped all four Save-side
144
+ * invariants while invariant 5 stayed enforced, because `Delete()` is an override and an
145
+ * override cannot be switched off. This restores the symmetry: now neither half depends on the
146
+ * caller's options.
147
+ *
148
+ * NOT CURRENTLY REACHABLE BY AN UNTRUSTED CALLER, and this is deliberately belt-and-braces
149
+ * rather than a live hole. Every wire path that accepts `ReplayOnly` was enumerated: the
150
+ * GraphQL `options___`/`DeleteOptionsInput` input exists only on the DELETE mutation (create and
151
+ * update carry no options input, and `ResolverBase.CreateRecord`/`UpdateRecord` call `Save()`
152
+ * with none); REST's `EntityCRUDHandler` does accept it, but calls `entity.Validate()`
153
+ * explicitly before `Save()`, so the guard still runs there; and `graphQLSystemUserClient`
154
+ * requires the system API key, i.e. a caller who is already superuser. The point is that the
155
+ * class docstring's "holds on EVERY write path" is a promise a future wire path forwarding save
156
+ * options would otherwise quietly break — a guard whose protection is one option away from off
157
+ * is not the guard this file claims to be.
158
+ *
159
+ * WHY REFUSE RATHER THAN RE-RUN THE INVARIANTS. Re-running invariants 1-4 here would duplicate
160
+ * `Validate()`'s logic in a second place that must then be kept in step with it — the exact
161
+ * duplicated-decision this repo's design rules call out. Refusing outright is smaller and
162
+ * strictly safer: `ReplayOnly` is a replication/replay facility for trusted sync paths, and a
163
+ * non-Owner has no legitimate reason to replay writes onto the user table. Owners are exempt,
164
+ * so replication and admin paths that run as an Owner are unaffected.
165
+ */
166
+ Save(options?: EntitySaveOptions): Promise<boolean>;
167
+ private refuseSave;
168
+ /**
169
+ * Refuses deletion of any `MJ: Users` row by a non-Owner caller. See invariant 5 above for why:
170
+ * MJ deactivates users via `IsActive` rather than deleting them, and an unguarded delete would
171
+ * let a non-Owner remove any account, including Owner accounts this class's other exemptions
172
+ * depend on.
173
+ *
174
+ * Reports the refusal the way `MJListEntityServer.Delete` does for its own row-level DELETE
175
+ * authorization check — a `BaseEntityResult` on the result history so `LatestResult.CompleteMessage`
176
+ * carries the reason (`Delete()` returns `false` on a logical rejection rather than throwing; see
177
+ * the CLAUDE.md Save/Delete error-handling contract) — rather than `MJUserRoutineEntityServer`'s
178
+ * `Delete()` override, which is FK-cleanup ordering, not an authorization decision, and reports
179
+ * nothing beyond a bare `false`.
180
+ */
181
+ Delete(options?: EntityDeleteOptions): Promise<boolean>;
182
+ private refuseDelete;
183
+ /**
184
+ * Invariant 1 — a non-Owner may not create a `MJ: Users` row at all. See the class docstring
185
+ * for why this is the correct scope (not merely "may not create an Owner"): `Name` has no
186
+ * unique index, so restricting only `Type='Owner'` on create would leave the INSERT path open
187
+ * to the same principal-redirection invariant 4 blocks on UPDATE.
188
+ */
189
+ private validateCreateRefused;
190
+ /**
191
+ * Invariant 2 — a non-Owner may not change `Type` on an EXISTING row (self-promotion, or
192
+ * demoting somebody else). Non-Owner creation is refused entirely by `validateCreateRefused`,
193
+ * so this method only needs to consider updates — `Validate()` only calls it on that branch.
194
+ */
195
+ private validateNoTypeChange;
196
+ /**
197
+ * Invariant 3 — a non-Owner may only modify their own user row, compared against the PRE-SAVE
198
+ * `ID` (see the class docstring for why the pre-save value is the meaningful comparison here).
199
+ *
200
+ * Fails CLOSED: if the pre-save identity cannot be established at all, the save is refused
201
+ * rather than silently permitted. This is currently unreachable in production only because
202
+ * `MJ: Users` has `TrackRecordChanges=1`, which forces `ResolverBase.UpdateRecord` to genuinely
203
+ * load the row from the database before applying edits — but that is an unrelated entity flag,
204
+ * not a guarantee this class should assume will always hold.
205
+ */
206
+ private validateOwnRowOnly;
207
+ /**
208
+ * Invariant 4 — a non-Owner may not change `Name` on an existing row.
209
+ *
210
+ * `Name` is the column the context-user ladder resolves against first, so it is effectively a
211
+ * capability name, not a display name. Non-Owner creation (where auto-provisioning legitimately
212
+ * sets `Name = email`) is already refused entirely by `validateCreateRefused`, so this method
213
+ * only runs on updates.
214
+ *
215
+ * `Email` is deliberately NOT frozen alongside `Name` — see the class docstring's invariant 4
216
+ * paragraph for why the ladder's other rung doesn't need the same treatment.
217
+ */
218
+ private validateNameImmutable;
219
+ /**
220
+ * True when the caller is an Owner (or when there is no caller to evaluate — the guard has
221
+ * nothing to compare against).
222
+ *
223
+ * The "no caller ⇒ exempt" default has DIFFERENT reachability for `Save()` vs. `Delete()`:
224
+ * - `Save()`: `BaseEntity.CheckPermissions` throws on a falsy `ActiveUser`
225
+ * (`baseEntity.ts:4003-4005`) and runs at `baseEntity.ts:3702`, BEFORE `Validate()` is
226
+ * called at `baseEntity.ts:3730` — so a caller-less `Save()` never reaches this method at
227
+ * all in production. The default is effectively decorative there.
228
+ * - `Delete()`: the sequencing is the OPPOSITE. THIS class's `Delete()` override calls
229
+ * `callerIsOwner()` as its very first statement, before `super.Delete()` is ever invoked —
230
+ * `CheckPermissions` only runs later, INSIDE `super.Delete()` (`baseEntity.ts:4612`). So a
231
+ * caller-less `Delete()` call DOES reach this method first, and the "no caller ⇒ exempt"
232
+ * default here is load-bearing: it lets the call proceed into `super.Delete()`, where
233
+ * `CheckPermissions` is the thing that actually refuses it. If this default were flipped to
234
+ * "no caller ⇒ refuse", a caller-less delete would be refused by `refuseDelete()` instead —
235
+ * same ultimate outcome (refused), different refusal mechanism and message.
236
+ *
237
+ * `Type` is an `NCHAR` column, so it arrives space-padded; casing is normalized for the same
238
+ * reason `principals.ts` does. Reads `ActiveUser` rather than `ContextCurrentUser` directly so
239
+ * a per-request provider's `CurrentUser` is honored on multi-provider servers.
240
+ */
241
+ private callerIsOwner;
242
+ }
243
+ //# sourceMappingURL=MJUserEntityServer.server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MJUserEntityServer.server.d.ts","sourceRoot":"","sources":["../../src/custom/MJUserEntityServer.server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,mBAAmB,EAAE,iBAAiB,EAA4C,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExK,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiIG;AACH,qBACa,kBAAmB,SAAQ,YAAY;IAChC,QAAQ,IAAI,gBAAgB;IAe5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACmB,IAAI,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC;IAOzE,OAAO,CAAC,UAAU;IAalB;;;;;;;;;;;;OAYG;IACmB,MAAM,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC;IAO7E,OAAO,CAAC,YAAY;IAYpB;;;;;OAKG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;;;OAIG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;;;;;OASG;IACH,OAAO,CAAC,kBAAkB;IAsB1B;;;;;;;;;;OAUG;IACH,OAAO,CAAC,qBAAqB;IAc7B;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,OAAO,CAAC,aAAa;CAOxB"}