@memberjunction/global 6.2.0-edge.5 → 6.2.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 (48) hide show
  1. package/dist/KeyedSerialTaskQueue.d.ts +18 -1
  2. package/dist/KeyedSerialTaskQueue.d.ts.map +1 -1
  3. package/dist/KeyedSerialTaskQueue.js +24 -3
  4. package/dist/KeyedSerialTaskQueue.js.map +1 -1
  5. package/dist/SQLExpressionValidator.d.ts +34 -9
  6. package/dist/SQLExpressionValidator.d.ts.map +1 -1
  7. package/dist/SQLExpressionValidator.js +58 -17
  8. package/dist/SQLExpressionValidator.js.map +1 -1
  9. package/dist/SQLScreeningScanner.d.ts +28 -2
  10. package/dist/SQLScreeningScanner.d.ts.map +1 -1
  11. package/dist/SQLScreeningScanner.js +166 -23
  12. package/dist/SQLScreeningScanner.js.map +1 -1
  13. package/dist/__tests__/FieldSecurityEgressCompliance.test.d.ts +2 -0
  14. package/dist/__tests__/FieldSecurityEgressCompliance.test.d.ts.map +1 -0
  15. package/dist/__tests__/FieldSecurityEgressCompliance.test.js +228 -0
  16. package/dist/__tests__/FieldSecurityEgressCompliance.test.js.map +1 -0
  17. package/dist/__tests__/KeyedSerialTaskQueue.test.js +92 -0
  18. package/dist/__tests__/KeyedSerialTaskQueue.test.js.map +1 -1
  19. package/dist/__tests__/ProofOfPossession.test.d.ts +2 -0
  20. package/dist/__tests__/ProofOfPossession.test.d.ts.map +1 -0
  21. package/dist/__tests__/ProofOfPossession.test.js +123 -0
  22. package/dist/__tests__/ProofOfPossession.test.js.map +1 -0
  23. package/dist/__tests__/SQLExpressionValidator.dialect.test.d.ts +2 -0
  24. package/dist/__tests__/SQLExpressionValidator.dialect.test.d.ts.map +1 -0
  25. package/dist/__tests__/SQLExpressionValidator.dialect.test.js +183 -0
  26. package/dist/__tests__/SQLExpressionValidator.dialect.test.js.map +1 -0
  27. package/dist/__tests__/SQLScreeningScanner.tokens.test.d.ts +2 -0
  28. package/dist/__tests__/SQLScreeningScanner.tokens.test.d.ts.map +1 -0
  29. package/dist/__tests__/SQLScreeningScanner.tokens.test.js +82 -0
  30. package/dist/__tests__/SQLScreeningScanner.tokens.test.js.map +1 -0
  31. package/dist/__tests__/UUIDCompliance.test.js +1 -1
  32. package/dist/__tests__/UserScopedCacheKey.test.d.ts +2 -0
  33. package/dist/__tests__/UserScopedCacheKey.test.d.ts.map +1 -0
  34. package/dist/__tests__/UserScopedCacheKey.test.js +25 -0
  35. package/dist/__tests__/UserScopedCacheKey.test.js.map +1 -0
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/dist/util/ProofOfPossession.d.ts +68 -0
  41. package/dist/util/ProofOfPossession.d.ts.map +1 -0
  42. package/dist/util/ProofOfPossession.js +163 -0
  43. package/dist/util/ProofOfPossession.js.map +1 -0
  44. package/dist/util/UserScopedCacheKey.d.ts +42 -0
  45. package/dist/util/UserScopedCacheKey.d.ts.map +1 -0
  46. package/dist/util/UserScopedCacheKey.js +49 -0
  47. package/dist/util/UserScopedCacheKey.js.map +1 -0
  48. package/package.json +1 -1
