@nextage/era-lib 1.0.191 → 1.0.193

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 (59) hide show
  1. package/build/constants.d.ts +48 -15
  2. package/build/constants.d.ts.map +1 -1
  3. package/build/constants.js +49 -16
  4. package/build/constants.js.map +1 -1
  5. package/build/enums.d.ts +26 -2
  6. package/build/enums.d.ts.map +1 -1
  7. package/build/enums.js +27 -2
  8. package/build/enums.js.map +1 -1
  9. package/build/events/base/base-event.controller.d.ts +1 -1
  10. package/build/events/base/base-event.controller.js +1 -1
  11. package/build/events/base/base-msg-listener.d.ts +1 -1
  12. package/build/events/base/base-msg-listener.js +1 -1
  13. package/build/events/publisher-registry.d.ts +1 -1
  14. package/build/events/publisher-registry.js +3 -3
  15. package/build/events/revocation-denylist.js +1 -1
  16. package/build/events/transport/nats-client-manager.d.ts +1 -1
  17. package/build/events/transport/nats-client-manager.js +1 -1
  18. package/build/index.d.ts +1 -0
  19. package/build/index.d.ts.map +1 -1
  20. package/build/index.js +1 -0
  21. package/build/index.js.map +1 -1
  22. package/build/interfaces.d.ts +50 -1
  23. package/build/interfaces.d.ts.map +1 -1
  24. package/build/types/base-user/index.d.ts +2 -1
  25. package/build/types/base-user/index.d.ts.map +1 -1
  26. package/build/types/nx-context.d.ts +18 -0
  27. package/build/types/nx-context.d.ts.map +1 -0
  28. package/build/types/nx-context.js +14 -0
  29. package/build/types/nx-context.js.map +1 -0
  30. package/build/utils/claims.d.ts +42 -0
  31. package/build/utils/claims.d.ts.map +1 -0
  32. package/build/utils/claims.interfaces.d.ts +36 -0
  33. package/build/utils/claims.interfaces.d.ts.map +1 -0
  34. package/build/utils/claims.interfaces.js +9 -0
  35. package/build/utils/claims.interfaces.js.map +1 -0
  36. package/build/utils/claims.js +64 -0
  37. package/build/utils/claims.js.map +1 -0
  38. package/build/utils/express.js +2 -2
  39. package/build/utils/functs.d.ts +3 -3
  40. package/build/utils/functs.js +3 -3
  41. package/build/utils/index.d.ts +4 -0
  42. package/build/utils/index.d.ts.map +1 -1
  43. package/build/utils/index.js +4 -0
  44. package/build/utils/index.js.map +1 -1
  45. package/build/utils/scope.d.ts +75 -0
  46. package/build/utils/scope.d.ts.map +1 -0
  47. package/build/utils/scope.interfaces.d.ts +20 -0
  48. package/build/utils/scope.interfaces.d.ts.map +1 -0
  49. package/build/utils/scope.interfaces.js +10 -0
  50. package/build/utils/scope.interfaces.js.map +1 -0
  51. package/build/utils/scope.js +121 -0
  52. package/build/utils/scope.js.map +1 -0
  53. package/build/utils/start-service.js +1 -1
  54. package/build/utils/tenant.d.ts +71 -38
  55. package/build/utils/tenant.d.ts.map +1 -1
  56. package/build/utils/tenant.interfaces.d.ts +5 -5
  57. package/build/utils/tenant.js +83 -54
  58. package/build/utils/tenant.js.map +1 -1
  59. package/package.json +1 -1
