@memberjunction/server 5.38.0 → 5.40.0

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 (180) hide show
  1. package/README.md +67 -0
  2. package/dist/apolloServer/index.d.ts +0 -8
  3. package/dist/apolloServer/index.d.ts.map +1 -1
  4. package/dist/apolloServer/index.js +61 -0
  5. package/dist/apolloServer/index.js.map +1 -1
  6. package/dist/auth/index.js +1 -1
  7. package/dist/auth/index.js.map +1 -1
  8. package/dist/auth/magicLink/MagicLinkKeys.d.ts +58 -0
  9. package/dist/auth/magicLink/MagicLinkKeys.d.ts.map +1 -0
  10. package/dist/auth/magicLink/MagicLinkKeys.js +99 -0
  11. package/dist/auth/magicLink/MagicLinkKeys.js.map +1 -0
  12. package/dist/auth/magicLink/MagicLinkRouter.d.ts +33 -0
  13. package/dist/auth/magicLink/MagicLinkRouter.d.ts.map +1 -0
  14. package/dist/auth/magicLink/MagicLinkRouter.js +184 -0
  15. package/dist/auth/magicLink/MagicLinkRouter.js.map +1 -0
  16. package/dist/auth/magicLink/MagicLinkService.d.ts +123 -0
  17. package/dist/auth/magicLink/MagicLinkService.d.ts.map +1 -0
  18. package/dist/auth/magicLink/MagicLinkService.js +605 -0
  19. package/dist/auth/magicLink/MagicLinkService.js.map +1 -0
  20. package/dist/auth/magicLink/index.d.ts +6 -0
  21. package/dist/auth/magicLink/index.d.ts.map +1 -0
  22. package/dist/auth/magicLink/index.js +6 -0
  23. package/dist/auth/magicLink/index.js.map +1 -0
  24. package/dist/auth/magicLink/magicLinkCore.d.ts +82 -0
  25. package/dist/auth/magicLink/magicLinkCore.d.ts.map +1 -0
  26. package/dist/auth/magicLink/magicLinkCore.js +164 -0
  27. package/dist/auth/magicLink/magicLinkCore.js.map +1 -0
  28. package/dist/auth/magicLink/redeemLanding.d.ts +22 -0
  29. package/dist/auth/magicLink/redeemLanding.d.ts.map +1 -0
  30. package/dist/auth/magicLink/redeemLanding.js +61 -0
  31. package/dist/auth/magicLink/redeemLanding.js.map +1 -0
  32. package/dist/auth/magicLink/types.d.ts +131 -0
  33. package/dist/auth/magicLink/types.d.ts.map +1 -0
  34. package/dist/auth/magicLink/types.js +6 -0
  35. package/dist/auth/magicLink/types.js.map +1 -0
  36. package/dist/config.d.ts +479 -0
  37. package/dist/config.d.ts.map +1 -1
  38. package/dist/config.js +84 -0
  39. package/dist/config.js.map +1 -1
  40. package/dist/context.d.ts.map +1 -1
  41. package/dist/context.js +223 -19
  42. package/dist/context.js.map +1 -1
  43. package/dist/generated/generated.d.ts +952 -4
  44. package/dist/generated/generated.d.ts.map +1 -1
  45. package/dist/generated/generated.js +25663 -20321
  46. package/dist/generated/generated.js.map +1 -1
  47. package/dist/generic/FireAndForgetHeartbeat.d.ts +51 -0
  48. package/dist/generic/FireAndForgetHeartbeat.d.ts.map +1 -0
  49. package/dist/generic/FireAndForgetHeartbeat.js +44 -0
  50. package/dist/generic/FireAndForgetHeartbeat.js.map +1 -0
  51. package/dist/generic/ResolverBase.d.ts.map +1 -1
  52. package/dist/generic/ResolverBase.js +35 -7
  53. package/dist/generic/ResolverBase.js.map +1 -1
  54. package/dist/index.d.ts +5 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +145 -2
  57. package/dist/index.js.map +1 -1
  58. package/dist/logging/NoLog.d.ts +50 -0
  59. package/dist/logging/NoLog.d.ts.map +1 -0
  60. package/dist/logging/NoLog.js +80 -0
  61. package/dist/logging/NoLog.js.map +1 -0
  62. package/dist/logging/bootAudit.d.ts +43 -0
  63. package/dist/logging/bootAudit.d.ts.map +1 -0
  64. package/dist/logging/bootAudit.js +83 -0
  65. package/dist/logging/bootAudit.js.map +1 -0
  66. package/dist/logging/boundaryLogPayload.d.ts +18 -0
  67. package/dist/logging/boundaryLogPayload.d.ts.map +1 -0
  68. package/dist/logging/boundaryLogPayload.js +18 -0
  69. package/dist/logging/boundaryLogPayload.js.map +1 -0
  70. package/dist/logging/secretRedactor.d.ts +23 -0
  71. package/dist/logging/secretRedactor.d.ts.map +1 -0
  72. package/dist/logging/secretRedactor.js +53 -0
  73. package/dist/logging/secretRedactor.js.map +1 -0
  74. package/dist/logging/shortenForLog.d.ts +8 -0
  75. package/dist/logging/shortenForLog.d.ts.map +1 -0
  76. package/dist/logging/shortenForLog.js +21 -0
  77. package/dist/logging/shortenForLog.js.map +1 -0
  78. package/dist/logging/variablesLoggingMiddleware.d.ts +22 -0
  79. package/dist/logging/variablesLoggingMiddleware.d.ts.map +1 -0
  80. package/dist/logging/variablesLoggingMiddleware.js +127 -0
  81. package/dist/logging/variablesLoggingMiddleware.js.map +1 -0
  82. package/dist/resolvers/CurrentUserContextResolver.d.ts +9 -3
  83. package/dist/resolvers/CurrentUserContextResolver.d.ts.map +1 -1
  84. package/dist/resolvers/CurrentUserContextResolver.js +19 -5
  85. package/dist/resolvers/CurrentUserContextResolver.js.map +1 -1
  86. package/dist/resolvers/EntityResolver.d.ts.map +1 -1
  87. package/dist/resolvers/EntityResolver.js +13 -2
  88. package/dist/resolvers/EntityResolver.js.map +1 -1
  89. package/dist/resolvers/GenerateSeedTaxonomyResolver.d.ts +28 -0
  90. package/dist/resolvers/GenerateSeedTaxonomyResolver.d.ts.map +1 -0
  91. package/dist/resolvers/GenerateSeedTaxonomyResolver.js +100 -0
  92. package/dist/resolvers/GenerateSeedTaxonomyResolver.js.map +1 -0
  93. package/dist/resolvers/GetDataResolver.d.ts.map +1 -1
  94. package/dist/resolvers/GetDataResolver.js +8 -4
  95. package/dist/resolvers/GetDataResolver.js.map +1 -1
  96. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts +259 -2
  97. package/dist/resolvers/IntegrationDiscoveryResolver.d.ts.map +1 -1
  98. package/dist/resolvers/IntegrationDiscoveryResolver.js +1276 -117
  99. package/dist/resolvers/IntegrationDiscoveryResolver.js.map +1 -1
  100. package/dist/resolvers/IntegrationProgressResolver.d.ts +90 -0
  101. package/dist/resolvers/IntegrationProgressResolver.d.ts.map +1 -0
  102. package/dist/resolvers/IntegrationProgressResolver.js +196 -0
  103. package/dist/resolvers/IntegrationProgressResolver.js.map +1 -0
  104. package/dist/resolvers/MCPResolver.d.ts.map +1 -1
  105. package/dist/resolvers/MCPResolver.js +19 -14
  106. package/dist/resolvers/MCPResolver.js.map +1 -1
  107. package/dist/resolvers/RunAIAgentResolver.d.ts.map +1 -1
  108. package/dist/resolvers/RunAIAgentResolver.js +26 -5
  109. package/dist/resolvers/RunAIAgentResolver.js.map +1 -1
  110. package/dist/resolvers/RunClusterAnalysisResolver.d.ts +74 -0
  111. package/dist/resolvers/RunClusterAnalysisResolver.d.ts.map +1 -0
  112. package/dist/resolvers/RunClusterAnalysisResolver.js +243 -0
  113. package/dist/resolvers/RunClusterAnalysisResolver.js.map +1 -0
  114. package/dist/resolvers/RunTestResolver.d.ts.map +1 -1
  115. package/dist/resolvers/RunTestResolver.js +12 -2
  116. package/dist/resolvers/RunTestResolver.js.map +1 -1
  117. package/dist/resolvers/SearchEntitiesResolver.d.ts +46 -0
  118. package/dist/resolvers/SearchEntitiesResolver.d.ts.map +1 -0
  119. package/dist/resolvers/SearchEntitiesResolver.js +216 -0
  120. package/dist/resolvers/SearchEntitiesResolver.js.map +1 -0
  121. package/dist/resolvers/UserResolver.d.ts +16 -2
  122. package/dist/resolvers/UserResolver.d.ts.map +1 -1
  123. package/dist/resolvers/UserResolver.js +45 -2
  124. package/dist/resolvers/UserResolver.js.map +1 -1
  125. package/dist/resolvers/VectorizeEntityResolver.d.ts.map +1 -1
  126. package/dist/resolvers/VectorizeEntityResolver.js +14 -1
  127. package/dist/resolvers/VectorizeEntityResolver.js.map +1 -1
  128. package/dist/rest/SignatureWebhookHandler.d.ts +19 -0
  129. package/dist/rest/SignatureWebhookHandler.d.ts.map +1 -0
  130. package/dist/rest/SignatureWebhookHandler.js +86 -0
  131. package/dist/rest/SignatureWebhookHandler.js.map +1 -0
  132. package/dist/services/ScheduledJobsService.d.ts.map +1 -1
  133. package/dist/services/ScheduledJobsService.js +14 -2
  134. package/dist/services/ScheduledJobsService.js.map +1 -1
  135. package/package.json +79 -74
  136. package/src/__tests__/NoLog.test.ts +76 -0
  137. package/src/__tests__/bootAudit.test.ts +188 -0
  138. package/src/__tests__/boundaryLogPayload.test.ts +31 -0
  139. package/src/__tests__/getDataTokenRedaction.test.ts +84 -0
  140. package/src/__tests__/magicLink.test.ts +387 -0
  141. package/src/__tests__/secretRedactor.test.ts +163 -0
  142. package/src/__tests__/subscriptionRedaction.test.ts +217 -0
  143. package/src/apolloServer/index.ts +58 -0
  144. package/src/auth/index.ts +2 -2
  145. package/src/auth/magicLink/MagicLinkKeys.ts +122 -0
  146. package/src/auth/magicLink/MagicLinkRouter.ts +209 -0
  147. package/src/auth/magicLink/MagicLinkService.ts +724 -0
  148. package/src/auth/magicLink/index.ts +17 -0
  149. package/src/auth/magicLink/magicLinkCore.ts +216 -0
  150. package/src/auth/magicLink/redeemLanding.ts +62 -0
  151. package/src/auth/magicLink/types.ts +137 -0
  152. package/src/config.ts +89 -0
  153. package/src/context.ts +249 -17
  154. package/src/generated/generated.ts +12528 -8866
  155. package/src/generic/FireAndForgetHeartbeat.ts +85 -0
  156. package/src/generic/ResolverBase.ts +35 -7
  157. package/src/generic/__tests__/FireAndForgetHeartbeat.test.ts +99 -0
  158. package/src/index.ts +165 -2
  159. package/src/logging/NoLog.ts +88 -0
  160. package/src/logging/bootAudit.ts +117 -0
  161. package/src/logging/boundaryLogPayload.ts +19 -0
  162. package/src/logging/secretRedactor.ts +82 -0
  163. package/src/logging/shortenForLog.ts +17 -0
  164. package/src/logging/variablesLoggingMiddleware.ts +191 -0
  165. package/src/resolvers/CurrentUserContextResolver.ts +21 -5
  166. package/src/resolvers/EntityResolver.ts +17 -5
  167. package/src/resolvers/GenerateSeedTaxonomyResolver.ts +90 -0
  168. package/src/resolvers/GetDataResolver.ts +9 -5
  169. package/src/resolvers/IntegrationDiscoveryResolver.ts +1111 -120
  170. package/src/resolvers/IntegrationProgressResolver.ts +220 -0
  171. package/src/resolvers/MCPResolver.ts +18 -14
  172. package/src/resolvers/RunAIAgentResolver.ts +28 -5
  173. package/src/resolvers/RunClusterAnalysisResolver.ts +249 -0
  174. package/src/resolvers/RunTestResolver.ts +14 -2
  175. package/src/resolvers/SearchEntitiesResolver.ts +173 -0
  176. package/src/resolvers/UserResolver.ts +38 -2
  177. package/src/resolvers/VectorizeEntityResolver.ts +14 -1
  178. package/src/resolvers/__tests__/IntegrationProgressResolver.test.ts +170 -0
  179. package/src/rest/SignatureWebhookHandler.ts +103 -0
  180. package/src/services/ScheduledJobsService.ts +15 -2
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export { generateRawToken, hashToken, evaluateInvite, buildSessionClaims, MAGIC_LINK_TOKEN_PREFIX, type InviteEvaluationInput, } from './magicLinkCore.js';
3
+ export { MagicLinkKeyManager } from './MagicLinkKeys.js';
4
+ export { MagicLinkService } from './MagicLinkService.js';
5
+ export { createMagicLinkHandler, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT_PATH, MAGIC_LINK_JWKS_PATH, } from './MagicLinkRouter.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/magicLink/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,uBAAuB,EACvB,KAAK,qBAAqB,GAC3B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export * from './types.js';
2
+ export { generateRawToken, hashToken, evaluateInvite, buildSessionClaims, MAGIC_LINK_TOKEN_PREFIX, } from './magicLinkCore.js';
3
+ export { MagicLinkKeyManager } from './MagicLinkKeys.js';
4
+ export { MagicLinkService } from './MagicLinkService.js';
5
+ export { createMagicLinkHandler, registerMagicLinkAuthProvider, MAGIC_LINK_MOUNT_PATH, MAGIC_LINK_JWKS_PATH, } from './MagicLinkRouter.js';
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/magicLink/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EACL,gBAAgB,EAChB,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,uBAAuB,GAExB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EACL,sBAAsB,EACtB,6BAA6B,EAC7B,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * @fileoverview Pure functional core for magic links — no DB, no email, no MJ
3
+ * runtime imports. Everything here is deterministic given its inputs (modulo
4
+ * crypto randomness) and unit-testable with plain assertions.
5
+ *
6
+ * @module @memberjunction/server/auth/magicLink
7
+ */
8
+ import type { MagicLinkJWTClaims, MagicLinkScopeEntry, RedeemErrorCode } from './types.js';
9
+ /** Token prefix, mirroring the API-key convention (`mj_sk_`). */
10
+ export declare const MAGIC_LINK_TOKEN_PREFIX = "mj_ml_";
11
+ /** Generates a cryptographically random raw magic-link token. */
12
+ export declare function generateRawToken(): string;
13
+ /** Generates an opaque per-session id (anonymous-session forensics correlation). */
14
+ export declare function generateSessionId(): string;
15
+ /**
16
+ * SHA-256 hash of a raw token, base64url-encoded — only the hash is ever
17
+ * persisted. base64url (43 chars) is shorter than hex (64 chars), URL-safe, and
18
+ * carries the full 256 bits. Both write (CreateInvite) and read (RedeemInvite)
19
+ * paths call this, so the encoding stays internally consistent.
20
+ */
21
+ export declare function hashToken(rawToken: string): string;
22
+ /** Minimal shape of an invite needed for redemption eligibility. */
23
+ export interface InviteEvaluationInput {
24
+ Status: string;
25
+ ExpiresAt: Date | string;
26
+ MaxUses: number;
27
+ UseCount: number;
28
+ }
29
+ /**
30
+ * Pure authorization check for WHO may issue magic-link invites.
31
+ *
32
+ * Owners are always allowed (Owner is MJ's superuser type). Otherwise the caller
33
+ * must be a member of one of `issuerRoleNames`. An empty `issuerRoleNames` means
34
+ * Owner-only — the secure default. This is what stops any authenticated user
35
+ * (including an external user already holding a restricted magic-link session)
36
+ * from minting invites.
37
+ */
38
+ export declare function canIssueInvites(userType: string | null | undefined, userRoleNames: readonly string[], issuerRoleNames: readonly string[]): boolean;
39
+ /**
40
+ * Pure check for WHAT role an invite may grant. The restricted role is always
41
+ * grantable; any other role must be explicitly listed in `grantableRoleNames`.
42
+ * Applied to every caller (Owners included) so a privileged role can never be
43
+ * attached to an external magic-link user unless the deployment opts in.
44
+ */
45
+ export declare function isRoleGrantable(roleName: string | null | undefined, restrictedRoleName: string, grantableRoleNames: readonly string[]): boolean;
46
+ /** Pure redemption-eligibility check. Returns ok + an error code when not. */
47
+ export declare function evaluateInvite(invite: InviteEvaluationInput, nowMs: number): {
48
+ ok: boolean;
49
+ errorCode?: RedeemErrorCode;
50
+ };
51
+ export declare function buildConsumeInviteSQL(qualifiedTable: string): string;
52
+ /**
53
+ * Pure scope-union: appends `next` to `prior` unless an entry from the same invite
54
+ * is already present (dedup by inviteId). This is how a session accumulates the union
55
+ * of scopes across multiple redeemed links — carried in the re-minted JWT, never as
56
+ * roles on a shared user, so anonymous sessions can't accrete into a superuser.
57
+ */
58
+ export declare function unionScopes(prior: readonly MagicLinkScopeEntry[] | undefined, next: MagicLinkScopeEntry): MagicLinkScopeEntry[];
59
+ /** Builds the session-token claims (pure). */
60
+ export declare function buildSessionClaims(args: {
61
+ issuer: string;
62
+ audience: string;
63
+ inviteId: string;
64
+ email: string;
65
+ firstName?: string;
66
+ lastName?: string;
67
+ applicationId: string;
68
+ roleName: string;
69
+ invitedByUserId?: string;
70
+ /** True for anonymous sessions — the server enforces scope from mj_scopes, not roles. */
71
+ anonymous?: boolean;
72
+ /** Opaque per-session id (anonymous forensics correlation). */
73
+ sessionId?: string;
74
+ /** Prior session's scope union to carry forward (multi-link anonymous sessions). */
75
+ priorScopes?: readonly MagicLinkScopeEntry[];
76
+ /** Resource-share/embed scope for this link's entry. */
77
+ resourceType?: string;
78
+ resourceId?: string;
79
+ nowSeconds: number;
80
+ ttlSeconds: number;
81
+ }): MagicLinkJWTClaims;
82
+ //# sourceMappingURL=magicLinkCore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magicLinkCore.d.ts","sourceRoot":"","sources":["../../../src/auth/magicLink/magicLinkCore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAE3F,iEAAiE;AACjE,eAAO,MAAM,uBAAuB,WAAW,CAAC;AAEhD,iEAAiE;AACjE,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,oEAAoE;AACpE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,IAAI,GAAG,MAAM,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAOD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,eAAe,EAAE,SAAS,MAAM,EAAE,GACjC,OAAO,CAST;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACnC,kBAAkB,EAAE,MAAM,EAC1B,kBAAkB,EAAE,SAAS,MAAM,EAAE,GACpC,OAAO,CAOT;AAED,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,qBAAqB,EAAE,KAAK,EAAE,MAAM,GAAG;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,eAAe,CAAA;CAAE,CAezH;AA8BD,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAcpE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,mBAAmB,EAAE,GAAG,SAAS,EAAE,IAAI,EAAE,mBAAmB,GAAG,mBAAmB,EAAE,CAM/H;AAED,8CAA8C;AAC9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,yFAAyF;IACzF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,+DAA+D;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,WAAW,CAAC,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAC7C,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,kBAAkB,CA2BrB"}
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @fileoverview Pure functional core for magic links — no DB, no email, no MJ
3
+ * runtime imports. Everything here is deterministic given its inputs (modulo
4
+ * crypto randomness) and unit-testable with plain assertions.
5
+ *
6
+ * @module @memberjunction/server/auth/magicLink
7
+ */
8
+ import { randomBytes, createHash } from 'node:crypto';
9
+ /** Token prefix, mirroring the API-key convention (`mj_sk_`). */
10
+ export const MAGIC_LINK_TOKEN_PREFIX = 'mj_ml_';
11
+ /** Generates a cryptographically random raw magic-link token. */
12
+ export function generateRawToken() {
13
+ return MAGIC_LINK_TOKEN_PREFIX + randomBytes(32).toString('hex');
14
+ }
15
+ /** Generates an opaque per-session id (anonymous-session forensics correlation). */
16
+ export function generateSessionId() {
17
+ return randomBytes(16).toString('base64url');
18
+ }
19
+ /**
20
+ * SHA-256 hash of a raw token, base64url-encoded — only the hash is ever
21
+ * persisted. base64url (43 chars) is shorter than hex (64 chars), URL-safe, and
22
+ * carries the full 256 bits. Both write (CreateInvite) and read (RedeemInvite)
23
+ * paths call this, so the encoding stays internally consistent.
24
+ */
25
+ export function hashToken(rawToken) {
26
+ return createHash('sha256').update(rawToken).digest('base64url');
27
+ }
28
+ /** Normalizes a role/name for case- and whitespace-insensitive comparison. */
29
+ function normalizeName(name) {
30
+ return name.trim().toLowerCase();
31
+ }
32
+ /**
33
+ * Pure authorization check for WHO may issue magic-link invites.
34
+ *
35
+ * Owners are always allowed (Owner is MJ's superuser type). Otherwise the caller
36
+ * must be a member of one of `issuerRoleNames`. An empty `issuerRoleNames` means
37
+ * Owner-only — the secure default. This is what stops any authenticated user
38
+ * (including an external user already holding a restricted magic-link session)
39
+ * from minting invites.
40
+ */
41
+ export function canIssueInvites(userType, userRoleNames, issuerRoleNames) {
42
+ if (normalizeName(userType ?? '') === 'owner') {
43
+ return true;
44
+ }
45
+ const allowed = new Set(issuerRoleNames.map(normalizeName));
46
+ if (allowed.size === 0) {
47
+ return false;
48
+ }
49
+ return userRoleNames.some((r) => allowed.has(normalizeName(r)));
50
+ }
51
+ /**
52
+ * Pure check for WHAT role an invite may grant. The restricted role is always
53
+ * grantable; any other role must be explicitly listed in `grantableRoleNames`.
54
+ * Applied to every caller (Owners included) so a privileged role can never be
55
+ * attached to an external magic-link user unless the deployment opts in.
56
+ */
57
+ export function isRoleGrantable(roleName, restrictedRoleName, grantableRoleNames) {
58
+ const target = normalizeName(roleName ?? '');
59
+ if (!target) {
60
+ return false;
61
+ }
62
+ const allowed = new Set([normalizeName(restrictedRoleName), ...grantableRoleNames.map(normalizeName)]);
63
+ return allowed.has(target);
64
+ }
65
+ /** Pure redemption-eligibility check. Returns ok + an error code when not. */
66
+ export function evaluateInvite(invite, nowMs) {
67
+ if (invite.Status === 'Revoked') {
68
+ return { ok: false, errorCode: 'revoked' };
69
+ }
70
+ const expiresMs = new Date(invite.ExpiresAt).getTime();
71
+ if (Number.isFinite(expiresMs) && expiresMs <= nowMs) {
72
+ return { ok: false, errorCode: 'expired' };
73
+ }
74
+ if (invite.Status === 'Consumed' || invite.UseCount >= invite.MaxUses) {
75
+ return { ok: false, errorCode: 'consumed' };
76
+ }
77
+ if (invite.Status !== 'Active') {
78
+ return { ok: false, errorCode: 'invalid' };
79
+ }
80
+ return { ok: true };
81
+ }
82
+ /**
83
+ * Builds the atomic compare-and-swap UPDATE that consumes one use of an invite.
84
+ *
85
+ * The WHERE clause re-checks every eligibility condition at the DB level, so the
86
+ * increment and the guard are a single atomic operation: concurrent redemptions
87
+ * of a single-use link race on the row and exactly one matches (the matched row
88
+ * is returned via OUTPUT). This is what actually enforces single-use — the
89
+ * JS-side `evaluateInvite` is only a friendly pre-check.
90
+ *
91
+ * The invite ID MUST be bound as parameter `@p0` by the caller — it is never
92
+ * interpolated into the string, so this builder is injection-safe regardless of
93
+ * the ID's contents.
94
+ *
95
+ * OUTPUT goes `INTO` a table variable (not a bare OUTPUT): SQL Server forbids a
96
+ * bare OUTPUT clause on a table that has enabled triggers, and CodeGen adds an
97
+ * `__mj_UpdatedAt` trigger to every MJ table. The trailing SELECT returns exactly
98
+ * the matched row(s) so the caller can detect a win (exactly one row) regardless
99
+ * of trigger behavior.
100
+ *
101
+ * `qualifiedTable` is asserted to be a bracket-quoted `[schema].[table]` (word
102
+ * chars only) before interpolation. The caller derives it from `EntityInfo`
103
+ * (never user input), so this is defense-in-depth: even a future careless caller
104
+ * cannot turn this into an injection vector — a non-conforming table throws.
105
+ *
106
+ * @param qualifiedTable bracket-quoted `[schema].[table]` for MagicLinkInvite
107
+ */
108
+ const QUALIFIED_TABLE_PATTERN = /^\[\w+\]\.\[\w+\]$/;
109
+ export function buildConsumeInviteSQL(qualifiedTable) {
110
+ if (!QUALIFIED_TABLE_PATTERN.test(qualifiedTable)) {
111
+ throw new Error(`buildConsumeInviteSQL: refusing to build SQL for non-whitelisted table identifier '${qualifiedTable}'.`);
112
+ }
113
+ return (`DECLARE @consumed TABLE (ID UNIQUEIDENTIFIER); ` +
114
+ `UPDATE ${qualifiedTable} ` +
115
+ `SET UseCount = UseCount + 1, ` +
116
+ `ConsumedAt = COALESCE(ConsumedAt, SYSUTCDATETIME()), ` +
117
+ `Status = CASE WHEN UseCount + 1 >= MaxUses THEN 'Consumed' ELSE Status END ` +
118
+ `OUTPUT INSERTED.ID INTO @consumed ` +
119
+ `WHERE ID = @p0 AND Status = 'Active' AND UseCount < MaxUses AND ExpiresAt > SYSUTCDATETIME(); ` +
120
+ `SELECT ID FROM @consumed;`);
121
+ }
122
+ /**
123
+ * Pure scope-union: appends `next` to `prior` unless an entry from the same invite
124
+ * is already present (dedup by inviteId). This is how a session accumulates the union
125
+ * of scopes across multiple redeemed links — carried in the re-minted JWT, never as
126
+ * roles on a shared user, so anonymous sessions can't accrete into a superuser.
127
+ */
128
+ export function unionScopes(prior, next) {
129
+ const result = [...(prior ?? [])];
130
+ if (!result.some((s) => s.inviteId === next.inviteId)) {
131
+ result.push(next);
132
+ }
133
+ return result;
134
+ }
135
+ /** Builds the session-token claims (pure). */
136
+ export function buildSessionClaims(args) {
137
+ const name = [args.firstName, args.lastName].filter(Boolean).join(' ') || undefined;
138
+ const scopeEntry = {
139
+ inviteId: args.inviteId,
140
+ appId: args.applicationId,
141
+ role: args.roleName,
142
+ resourceType: args.resourceType,
143
+ resourceId: args.resourceId,
144
+ };
145
+ return {
146
+ iss: args.issuer,
147
+ aud: args.audience,
148
+ sub: `magic-link|${args.inviteId}`,
149
+ iat: args.nowSeconds,
150
+ exp: args.nowSeconds + args.ttlSeconds,
151
+ email: args.email,
152
+ given_name: args.firstName,
153
+ family_name: args.lastName,
154
+ name,
155
+ mj_app_id: args.applicationId,
156
+ mj_role: args.roleName,
157
+ mj_invited_by: args.invitedByUserId,
158
+ mj_scopes: unionScopes(args.priorScopes, scopeEntry),
159
+ mj_anon: args.anonymous ? true : undefined,
160
+ mj_sid: args.sessionId,
161
+ mj_magic_link: true,
162
+ };
163
+ }
164
+ //# sourceMappingURL=magicLinkCore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"magicLinkCore.js","sourceRoot":"","sources":["../../../src/auth/magicLink/magicLinkCore.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGtD,iEAAiE;AACjE,MAAM,CAAC,MAAM,uBAAuB,GAAG,QAAQ,CAAC;AAEhD,iEAAiE;AACjE,MAAM,UAAU,gBAAgB;IAC9B,OAAO,uBAAuB,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACnE,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,iBAAiB;IAC/B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;AACnE,CAAC;AAUD,8EAA8E;AAC9E,SAAS,aAAa,CAAC,IAAY;IACjC,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACnC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAmC,EACnC,aAAgC,EAChC,eAAkC;IAElC,IAAI,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC,KAAK,OAAO,EAAE,CAAC;QAC9C,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC;IAC5D,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAC7B,QAAmC,EACnC,kBAA0B,EAC1B,kBAAqC;IAErC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,CAAC,aAAa,CAAC,kBAAkB,CAAC,EAAE,GAAG,kBAAkB,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;IAC/G,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,cAAc,CAAC,MAA6B,EAAE,KAAa;IACzE,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC7C,CAAC;IACD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,CAAC;IACvD,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,KAAK,EAAE,CAAC;QACrD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC7C,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,UAAU,IAAI,MAAM,CAAC,QAAQ,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACtE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,uBAAuB,GAAG,oBAAoB,CAAC;AAErD,MAAM,UAAU,qBAAqB,CAAC,cAAsB;IAC1D,IAAI,CAAC,uBAAuB,CAAC,IAAI,CAAC,cAAc,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,sFAAsF,cAAc,IAAI,CAAC,CAAC;IAC5H,CAAC;IACD,OAAO,CACL,iDAAiD;QACjD,UAAU,cAAc,GAAG;QAC3B,+BAA+B;QAC/B,uDAAuD;QACvD,6EAA6E;QAC7E,oCAAoC;QACpC,gGAAgG;QAChG,2BAA2B,CAC5B,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAiD,EAAE,IAAyB;IACtG,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,kBAAkB,CAAC,IAqBlC;IACC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC;IACpF,MAAM,UAAU,GAAwB;QACtC,QAAQ,EAAE,IAAI,CAAC,QAAQ;QACvB,KAAK,EAAE,IAAI,CAAC,aAAa;QACzB,IAAI,EAAE,IAAI,CAAC,QAAQ;QACnB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,UAAU,EAAE,IAAI,CAAC,UAAU;KAC5B,CAAC;IACF,OAAO;QACL,GAAG,EAAE,IAAI,CAAC,MAAM;QAChB,GAAG,EAAE,IAAI,CAAC,QAAQ;QAClB,GAAG,EAAE,cAAc,IAAI,CAAC,QAAQ,EAAE;QAClC,GAAG,EAAE,IAAI,CAAC,UAAU;QACpB,GAAG,EAAE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU;QACtC,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,UAAU,EAAE,IAAI,CAAC,SAAS;QAC1B,WAAW,EAAE,IAAI,CAAC,QAAQ;QAC1B,IAAI;QACJ,SAAS,EAAE,IAAI,CAAC,aAAa;QAC7B,OAAO,EAAE,IAAI,CAAC,QAAQ;QACtB,aAAa,EAAE,IAAI,CAAC,eAAe;QACnC,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC;QACpD,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAC1C,MAAM,EAAE,IAAI,CAAC,SAAS;QACtB,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @fileoverview Pure HTML for the magic-link redemption interstitial. No DB, no
3
+ * MJ runtime imports — deterministic string in, string out.
4
+ * @module @memberjunction/server/auth/magicLink
5
+ */
6
+ /** Minimal HTML-escape for safe interpolation into attribute/text content. */
7
+ export declare function escapeHtml(s: string): string;
8
+ /**
9
+ * Builds the redemption landing page served by `GET /magic-link/redeem`.
10
+ *
11
+ * GET must be SAFE: link prefetchers, email security scanners, and browser
12
+ * preconnect routinely fetch URLs, and a side-effectful GET would let them burn
13
+ * the single-use token before the human ever clicks. So GET renders this page
14
+ * and the actual redemption happens only when the user submits the form (POST).
15
+ * The "Continue" button is NOT auto-submitted — a bot that merely loads the page
16
+ * (even one that executes JS) does not consume the token; a human gesture does.
17
+ *
18
+ * @param token the raw magic-link token (embedded in a hidden field)
19
+ * @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
20
+ */
21
+ export declare function buildRedeemLandingHtml(token: string, actionPath: string): string;
22
+ //# sourceMappingURL=redeemLanding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeemLanding.d.ts","sourceRoot":"","sources":["../../../src/auth/magicLink/redeemLanding.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAO5C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAgChF"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * @fileoverview Pure HTML for the magic-link redemption interstitial. No DB, no
3
+ * MJ runtime imports — deterministic string in, string out.
4
+ * @module @memberjunction/server/auth/magicLink
5
+ */
6
+ /** Minimal HTML-escape for safe interpolation into attribute/text content. */
7
+ export function escapeHtml(s) {
8
+ return s
9
+ .replace(/&/g, '&amp;')
10
+ .replace(/</g, '&lt;')
11
+ .replace(/>/g, '&gt;')
12
+ .replace(/"/g, '&quot;')
13
+ .replace(/'/g, '&#39;');
14
+ }
15
+ /**
16
+ * Builds the redemption landing page served by `GET /magic-link/redeem`.
17
+ *
18
+ * GET must be SAFE: link prefetchers, email security scanners, and browser
19
+ * preconnect routinely fetch URLs, and a side-effectful GET would let them burn
20
+ * the single-use token before the human ever clicks. So GET renders this page
21
+ * and the actual redemption happens only when the user submits the form (POST).
22
+ * The "Continue" button is NOT auto-submitted — a bot that merely loads the page
23
+ * (even one that executes JS) does not consume the token; a human gesture does.
24
+ *
25
+ * @param token the raw magic-link token (embedded in a hidden field)
26
+ * @param actionPath the same-origin path the form POSTs to (e.g. `/magic-link/redeem`)
27
+ */
28
+ export function buildRedeemLandingHtml(token, actionPath) {
29
+ const t = escapeHtml(token);
30
+ const action = escapeHtml(actionPath);
31
+ // Hardcoded colors are intentional here — this is a standalone server-rendered
32
+ // HTML response, not Angular component CSS, so the design-token rule doesn't apply.
33
+ return `<!doctype html>
34
+ <html lang="en">
35
+ <head>
36
+ <meta charset="utf-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ <meta name="robots" content="noindex, nofollow">
39
+ <title>Sign in</title>
40
+ <style>
41
+ body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; background:#f5f5f5; margin:0; display:flex; min-height:100vh; align-items:center; justify-content:center; }
42
+ .card { background:#fff; padding:2.5rem; border-radius:12px; box-shadow:0 1px 4px rgba(0,0,0,.12); max-width:24rem; text-align:center; }
43
+ h1 { font-size:1.25rem; margin:0 0 .5rem; color:#1f2937; }
44
+ p { color:#4b5563; margin:0 0 1.5rem; font-size:.95rem; }
45
+ button { background:#264FAF; color:#fff; border:0; border-radius:8px; padding:.75rem 1.5rem; font-size:1rem; cursor:pointer; width:100%; }
46
+ button:hover { background:#1e3f8c; }
47
+ </style>
48
+ </head>
49
+ <body>
50
+ <div class="card">
51
+ <h1>Sign in to continue</h1>
52
+ <p>Click below to securely sign in with your invitation link.</p>
53
+ <form method="POST" action="${action}">
54
+ <input type="hidden" name="token" value="${t}">
55
+ <button type="submit">Continue to sign in</button>
56
+ </form>
57
+ </div>
58
+ </body>
59
+ </html>`;
60
+ }
61
+ //# sourceMappingURL=redeemLanding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redeemLanding.js","sourceRoot":"","sources":["../../../src/auth/magicLink/redeemLanding.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,CAAS;IAClC,OAAO,CAAC;SACL,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,UAAkB;IACtE,MAAM,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IACtC,+EAA+E;IAC/E,oFAAoF;IACpF,OAAO;;;;;;;;;;;;;;;;;;;;kCAoByB,MAAM;iDACS,CAAC;;;;;QAK1C,CAAC;AACT,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * @fileoverview Types for the MemberJunction magic-link feature.
3
+ * @module @memberjunction/server/auth/magicLink
4
+ */
5
+ /**
6
+ * One unit of scope a magic-link session holds. The union of these across all links
7
+ * a session has redeemed is the anonymous-session authorization boundary (enforced
8
+ * server-side against the claims, never via roles on the shared Anonymous principal).
9
+ */
10
+ export interface MagicLinkScopeEntry {
11
+ /** The invite that granted this scope entry. */
12
+ inviteId: string;
13
+ /** The Application this entry grants access to. */
14
+ appId: string;
15
+ /** The role name this entry carries (email/app-session links). */
16
+ role?: string;
17
+ /** For resource-share/embed links: the shared resource's type + id. */
18
+ resourceType?: string;
19
+ resourceId?: string;
20
+ }
21
+ /**
22
+ * Claims carried by a MJ-issued magic-link session JWT.
23
+ * Identity claims (email/given_name/family_name) follow standard OIDC so the
24
+ * existing provider extraction works; the `mj_*` claims describe the scope.
25
+ */
26
+ export interface MagicLinkJWTClaims {
27
+ /** Issuer — MJ's public URL; resolves to the registered `magic-link` auth provider. */
28
+ iss: string;
29
+ /** Audience — the configured magic-link audience (default `mj-magic-link`). */
30
+ aud: string;
31
+ /** Subject — `magic-link|<inviteId>`. */
32
+ sub: string;
33
+ /** Issued-at (epoch seconds). */
34
+ iat: number;
35
+ /** Expiry (epoch seconds). */
36
+ exp: number;
37
+ email: string;
38
+ given_name?: string;
39
+ family_name?: string;
40
+ name?: string;
41
+ /** The single Application this session is scoped to. */
42
+ mj_app_id: string;
43
+ /** The restricted role name assigned to this user (informational; enforcement is server-side via the role's entity permissions). */
44
+ mj_role: string;
45
+ /** UserID of the internal user who issued the invite (attribution/audit). Absent on links whose inviter could not be resolved. */
46
+ mj_invited_by?: string;
47
+ /**
48
+ * Per-session scope UNION (Phase 4+). Each entry is one redeemed link's grant.
49
+ * The server enforces anonymous sessions against THIS set (not the user's role
50
+ * set), which is why two anon visitors sharing the Anonymous principal can hold
51
+ * different scopes without accretion. For email sessions the singular mj_app_id/
52
+ * mj_role remain authoritative; this carries the union when links are stacked.
53
+ */
54
+ mj_scopes?: MagicLinkScopeEntry[];
55
+ /** True when this session resolves to the shared Anonymous principal (claims-based enforcement). */
56
+ mj_anon?: boolean;
57
+ /** Opaque per-session id — correlates one anonymous session's activity across audit rows without a real user. */
58
+ mj_sid?: string;
59
+ /** Marks the session as magic-link so the Explorer can confine the UI. */
60
+ mj_magic_link: true;
61
+ }
62
+ /** Parameters for creating a magic-link invite (POST /magic-link/create). */
63
+ export interface CreateMagicLinkInviteParams {
64
+ /** Recipient email — becomes the provisioned user's email. */
65
+ email: string;
66
+ /** The Application the invite grants access to. */
67
+ applicationId: string;
68
+ /** Restricted role to assign. Defaults to the configured `restrictedRoleName`. */
69
+ roleId?: string;
70
+ /** Hours until the link expires. Defaults to config `defaultExpiresInHours`. */
71
+ expiresInHours?: number;
72
+ /** Maximum redemptions. Defaults to 1 (single-use). */
73
+ maxUses?: number;
74
+ /** Optional given/family name to seed the provisioned user. */
75
+ firstName?: string;
76
+ lastName?: string;
77
+ }
78
+ /** Result of creating an invite. */
79
+ export interface CreateMagicLinkInviteResult {
80
+ success: boolean;
81
+ inviteId?: string;
82
+ /** Full redemption URL to share. */
83
+ redemptionUrl?: string;
84
+ /**
85
+ * The raw token, returned ONLY when no email provider is configured (so the
86
+ * caller can deliver the link out of band). Never returned once email send
87
+ * succeeds, and never persisted.
88
+ */
89
+ rawToken?: string;
90
+ /** Whether the invite email was dispatched. */
91
+ emailSent?: boolean;
92
+ /** ISO expiry timestamp. */
93
+ expiresAt?: string;
94
+ error?: string;
95
+ /**
96
+ * Set when the request was rejected for authorization reasons:
97
+ * `forbidden` — caller may not issue invites; `invalid_role` — the requested
98
+ * role is not grantable via magic-link. Drives the HTTP status in the router.
99
+ */
100
+ errorCode?: 'forbidden' | 'invalid_role';
101
+ }
102
+ /** Why a redemption failed. */
103
+ export type RedeemErrorCode = 'not_found' | 'expired' | 'consumed' | 'revoked' | 'invalid' | 'provisioning_failed' | 'server_error';
104
+ /**
105
+ * Per-request forensic context captured for the redemption audit trail
106
+ * (`MJ: Magic Link Redemptions`). Sourced from the HTTP request at the router;
107
+ * all fields optional because the API/JSON flow may omit them.
108
+ */
109
+ export interface RedeemAuditContext {
110
+ ipAddress?: string;
111
+ userAgent?: string;
112
+ origin?: string;
113
+ }
114
+ /** Result of redeeming an invite. */
115
+ export interface RedeemMagicLinkResult {
116
+ success: boolean;
117
+ /** The minted session JWT (RS256). */
118
+ token?: string;
119
+ /** ISO expiry of the session token. */
120
+ expiresAt?: string;
121
+ /** The Application the session is scoped to. */
122
+ applicationId?: string;
123
+ /** The Application's display name (used to deep-link the browser into that app). */
124
+ applicationName?: string;
125
+ /** The Application's URL path slug (preferred for deep-linking; falls back to name). */
126
+ applicationPath?: string;
127
+ email?: string;
128
+ error?: string;
129
+ errorCode?: RedeemErrorCode;
130
+ }
131
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/auth/magicLink/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * @fileoverview Types for the MemberJunction magic-link feature.
3
+ * @module @memberjunction/server/auth/magicLink
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/auth/magicLink/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}