@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,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SseNotifyRegistry = void 0;
4
+ exports.sseNotify = sseNotify;
5
+ /**
6
+ * Global registry and dispatcher for decorated SSE notifications.
7
+ */
8
+ class SseNotifyRegistry {
9
+ /**
10
+ * Set the active SSE manager used for broadcasts. Should be called
11
+ * during application initialization.
12
+ */
13
+ static setManager(manager) {
14
+ this.manager = manager;
15
+ }
16
+ /**
17
+ * Perform a broadcast if a manager is registered.
18
+ */
19
+ static notify(topic, event) {
20
+ if (!this.manager)
21
+ return;
22
+ this.manager.broadcast(topic, event);
23
+ }
24
+ /**
25
+ * Internal helper to execute notification logic.
26
+ * Can be called manually to bypass decorators.
27
+ */
28
+ static async executeNotify(options, args, result) {
29
+ try {
30
+ const topic = typeof options.topic === 'function' ? options.topic(args, result) : options.topic;
31
+ const data = options.payload ? options.payload(result, args) : result;
32
+ const eventType = options.event ?? 'notification';
33
+ const tenantId = options.tenantId ? options.tenantId(result, args) : undefined;
34
+ const userId = options.userId ? options.userId(result, args) : undefined;
35
+ this.notify(topic, {
36
+ type: eventType,
37
+ data,
38
+ tenantId,
39
+ userId,
40
+ });
41
+ }
42
+ catch (err) {
43
+ console.error('[SseNotifyRegistry] Error in notification logic:', err);
44
+ }
45
+ }
46
+ }
47
+ exports.SseNotifyRegistry = SseNotifyRegistry;
48
+ SseNotifyRegistry.manager = null;
49
+ /**
50
+ * Method decorator that automatically triggers an SSE broadcast after
51
+ * successful execution.
52
+ *
53
+ * Uses the **TC39 Stage 3** decorator syntax (TypeScript 5.x+).
54
+ * For legacy environments, use manual notification via `SseNotifyRegistry.notify()`.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * class UserService {
59
+ * @sseNotify({
60
+ * topic: (_args, user) => `user:${user.id}`,
61
+ * event: 'user.updated',
62
+ * })
63
+ * async updateProfile(userId: string, data: any) {
64
+ * return await db.users.update(userId, data);
65
+ * }
66
+ * }
67
+ * ```
68
+ */
69
+ function sseNotify(options) {
70
+ return function (target, _context) {
71
+ return async function (...args) {
72
+ const result = await target.apply(this, args);
73
+ await SseNotifyRegistry.executeNotify(options, args, result);
74
+ return result;
75
+ };
76
+ };
77
+ }
78
+ //# sourceMappingURL=sse-notify.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-notify.decorator.js","sourceRoot":"","sources":["../../src/tools/sse-notify.decorator.ts"],"names":[],"mappings":";;;AAmGA,8BAWC;AA7ED;;GAEG;AACH,MAAa,iBAAiB;IAG1B;;;OAGG;IACH,MAAM,CAAC,UAAU,CAAC,OAA0B;QACxC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,KAAa,EAAE,KAAsE;QAC/F,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,OAAyB,EAAE,IAAW,EAAE,MAAW;QAC1E,IAAI,CAAC;YACD,MAAM,KAAK,GAAG,OAAO,OAAO,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;YAChG,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACtE,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,cAAc,CAAC;YAClD,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAC/E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;YAEzE,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE;gBACf,IAAI,EAAE,SAAS;gBACf,IAAI;gBACJ,QAAQ;gBACR,MAAM;aACT,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,kDAAkD,EAAE,GAAG,CAAC,CAAC;QAC3E,CAAC;IACL,CAAC;;AAxCL,8CAyCC;AAxCkB,yBAAO,GAAsB,IAAI,CAAC;AA0CrD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAgB,SAAS,CAAC,OAAyB;IAC/C,OAAO,UACH,MAA+D,EAC/D,QAAoG;QAEpG,OAAO,KAAK,WAAuB,GAAG,IAAU;YAC5C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;YAC9C,MAAM,iBAAiB,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAC7D,OAAO,MAAM,CAAC;QAClB,CAAC,CAAC;IACN,CAAC,CAAC;AACN,CAAC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * @webhookAction decorator and ActionRegistry.
3
+ *
4
+ * Developers annotate service methods with `@webhookAction` to expose them
5
+ * safely inside the `vm` sandbox when an inbound webhook fires. The admin UI
6
+ * can then:
7
+ * 1. Globally enable / disable each action (via `ISettingsStore.enabledWebhookActions`).
8
+ * 2. Assign a per-webhook subset of those enabled actions to each inbound
9
+ * webhook config (`WebhookConfig.allowedActions`).
10
+ *
11
+ * The sandbox only receives the **intersection** of both sets, enforcing the
12
+ * Principle of Least Privilege.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { webhookAction } from '@awesome-lang-auth/node';
17
+ *
18
+ * class UserService {
19
+ * \@webhookAction({
20
+ * id: 'user.suspend',
21
+ * label: 'Suspend user',
22
+ * category: 'Users',
23
+ * description: 'Set isActive=false on a user record.',
24
+ * })
25
+ * async suspendUser(userId: string): Promise<void> { ... }
26
+ * }
27
+ *
28
+ * const svc = new UserService();
29
+ * ActionRegistry.register({ id: 'user.suspend', ..., fn: svc.suspendUser.bind(svc) });
30
+ * ```
31
+ */
32
+ /** Metadata for a single webhook action. */
33
+ export interface WebhookActionMeta {
34
+ /** Stable machine identifier, e.g. `'user.suspend'`. */
35
+ id: string;
36
+ /** Human-readable label shown in the Admin UI. */
37
+ label: string;
38
+ /** Grouping category shown in the Admin UI. */
39
+ category: string;
40
+ /** Short description shown in the Admin UI. */
41
+ description: string;
42
+ /**
43
+ * Other action IDs that must be globally enabled for this action to be
44
+ * available. The UI will disable this action's toggle if any dependency
45
+ * is not met.
46
+ */
47
+ dependsOn?: string[];
48
+ }
49
+ /** Registered action entry (metadata + callable function reference). */
50
+ export interface RegisteredAction extends WebhookActionMeta {
51
+ fn: (...args: unknown[]) => unknown;
52
+ }
53
+ /**
54
+ * Registry of all webhook-exposed actions.
55
+ *
56
+ * All methods are static so the registry is accessible anywhere without
57
+ * instantiation.
58
+ */
59
+ export declare class ActionRegistry {
60
+ /**
61
+ * Register an action. Called automatically by the `@webhookAction`
62
+ * decorator, but can also be called manually (useful when the action
63
+ * function is not on a class instance at decoration time).
64
+ */
65
+ static register(entry: RegisteredAction): void;
66
+ /** Return all registered actions (metadata + function). */
67
+ static getAll(): RegisteredAction[];
68
+ /** Return metadata for all registered actions (no function references). */
69
+ static getAllMeta(): WebhookActionMeta[];
70
+ /**
71
+ * Look up a single registered action by ID.
72
+ * Returns `undefined` when not found.
73
+ */
74
+ static get(id: string): RegisteredAction | undefined;
75
+ /**
76
+ * Build the `actions` context object injected into the `vm` sandbox.
77
+ *
78
+ * Only includes actions that satisfy **all three** conditions:
79
+ * 1. Registered in the registry.
80
+ * 2. Listed in `enabledIds` (globally enabled by the admin).
81
+ * 3. Listed in `allowedIds` (assigned to this specific webhook).
82
+ *
83
+ * Additionally, a registered action is excluded if any of its `dependsOn`
84
+ * entries are not in the effective allowed set.
85
+ *
86
+ * @param enabledIds Globally enabled action IDs (`AuthSettings.enabledWebhookActions`).
87
+ * @param allowedIds Per-webhook allowed action IDs (`WebhookConfig.allowedActions`).
88
+ * @returns A `Record<id, fn>` object safe to pass as the `actions` sandbox variable.
89
+ */
90
+ static buildContext(enabledIds: string[], allowedIds: string[]): Record<string, (...args: unknown[]) => unknown>;
91
+ /** Remove all entries (useful in tests). */
92
+ static clear(): void;
93
+ }
94
+ /**
95
+ * Method decorator that registers the decorated method in the `ActionRegistry`
96
+ * using the provided metadata.
97
+ *
98
+ * Uses the **TC39 Stage 3** decorator syntax (TypeScript 5.x without
99
+ * `experimentalDecorators`).
100
+ *
101
+ * The function reference stored in the registry is the **unbound** method.
102
+ * For instance methods, pass a bound function when calling
103
+ * `ActionRegistry.register` at runtime, or bind the instance manually:
104
+ *
105
+ * ```typescript
106
+ * const svc = new BillingService();
107
+ * ActionRegistry.register({
108
+ * id: 'billing.cancelSubscription',
109
+ * label: 'Cancel subscription',
110
+ * category: 'Billing',
111
+ * description: 'Cancel a subscription.',
112
+ * fn: svc.cancelSubscription.bind(svc),
113
+ * });
114
+ * ```
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * class BillingService {
119
+ * \@webhookAction({
120
+ * id: 'billing.cancelSubscription',
121
+ * label: 'Cancel subscription',
122
+ * category: 'Billing',
123
+ * description: 'Cancel a user subscription in the billing system.',
124
+ * })
125
+ * async cancelSubscription(subscriptionId: string): Promise<void> { ... }
126
+ * }
127
+ * ```
128
+ */
129
+ export declare function webhookAction(meta: WebhookActionMeta): <This, Args extends unknown[], Return>(value: (this: This, ...args: Args) => Return, _context: ClassMethodDecoratorContext) => (this: This, ...args: Args) => Return;
130
+ //# sourceMappingURL=webhook-action.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-action.d.ts","sourceRoot":"","sources":["../../src/tools/webhook-action.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,4CAA4C;AAC5C,MAAM,WAAW,iBAAiB;IAChC,wDAAwD;IACxD,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,wEAAwE;AACxE,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;CACrC;AAKD;;;;;GAKG;AACH,qBAAa,cAAc;IACzB;;;;OAIG;IAEH,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAG9C,2DAA2D;IAC3D,MAAM,CAAC,MAAM,IAAI,gBAAgB,EAAE;IAInC,2EAA2E;IAC3E,MAAM,CAAC,UAAU,IAAI,iBAAiB,EAAE;IAIxC;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAIpD;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;IAWhH,4CAA4C;IAC5C,MAAM,CAAC,KAAK,IAAI,IAAI;CAGrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,iBAAiB,IAClC,IAAI,EAAE,IAAI,SAAS,OAAO,EAAE,EAAE,MAAM,EACnD,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,MAAM,EAC5C,UAAU,2BAA2B,KACpC,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,IAAI,KAAK,MAAM,CAKzC"}
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ /**
3
+ * @webhookAction decorator and ActionRegistry.
4
+ *
5
+ * Developers annotate service methods with `@webhookAction` to expose them
6
+ * safely inside the `vm` sandbox when an inbound webhook fires. The admin UI
7
+ * can then:
8
+ * 1. Globally enable / disable each action (via `ISettingsStore.enabledWebhookActions`).
9
+ * 2. Assign a per-webhook subset of those enabled actions to each inbound
10
+ * webhook config (`WebhookConfig.allowedActions`).
11
+ *
12
+ * The sandbox only receives the **intersection** of both sets, enforcing the
13
+ * Principle of Least Privilege.
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * import { webhookAction } from '@awesome-lang-auth/node';
18
+ *
19
+ * class UserService {
20
+ * \@webhookAction({
21
+ * id: 'user.suspend',
22
+ * label: 'Suspend user',
23
+ * category: 'Users',
24
+ * description: 'Set isActive=false on a user record.',
25
+ * })
26
+ * async suspendUser(userId: string): Promise<void> { ... }
27
+ * }
28
+ *
29
+ * const svc = new UserService();
30
+ * ActionRegistry.register({ id: 'user.suspend', ..., fn: svc.suspendUser.bind(svc) });
31
+ * ```
32
+ */
33
+ Object.defineProperty(exports, "__esModule", { value: true });
34
+ exports.ActionRegistry = void 0;
35
+ exports.webhookAction = webhookAction;
36
+ // Module-level registry (populated at boot time via `@webhookAction` or manual calls)
37
+ const _registry = new Map();
38
+ /**
39
+ * Registry of all webhook-exposed actions.
40
+ *
41
+ * All methods are static so the registry is accessible anywhere without
42
+ * instantiation.
43
+ */
44
+ class ActionRegistry {
45
+ /**
46
+ * Register an action. Called automatically by the `@webhookAction`
47
+ * decorator, but can also be called manually (useful when the action
48
+ * function is not on a class instance at decoration time).
49
+ */
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ static register(entry) {
52
+ _registry.set(entry.id, entry);
53
+ }
54
+ /** Return all registered actions (metadata + function). */
55
+ static getAll() {
56
+ return [..._registry.values()];
57
+ }
58
+ /** Return metadata for all registered actions (no function references). */
59
+ static getAllMeta() {
60
+ return [..._registry.values()].map(({ fn: _fn, ...meta }) => meta);
61
+ }
62
+ /**
63
+ * Look up a single registered action by ID.
64
+ * Returns `undefined` when not found.
65
+ */
66
+ static get(id) {
67
+ return _registry.get(id);
68
+ }
69
+ /**
70
+ * Build the `actions` context object injected into the `vm` sandbox.
71
+ *
72
+ * Only includes actions that satisfy **all three** conditions:
73
+ * 1. Registered in the registry.
74
+ * 2. Listed in `enabledIds` (globally enabled by the admin).
75
+ * 3. Listed in `allowedIds` (assigned to this specific webhook).
76
+ *
77
+ * Additionally, a registered action is excluded if any of its `dependsOn`
78
+ * entries are not in the effective allowed set.
79
+ *
80
+ * @param enabledIds Globally enabled action IDs (`AuthSettings.enabledWebhookActions`).
81
+ * @param allowedIds Per-webhook allowed action IDs (`WebhookConfig.allowedActions`).
82
+ * @returns A `Record<id, fn>` object safe to pass as the `actions` sandbox variable.
83
+ */
84
+ static buildContext(enabledIds, allowedIds) {
85
+ const effectiveSet = new Set(allowedIds.filter((id) => enabledIds.includes(id)));
86
+ const ctx = {};
87
+ for (const [id, entry] of _registry) {
88
+ if (!effectiveSet.has(id))
89
+ continue;
90
+ const depsOk = (entry.dependsOn ?? []).every((dep) => effectiveSet.has(dep));
91
+ if (depsOk)
92
+ ctx[id] = entry.fn;
93
+ }
94
+ return ctx;
95
+ }
96
+ /** Remove all entries (useful in tests). */
97
+ static clear() {
98
+ _registry.clear();
99
+ }
100
+ }
101
+ exports.ActionRegistry = ActionRegistry;
102
+ /**
103
+ * Method decorator that registers the decorated method in the `ActionRegistry`
104
+ * using the provided metadata.
105
+ *
106
+ * Uses the **TC39 Stage 3** decorator syntax (TypeScript 5.x without
107
+ * `experimentalDecorators`).
108
+ *
109
+ * The function reference stored in the registry is the **unbound** method.
110
+ * For instance methods, pass a bound function when calling
111
+ * `ActionRegistry.register` at runtime, or bind the instance manually:
112
+ *
113
+ * ```typescript
114
+ * const svc = new BillingService();
115
+ * ActionRegistry.register({
116
+ * id: 'billing.cancelSubscription',
117
+ * label: 'Cancel subscription',
118
+ * category: 'Billing',
119
+ * description: 'Cancel a subscription.',
120
+ * fn: svc.cancelSubscription.bind(svc),
121
+ * });
122
+ * ```
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * class BillingService {
127
+ * \@webhookAction({
128
+ * id: 'billing.cancelSubscription',
129
+ * label: 'Cancel subscription',
130
+ * category: 'Billing',
131
+ * description: 'Cancel a user subscription in the billing system.',
132
+ * })
133
+ * async cancelSubscription(subscriptionId: string): Promise<void> { ... }
134
+ * }
135
+ * ```
136
+ */
137
+ function webhookAction(meta) {
138
+ return function (value, _context) {
139
+ // Register the unbound method
140
+ ActionRegistry.register({ ...meta, fn: value });
141
+ return value;
142
+ };
143
+ }
144
+ //# sourceMappingURL=webhook-action.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-action.js","sourceRoot":"","sources":["../../src/tools/webhook-action.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;;;AAiIH,sCASC;AAjHD,sFAAsF;AACtF,MAAM,SAAS,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEtD;;;;;GAKG;AACH,MAAa,cAAc;IACzB;;;;OAIG;IACH,8DAA8D;IAC9D,MAAM,CAAC,QAAQ,CAAC,KAAuB;QAAa,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;IACnF,CAAC;IAED,2DAA2D;IAC3D,MAAM,CAAC,MAAM;QACX,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC;IACjC,CAAC;IAED,2EAA2E;IAC3E,MAAM,CAAC,UAAU;QACf,OAAO,CAAC,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC;IACrE,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,GAAG,CAAC,EAAU;QACnB,OAAO,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC3B,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,YAAY,CAAC,UAAoB,EAAE,UAAoB;QAC5D,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACjF,MAAM,GAAG,GAAoD,EAAE,CAAC;QAChE,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,SAAS,EAAE,CAAC;YACpC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YACpC,MAAM,MAAM,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC7E,IAAI,MAAM;gBAAE,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;QACjC,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,4CAA4C;IAC5C,MAAM,CAAC,KAAK;QACV,SAAS,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;CACF;AA1DD,wCA0DC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,SAAgB,aAAa,CAAC,IAAuB;IACnD,OAAO,UACL,KAA4C,EAC5C,QAAqC;QAErC,8BAA8B;QAC9B,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,KAAwC,EAAE,CAAC,CAAC;QACnF,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { WebhookConfig, OutgoingWebhookEvent } from '../interfaces/webhook-store.interface';
2
+ /**
3
+ * Sends outgoing webhooks with optional HMAC signing and exponential
4
+ * back-off retry.
5
+ */
6
+ export declare class WebhookSender {
7
+ /**
8
+ * Deliver an event to a single webhook endpoint.
9
+ * Retries on transient HTTP errors according to the webhook's `maxRetries`
10
+ * and `retryDelayMs` settings.
11
+ *
12
+ * @param config Webhook configuration (URL, secret, retry policy, …).
13
+ * @param event The event payload to deliver.
14
+ */
15
+ send(config: WebhookConfig, event: OutgoingWebhookEvent): Promise<void>;
16
+ /**
17
+ * Compute an HMAC-SHA256 signature of `body` using `secret`.
18
+ * The signature is returned as `sha256=<hex>` so receivers can verify it.
19
+ */
20
+ sign(body: string, secret: string): string;
21
+ /**
22
+ * Verify an inbound webhook signature.
23
+ * Returns `true` when the `X-Webhook-Signature` header matches the
24
+ * expected HMAC-SHA256 of the raw request body.
25
+ *
26
+ * Use constant-time comparison to prevent timing attacks.
27
+ */
28
+ verify(body: string, secret: string, signature: string): boolean;
29
+ private delay;
30
+ }
31
+ //# sourceMappingURL=webhook-sender.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-sender.d.ts","sourceRoot":"","sources":["../../src/tools/webhook-sender.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,uCAAuC,CAAC;AAE5F;;;GAGG;AACH,qBAAa,aAAa;IACxB;;;;;;;OAOG;IACG,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC;IAiC7E;;;OAGG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM;IAI1C;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAWhE,OAAO,CAAC,KAAK;CAGd"}
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WebhookSender = void 0;
4
+ const crypto_1 = require("crypto");
5
+ /**
6
+ * Sends outgoing webhooks with optional HMAC signing and exponential
7
+ * back-off retry.
8
+ */
9
+ class WebhookSender {
10
+ /**
11
+ * Deliver an event to a single webhook endpoint.
12
+ * Retries on transient HTTP errors according to the webhook's `maxRetries`
13
+ * and `retryDelayMs` settings.
14
+ *
15
+ * @param config Webhook configuration (URL, secret, retry policy, …).
16
+ * @param event The event payload to deliver.
17
+ */
18
+ async send(config, event) {
19
+ const maxRetries = config.maxRetries ?? 3;
20
+ const baseDelayMs = config.retryDelayMs ?? 1000;
21
+ const body = JSON.stringify(event);
22
+ let attempt = 0;
23
+ while (attempt <= maxRetries) {
24
+ const headers = {
25
+ 'Content-Type': 'application/json',
26
+ 'X-Webhook-Event': event.event,
27
+ 'X-Webhook-Delivery': (0, crypto_1.randomUUID)(),
28
+ 'X-Webhook-Timestamp': event.timestamp,
29
+ };
30
+ if (config.secret) {
31
+ headers['X-Webhook-Signature'] = this.sign(body, config.secret);
32
+ }
33
+ try {
34
+ const res = await fetch(config.url, { method: 'POST', headers, body });
35
+ if (res.ok)
36
+ return;
37
+ // Non-2xx: retry if attempts remain
38
+ if (attempt >= maxRetries)
39
+ return;
40
+ }
41
+ catch {
42
+ // Network error: retry if attempts remain
43
+ if (attempt >= maxRetries)
44
+ return;
45
+ }
46
+ attempt++;
47
+ await this.delay(baseDelayMs * Math.pow(2, attempt - 1));
48
+ }
49
+ }
50
+ /**
51
+ * Compute an HMAC-SHA256 signature of `body` using `secret`.
52
+ * The signature is returned as `sha256=<hex>` so receivers can verify it.
53
+ */
54
+ sign(body, secret) {
55
+ return 'sha256=' + (0, crypto_1.createHmac)('sha256', secret).update(body).digest('hex');
56
+ }
57
+ /**
58
+ * Verify an inbound webhook signature.
59
+ * Returns `true` when the `X-Webhook-Signature` header matches the
60
+ * expected HMAC-SHA256 of the raw request body.
61
+ *
62
+ * Use constant-time comparison to prevent timing attacks.
63
+ */
64
+ verify(body, secret, signature) {
65
+ const expected = this.sign(body, secret);
66
+ // Constant-time comparison
67
+ if (expected.length !== signature.length)
68
+ return false;
69
+ let diff = 0;
70
+ for (let i = 0; i < expected.length; i++) {
71
+ diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
72
+ }
73
+ return diff === 0;
74
+ }
75
+ delay(ms) {
76
+ return new Promise((resolve) => setTimeout(resolve, ms));
77
+ }
78
+ }
79
+ exports.WebhookSender = WebhookSender;
80
+ //# sourceMappingURL=webhook-sender.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-sender.js","sourceRoot":"","sources":["../../src/tools/webhook-sender.ts"],"names":[],"mappings":";;;AAAA,mCAAgD;AAGhD;;;GAGG;AACH,MAAa,aAAa;IACxB;;;;;;;OAOG;IACH,KAAK,CAAC,IAAI,CAAC,MAAqB,EAAE,KAA2B;QAC3D,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,CAAC,CAAC;QAC1C,MAAM,WAAW,GAAG,MAAM,CAAC,YAAY,IAAI,IAAK,CAAC;QACjD,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAEnC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,OAAO,OAAO,IAAI,UAAU,EAAE,CAAC;YAC7B,MAAM,OAAO,GAA2B;gBACtC,cAAc,EAAE,kBAAkB;gBAClC,iBAAiB,EAAE,KAAK,CAAC,KAAK;gBAC9B,oBAAoB,EAAE,IAAA,mBAAU,GAAE;gBAClC,qBAAqB,EAAE,KAAK,CAAC,SAAS;aACvC,CAAC;YAEF,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,OAAO,CAAC,qBAAqB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;YAClE,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;gBACvE,IAAI,GAAG,CAAC,EAAE;oBAAE,OAAO;gBACnB,oCAAoC;gBACpC,IAAI,OAAO,IAAI,UAAU;oBAAE,OAAO;YACpC,CAAC;YAAC,MAAM,CAAC;gBACP,0CAA0C;gBAC1C,IAAI,OAAO,IAAI,UAAU;oBAAE,OAAO;YACpC,CAAC;YAED,OAAO,EAAE,CAAC;YACV,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,IAAI,CAAC,IAAY,EAAE,MAAc;QAC/B,OAAO,SAAS,GAAG,IAAA,mBAAU,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;OAMG;IACH,MAAM,CAAC,IAAY,EAAE,MAAc,EAAE,SAAiB;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACzC,2BAA2B;QAC3B,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QACvD,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACzC,IAAI,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAC3D,CAAC;QACD,OAAO,IAAI,KAAK,CAAC,CAAC;IACpB,CAAC;IAEO,KAAK,CAAC,EAAU;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;CACF;AAvED,sCAuEC"}
@@ -0,0 +1,115 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>2FA Verification - Awesome Node Auth</title>
8
+ <link rel="stylesheet" href="base.css">
9
+ </head>
10
+
11
+ <body>
12
+ <div class="auth-container">
13
+ <img src="" alt="Logo" class="logo hidden">
14
+ <h1 class="site-name" data-i18n="site_name">Awesome Node Auth</h1>
15
+ <h2 data-i18n="two_fa_title">Two-Factor Authentication</h2>
16
+
17
+ <div id="alert" class="alert alert-error hidden"></div>
18
+
19
+ <form id="2fa-form">
20
+ <div class="form-group">
21
+ <label for="code" data-i18n="two_fa_code_label">Verification Code</label>
22
+ <p id="method-text" class="footer-links" style="margin-bottom: 8px; text-align: left; font-size: 12px;">
23
+ </p>
24
+ <input type="text" id="code" name="code" required placeholder="000000" data-i18n="two_fa_code_placeholder"
25
+ style="text-align: center; letter-spacing: 4px; font-size: 18px;">
26
+ </div>
27
+ <button type="submit" id="submit-btn" data-i18n="verify_code_button">Verify Code</button>
28
+ </form>
29
+
30
+ <div id="setup-section" class="hidden">
31
+ <p class="footer-links" data-i18n="two_fa_setup_instruction">Scan this QR code with your authenticator app:</p>
32
+ <img id="qr-code" style="display: block; margin: 16px auto; width: 200px; height: 200px;">
33
+ <p class="footer-links" style="font-size: 12px;">Or enter this code manually: <strong id="secret"></strong>
34
+ </p>
35
+ </div>
36
+
37
+ <div class="footer-links">
38
+ <a href="login">Back to login</a>
39
+ </div>
40
+ </div>
41
+
42
+ <script src="auth.js?v=2"></script>
43
+ <script>
44
+ document.addEventListener('DOMContentLoaded', async () => {
45
+ await AuthService.init();
46
+
47
+ const urlParams = new URLSearchParams(window.location.search);
48
+ const token = urlParams.get('token');
49
+ const mode = urlParams.get('mode') || 'verify'; // verify or setup
50
+
51
+ if (!token && mode !== 'setup') {
52
+ window.location.href = 'login';
53
+ return;
54
+ }
55
+
56
+ const form = document.getElementById('2fa-form');
57
+ const alert = document.getElementById('alert');
58
+ const submitBtn = document.getElementById('submit-btn');
59
+ const methodText = document.getElementById('method-text');
60
+
61
+ if (mode === 'setup') {
62
+ document.querySelector('h2').textContent = '2FA Setup';
63
+ document.getElementById('setup-section').classList.remove('hidden');
64
+
65
+ // Fetch setup data
66
+ const setupData = await AuthService.apiCall('/2fa/setup', 'POST');
67
+ if (setupData.qrCode) {
68
+ document.getElementById('qr-code').src = setupData.qrCode;
69
+ document.getElementById('secret').textContent = setupData.secret;
70
+ } else {
71
+ alert.textContent = 'Failed to load 2FA setup';
72
+ alert.classList.remove('hidden');
73
+ }
74
+ } else {
75
+ methodText.textContent = 'Enter the code from your Authenticator App or SMS.';
76
+ }
77
+
78
+ form.addEventListener('submit', async (e) => {
79
+ e.preventDefault();
80
+ alert.className = 'alert alert-error hidden';
81
+ submitBtn.disabled = true;
82
+ submitBtn.textContent = 'Verifying...';
83
+
84
+ const code = form.code.value;
85
+
86
+ try {
87
+ let endpoint = '/2fa/verify';
88
+ let payload = { tempToken: token, totpCode: code };
89
+
90
+ if (mode === 'setup') {
91
+ endpoint = '/2fa/verify-setup';
92
+ payload = { token: code, secret: document.getElementById('secret').textContent };
93
+ }
94
+
95
+ const result = await AuthService.apiCall(endpoint, 'POST', payload);
96
+
97
+ if (result.success) {
98
+ window.location.href = '/';
99
+ } else {
100
+ alert.textContent = result.error || 'Verification failed';
101
+ alert.classList.remove('hidden');
102
+ }
103
+ } catch (err) {
104
+ alert.textContent = 'An unexpected error occurred';
105
+ alert.classList.remove('hidden');
106
+ } finally {
107
+ submitBtn.disabled = false;
108
+ submitBtn.textContent = mode === 'setup' ? 'Verify Setup' : 'Verify Code';
109
+ }
110
+ });
111
+ });
112
+ </script>
113
+ </body>
114
+
115
+ </html>