@@ -0,0 +1,42 @@
1
+ /*********************************************************************************************
2
+ * *
3
+ * Identity claims - the SINGLE definition of what identity travels inside an access token. *
4
+ * *
5
+ * Two operations, kept together on purpose because they MUST stay each other's inverse: *
6
+ * *
7
+ * buildAuthClaims - auth-srv, when signing a token (login and refresh); *
8
+ * identityFromClaims - every resource service, when rebuilding the caller per request. *
9
+ * *
10
+ * They used to be four hand-written literals across two services. A drift between them is *
11
+ * not a cosmetic bug: a claim that is written but not read means the authorization data *
12
+ * silently disappears, and the checks that depend on it fail OPEN or CLOSED at random. *
13
+ * *
14
+ * NOTE: resource services do NOT re-read the user from the database - they trust these *
15
+ * claims (ADR-0005 section 5.1). Revoking a grant therefore takes effect only after the *
16
+ * token is refreshed: a declared property, bounded by the access-token TTL. *
17
+ * *
18
+ *********************************************************************************************/
19
+ import { BaseUser } from '@nextage/nx-frame-be';
20
+ import { AuthClaims, ClaimIdentity } from './claims.interfaces';
21
+ /**
22
+ * Builds the identity claims to embed in an access token.
23
+ *
24
+ * `grn` is omitted entirely when the user holds no grant, so the common case adds
25
+ * nothing to the token.
26
+ *
27
+ * @param user the user the token is being issued for, as read from the database
28
+ * @returns the identity claims to sign
29
+ */
30
+ export declare function buildAuthClaims(user: Partial<BaseUser>): AuthClaims;
31
+ /**
32
+ * Rebuilds the caller's identity from the claims of a verified token.
33
+ *
34
+ * Tolerant of a MISSING key but never of a malformed one: an older token issued
35
+ * before grants existed simply yields an empty list, whereas an entry without an
36
+ * `id` or a `role` is dropped rather than trusted half-formed.
37
+ *
38
+ * @param data the `data` payload of the verified access token
39
+ * @returns roles, memberships and grants, shaped as the `BaseUser` augmentation
40
+ */
41
+ export declare function identityFromClaims(data?: Partial<AuthClaims>): ClaimIdentity;
42
+ //# sourceMappingURL=claims.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.d.ts","sourceRoot":"","sources":["../../src/utils/claims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;+FAiB+F;AAE/F,OAAO,EAAE,QAAQ,EAAE,MAAuB,sBAAsB,CAAC;AAGjE,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEhE;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,GAAG,UAAU,CAcnE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,aAAa,CAS5E"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * JWT identity-claim types (ADR-0005 section 5.1).
3
+ *
4
+ * Kept separate from the logic in `claims.ts` (house-style: types live in a
5
+ * dedicated `*.interfaces.ts`, never mixed with the logic that consumes them).
6
+ */
7
+ import { BaseEntity, OrgGrant } from '../interfaces';
8
+ /**
9
+ * The identity portion of the access-token payload.
10
+ *
11
+ * Deliberately terse: these keys are repeated in every request. `rls` and `orgs`
12
+ * predate this ADR and keep their historic names and shapes.
13
+ *
14
+ * `grn` carries `OrgGrant` VERBATIM, not a token-specific shape: the pair
15
+ * `{ id, role }` is the whole of authorization, so a separate claim type could
16
+ * only ever drift from it. Provenance (`OrgGrantRecord.grantedBy`/`grantedAt`)
17
+ * stays out - it is audit data, and the token is neither the place to repeat it
18
+ * on every request nor a place anything can be revoked from before expiry.
19
+ */
20
+ export interface AuthClaims {
21
+ rls: string[];
22
+ orgs: string[];
23
+ grn?: OrgGrant[];
24
+ }
25
+ /**
26
+ * The identity rebuilt from a token by a resource service.
27
+ *
28
+ * Mirrors the fields the `BaseUser` augmentation adds, so the result can be spread
29
+ * straight onto the user stand-in a resource service builds per request.
30
+ */
31
+ export interface ClaimIdentity {
32
+ roles: string[];
33
+ organizations: BaseEntity[];
34
+ grants: OrgGrant[];
35
+ }
36
+ //# sourceMappingURL=claims.interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.interfaces.d.ts","sourceRoot":"","sources":["../../src/utils/claims.interfaces.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,EAAG,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAU,MAAM,EAAE,CAAC;IACxB,aAAa,EAAE,UAAU,EAAE,CAAC;IAC5B,MAAM,EAAS,QAAQ,EAAE,CAAC;CAC3B"}
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ /**
3
+ * JWT identity-claim types (ADR-0005 section 5.1).
4
+ *
5
+ * Kept separate from the logic in `claims.ts` (house-style: types live in a
6
+ * dedicated `*.interfaces.ts`, never mixed with the logic that consumes them).
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ //# sourceMappingURL=claims.interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.interfaces.js","sourceRoot":"","sources":["../../src/utils/claims.interfaces.ts"],"names":[],"mappings":";AAAA;;;;;GAKG"}
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ /*********************************************************************************************
3
+ * *
4
+ * Identity claims - the SINGLE definition of what identity travels inside an access token. *
5
+ * *
6
+ * Two operations, kept together on purpose because they MUST stay each other's inverse: *
7
+ * *
8
+ * buildAuthClaims - auth-srv, when signing a token (login and refresh); *
9
+ * identityFromClaims - every resource service, when rebuilding the caller per request. *
10
+ * *
11
+ * They used to be four hand-written literals across two services. A drift between them is *
12
+ * not a cosmetic bug: a claim that is written but not read means the authorization data *
13
+ * silently disappears, and the checks that depend on it fail OPEN or CLOSED at random. *
14
+ * *
15
+ * NOTE: resource services do NOT re-read the user from the database - they trust these *
16
+ * claims (ADR-0005 section 5.1). Revoking a grant therefore takes effect only after the *
17
+ * token is refreshed: a declared property, bounded by the access-token TTL. *
18
+ * *
19
+ *********************************************************************************************/
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.buildAuthClaims = buildAuthClaims;
22
+ exports.identityFromClaims = identityFromClaims;
23
+ /**
24
+ * Builds the identity claims to embed in an access token.
25
+ *
26
+ * `grn` is omitted entirely when the user holds no grant, so the common case adds
27
+ * nothing to the token.
28
+ *
29
+ * @param user the user the token is being issued for, as read from the database
30
+ * @returns the identity claims to sign
31
+ */
32
+ function buildAuthClaims(user) {
33
+ // rebuilt entry by entry, never spread: `user.grants` is an `OrgGrantRecord[]`
34
+ // read from the database, and spreading it would carry `grantedBy`/`grantedAt`
35
+ // into every request's token. The pair below is the whole of authorization.
36
+ const grants = (user.grants ?? [])
37
+ .filter(g => g.id && g.role)
38
+ .map(g => ({ id: String(g.id), role: g.role }));
39
+ return {
40
+ rls: user.roles ?? [],
41
+ orgs: (user.organizations ?? []).map(o => o.id).filter(Boolean),
42
+ ...(grants.length && { grn: grants })
43
+ };
44
+ }
45
+ /**
46
+ * Rebuilds the caller's identity from the claims of a verified token.
47
+ *
48
+ * Tolerant of a MISSING key but never of a malformed one: an older token issued
49
+ * before grants existed simply yields an empty list, whereas an entry without an
50
+ * `id` or a `role` is dropped rather than trusted half-formed.
51
+ *
52
+ * @param data the `data` payload of the verified access token
53
+ * @returns roles, memberships and grants, shaped as the `BaseUser` augmentation
54
+ */
55
+ function identityFromClaims(data) {
56
+ return {
57
+ roles: data?.rls ?? [],
58
+ organizations: (data?.orgs ?? []).map(id => ({ id })),
59
+ grants: (data?.grn ?? [])
60
+ .filter(g => g?.id && g?.role)
61
+ .map(g => ({ id: g.id, role: g.role }))
62
+ };
63
+ }
64
+ //# sourceMappingURL=claims.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claims.js","sourceRoot":"","sources":["../../src/utils/claims.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;+FAiB+F;;AAgB/F,0CAcC;AAYD,gDASC;AA5CD;;;;;;;;GAQG;AACH,SAAgB,eAAe,CAAC,IAAuB;IAErD,+EAA+E;IAC/E,+EAA+E;IAC/E,4EAA4E;IAC5E,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC;SAC/B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAElD,OAAO;QACL,GAAG,EAAG,IAAI,CAAC,KAAK,IAAI,EAAE;QACtB,IAAI,EAAE,CAAC,IAAI,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QAChE,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;KACtC,CAAC;AACJ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAgB,kBAAkB,CAAC,IAA0B;IAE3D,OAAO;QACL,KAAK,EAAU,IAAI,EAAE,GAAG,IAAI,EAAE;QAC9B,aAAa,EAAE,CAAC,IAAI,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrD,MAAM,EAAS,CAAC,IAAI,EAAE,GAAG,IAAI,EAAE,CAAC;aAC7B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAiB,EAAE,CAAC,CAAC;KACvD,CAAC;AACJ,CAAC"}
@@ -63,11 +63,11 @@ function allowMethods(methods = [], message = 'Method Not Allowed') {
63
63
  }