@@ -361,7 +361,7 @@ const NESTED_SCAN_BASELINE = {
361
361
  'MJCoreEntitiesServer/src/custom/MJApplicationEntityServer.server.ts': 1,
362
362
  'MJCoreEntitiesServer/src/custom/MJEntityFieldPermissionEntityServer.server.ts': 1,
363
363
  'MJCoreEntitiesServer/src/custom/MJUserRoleEntityServer.server.ts': 2,
364
- 'MJCoreEntitiesServer/src/custom/query-extraction/sync.ts': 7,
364
+ 'MJCoreEntitiesServer/src/custom/query-extraction/sync.ts': 5,
365
365
  'MJCoreEntitiesServer/src/custom/rubrics/evaluationSubmit.ts': 2,
366
366
  'MJCoreEntitiesServer/src/custom/rubrics/versionPublish.ts': 1,
367
367
  'MJServer/src/resolvers/IntegrationDiscoveryResolver.ts': 1,
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=UserScopedCacheKey.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserScopedCacheKey.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/UserScopedCacheKey.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,25 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { UserCacheKeySegment, UserScopedCacheKey } from '../util/UserScopedCacheKey.js';
3
+ describe('UserCacheKeySegment', () => {
4
+ it('normalizes the user ID so the same user always maps to one segment', () => {
5
+ expect(UserCacheKeySegment(' A1B2-C3D4 ')).toBe('u:a1b2-c3d4');
6
+ expect(UserCacheKeySegment('a1b2-c3d4')).toBe('u:a1b2-c3d4');
7
+ });
8
+ it('returns null when there is no user, so the caller does not cache', () => {
9
+ expect(UserCacheKeySegment(undefined)).toBeNull();
10
+ expect(UserCacheKeySegment(null)).toBeNull();
11
+ expect(UserCacheKeySegment(' ')).toBeNull();
12
+ });
13
+ });
14
+ describe('UserScopedCacheKey', () => {
15
+ it('gives two users different keys for the same entry', () => {
16
+ const a = UserScopedCacheKey('user-a', 'agent-1', 'Customers');
17
+ const b = UserScopedCacheKey('user-b', 'agent-1', 'Customers');
18
+ expect(a).toBe('agent-1:Customers:u:user-a');
19
+ expect(a).not.toBe(b);
20
+ });
21
+ it('returns null when there is no user', () => {
22
+ expect(UserScopedCacheKey(undefined, 'agent-1', 'Customers')).toBeNull();
23
+ });
24
+ });
25
+ //# sourceMappingURL=UserScopedCacheKey.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserScopedCacheKey.test.js","sourceRoot":"","sources":["../../src/__tests__/UserScopedCacheKey.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAErF,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,oEAAoE,EAAE,GAAG,EAAE;QAC5E,MAAM,CAAC,mBAAmB,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC/D,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,mBAAmB,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAClD,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC7C,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,CAAC,GAAG,kBAAkB,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC/D,MAAM,CAAC,GAAG,kBAAkB,CAAC,QAAQ,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC/D,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC7C,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,CAAC,kBAAkB,CAAC,SAAS,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC3E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
package/dist/index.d.ts CHANGED
@@ -12,9 +12,11 @@ export * from './util/PatternUtils.js';
12
12
  export * from './util/ResourcePatternUtils.js';
13
13
  export * from './util/UUIDUtils.js';
14
14
  export * from './util/UUIDCollections.js';
15
+ export * from './util/UserScopedCacheKey.js';
15
16
  export * from './util/CronUtils.js';
16
17
  export * from './util/SerializationUtils.js';
17
18
  export * from './util/BinaryEncoding.js';
19
+ export * from './util/ProofOfPossession.js';
18
20
  export * from './ValidationTypes.js';
19
21
  export * from './JSONValidator.js';
20
22
  export * from './SafeExpressionEvaluator.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAMtC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAMtC,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA"}
package/dist/index.js CHANGED
@@ -13,9 +13,11 @@ export * from './util/PatternUtils.js';
13
13
  export * from './util/ResourcePatternUtils.js';
14
14
  export * from './util/UUIDUtils.js';
15
15
  export * from './util/UUIDCollections.js';
16
+ export * from './util/UserScopedCacheKey.js';
16
17
  export * from './util/CronUtils.js';
17
18
  export * from './util/SerializationUtils.js';
18
19
  export * from './util/BinaryEncoding.js';
20
+ export * from './util/ProofOfPossession.js';
19
21
  export * from './ValidationTypes.js';
20
22
  export * from './JSONValidator.js';
21
23
  export * from './SafeExpressionEvaluator.js';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,2DAA2D;AAC3D,oCAAoC;AAEpC,0BAA0B;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA;AAE7C,6DAA6D;AAC7D,oCAAoC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iCAAiC;AACjC,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAChE,cAAc,aAAa,CAAA;AAC3B,cAAc,QAAQ,CAAA;AACtB,cAAc,gBAAgB,CAAA;AAC9B,cAAc,eAAe,CAAA;AAC7B,cAAc,cAAc,CAAA;AAC5B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,kBAAkB,CAAC;AACjC,cAAc,wBAAwB,CAAC;AACvC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,kBAAkB,CAAC;AACjC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,uBAAuB,CAAC;AACtC,cAAc,0BAA0B,CAAC;AACzC,cAAc,mBAAmB,CAAA;AACjC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,2BAA2B,CAAA;AACzC,cAAc,cAAc,CAAA;AAC5B,cAAc,uBAAuB,CAAA;AACrC,cAAc,0BAA0B,CAAA;AACxC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,uBAAuB,CAAA;AACrC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AACtC,cAAc,wBAAwB,CAAA;AAEtC,2DAA2D;AAC3D,oCAAoC;AAEpC,0BAA0B;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,+BAA+B,CAAA;AAE7C,6DAA6D;AAC7D,oCAAoC"}
@@ -0,0 +1,68 @@
1
+ /** The public half of a proof key, as a JWK with only the members RFC 7638 hashes. */
2
+ export interface ProofPublicJwk {
3
+ kty: 'EC';
4
+ crv: 'P-256';
5
+ x: string;
6
+ y: string;
7
+ }
8
+ /** A proof key: the non-extractable private key that signs, and its public JWK that the server records. */
9
+ export interface ProofOfPossessionKey {
10
+ PrivateKey: CryptoKey;
11
+ PublicJwk: ProofPublicJwk;
12
+ }
13
+ /** What one proof is made for. */
14
+ export interface ProofOfPossessionRequest {
15
+ /** HTTP method of the protected request (`htm`). */
16
+ Method: string;
17
+ /** Full URL of the protected request (`htu`). */
18
+ Url: string;
19
+ /** The bearer token sent with the request; the proof carries its hash (`ath`), so it works for that token only. */
20
+ AccessToken: string;
21
+ /** Signing time (`iat`), epoch seconds. Defaults to now. */
22
+ NowSeconds?: number;
23
+ }
24
+ /** What the verifier expects of a proof. */
25
+ export interface ProofOfPossessionExpectation {
26
+ Method: string;
27
+ /**
28
+ * The path the proof must name, matched as a suffix of the proof URL's path. This binds a proof to one
29
+ * endpoint, so a proof made for another route of the same API cannot be reused here. The origin and any
30
+ * base path are not compared: one API can be reached under several public origins and base paths (a
31
+ * proxy, `MJAPI_BASE_PATH`), and the caller cannot reliably list them. Leaving them open grants nothing,
32
+ * because only the holder of the bound key can sign a proof, and `ath` ties the proof to one token.
33
+ */
34
+ PathSuffix: string;
35
+ /** The bearer token presented with the proof. */
36
+ AccessToken: string;
37
+ NowSeconds: number;
38
+ /** How far `iat` may be from `NowSeconds`, either way. */
39
+ MaxSkewSeconds: number;
40
+ }
41
+ /** Why a proof was refused. */
42
+ export type ProofOfPossessionRefusal = 'malformed' | 'bad_signature' | 'request_mismatch' | 'token_mismatch' | 'stale';
43
+ /** Outcome of {@link VerifyProofOfPossessionJwt}; `Thumbprint` identifies the key that signed. */
44
+ export type ProofOfPossessionVerification = {
45
+ Valid: true;
46
+ Thumbprint: string;
47
+ } | {
48
+ Valid: false;
49
+ Reason: ProofOfPossessionRefusal;
50
+ };
51
+ /** Creates a proof key. The private key is non-extractable, so no script can read it out, only use it. */
52
+ export declare function CreateProofOfPossessionKey(): Promise<ProofOfPossessionKey>;
53
+ /** The RFC 7638 SHA-256 thumbprint of a proof key, base64url-encoded (the value of a token's `cnf.jkt`). */
54
+ export declare function ComputeJwkThumbprint(jwk: ProofPublicJwk): Promise<string>;
55
+ /** Signs a proof that the holder of `key` is making `request`. */
56
+ export declare function CreateProofOfPossessionJwt(key: ProofOfPossessionKey, request: ProofOfPossessionRequest): Promise<string>;
57
+ /**
58
+ * Checks a proof against what the request expects. Never throws: anything unparseable is `malformed`.
59
+ * Does not keep a replay cache of `jti`; a proof is bound to one token (`ath`) and to `MaxSkewSeconds`, so a
60
+ * replay needs both the proof and that token within the window.
61
+ */
62
+ export declare function VerifyProofOfPossessionJwt(proof: string, expected: ProofOfPossessionExpectation): Promise<ProofOfPossessionVerification>;
63
+ /**
64
+ * Narrows a value (e.g. a JWK a client sent) to a public P-256 JWK, keeping only the members RFC 7638 hashes;
65
+ * null for anything else, including a JWK that carries a private key.
66
+ */
67
+ export declare function ToProofPublicJwk(value: unknown): ProofPublicJwk | null;
68
+ //# sourceMappingURL=ProofOfPossession.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProofOfPossession.d.ts","sourceRoot":"","sources":["../../src/util/ProofOfPossession.ts"],"names":[],"mappings":"AAaA,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,IAAI,CAAC;IACV,GAAG,EAAE,OAAO,CAAC;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACb;AAED,2GAA2G;AAC3G,MAAM,WAAW,oBAAoB;IACjC,UAAU,EAAE,SAAS,CAAC;IACtB,SAAS,EAAE,cAAc,CAAC;CAC7B;AAED,kCAAkC;AAClC,MAAM,WAAW,wBAAwB;IACrC,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,mHAAmH;IACnH,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,4CAA4C;AAC5C,MAAM,WAAW,4BAA4B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;OAMG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,+BAA+B;AAC/B,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,eAAe,GAAG,kBAAkB,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAEvH,kGAAkG;AAClG,MAAM,MAAM,6BAA6B,GACnC;IAAE,KAAK,EAAE,IAAI,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,wBAAwB,CAAA;CAAE,CAAC;AAOzD,0GAA0G;AAC1G,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAShF;AAED,4GAA4G;AAC5G,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,CAI/E;AAED,kEAAkE;AAClE,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,oBAAoB,EAAE,OAAO,EAAE,wBAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAY9H;AAED;;;;GAIG;AACH,wBAAsB,0BAA0B,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,4BAA4B,GAAG,OAAO,CAAC,6BAA6B,CAAC,CA6B9I;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAStE"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * @fileoverview Proof of possession for bearer tokens, in the DPoP shape (RFC 9449), shared by the browser
3
+ * that holds a key and the server that checks it.
4
+ *
5
+ * The holder makes a P-256 key pair whose private half can never be exported, registers the public half
6
+ * when its token is issued (the server records the key's RFC 7638 thumbprint in the token), and signs a
7
+ * short-lived proof for each protected request. A token copied off the holder's machine is then useless for
8
+ * that request: the copier cannot produce a proof, because the key never left the browser.
9
+ *
10
+ * Uses only the Web Crypto API (`crypto.subtle`), available in browsers (secure contexts) and Node.js 15+.
11
+ */
12
+ import { Base64ToBytes, BytesToBase64 } from './BinaryEncoding.js';
13
+ const PROOF_TYPE = 'dpop+jwt';
14
+ const PROOF_ALGORITHM = 'ES256';
15
+ const KEY_ALGORITHM = { name: 'ECDSA', namedCurve: 'P-256' };
16
+ const SIGN_ALGORITHM = { name: 'ECDSA', hash: 'SHA-256' };
17
+ /** Creates a proof key. The private key is non-extractable, so no script can read it out, only use it. */
18
+ export async function CreateProofOfPossessionKey() {
19
+ const subtle = requireSubtle();
20
+ const pair = await subtle.generateKey(KEY_ALGORITHM, false, ['sign', 'verify']);
21
+ const exported = await subtle.exportKey('jwk', pair.publicKey);
22
+ const publicJwk = ToProofPublicJwk(exported);
23
+ if (!publicJwk) {
24
+ throw new Error('CreateProofOfPossessionKey: the generated public key did not export as a P-256 JWK.');
25
+ }
26
+ return { PrivateKey: pair.privateKey, PublicJwk: publicJwk };
27
+ }
28
+ /** The RFC 7638 SHA-256 thumbprint of a proof key, base64url-encoded (the value of a token's `cnf.jkt`). */
29
+ export async function ComputeJwkThumbprint(jwk) {
30
+ // RFC 7638 §3.2: the required members only, in lexicographic order, with no whitespace.
31
+ const canonical = JSON.stringify({ crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y });
32
+ return sha256Base64Url(canonical);
33
+ }
34
+ /** Signs a proof that the holder of `key` is making `request`. */
35
+ export async function CreateProofOfPossessionJwt(key, request) {
36
+ const header = { typ: PROOF_TYPE, alg: PROOF_ALGORITHM, jwk: key.PublicJwk };
37
+ const payload = {
38
+ htm: request.Method,
39
+ htu: request.Url,
40
+ iat: request.NowSeconds ?? Math.floor(Date.now() / 1000),
41
+ jti: bytesToBase64Url(requireCrypto().getRandomValues(new Uint8Array(16))),
42
+ ath: await sha256Base64Url(request.AccessToken),
43
+ };
44
+ const signingInput = `${encodeJsonPart(header)}.${encodeJsonPart(payload)}`;
45
+ const signature = await requireSubtle().sign(SIGN_ALGORITHM, key.PrivateKey, new TextEncoder().encode(signingInput));
46
+ return `${signingInput}.${bytesToBase64Url(new Uint8Array(signature))}`;
47
+ }
48
+ /**
49
+ * Checks a proof against what the request expects. Never throws: anything unparseable is `malformed`.
50
+ * Does not keep a replay cache of `jti`; a proof is bound to one token (`ath`) and to `MaxSkewSeconds`, so a
51
+ * replay needs both the proof and that token within the window.
52
+ */
53
+ export async function VerifyProofOfPossessionJwt(proof, expected) {
54
+ const parts = typeof proof === 'string' ? proof.split('.') : [];
55
+ if (parts.length !== 3) {
56
+ return { Valid: false, Reason: 'malformed' };
57
+ }
58
+ const header = decodeJsonPart(parts[0]);
59
+ const payload = decodeJsonPart(parts[1]);
60
+ const signature = decodeBase64Url(parts[2]);
61
+ if (!header || !payload || !signature || header.typ !== PROOF_TYPE || header.alg !== PROOF_ALGORITHM) {
62
+ return { Valid: false, Reason: 'malformed' };
63
+ }
64
+ const jwk = ToProofPublicJwk(header.jwk);
65
+ if (!jwk) {
66
+ return { Valid: false, Reason: 'malformed' };
67
+ }
68
+ const signed = await verifySignature(jwk, `${parts[0]}.${parts[1]}`, signature);
69
+ if (!signed) {
70
+ return { Valid: false, Reason: 'bad_signature' };
71
+ }
72
+ if (payload.htm !== expected.Method || !pathEndsWith(payload.htu, expected.PathSuffix)) {
73
+ return { Valid: false, Reason: 'request_mismatch' };
74
+ }
75
+ if (typeof payload.iat !== 'number' || Math.abs(expected.NowSeconds - payload.iat) > expected.MaxSkewSeconds) {
76
+ return { Valid: false, Reason: 'stale' };
77
+ }
78
+ if (payload.ath !== (await sha256Base64Url(expected.AccessToken))) {
79
+ return { Valid: false, Reason: 'token_mismatch' };
80
+ }
81
+ return { Valid: true, Thumbprint: await ComputeJwkThumbprint(jwk) };
82
+ }
83
+ /**
84
+ * Narrows a value (e.g. a JWK a client sent) to a public P-256 JWK, keeping only the members RFC 7638 hashes;
85
+ * null for anything else, including a JWK that carries a private key.
86
+ */
87
+ export function ToProofPublicJwk(value) {
88
+ if (typeof value !== 'object' || value === null) {
89
+ return null;
90
+ }
91
+ const jwk = value;
92
+ if (jwk.kty !== 'EC' || jwk.crv !== 'P-256' || typeof jwk.x !== 'string' || typeof jwk.y !== 'string' || 'd' in jwk) {
93
+ return null;
94
+ }
95
+ return { kty: 'EC', crv: 'P-256', x: jwk.x, y: jwk.y };
96
+ }
97
+ async function verifySignature(jwk, signingInput, signature) {
98
+ try {
99
+ const subtle = requireSubtle();
100
+ const publicKey = await subtle.importKey('jwk', { ...jwk, ext: true }, KEY_ALGORITHM, false, ['verify']);
101
+ return await subtle.verify(SIGN_ALGORITHM, publicKey, signature, new TextEncoder().encode(signingInput));
102
+ }
103
+ catch {
104
+ // An unimportable key (e.g. coordinates off the curve) is the same as a bad signature: the proof is refused.
105
+ return false;
106
+ }
107
+ }
108
+ function pathEndsWith(url, suffix) {
109
+ if (typeof url !== 'string') {
110
+ return false;
111
+ }
112
+ try {
113
+ return new URL(url).pathname.endsWith(suffix);
114
+ }
115
+ catch {
116
+ return false;
117
+ }
118
+ }
119
+ async function sha256Base64Url(text) {
120
+ const digest = await requireSubtle().digest('SHA-256', new TextEncoder().encode(text));
121
+ return bytesToBase64Url(new Uint8Array(digest));
122
+ }
123
+ function encodeJsonPart(value) {
124
+ return bytesToBase64Url(new TextEncoder().encode(JSON.stringify(value)));
125
+ }
126
+ function decodeJsonPart(part) {
127
+ const bytes = decodeBase64Url(part);
128
+ if (!bytes) {
129
+ return null;
130
+ }
131
+ try {
132
+ const parsed = JSON.parse(new TextDecoder().decode(bytes));
133
+ return typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed) ? parsed : null;
134
+ }
135
+ catch {
136
+ return null;
137
+ }
138
+ }
139
+ function bytesToBase64Url(bytes) {
140
+ return BytesToBase64(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
141
+ }
142
+ function decodeBase64Url(part) {
143
+ if (!/^[A-Za-z0-9_-]+$/.test(part)) {
144
+ return null;
145
+ }
146
+ const base64 = part.replace(/-/g, '+').replace(/_/g, '/');
147
+ try {
148
+ return Base64ToBytes(base64 + '='.repeat((4 - (base64.length % 4)) % 4));
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ function requireCrypto() {
155
+ if (typeof crypto === 'undefined' || !crypto.subtle) {
156
+ throw new Error('Web Crypto API not available: proof of possession needs a secure context. Serve the page over HTTPS (or from localhost).');
157
+ }
158
+ return crypto;
159
+ }
160
+ function requireSubtle() {
161
+ return requireCrypto().subtle;
162
+ }
163
+ //# sourceMappingURL=ProofOfPossession.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ProofOfPossession.js","sourceRoot":"","sources":["../../src/util/ProofOfPossession.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAsDhE,MAAM,UAAU,GAAG,UAAU,CAAC;AAC9B,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,aAAa,GAAmB,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AAC7E,MAAM,cAAc,GAAgB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAEvE,0GAA0G;AAC1G,MAAM,CAAC,KAAK,UAAU,0BAA0B;IAC5C,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,aAAa,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;IAChF,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAC7C,IAAI,CAAC,SAAS,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,qFAAqF,CAAC,CAAC;IAC3G,CAAC;IACD,OAAO,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;AACjE,CAAC;AAED,4GAA4G;AAC5G,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,GAAmB;IAC1D,wFAAwF;IACxF,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IACrF,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC;AACtC,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,GAAyB,EAAE,OAAiC;IACzG,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,UAAU,EAAE,GAAG,EAAE,eAAe,EAAE,GAAG,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;IAC7E,MAAM,OAAO,GAAG;QACZ,GAAG,EAAE,OAAO,CAAC,MAAM;QACnB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QACxD,GAAG,EAAE,gBAAgB,CAAC,aAAa,EAAE,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1E,GAAG,EAAE,MAAM,eAAe,CAAC,OAAO,CAAC,WAAW,CAAC;KAClD,CAAC;IACF,MAAM,YAAY,GAAG,GAAG,cAAc,CAAC,MAAM,CAAC,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;IAC5E,MAAM,SAAS,GAAG,MAAM,aAAa,EAAE,CAAC,IAAI,CAAC,cAAc,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;IACrH,OAAO,GAAG,YAAY,IAAI,gBAAgB,CAAC,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAAC,KAAa,EAAE,QAAsC;IAClG,MAAM,KAAK,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,MAAM,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM,IAAI,CAAC,OAAO,IAAI,CAAC,SAAS,IAAI,MAAM,CAAC,GAAG,KAAK,UAAU,IAAI,MAAM,CAAC,GAAG,KAAK,eAAe,EAAE,CAAC;QACnG,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,GAAG,GAAG,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACjD,CAAC;IACD,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAChF,IAAI,CAAC,MAAM,EAAE,CAAC;QACV,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACrD,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACrF,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IACxD,CAAC;IACD,IAAI,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,cAAc,EAAE,CAAC;QAC3G,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,eAAe,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IACtD,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC3C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,KAAK,OAAO,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK,QAAQ,IAAI,OAAO,GAAG,CAAC,CAAC,KAAK,QAAQ,IAAI,GAAG,IAAI,GAAG,EAAE,CAAC;QAClH,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,GAAmB,EAAE,YAAoB,EAAE,SAAqB;IAC3F,IAAI,CAAC;QACD,MAAM,MAAM,GAAG,aAAa,EAAE,CAAC;QAC/B,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QACzG,OAAO,MAAM,MAAM,CAAC,MAAM,CAAC,cAAc,EAAE,SAAS,EAAE,SAAyB,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC;IAC7H,CAAC;IAAC,MAAM,CAAC;QACL,6GAA6G;QAC7G,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CAAC,GAAY,EAAE,MAAc;IAC9C,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC;IACjB,CAAC;IACD,IAAI,CAAC;QACD,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,KAAK,CAAC;IACjB,CAAC;AACL,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,IAAY;IACvC,MAAM,MAAM,GAAG,MAAM,aAAa,EAAE,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IACvF,OAAO,gBAAgB,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACjC,OAAO,gBAAgB,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAChC,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACT,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,IAAI,CAAC;QACD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAE,MAAkC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChI,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAS,gBAAgB,CAAC,KAAiB;IACvC,OAAO,aAAa,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;AAC3F,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACjC,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC1D,IAAI,CAAC;QACD,OAAO,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED,SAAS,aAAa;IAClB,IAAI,OAAO,MAAM,KAAK,WAAW,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,0HAA0H,CAAC,CAAC;IAChJ,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED,SAAS,aAAa;IAClB,OAAO,aAAa,EAAE,CAAC,MAAM,CAAC;AAClC,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @fileoverview Cache keys for in-process caches that hold user-scoped data.
3
+ *
4
+ * Data loaded under a user's permissions (row-level filters, field-level security, entity
5
+ * permissions) differs from one user to the next. An in-process cache that stores such data must
6
+ * therefore key every entry by the user it was loaded for; a key without a user hands the first
7
+ * caller's rows to everyone who asks next. These helpers make that segment uniform across caches.
8
+ *
9
+ * The user ID is the segment: it is always correct, at the cost of one entry per user. Sharing an
10
+ * entry between users with identical permissions is a valid optimization only when the key encodes
11
+ * everything that narrows the load (role filters, API-key ceilings, denied fields) — that is the
12
+ * job of the RunView cache's own fingerprint, not of an ad-hoc cache.
13
+ *
14
+ * @module @memberjunction/global/UserScopedCacheKey
15
+ */
16
+ /**
17
+ * Returns the cache key segment that identifies the user an entry was loaded for, or `null` when
18
+ * there is no user. A `null` result means the data must not be cached at all, because any key built
19
+ * without a user would be shared by every caller.
20
+ *
21
+ * @param userID - the ID of the user the data was loaded for
22
+ * @returns `u:<normalized user ID>`, or `null` when `userID` is empty
23
+ *
24
+ * @example
25
+ * UserCacheKeySegment('A1B2C3D4-E5F6-7890-ABCD-EF1234567890') // 'u:a1b2c3d4-e5f6-7890-abcd-ef1234567890'
26
+ * UserCacheKeySegment(undefined) // null
27
+ */
28
+ export declare function UserCacheKeySegment(userID: string | null | undefined): string | null;
29
+ /**
30
+ * Builds a cache key for user-scoped data from the entry's own key parts plus the user segment
31
+ * ({@link UserCacheKeySegment}). Returns `null` when there is no user; callers must then skip the
32
+ * cache for both read and write.
33
+ *
34
+ * @param userID - the ID of the user the data was loaded for
35
+ * @param parts - the parts that identify the entry apart from the user, in a fixed order
36
+ * @returns the parts and the user segment joined with `:`, or `null` when `userID` is empty
37
+ *
38
+ * @example
39
+ * UserScopedCacheKey(contextUser.ID, agentID, sourceName) // '<agentID>:<sourceName>:u:<userID>'
40
+ */
41
+ export declare function UserScopedCacheKey(userID: string | null | undefined, ...parts: string[]): string | null;
42
+ //# sourceMappingURL=UserScopedCacheKey.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserScopedCacheKey.d.ts","sourceRoot":"","sources":["../../src/util/UserScopedCacheKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGpF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAGvG"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @fileoverview Cache keys for in-process caches that hold user-scoped data.
3
+ *
4
+ * Data loaded under a user's permissions (row-level filters, field-level security, entity
5
+ * permissions) differs from one user to the next. An in-process cache that stores such data must
6
+ * therefore key every entry by the user it was loaded for; a key without a user hands the first
7
+ * caller's rows to everyone who asks next. These helpers make that segment uniform across caches.
8
+ *
9
+ * The user ID is the segment: it is always correct, at the cost of one entry per user. Sharing an
10
+ * entry between users with identical permissions is a valid optimization only when the key encodes
11
+ * everything that narrows the load (role filters, API-key ceilings, denied fields) — that is the
12
+ * job of the RunView cache's own fingerprint, not of an ad-hoc cache.
13
+ *
14
+ * @module @memberjunction/global/UserScopedCacheKey
15
+ */
16
+ import { NormalizeUUID } from './UUIDUtils.js';
17
+ /**
18
+ * Returns the cache key segment that identifies the user an entry was loaded for, or `null` when
19
+ * there is no user. A `null` result means the data must not be cached at all, because any key built
20
+ * without a user would be shared by every caller.
21
+ *
22
+ * @param userID - the ID of the user the data was loaded for
23
+ * @returns `u:<normalized user ID>`, or `null` when `userID` is empty
24
+ *
25
+ * @example
26
+ * UserCacheKeySegment('A1B2C3D4-E5F6-7890-ABCD-EF1234567890') // 'u:a1b2c3d4-e5f6-7890-abcd-ef1234567890'
27
+ * UserCacheKeySegment(undefined) // null
28
+ */
29
+ export function UserCacheKeySegment(userID) {
30
+ const normalized = NormalizeUUID(userID);
31
+ return normalized.length > 0 ? `u:${normalized}` : null;
32
+ }
33
+ /**
34
+ * Builds a cache key for user-scoped data from the entry's own key parts plus the user segment
35
+ * ({@link UserCacheKeySegment}). Returns `null` when there is no user; callers must then skip the
36
+ * cache for both read and write.
37
+ *
38
+ * @param userID - the ID of the user the data was loaded for
39
+ * @param parts - the parts that identify the entry apart from the user, in a fixed order
40
+ * @returns the parts and the user segment joined with `:`, or `null` when `userID` is empty
41
+ *
42
+ * @example
43
+ * UserScopedCacheKey(contextUser.ID, agentID, sourceName) // '<agentID>:<sourceName>:u:<userID>'
44
+ */
45
+ export function UserScopedCacheKey(userID, ...parts) {
46
+ const segment = UserCacheKeySegment(userID);
47
+ return segment === null ? null : [...parts, segment].join(':');
48
+ }
49
+ //# sourceMappingURL=UserScopedCacheKey.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UserScopedCacheKey.js","sourceRoot":"","sources":["../../src/util/UserScopedCacheKey.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAiC;IACjE,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,UAAU,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAiC,EAAE,GAAG,KAAe;IACpF,MAAM,OAAO,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACnE,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@memberjunction/global",
3
3
  "type": "module",
4
- "version": "6.2.0-edge.5",
4
+ "version": "6.2.0-edge.6",
5
5
  "description": "MemberJunction: Global Object - Needed for ALL other MJ components",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",