@awesome-lang-auth/node 1.10.1

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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.detailed.md +4059 -0
  3. package/README.md +248 -0
  4. package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
  5. package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
  6. package/dist/abstract/base-auth-strategy.abstract.js +7 -0
  7. package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
  8. package/dist/abstract/base-oauth-strategy.abstract.d.ts +27 -0
  9. package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
  10. package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
  11. package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
  12. package/dist/adapters/express.d.ts +45 -0
  13. package/dist/adapters/express.d.ts.map +1 -0
  14. package/dist/adapters/express.js +49 -0
  15. package/dist/adapters/express.js.map +1 -0
  16. package/dist/adapters/fastify.d.ts +72 -0
  17. package/dist/adapters/fastify.d.ts.map +1 -0
  18. package/dist/adapters/fastify.js +63 -0
  19. package/dist/adapters/fastify.js.map +1 -0
  20. package/dist/auth-configurator.d.ts +65 -0
  21. package/dist/auth-configurator.d.ts.map +1 -0
  22. package/dist/auth-configurator.js +127 -0
  23. package/dist/auth-configurator.js.map +1 -0
  24. package/dist/events/auth-event-bus.d.ts +68 -0
  25. package/dist/events/auth-event-bus.d.ts.map +1 -0
  26. package/dist/events/auth-event-bus.js +60 -0
  27. package/dist/events/auth-event-bus.js.map +1 -0
  28. package/dist/events/auth-event-names.d.ts +34 -0
  29. package/dist/events/auth-event-names.d.ts.map +1 -0
  30. package/dist/events/auth-event-names.js +41 -0
  31. package/dist/events/auth-event-names.js.map +1 -0
  32. package/dist/http-types.d.ts +135 -0
  33. package/dist/http-types.d.ts.map +1 -0
  34. package/dist/http-types.js +18 -0
  35. package/dist/http-types.js.map +1 -0
  36. package/dist/index.d.ts +80 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +88 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/interfaces/api-key-store.interface.d.ts +94 -0
  41. package/dist/interfaces/api-key-store.interface.d.ts.map +1 -0
  42. package/dist/interfaces/api-key-store.interface.js +3 -0
  43. package/dist/interfaces/api-key-store.interface.js.map +1 -0
  44. package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
  45. package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
  46. package/dist/interfaces/auth-strategy.interface.js +3 -0
  47. package/dist/interfaces/auth-strategy.interface.js.map +1 -0
  48. package/dist/interfaces/linked-accounts-store.interface.d.ts +74 -0
  49. package/dist/interfaces/linked-accounts-store.interface.d.ts.map +1 -0
  50. package/dist/interfaces/linked-accounts-store.interface.js +3 -0
  51. package/dist/interfaces/linked-accounts-store.interface.js.map +1 -0
  52. package/dist/interfaces/pending-link-store.interface.d.ts +77 -0
  53. package/dist/interfaces/pending-link-store.interface.d.ts.map +1 -0
  54. package/dist/interfaces/pending-link-store.interface.js +3 -0
  55. package/dist/interfaces/pending-link-store.interface.js.map +1 -0
  56. package/dist/interfaces/roles-permissions-store.interface.d.ts +129 -0
  57. package/dist/interfaces/roles-permissions-store.interface.d.ts.map +1 -0
  58. package/dist/interfaces/roles-permissions-store.interface.js +3 -0
  59. package/dist/interfaces/roles-permissions-store.interface.js.map +1 -0
  60. package/dist/interfaces/session-store.interface.d.ts +92 -0
  61. package/dist/interfaces/session-store.interface.d.ts.map +1 -0
  62. package/dist/interfaces/session-store.interface.js +3 -0
  63. package/dist/interfaces/session-store.interface.js.map +1 -0
  64. package/dist/interfaces/settings-store.interface.d.ts +91 -0
  65. package/dist/interfaces/settings-store.interface.d.ts.map +1 -0
  66. package/dist/interfaces/settings-store.interface.js +3 -0
  67. package/dist/interfaces/settings-store.interface.js.map +1 -0
  68. package/dist/interfaces/sse-distributor.interface.d.ts +25 -0
  69. package/dist/interfaces/sse-distributor.interface.d.ts.map +1 -0
  70. package/dist/interfaces/sse-distributor.interface.js +3 -0
  71. package/dist/interfaces/sse-distributor.interface.js.map +1 -0
  72. package/dist/interfaces/telemetry-store.interface.d.ts +72 -0
  73. package/dist/interfaces/telemetry-store.interface.d.ts.map +1 -0
  74. package/dist/interfaces/telemetry-store.interface.js +3 -0
  75. package/dist/interfaces/telemetry-store.interface.js.map +1 -0
  76. package/dist/interfaces/template-store.interface.d.ts +37 -0
  77. package/dist/interfaces/template-store.interface.d.ts.map +1 -0
  78. package/dist/interfaces/template-store.interface.js +3 -0
  79. package/dist/interfaces/template-store.interface.js.map +1 -0
  80. package/dist/interfaces/tenant-store.interface.d.ts +97 -0
  81. package/dist/interfaces/tenant-store.interface.d.ts.map +1 -0
  82. package/dist/interfaces/tenant-store.interface.js +3 -0
  83. package/dist/interfaces/tenant-store.interface.js.map +1 -0
  84. package/dist/interfaces/token-store.interface.d.ts +10 -0
  85. package/dist/interfaces/token-store.interface.d.ts.map +1 -0
  86. package/dist/interfaces/token-store.interface.js +3 -0
  87. package/dist/interfaces/token-store.interface.js.map +1 -0
  88. package/dist/interfaces/user-metadata-store.interface.d.ts +50 -0
  89. package/dist/interfaces/user-metadata-store.interface.d.ts.map +1 -0
  90. package/dist/interfaces/user-metadata-store.interface.js +3 -0
  91. package/dist/interfaces/user-metadata-store.interface.js.map +1 -0
  92. package/dist/interfaces/user-store.interface.d.ts +113 -0
  93. package/dist/interfaces/user-store.interface.d.ts.map +1 -0
  94. package/dist/interfaces/user-store.interface.js +3 -0
  95. package/dist/interfaces/user-store.interface.js.map +1 -0
  96. package/dist/interfaces/webhook-store.interface.d.ts +139 -0
  97. package/dist/interfaces/webhook-store.interface.d.ts.map +1 -0
  98. package/dist/interfaces/webhook-store.interface.js +3 -0
  99. package/dist/interfaces/webhook-store.interface.js.map +1 -0
  100. package/dist/middleware/api-key.middleware.d.ts +35 -0
  101. package/dist/middleware/api-key.middleware.d.ts.map +1 -0
  102. package/dist/middleware/api-key.middleware.js +45 -0
  103. package/dist/middleware/api-key.middleware.js.map +1 -0
  104. package/dist/middleware/auth.middleware.d.ts +13 -0
  105. package/dist/middleware/auth.middleware.d.ts.map +1 -0
  106. package/dist/middleware/auth.middleware.js +55 -0
  107. package/dist/middleware/auth.middleware.js.map +1 -0
  108. package/dist/middleware/jwks-auth.middleware.d.ts +18 -0
  109. package/dist/middleware/jwks-auth.middleware.d.ts.map +1 -0
  110. package/dist/middleware/jwks-auth.middleware.js +77 -0
  111. package/dist/middleware/jwks-auth.middleware.js.map +1 -0
  112. package/dist/models/api-key.model.d.ts +66 -0
  113. package/dist/models/api-key.model.d.ts.map +1 -0
  114. package/dist/models/api-key.model.js +3 -0
  115. package/dist/models/api-key.model.js.map +1 -0
  116. package/dist/models/auth-config.model.d.ts +387 -0
  117. package/dist/models/auth-config.model.d.ts.map +1 -0
  118. package/dist/models/auth-config.model.js +3 -0
  119. package/dist/models/auth-config.model.js.map +1 -0
  120. package/dist/models/errors.d.ts +7 -0
  121. package/dist/models/errors.d.ts.map +1 -0
  122. package/dist/models/errors.js +14 -0
  123. package/dist/models/errors.js.map +1 -0
  124. package/dist/models/session.model.d.ts +28 -0
  125. package/dist/models/session.model.d.ts.map +1 -0
  126. package/dist/models/session.model.js +3 -0
  127. package/dist/models/session.model.js.map +1 -0
  128. package/dist/models/tenant.model.d.ts +20 -0
  129. package/dist/models/tenant.model.d.ts.map +1 -0
  130. package/dist/models/tenant.model.js +3 -0
  131. package/dist/models/tenant.model.js.map +1 -0
  132. package/dist/models/token.model.d.ts +21 -0
  133. package/dist/models/token.model.d.ts.map +1 -0
  134. package/dist/models/token.model.js +3 -0
  135. package/dist/models/token.model.js.map +1 -0
  136. package/dist/models/user.model.d.ts +92 -0
  137. package/dist/models/user.model.d.ts.map +1 -0
  138. package/dist/models/user.model.js +3 -0
  139. package/dist/models/user.model.js.map +1 -0
  140. package/dist/router/admin.router.d.ts +188 -0
  141. package/dist/router/admin.router.d.ts.map +1 -0
  142. package/dist/router/admin.router.js +1507 -0
  143. package/dist/router/admin.router.js.map +1 -0
  144. package/dist/router/auth.router.d.ts +199 -0
  145. package/dist/router/auth.router.d.ts.map +1 -0
  146. package/dist/router/auth.router.js +1636 -0
  147. package/dist/router/auth.router.js.map +1 -0
  148. package/dist/router/openapi.d.ts +98 -0
  149. package/dist/router/openapi.d.ts.map +1 -0
  150. package/dist/router/openapi.js +1518 -0
  151. package/dist/router/openapi.js.map +1 -0
  152. package/dist/router/router-events.d.ts +38 -0
  153. package/dist/router/router-events.d.ts.map +1 -0
  154. package/dist/router/router-events.js +74 -0
  155. package/dist/router/router-events.js.map +1 -0
  156. package/dist/router/tools.router.d.ts +104 -0
  157. package/dist/router/tools.router.d.ts.map +1 -0
  158. package/dist/router/tools.router.js +227 -0
  159. package/dist/router/tools.router.js.map +1 -0
  160. package/dist/router/ui.router.d.ts +49 -0
  161. package/dist/router/ui.router.d.ts.map +1 -0
  162. package/dist/router/ui.router.js +272 -0
  163. package/dist/router/ui.router.js.map +1 -0
  164. package/dist/services/api-key.service.d.ts +53 -0
  165. package/dist/services/api-key.service.d.ts.map +1 -0
  166. package/dist/services/api-key.service.js +66 -0
  167. package/dist/services/api-key.service.js.map +1 -0
  168. package/dist/services/jwks.service.d.ts +73 -0
  169. package/dist/services/jwks.service.d.ts.map +1 -0
  170. package/dist/services/jwks.service.js +174 -0
  171. package/dist/services/jwks.service.js.map +1 -0
  172. package/dist/services/mailer.service.d.ts +30 -0
  173. package/dist/services/mailer.service.d.ts.map +1 -0
  174. package/dist/services/mailer.service.js +248 -0
  175. package/dist/services/mailer.service.js.map +1 -0
  176. package/dist/services/notification.service.d.ts +110 -0
  177. package/dist/services/notification.service.d.ts.map +1 -0
  178. package/dist/services/notification.service.js +79 -0
  179. package/dist/services/notification.service.js.map +1 -0
  180. package/dist/services/password.service.d.ts +5 -0
  181. package/dist/services/password.service.d.ts.map +1 -0
  182. package/dist/services/password.service.js +17 -0
  183. package/dist/services/password.service.js.map +1 -0
  184. package/dist/services/sms.service.d.ts +14 -0
  185. package/dist/services/sms.service.d.ts.map +1 -0
  186. package/dist/services/sms.service.js +51 -0
  187. package/dist/services/sms.service.js.map +1 -0
  188. package/dist/services/token.service.d.ts +62 -0
  189. package/dist/services/token.service.d.ts.map +1 -0
  190. package/dist/services/token.service.js +354 -0
  191. package/dist/services/token.service.js.map +1 -0
  192. package/dist/stores/memory-template.store.d.ts +12 -0
  193. package/dist/stores/memory-template.store.d.ts.map +1 -0
  194. package/dist/stores/memory-template.store.js +35 -0
  195. package/dist/stores/memory-template.store.js.map +1 -0
  196. package/dist/strategies/api-key/api-key.strategy.d.ts +72 -0
  197. package/dist/strategies/api-key/api-key.strategy.d.ts.map +1 -0
  198. package/dist/strategies/api-key/api-key.strategy.js +182 -0
  199. package/dist/strategies/api-key/api-key.strategy.js.map +1 -0
  200. package/dist/strategies/local/local.strategy.d.ts +19 -0
  201. package/dist/strategies/local/local.strategy.d.ts.map +1 -0
  202. package/dist/strategies/local/local.strategy.js +45 -0
  203. package/dist/strategies/local/local.strategy.js.map +1 -0
  204. package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
  205. package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
  206. package/dist/strategies/magic-link/magic-link.strategy.js +53 -0
  207. package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
  208. package/dist/strategies/oauth/generic-oauth.strategy.d.ts +120 -0
  209. package/dist/strategies/oauth/generic-oauth.strategy.d.ts.map +1 -0
  210. package/dist/strategies/oauth/generic-oauth.strategy.js +88 -0
  211. package/dist/strategies/oauth/generic-oauth.strategy.js.map +1 -0
  212. package/dist/strategies/oauth/github.strategy.d.ts +31 -0
  213. package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
  214. package/dist/strategies/oauth/github.strategy.js +72 -0
  215. package/dist/strategies/oauth/github.strategy.js.map +1 -0
  216. package/dist/strategies/oauth/google.strategy.d.ts +31 -0
  217. package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
  218. package/dist/strategies/oauth/google.strategy.js +61 -0
  219. package/dist/strategies/oauth/google.strategy.js.map +1 -0
  220. package/dist/strategies/sms/sms.strategy.d.ts +7 -0
  221. package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
  222. package/dist/strategies/sms/sms.strategy.js +39 -0
  223. package/dist/strategies/sms/sms.strategy.js.map +1 -0
  224. package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
  225. package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
  226. package/dist/strategies/two-factor/totp.strategy.js +32 -0
  227. package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
  228. package/dist/tools/auth-tools.d.ts +200 -0
  229. package/dist/tools/auth-tools.d.ts.map +1 -0
  230. package/dist/tools/auth-tools.js +232 -0
  231. package/dist/tools/auth-tools.js.map +1 -0
  232. package/dist/tools/sse-manager.d.ts +118 -0
  233. package/dist/tools/sse-manager.d.ts.map +1 -0
  234. package/dist/tools/sse-manager.js +163 -0
  235. package/dist/tools/sse-manager.js.map +1 -0
  236. package/dist/tools/sse-notify.decorator.d.ts +74 -0
  237. package/dist/tools/sse-notify.decorator.d.ts.map +1 -0
  238. package/dist/tools/sse-notify.decorator.js +78 -0
  239. package/dist/tools/sse-notify.decorator.js.map +1 -0
  240. package/dist/tools/webhook-action.d.ts +130 -0
  241. package/dist/tools/webhook-action.d.ts.map +1 -0
  242. package/dist/tools/webhook-action.js +144 -0
  243. package/dist/tools/webhook-action.js.map +1 -0
  244. package/dist/tools/webhook-sender.d.ts +31 -0
  245. package/dist/tools/webhook-sender.d.ts.map +1 -0
  246. package/dist/tools/webhook-sender.js +80 -0
  247. package/dist/tools/webhook-sender.js.map +1 -0
  248. package/dist/ui-assets/2fa.html +115 -0
  249. package/dist/ui-assets/account-conflict.html +108 -0
  250. package/dist/ui-assets/admin.css +263 -0
  251. package/dist/ui-assets/admin.js +1927 -0
  252. package/dist/ui-assets/auth.js +725 -0
  253. package/dist/ui-assets/base.css +219 -0
  254. package/dist/ui-assets/forgot-password.html +93 -0
  255. package/dist/ui-assets/link-verify.html +74 -0
  256. package/dist/ui-assets/login.html +135 -0
  257. package/dist/ui-assets/magic-link.html +112 -0
  258. package/dist/ui-assets/register.html +122 -0
  259. package/dist/ui-assets/reset-password.html +103 -0
  260. package/dist/ui-assets/ui-i18n-keys.json +78 -0
  261. package/dist/ui-assets/verify-email.html +66 -0
  262. package/package.json +89 -0