64
64
  ;
65
65
  /**
66
- * Normalise an array of HTTP methods.
66
+ * Normalize an array of HTTP methods.
67
67
  *
68
68
  * @access private
69
69
  * @param {Array<string>} methods
70
- * The HTTP methods to normalise.
70
+ * The HTTP methods to normalize.
71
71
  * @returns {Array<string>}
72
72
  * Returns an array of capitalised HTTP methods.
73
73
  */
@@ -18,7 +18,7 @@ export declare function initConfig(dir: string): Promise<AppData>;
18
18
  */
19
19
  export declare function buildMongoDBConfig(): Promise<AppMongoDB>;
20
20
  /**
21
- * Reads and normalises the application environment.
21
+ * Reads and normalizes the application environment.
22
22
  *
23
23
  * Loads the dotenv file for the active `NODE_ENV`, applies the {@link ENV_DEFAULTS}
24
24
  * table for any unset variable, resolves the few defaults that depend on other
@@ -120,13 +120,13 @@ export declare function getEntityI18nString(input: NxTypeObject<string> | string
120
120
  */
121
121
  export declare function setEntityI18nString(input: NxTypeObject<string> | string, lang: string): NxTypeObject<string>;
122
122
  /**
123
- * Validates and normalises an absolute http(s) URL (SSRF guard).
123
+ * Validates and normalizes an absolute http(s) URL (SSRF guard).
124
124
  *
125
125
  * Requires an explicit `http`/`https` protocol and rejects loopback hosts
126
126
  * (`localhost`/`127.0.0.1`) to prevent server-side request forgery.
127
127
  *
128
128
  * @param input the URL to validate
129
- * @returns the parsed, normalised URL string
129
+ * @returns the parsed, normalized URL string
130
130
  * @throws `Invalid URL` when malformed, `Forbidden URL` when pointing at loopback
131
131
  */
132
132
  export declare function validateUrl(input: string): string;
@@ -258,7 +258,7 @@ function validateEnv() {
258
258
  }
259
259
  }
260
260
  /**
261
- * Reads and normalises the application environment.
261
+ * Reads and normalizes the application environment.
262
262
  *
263
263
  * Loads the dotenv file for the active `NODE_ENV`, applies the {@link ENV_DEFAULTS}
264
264
  * table for any unset variable, resolves the few defaults that depend on other
@@ -500,13 +500,13 @@ function setEntityI18nString(input, lang) {
500
500
  return res;
501
501
  }
502
502
  /**
503
- * Validates and normalises an absolute http(s) URL (SSRF guard).
503
+ * Validates and normalizes an absolute http(s) URL (SSRF guard).
504
504
  *
505
505
  * Requires an explicit `http`/`https` protocol and rejects loopback hosts
506
506
  * (`localhost`/`127.0.0.1`) to prevent server-side request forgery.
507
507
  *
508
508
  * @param input the URL to validate
509
- * @returns the parsed, normalised URL string
509
+ * @returns the parsed, normalized URL string
510
510
  * @throws `Invalid URL` when malformed, `Forbidden URL` when pointing at loopback
511
511
  */
512
512
  function validateUrl(input) {
@@ -1,8 +1,12 @@
1
+ export * from './claims';
2
+ export * from './claims.interfaces';
1
3
  export * from './express';
2
4
  export * from './functs';
3
5
  export * from './jwt';
4
6
  export * from './jwt.constants';
5
7
  export * from './jwt.interfaces';
8
+ export * from './scope';
9
+ export * from './scope.interfaces';
6
10
  export * from './start-service';
7
11
  export * from './tenant';
8
12
  export * from './tenant.interfaces';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,SAAS,CAAC;AACxB,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,UAAU,CAAC;AACzB,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC"}
@@ -14,11 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./claims"), exports);
18
+ __exportStar(require("./claims.interfaces"), exports);
17
19
  __exportStar(require("./express"), exports);
18
20
  __exportStar(require("./functs"), exports);
19
21
  __exportStar(require("./jwt"), exports);
20
22
  __exportStar(require("./jwt.constants"), exports);
21
23
  __exportStar(require("./jwt.interfaces"), exports);
24
+ __exportStar(require("./scope"), exports);
25
+ __exportStar(require("./scope.interfaces"), exports);
22
26
  __exportStar(require("./start-service"), exports);
23
27
  __exportStar(require("./tenant"), exports);
24
28
  __exportStar(require("./tenant.interfaces"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,4CAA0B;AAC1B,2CAAyB;AACzB,wCAAsB;AACtB,kDAAgC;AAChC,mDAAiC;AACjC,kDAAgC;AAChC,2CAAyB;AACzB,sDAAoC;AACpC,4DAA0C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,sDAAoC;AACpC,4CAA0B;AAC1B,2CAAyB;AACzB,wCAAsB;AACtB,kDAAgC;AAChC,mDAAiC;AACjC,0CAAwB;AACxB,qDAAmC;AACnC,kDAAgC;AAChC,2CAAyB;AACzB,sDAAoC;AACpC,4DAA0C"}
@@ -0,0 +1,75 @@
1
+ /*********************************************************************************************
2
+ * *
3
+ * Organization scope - THE single answer to "may this user operate on THIS organization?" *
4
+ * *
5
+ * The organization arrives EXPLICITLY from the request (an `oid`/`uid` on a mutation). *
6
+ * The sibling question - "which organization is this user operating on?", where the tenant *
7
+ * must be DEDUCED - is answered by `resolveTenant` in `tenant.ts`. Two questions, two *
8
+ * functions, no overlap. *
9
+ * *
10
+ * SINGLE SOURCE OF TRUTH: every caller imports from here and NEVER re-implements the *
11
+ * check. `@auth(requires:)` alone is never sufficient - it verifies the ROLE, never the *
12
+ * ORGANIZATION (ADR-0005 section 5). *
13
+ * *
14
+ *********************************************************************************************/
15
+ import { BaseUser } from '@nextage/nx-frame-be';
16
+ import { OrgScope } from './scope.interfaces';
17
+ /**
18
+ * Resolves how `user` reaches the organization `orgId`, or `null` if they cannot.
19
+ *
20
+ * Resolution order (ADR-0005 section 5) - the first match wins:
21
+ *
22
+ * 1. cross-tenant level -> { tier: PROVIDER } // no organization constraint
23
+ * 2. membership on `orgId` -> { tier: MEMBER, role } // role from `roles[]`
24
+ * 3. grant on `orgId` -> { tier: GRANTED, role } // role from the grant
25
+ * 4. otherwise -> null // denied
26
+ *
27
+ * Membership is checked BEFORE grants because it is identity: a user who belongs to
28
+ * the organization already reaches it, and a grant on one's own organization would
29
+ * be redundant. Note the two carry the role differently - membership entries hold no
30
+ * role (it lives in `users.roles[]`, one organization-tier role per user), whereas a
31
+ * grant carries its own, which is what allows a different role per organization.
32
+ *
33
+ * FAIL-CLOSED: a missing user, a missing `orgId` or no match at all all yield `null`.
34
+ * Callers must treat `null` as "denied" and never as "unknown".
35
+ *
36
+ * @param user the authenticated user (`NxContext.user`)
37
+ * @param orgId the target organization id, as received from the request
38
+ * @returns the scope through which the user reaches the organization, or `null`
39
+ */
40
+ export declare function resolveOrgScope(user?: BaseUser, orgId?: string): OrgScope | null;
41
+ /**
42
+ * Throwing variant of `resolveOrgScope`, for the call sites that must simply deny.
43
+ *
44
+ * Raises the same error whether the organization is out of scope or does not exist:
45
+ * distinguishing the two would confirm the existence of somebody else's organization.
46
+ *
47
+ * @param user the authenticated user (`NxContext.user`)
48
+ * @param orgId the target organization id, as received from the request
49
+ * @returns the resolved scope (never `null`)
50
+ * @throws `AppError('messages.authorization.missingRole')` when out of scope
51
+ */
52
+ export declare function assertOrgScope(user?: BaseUser, orgId?: string): OrgScope;
53
+ /**
54
+ * Every organization the user reaches, for the LIST case.
55
+ *
56
+ * `resolveOrgScope` answers about ONE organization named by the request; this answers
57
+ * "which ones may appear at all?", which is what a model's `updateFilters` needs. The
58
+ * two share the same rules deliberately: a list that showed an organization the point
59
+ * check would then deny would be a leak by itself.
60
+ *
61
+ * Three outcomes, and the caller MUST tell them apart:
62
+ *
63
+ * `undefined` -> no constraint: a cross-tenant user, every organization is visible
64
+ * `string[]` -> exactly these ids (memberships UNION grants), never any other
65
+ * `[]` -> nothing is visible; the caller must yield an EMPTY result, never all
66
+ *
67
+ * The union is what makes a grant visible in the organization switcher: an
68
+ * `ORG_EDITOR` granted on another organization must find it in the list, or the
69
+ * grant would be unusable (ADR-0005 section 3.2).
70
+ *
71
+ * @param user the authenticated user (`NxContext.user`)
72
+ * @returns `undefined` for no constraint, otherwise the exact set of reachable ids
73
+ */
74
+ export declare function reachableOrgIds(user?: BaseUser): string[] | undefined;
75
+ //# sourceMappingURL=scope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../../src/utils/scope.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;+FAa+F;AAE/F,OAAO,EAAY,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAM1D,OAAO,EAAE,QAAQ,EAAE,MAAgB,oBAAoB,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAqBhF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,QAAQ,CAQxE;AAcD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,EAAE,GAAG,SAAS,CAcrE"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Organization-scope types & interfaces (ADR-0005 section 5).
3
+ *
4
+ * Kept separate from the logic in `scope.ts` (house-style: types live in a
5
+ * dedicated `*.interfaces.ts`, never mixed with the logic that consumes them).
6
+ * `OrgScopeTier` itself lives in `enums.ts`, next to `BaseRoles`/`RolesType`.
7
+ */
8
+ import { BaseRoles, OrgScopeTier } from '../enums';
9
+ /**
10
+ * Outcome of `resolveOrgScope`: the tier through which the user reaches the
11
+ * organization, plus the organization-tier role that applies THERE.
12
+ *
13
+ * `role` is undefined only for the `PROVIDER` tier, whose capabilities come from
14
+ * the capability matrix (ADR-0005 section 4) and not from an organization role.
15
+ */
16
+ export interface OrgScope {
17
+ tier: OrgScopeTier;
18
+ role?: BaseRoles;
19
+ }
20
+ //# sourceMappingURL=scope.interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.interfaces.d.ts","sourceRoot":"","sources":["../../src/utils/scope.interfaces.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAG,YAAY,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ /**
3
+ * Organization-scope types & interfaces (ADR-0005 section 5).
4
+ *
5
+ * Kept separate from the logic in `scope.ts` (house-style: types live in a
6
+ * dedicated `*.interfaces.ts`, never mixed with the logic that consumes them).
7
+ * `OrgScopeTier` itself lives in `enums.ts`, next to `BaseRoles`/`RolesType`.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ //# sourceMappingURL=scope.interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.interfaces.js","sourceRoot":"","sources":["../../src/utils/scope.interfaces.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG"}
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ /*********************************************************************************************
3
+ * *
4
+ * Organization scope - THE single answer to "may this user operate on THIS organization?" *
5
+ * *
6
+ * The organization arrives EXPLICITLY from the request (an `oid`/`uid` on a mutation). *
7
+ * The sibling question - "which organization is this user operating on?", where the tenant *
8
+ * must be DEDUCED - is answered by `resolveTenant` in `tenant.ts`. Two questions, two *
9
+ * functions, no overlap. *
10
+ * *
11
+ * SINGLE SOURCE OF TRUTH: every caller imports from here and NEVER re-implements the *
12
+ * check. `@auth(requires:)` alone is never sufficient - it verifies the ROLE, never the *
13
+ * ORGANIZATION (ADR-0005 section 5). *
14
+ * *
15
+ *********************************************************************************************/
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.resolveOrgScope = resolveOrgScope;
18
+ exports.assertOrgScope = assertOrgScope;
19
+ exports.reachableOrgIds = reachableOrgIds;
20
+ const nx_frame_be_1 = require("@nextage/nx-frame-be");
21
+ const constants_1 = require("../constants");
22
+ const constants_2 = require("../constants");
23
+ const enums_1 = require("../enums");
24
+ /**
25
+ * Resolves how `user` reaches the organization `orgId`, or `null` if they cannot.
26
+ *
27
+ * Resolution order (ADR-0005 section 5) - the first match wins:
28
+ *
29
+ * 1. cross-tenant level -> { tier: PROVIDER } // no organization constraint
30
+ * 2. membership on `orgId` -> { tier: MEMBER, role } // role from `roles[]`
31
+ * 3. grant on `orgId` -> { tier: GRANTED, role } // role from the grant
32
+ * 4. otherwise -> null // denied
33
+ *
34
+ * Membership is checked BEFORE grants because it is identity: a user who belongs to
35
+ * the organization already reaches it, and a grant on one's own organization would
36
+ * be redundant. Note the two carry the role differently - membership entries hold no
37
+ * role (it lives in `users.roles[]`, one organization-tier role per user), whereas a
38
+ * grant carries its own, which is what allows a different role per organization.
39
+ *
40
+ * FAIL-CLOSED: a missing user, a missing `orgId` or no match at all all yield `null`.
41
+ * Callers must treat `null` as "denied" and never as "unknown".
42
+ *
43
+ * @param user the authenticated user (`NxContext.user`)
44
+ * @param orgId the target organization id, as received from the request
45
+ * @returns the scope through which the user reaches the organization, or `null`
46
+ */
47
+ function resolveOrgScope(user, orgId) {
48
+ if (!user || !orgId)
49
+ return null;
50
+ // 1. cross-tenant level: reaches every organization by definition of level.
51
+ if (user.hasRole?.(constants_1.CROSS_TENANT_ROLES))
52
+ return { tier: enums_1.OrgScopeTier.PROVIDER };
53
+ // 2. membership (identity): the role is the highest organization-tier one held.
54
+ if (user.organizations?.some(o => o.id === orgId))
55
+ return { tier: enums_1.OrgScopeTier.MEMBER, role: highestOrgRole(user) };
56
+ // 3. grant (authorization): the role travels WITH the grant.
57
+ const grant = user.grants?.find(g => g.id === orgId);
58
+ if (grant)
59
+ return { tier: enums_1.OrgScopeTier.GRANTED, role: grant.role };
60
+ // 4. no path to this organization.
61
+ return null;
62
+ }
63
+ /**
64
+ * Throwing variant of `resolveOrgScope`, for the call sites that must simply deny.
65
+ *
66
+ * Raises the same error whether the organization is out of scope or does not exist:
67
+ * distinguishing the two would confirm the existence of somebody else's organization.
68
+ *
69
+ * @param user the authenticated user (`NxContext.user`)
70
+ * @param orgId the target organization id, as received from the request
71
+ * @returns the resolved scope (never `null`)
72
+ * @throws `AppError('messages.authorization.missingRole')` when out of scope
73
+ */
74
+ function assertOrgScope(user, orgId) {
75
+ const scope = resolveOrgScope(user, orgId);
76
+ if (!scope)
77
+ throw new nx_frame_be_1.AppError('messages.authorization.missingRole');
78
+ return scope;
79
+ }
80
+ /**
81
+ * The highest organization-tier role the user holds, if any.
82
+ *
83
+ * `ORG_MEMBER_ROLES` is ordered from the highest to the lowest privilege, so the
84
+ * first match IS the highest. Returns `undefined` for a user with no
85
+ * organization-tier role at all (possible for a pure provider-level account).
86
+ */
87
+ function highestOrgRole(user) {
88
+ return constants_2.ORG_MEMBER_ROLES.find(r => user.hasRole?.(r));
89
+ }
90
+ /**
91
+ * Every organization the user reaches, for the LIST case.
92
+ *
93
+ * `resolveOrgScope` answers about ONE organization named by the request; this answers
94
+ * "which ones may appear at all?", which is what a model's `updateFilters` needs. The
95
+ * two share the same rules deliberately: a list that showed an organization the point
96
+ * check would then deny would be a leak by itself.
97
+ *
98
+ * Three outcomes, and the caller MUST tell them apart:
99
+ *
100
+ * `undefined` -> no constraint: a cross-tenant user, every organization is visible
101
+ * `string[]` -> exactly these ids (memberships UNION grants), never any other
102
+ * `[]` -> nothing is visible; the caller must yield an EMPTY result, never all
103
+ *
104
+ * The union is what makes a grant visible in the organization switcher: an
105
+ * `ORG_EDITOR` granted on another organization must find it in the list, or the
106
+ * grant would be unusable (ADR-0005 section 3.2).
107
+ *
108
+ * @param user the authenticated user (`NxContext.user`)
109
+ * @returns `undefined` for no constraint, otherwise the exact set of reachable ids
110
+ */
111
+ function reachableOrgIds(user) {
112
+ if (!user)
113
+ return [];
114
+ if (user.hasRole?.(constants_1.CROSS_TENANT_ROLES))
115
+ return undefined;
116
+ const ids = new Set();
117
+ user.organizations?.forEach(o => o.id && ids.add(String(o.id)));
118
+ user.grants?.forEach(g => g.id && ids.add(String(g.id)));
119
+ return [...ids];
120
+ }
121
+ //# sourceMappingURL=scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../../src/utils/scope.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;+FAa+F;;AAiC/F,0CAqBC;AAaD,wCAQC;AAmCD,0CAcC;AA1HD,sDAA0D;AAE1D,4CAAkD;AAClD,4CAAkD;AAElD,oCAA8C;AAG9C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,SAAgB,eAAe,CAAC,IAAe,EAAE,KAAc;IAE7D,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK;QACjB,OAAO,IAAI,CAAC;IAEd,4EAA4E;IAC5E,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,8BAAkB,CAAC;QACpC,OAAO,EAAE,IAAI,EAAE,oBAAY,CAAC,QAAQ,EAAE,CAAC;IAEzC,gFAAgF;IAChF,IAAI,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC;QAC/C,OAAO,EAAE,IAAI,EAAE,oBAAY,CAAC,MAAM,EAAE,IAAI,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,CAAC;IAEnE,6DAA6D;IAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;IAErD,IAAI,KAAK;QACP,OAAO,EAAE,IAAI,EAAE,oBAAY,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;IAE1D,mCAAmC;IACnC,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAgB,cAAc,CAAC,IAAe,EAAE,KAAc;IAE5D,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAE3C,IAAI,CAAC,KAAK;QACR,MAAM,IAAI,sBAAQ,CAAC,oCAAoC,CAAC,CAAC;IAE3D,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,cAAc,CAAC,IAAc;IAEpC,OAAO,4BAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,SAAgB,eAAe,CAAC,IAAe;IAE7C,IAAI,CAAC,IAAI;QACP,OAAO,EAAE,CAAC;IAEZ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC,8BAAkB,CAAC;QACpC,OAAO,SAAS,CAAC;IAEnB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAE9B,IAAI,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAChE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEzD,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC;AAClB,CAAC"}
@@ -152,7 +152,7 @@ async function safeCheck(check) {
152
152
  }
153
153
  }
154
154
  /**
155
- * Normalises an unknown thrown value into a readable message.
155
+ * Normalizes an unknown thrown value into a readable message.
156
156
  */
157
157
  function errMessage(err) {
158
158
  if (err instanceof Error)