@@ -0,0 +1,232 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AuthTools = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const sse_manager_1 = require("./sse-manager");
6
+ const webhook_sender_1 = require("./webhook-sender");
7
+ const sse_notify_decorator_1 = require("./sse-notify.decorator");
8
+ const notification_service_1 = require("../services/notification.service");
9
+ /**
10
+ * The main tools module.
11
+ *
12
+ * Provides a unified API for telemetry tracking, real-time SSE notifications,
13
+ * and outgoing webhooks. All capabilities are optional and have zero overhead
14
+ * when disabled.
15
+ *
16
+ * ```ts
17
+ * import { AuthTools, AuthEventBus } from '@awesome-lang-auth/node';
18
+ *
19
+ * const bus = new AuthEventBus();
20
+ * const tools = new AuthTools(bus, {
21
+ * telemetryStore: myStore,
22
+ * webhookStore: myWebhookStore,
23
+ * sse: true,
24
+ * });
25
+ *
26
+ * // Track an event programmatically
27
+ * await tools.track('identity.auth.login.success', { user }, { userId: user.id });
28
+ *
29
+ * // Notify a topic over SSE
30
+ * await tools.notify('user:123', { message: 'Welcome!' });
31
+ * ```
32
+ */
33
+ class AuthTools {
34
+ constructor(eventBus, options = {}) {
35
+ this.eventBus = eventBus;
36
+ this.telemetryStore = options.telemetryStore;
37
+ this.webhookStore = options.webhookStore;
38
+ this.webhookSender = new webhook_sender_1.WebhookSender();
39
+ this.webhookVersion = options.webhookVersion ?? '1';
40
+ this.sseManager = options.sse ? new sse_manager_1.SseManager(options.sseOptions) : null;
41
+ this.sseDistributor = options.sseDistributor;
42
+ this.userStore = options.userStore;
43
+ if (options.sse && options.sseDistributor) {
44
+ process.stderr.write('[awesome-node-auth] WARN: AuthTools received both sse=true and sseDistributor; notify() will use the custom distributor.\n');
45
+ }
46
+ if (options.emailConfig || options.smsConfig) {
47
+ this.notificationService = new notification_service_1.NotificationService({
48
+ email: options.emailConfig,
49
+ sms: options.smsConfig,
50
+ });
51
+ }
52
+ if (this.sseManager) {
53
+ sse_notify_decorator_1.SseNotifyRegistry.setManager(this.sseManager);
54
+ }
55
+ }
56
+ /**
57
+ * Track an event: persist it (optional), emit it on the event bus, broadcast
58
+ * it to SSE subscribers and fire matching outgoing webhooks.
59
+ *
60
+ * @param eventName Standardised event name (see `AuthEventNames`).
61
+ * @param data Arbitrary event data.
62
+ * @param options Metadata (userId, tenantId, …).
63
+ */
64
+ async track(eventName, data, options = {}) {
65
+ const id = (0, crypto_1.randomUUID)();
66
+ const timestamp = new Date().toISOString();
67
+ const telemetryEvent = {
68
+ id,
69
+ event: eventName,
70
+ timestamp,
71
+ data,
72
+ userId: options.userId,
73
+ tenantId: options.tenantId,
74
+ sessionId: options.sessionId,
75
+ correlationId: options.correlationId,
76
+ ip: options.ip,
77
+ userAgent: options.userAgent,
78
+ };
79
+ // 1. Persist
80
+ if (this.telemetryStore) {
81
+ await this.telemetryStore.save(telemetryEvent).catch(() => { });
82
+ }
83
+ // 2. Emit on the event bus
84
+ const busPayload = {
85
+ data,
86
+ userId: options.userId,
87
+ tenantId: options.tenantId,
88
+ sessionId: options.sessionId,
89
+ correlationId: options.correlationId,
90
+ ip: options.ip,
91
+ userAgent: options.userAgent,
92
+ };
93
+ this.eventBus.publish(eventName, busPayload);
94
+ // 3. SSE broadcast (topic per tenant or global)
95
+ if (this.sseManager) {
96
+ const topics = this.resolveTopics(eventName, options);
97
+ const streamEvent = {
98
+ id,
99
+ timestamp,
100
+ type: eventName,
101
+ data: telemetryEvent,
102
+ userId: options.userId,
103
+ tenantId: options.tenantId,
104
+ };
105
+ for (const topic of topics) {
106
+ this.sseManager.broadcast(topic, streamEvent);
107
+ }
108
+ }
109
+ // 4. Outgoing webhooks
110
+ if (this.webhookStore) {
111
+ const configs = await this.webhookStore.findByEvent(eventName, options.tenantId).catch(() => []);
112
+ const webhookEvent = {
113
+ event: eventName,
114
+ version: this.webhookVersion,
115
+ timestamp,
116
+ data: data ?? null,
117
+ metadata: {
118
+ userId: options.userId,
119
+ tenantId: options.tenantId,
120
+ sessionId: options.sessionId,
121
+ correlationId: options.correlationId,
122
+ },
123
+ };
124
+ for (const config of configs) {
125
+ // Fire-and-forget; don't let webhook failures block the caller
126
+ this.webhookSender.send(config, webhookEvent).catch(() => { });
127
+ }
128
+ }
129
+ }
130
+ /**
131
+ * Send a notification to one or more delivery channels.
132
+ *
133
+ * By default (or when `channels` is `['sse']`) the behaviour is identical to
134
+ * the original synchronous broadcast. Pass additional channels to also
135
+ * deliver the notification via email and/or SMS.
136
+ *
137
+ * When `'email'` or `'sms'` is included the method becomes asynchronous and
138
+ * requires:
139
+ * - `userStore` — to look up the user's contact details by `options.userId`.
140
+ * - `emailConfig` / `smsConfig` — transport configuration in
141
+ * {@link AuthToolsOptions}.
142
+ *
143
+ * Channel failures are best-effort and do not reject the returned Promise.
144
+ *
145
+ * @param target Target SSE topic (e.g. `'user:123'`, `'tenant:acme'`).
146
+ * @param data Payload to deliver.
147
+ * @param options Channels, type, tenantId, userId, metadata, etc.
148
+ *
149
+ * @since 1.8.0 (async + multi-channel)
150
+ */
151
+ async notify(target, data, options = {}) {
152
+ const channels = options.channels ?? ['sse'];
153
+ // ── 1. SSE ──────────────────────────────────────────────────────────────
154
+ if (channels.includes('sse')) {
155
+ const streamEvent = {
156
+ type: options.type ?? 'notification',
157
+ data,
158
+ tenantId: options.tenantId,
159
+ userId: options.userId,
160
+ metadata: options.metadata,
161
+ };
162
+ if (this.sseDistributor) {
163
+ // Publish a complete StreamEvent, as SseManager.broadcast() does:
164
+ // receivers (SseManager included) deduplicate on `id`.
165
+ const fullEvent = {
166
+ id: (0, crypto_1.randomUUID)(),
167
+ timestamp: new Date().toISOString(),
168
+ topic: target,
169
+ ...streamEvent,
170
+ };
171
+ this.sseDistributor.publish(target, fullEvent).catch(() => { });
172
+ }
173
+ else if (this.sseManager) {
174
+ this.sseManager.broadcast(target, streamEvent);
175
+ }
176
+ }
177
+ // ── 2. Email / SMS (only when userId is provided and userStore is set) ───
178
+ if ((channels.includes('email') || channels.includes('sms')) && options.userId && this.userStore) {
179
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
180
+ let user = null;
181
+ try {
182
+ user = await this.userStore.findById(options.userId);
183
+ }
184
+ catch {
185
+ user = null;
186
+ }
187
+ if (user) {
188
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
189
+ const u = user;
190
+ // ── 2a. Email ─────────────────────────────────────────────────────
191
+ if (channels.includes('email') && u.email && this.notificationService?.hasEmail) {
192
+ const subject = options.emailSubject ?? (options.type ? String(options.type) : 'Notification');
193
+ const body = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
194
+ this.notificationService.sendEmail({
195
+ to: u.email,
196
+ subject,
197
+ html: `<p>${body.replace(/\n/g, '<br>')}</p>`,
198
+ text: body,
199
+ }).catch(() => { });
200
+ }
201
+ // ── 2b. SMS ───────────────────────────────────────────────────────
202
+ if (channels.includes('sms') && u.phoneNumber && this.notificationService?.hasSms) {
203
+ const message = options.smsMessage ?? (typeof data === 'string' ? data : JSON.stringify(data));
204
+ this.notificationService.sendSms({
205
+ to: u.phoneNumber,
206
+ message,
207
+ }).catch(() => { });
208
+ }
209
+ }
210
+ }
211
+ }
212
+ // ---------------------------------------------------------------------------
213
+ // Helpers
214
+ // ---------------------------------------------------------------------------
215
+ /**
216
+ * Resolve the SSE topics an event should be broadcast to.
217
+ * Always includes `'global'`; additionally adds tenant and user topics when
218
+ * the corresponding IDs are present.
219
+ */
220
+ resolveTopics(eventName, options) {
221
+ const topics = ['global'];
222
+ if (options.tenantId)
223
+ topics.push(`tenant:${options.tenantId}`);
224
+ if (options.userId)
225
+ topics.push(`user:${options.userId}`);
226
+ if (options.sessionId)
227
+ topics.push(`session:${options.sessionId}`);
228
+ return topics;
229
+ }
230
+ }
231
+ exports.AuthTools = AuthTools;
232
+ //# sourceMappingURL=auth-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-tools.js","sourceRoot":"","sources":["../../src/tools/auth-tools.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,118 @@
1
+ import { Response } from 'express';
2
+ import { ISseDistributor } from '../interfaces/sse-distributor.interface';
3
+ /**
4
+ * A single real-time SSE event delivered to subscribers.
5
+ */
6
+ export interface StreamEvent<T = unknown> {
7
+ /** Unique event identifier (auto-generated if not provided). */
8
+ id: string;
9
+ /** Event type / name. */
10
+ type: string;
11
+ /** ISO 8601 timestamp. */
12
+ timestamp: string;
13
+ /** Topic / channel this event was published to. */
14
+ topic: string;
15
+ /** Arbitrary event data. */
16
+ data: T;
17
+ /** User ID (optional). */
18
+ userId?: string;
19
+ /** Tenant ID (optional). */
20
+ tenantId?: string;
21
+ /** Optional extra metadata. */
22
+ metadata?: Record<string, unknown>;
23
+ }
24
+ /**
25
+ * Options for the {@link SseManager}.
26
+ */
27
+ export interface SseManagerOptions {
28
+ /**
29
+ * Interval in milliseconds between heartbeat comments (`:heartbeat`).
30
+ * Set to `0` to disable heartbeats.
31
+ * @default 30000
32
+ */
33
+ heartbeatIntervalMs?: number;
34
+ /**
35
+ * Optional distributor for cross-instance event synchronization.
36
+ * Required for horizontal scaling.
37
+ */
38
+ distributor?: ISseDistributor;
39
+ /**
40
+ * When `true` (default), events with the same ID are deduplicated per connection.
41
+ * This prevents clients from receiving the same event multiple times when
42
+ * subscribed to multiple matching topics.
43
+ * @default true
44
+ */
45
+ deduplicate?: boolean;
46
+ }
47
+ /**
48
+ * Manages Server-Sent Events connections.
49
+ *
50
+ * Tracks open HTTP streams and broadcasts `StreamEvent` objects to all
51
+ * subscribers matching a given topic. Topics follow the hierarchical
52
+ * channel scheme:
53
+ *
54
+ * ```
55
+ * global
56
+ * tenant:{tenantId}
57
+ * tenant:{tenantId}:role:{role}
58
+ * tenant:{tenantId}:group:{groupId}
59
+ * user:{userId}
60
+ * session:{sessionId}
61
+ * custom:{namespace}
62
+ * ```
63
+ *
64
+ * The server controls which topics a connection is allowed to subscribe to —
65
+ * clients cannot self-declare channels.
66
+ */
67
+ export declare class SseManager {
68
+ private connections;
69
+ private readonly distributor?;
70
+ /**
71
+ * Interval in milliseconds between heartbeat comments (`:heartbeat`).
72
+ * Set to `0` to disable heartbeats.
73
+ * @default 30000
74
+ */
75
+ readonly heartbeatIntervalMs: number;
76
+ /**
77
+ * Whether to deduplicate events by ID per connection.
78
+ */
79
+ readonly deduplicate: boolean;
80
+ constructor(options?: SseManagerOptions);
81
+ /**
82
+ * Attach an SSE response to a new connection and register its authorised
83
+ * topics.
84
+ *
85
+ * @param res The HTTP response to write events to.
86
+ * @param topics Topics this connection may receive (enforced server-side).
87
+ * @param meta Optional user / tenant context.
88
+ * @returns The generated connection ID.
89
+ */
90
+ connect(res: Response, topics: string[], meta?: {
91
+ userId?: string;
92
+ tenantId?: string;
93
+ }): string;
94
+ /**
95
+ * Broadcast a `StreamEvent` to all connections subscribed to `topic`.
96
+ * If a distributor is configured, the event is published to it for
97
+ * cross-instance delivery.
98
+ */
99
+ broadcast<T = unknown>(topic: string, event: Omit<StreamEvent<T>, 'id' | 'timestamp' | 'topic'> & {
100
+ id?: string;
101
+ timestamp?: string;
102
+ }): void;
103
+ /**
104
+ * Broadcast an event to ONLY the local connections.
105
+ * This is typically called by the distributor's subscription callback.
106
+ */
107
+ private broadcastLocal;
108
+ /**
109
+ * Close and remove a connection by its ID.
110
+ */
111
+ disconnect(connectionId: string): void;
112
+ /**
113
+ * Number of currently open connections.
114
+ */
115
+ get connectionCount(): number;
116
+ private write;
117
+ }
118
+ //# sourceMappingURL=sse-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-manager.d.ts","sourceRoot":"","sources":["../../src/tools/sse-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAEnC,OAAO,EAAE,eAAe,EAAE,MAAM,yCAAyC,CAAC;AAE1E;;GAEG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,gEAAgE;IAChE,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,IAAI,EAAE,CAAC,CAAC;IACR,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAiBD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,WAAW,CAAC,EAAE,eAAe,CAAC;IAE9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAoC;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAkB;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IAErC;;OAEG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;gBAElB,OAAO,CAAC,EAAE,iBAAiB;IAYvC;;;;;;;;OAQG;IACH,OAAO,CACL,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,MAAM,EAAE,EAChB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,MAAM;IA6CT;;;;OAIG;IACH,SAAS,CAAC,CAAC,GAAG,OAAO,EACnB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9F,IAAI;IAkBP;;;OAGG;IACH,OAAO,CAAC,cAAc;IAmBtB;;OAEG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAYtC;;OAEG;IACH,IAAI,eAAe,IAAI,MAAM,CAE5B;IAMD,OAAO,CAAC,KAAK;CAKd"}
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SseManager = void 0;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Manages Server-Sent Events connections.
7
+ *
8
+ * Tracks open HTTP streams and broadcasts `StreamEvent` objects to all
9
+ * subscribers matching a given topic. Topics follow the hierarchical
10
+ * channel scheme:
11
+ *
12
+ * ```
13
+ * global
14
+ * tenant:{tenantId}
15
+ * tenant:{tenantId}:role:{role}
16
+ * tenant:{tenantId}:group:{groupId}
17
+ * user:{userId}
18
+ * session:{sessionId}
19
+ * custom:{namespace}
20
+ * ```
21
+ *
22
+ * The server controls which topics a connection is allowed to subscribe to —
23
+ * clients cannot self-declare channels.
24
+ */
25
+ class SseManager {
26
+ constructor(options) {
27
+ this.connections = new Map();
28
+ this.heartbeatIntervalMs = options?.heartbeatIntervalMs ?? 30000;
29
+ this.deduplicate = options?.deduplicate !== false;
30
+ this.distributor = options?.distributor;
31
+ if (this.distributor) {
32
+ this.distributor.subscribe((topic, event) => {
33
+ this.broadcastLocal(topic, event);
34
+ });
35
+ }
36
+ }
37
+ /**
38
+ * Attach an SSE response to a new connection and register its authorised
39
+ * topics.
40
+ *
41
+ * @param res The HTTP response to write events to.
42
+ * @param topics Topics this connection may receive (enforced server-side).
43
+ * @param meta Optional user / tenant context.
44
+ * @returns The generated connection ID.
45
+ */
46
+ connect(res, topics, meta) {
47
+ const id = (0, crypto_1.randomUUID)();
48
+ // SSE headers
49
+ res.setHeader('Content-Type', 'text/event-stream');
50
+ res.setHeader('Cache-Control', 'no-cache');
51
+ res.setHeader('Connection', 'keep-alive');
52
+ res.setHeader('X-Accel-Buffering', 'no');
53
+ res.flushHeaders?.();
54
+ // Send initial connection event
55
+ this.write(res, {
56
+ id: (0, crypto_1.randomUUID)(),
57
+ type: 'connected',
58
+ timestamp: new Date().toISOString(),
59
+ topic: 'meta',
60
+ data: { connectionId: id, topics },
61
+ });
62
+ const conn = {
63
+ id,
64
+ res,
65
+ topics: new Set(topics),
66
+ userId: meta?.userId,
67
+ tenantId: meta?.tenantId,
68
+ };
69
+ if (this.heartbeatIntervalMs > 0) {
70
+ conn.heartbeatTimer = setInterval(() => {
71
+ try {
72
+ res.write(': heartbeat\n\n');
73
+ }
74
+ catch {
75
+ this.disconnect(id);
76
+ }
77
+ }, this.heartbeatIntervalMs);
78
+ }
79
+ this.connections.set(id, conn);
80
+ // Clean up when the client disconnects
81
+ res.on('close', () => this.disconnect(id));
82
+ return id;
83
+ }
84
+ /**
85
+ * Broadcast a `StreamEvent` to all connections subscribed to `topic`.
86
+ * If a distributor is configured, the event is published to it for
87
+ * cross-instance delivery.
88
+ */
89
+ broadcast(topic, event) {
90
+ const full = {
91
+ id: event.id ?? (0, crypto_1.randomUUID)(),
92
+ timestamp: event.timestamp ?? new Date().toISOString(),
93
+ topic,
94
+ ...event,
95
+ };
96
+ if (this.distributor) {
97
+ this.distributor.publish(topic, full).catch(() => {
98
+ // Fallback to local broadcast if distribution fails
99
+ this.broadcastLocal(topic, full);
100
+ });
101
+ }
102
+ else {
103
+ this.broadcastLocal(topic, full);
104
+ }
105
+ }
106
+ /**
107
+ * Broadcast an event to ONLY the local connections.
108
+ * This is typically called by the distributor's subscription callback.
109
+ */
110
+ broadcastLocal(topic, event) {
111
+ for (const conn of this.connections.values()) {
112
+ if (!conn.topics.has(topic))
113
+ continue;
114
+ // Tenant isolation: if the event has a tenantId the connection must
115
+ // belong to the same tenant (unless it is a global connection with no
116
+ // tenantId set).
117
+ if (event.tenantId && conn.tenantId && conn.tenantId !== event.tenantId)
118
+ continue;
119
+ try {
120
+ if (this.deduplicate && event.id === conn.lastEventId)
121
+ continue;
122
+ conn.lastEventId = event.id;
123
+ this.write(conn.res, event);
124
+ }
125
+ catch {
126
+ this.disconnect(conn.id);
127
+ }
128
+ }
129
+ }
130
+ /**
131
+ * Close and remove a connection by its ID.
132
+ */
133
+ disconnect(connectionId) {
134
+ const conn = this.connections.get(connectionId);
135
+ if (!conn)
136
+ return;
137
+ if (conn.heartbeatTimer)
138
+ clearInterval(conn.heartbeatTimer);
139
+ this.connections.delete(connectionId);
140
+ try {
141
+ conn.res.end();
142
+ }
143
+ catch {
144
+ // already closed
145
+ }
146
+ }
147
+ /**
148
+ * Number of currently open connections.
149
+ */
150
+ get connectionCount() {
151
+ return this.connections.size;
152
+ }
153
+ // ---------------------------------------------------------------------------
154
+ // Private helpers
155
+ // ---------------------------------------------------------------------------
156
+ write(res, event) {
157
+ res.write(`id: ${event.id}\n`);
158
+ res.write(`event: ${event.type}\n`);
159
+ res.write(`data: ${JSON.stringify({ ...event, data: undefined, rawData: event.data })}\n\n`);
160
+ }
161
+ }
162
+ exports.SseManager = SseManager;
163
+ //# sourceMappingURL=sse-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-manager.js","sourceRoot":"","sources":["../../src/tools/sse-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { SseManager } from './sse-manager';
2
+ /**
3
+ * Options for the `@sseNotify` decorator.
4
+ */
5
+ export interface SseNotifyOptions {
6
+ /**
7
+ * The SSE topic to broadcast to. Can be a static string or a function
8
+ * that resolves the topic based on method arguments and execution result.
9
+ */
10
+ topic: string | ((args: any[], result: any) => string);
11
+ /**
12
+ * The event type name. Defaults to `'notification'`.
13
+ */
14
+ event?: string;
15
+ /**
16
+ * Optional payload transformer. By default, the method's return value is sent.
17
+ */
18
+ payload?: (result: any, args: any[]) => any;
19
+ /**
20
+ * Optional tenant ID resolver.
21
+ */
22
+ tenantId?: (result: any, args: any[]) => string | undefined;
23
+ /**
24
+ * Optional user ID resolver.
25
+ */
26
+ userId?: (result: any, args: any[]) => string | undefined;
27
+ }
28
+ /**
29
+ * Global registry and dispatcher for decorated SSE notifications.
30
+ */
31
+ export declare class SseNotifyRegistry {
32
+ private static manager;
33
+ /**
34
+ * Set the active SSE manager used for broadcasts. Should be called
35
+ * during application initialization.
36
+ */
37
+ static setManager(manager: SseManager | null): void;
38
+ /**
39
+ * Perform a broadcast if a manager is registered.
40
+ */
41
+ static notify(topic: string, event: {
42
+ type: string;
43
+ data: any;
44
+ tenantId?: string;
45
+ userId?: string;
46
+ }): void;
47
+ /**
48
+ * Internal helper to execute notification logic.
49
+ * Can be called manually to bypass decorators.
50
+ */
51
+ static executeNotify(options: SseNotifyOptions, args: any[], result: any): Promise<void>;
52
+ }
53
+ /**
54
+ * Method decorator that automatically triggers an SSE broadcast after
55
+ * successful execution.
56
+ *
57
+ * Uses the **TC39 Stage 3** decorator syntax (TypeScript 5.x+).
58
+ * For legacy environments, use manual notification via `SseNotifyRegistry.notify()`.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * class UserService {
63
+ * @sseNotify({
64
+ * topic: (_args, user) => `user:${user.id}`,
65
+ * event: 'user.updated',
66
+ * })
67
+ * async updateProfile(userId: string, data: any) {
68
+ * return await db.users.update(userId, data);
69
+ * }
70
+ * }
71
+ * ```
72
+ */
73
+ export declare function sseNotify(options: SseNotifyOptions): <This, Args extends unknown[], Return>(target: (this: This, ...args: Args) => Promise<Return> | Return, _context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Promise<Return> | Return>) => (this: This, ...args: Args) => Promise<Return>;
74
+ //# sourceMappingURL=sse-notify.decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-notify.decorator.d.ts","sourceRoot":"","sources":["../../src/tools/sse-notify.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;OAGG;IACH,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,KAAK,MAAM,CAAC,CAAC;IAEvD;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAC;IAE5C;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;IAE5D;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,GAAG,SAAS,CAAC;CAC7D;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAC1B,OAAO,CAAC,MAAM,CAAC,OAAO,CAA2B;IAEjD;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,GAAG,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAK1G;;;OAGG;WACU,aAAa,CAAC,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;CAkBjG;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,IAC9B,IAAI,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,EACjD,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EAC/D,UAAU,2BAA2B,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,MAE7E,MAAM,IAAI,EAAE,GAAG,MAAM,IAAI,KAAG,OAAO,CAAC,MAAM,CAAC,CAMzE"}