@techelusive/nest-auth 0.0.0-stage → 0.2.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 (340) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/LICENSE +21 -0
  3. package/README.md +204 -2
  4. package/api/openapi.json +4399 -0
  5. package/api/techelusive-authentication.postman_collection.json +3178 -0
  6. package/dist/auth-options.d.ts +156 -0
  7. package/dist/auth-options.js +3 -0
  8. package/dist/auth-options.js.map +1 -0
  9. package/dist/auth-options.validation.d.ts +9 -0
  10. package/dist/auth-options.validation.js +74 -0
  11. package/dist/auth-options.validation.js.map +1 -0
  12. package/dist/auth.module.d.ts +38 -0
  13. package/dist/auth.module.js +173 -0
  14. package/dist/auth.module.js.map +1 -0
  15. package/dist/common/auth-error.d.ts +16 -0
  16. package/dist/common/auth-error.js +23 -0
  17. package/dist/common/auth-error.js.map +1 -0
  18. package/dist/common/auth-exception.filter.d.ts +24 -0
  19. package/dist/common/auth-exception.filter.js +66 -0
  20. package/dist/common/auth-exception.filter.js.map +1 -0
  21. package/dist/common/crypto/jwt.d.ts +89 -0
  22. package/dist/common/crypto/jwt.js +144 -0
  23. package/dist/common/crypto/jwt.js.map +1 -0
  24. package/dist/common/crypto/random-secrets.d.ts +78 -0
  25. package/dist/common/crypto/random-secrets.js +113 -0
  26. package/dist/common/crypto/random-secrets.js.map +1 -0
  27. package/dist/common/crypto/remote-public-keys.d.ts +49 -0
  28. package/dist/common/crypto/remote-public-keys.js +105 -0
  29. package/dist/common/crypto/remote-public-keys.js.map +1 -0
  30. package/dist/common/crypto/totp.d.ts +68 -0
  31. package/dist/common/crypto/totp.js +148 -0
  32. package/dist/common/crypto/totp.js.map +1 -0
  33. package/dist/common/encryption.service.d.ts +36 -0
  34. package/dist/common/encryption.service.js +111 -0
  35. package/dist/common/encryption.service.js.map +1 -0
  36. package/dist/common/identifiers.d.ts +48 -0
  37. package/dist/common/identifiers.js +89 -0
  38. package/dist/common/identifiers.js.map +1 -0
  39. package/dist/common/network-safety.d.ts +19 -0
  40. package/dist/common/network-safety.js +72 -0
  41. package/dist/common/network-safety.js.map +1 -0
  42. package/dist/common/rate-limiter.service.d.ts +63 -0
  43. package/dist/common/rate-limiter.service.js +158 -0
  44. package/dist/common/rate-limiter.service.js.map +1 -0
  45. package/dist/common/refresh-token-cookie.d.ts +78 -0
  46. package/dist/common/refresh-token-cookie.js +185 -0
  47. package/dist/common/refresh-token-cookie.js.map +1 -0
  48. package/dist/common/request-context.d.ts +14 -0
  49. package/dist/common/request-context.js +22 -0
  50. package/dist/common/request-context.js.map +1 -0
  51. package/dist/common/strict-validation.pipe.d.ts +8 -0
  52. package/dist/common/strict-validation.pipe.js +16 -0
  53. package/dist/common/strict-validation.pipe.js.map +1 -0
  54. package/dist/common/time.d.ts +15 -0
  55. package/dist/common/time.js +34 -0
  56. package/dist/common/time.js.map +1 -0
  57. package/dist/constants.d.ts +1326 -0
  58. package/dist/constants.js +1142 -0
  59. package/dist/constants.js.map +1 -0
  60. package/dist/database/create-auth-tables.migration.d.ts +26 -0
  61. package/dist/database/create-auth-tables.migration.js +291 -0
  62. package/dist/database/create-auth-tables.migration.js.map +1 -0
  63. package/dist/database/email-change-and-retention.migration.d.ts +25 -0
  64. package/dist/database/email-change-and-retention.migration.js +54 -0
  65. package/dist/database/email-change-and-retention.migration.js.map +1 -0
  66. package/dist/database/entities/api-key.entity.d.ts +17 -0
  67. package/dist/database/entities/api-key.entity.js +80 -0
  68. package/dist/database/entities/api-key.entity.js.map +1 -0
  69. package/dist/database/entities/audit-log.entity.d.ts +13 -0
  70. package/dist/database/entities/audit-log.entity.js +64 -0
  71. package/dist/database/entities/audit-log.entity.js.map +1 -0
  72. package/dist/database/entities/backup-code.entity.d.ts +9 -0
  73. package/dist/database/entities/backup-code.entity.js +52 -0
  74. package/dist/database/entities/backup-code.entity.js.map +1 -0
  75. package/dist/database/entities/index.d.ts +43 -0
  76. package/dist/database/entities/index.js +82 -0
  77. package/dist/database/entities/index.js.map +1 -0
  78. package/dist/database/entities/json-object.d.ts +12 -0
  79. package/dist/database/entities/json-object.js +3 -0
  80. package/dist/database/entities/json-object.js.map +1 -0
  81. package/dist/database/entities/link-token.entity.d.ts +15 -0
  82. package/dist/database/entities/link-token.entity.js +64 -0
  83. package/dist/database/entities/link-token.entity.js.map +1 -0
  84. package/dist/database/entities/one-time-code.entity.d.ts +16 -0
  85. package/dist/database/entities/one-time-code.entity.js +69 -0
  86. package/dist/database/entities/one-time-code.entity.js.map +1 -0
  87. package/dist/database/entities/organization-invitation.entity.d.ts +14 -0
  88. package/dist/database/entities/organization-invitation.entity.js +73 -0
  89. package/dist/database/entities/organization-invitation.entity.js.map +1 -0
  90. package/dist/database/entities/organization-member.entity.d.ts +9 -0
  91. package/dist/database/entities/organization-member.entity.js +48 -0
  92. package/dist/database/entities/organization-member.entity.js.map +1 -0
  93. package/dist/database/entities/organization.entity.d.ts +12 -0
  94. package/dist/database/entities/organization.entity.js +58 -0
  95. package/dist/database/entities/organization.entity.js.map +1 -0
  96. package/dist/database/entities/passkey.entity.d.ts +19 -0
  97. package/dist/database/entities/passkey.entity.js +78 -0
  98. package/dist/database/entities/passkey.entity.js.map +1 -0
  99. package/dist/database/entities/password.entity.d.ts +6 -0
  100. package/dist/database/entities/password.entity.js +36 -0
  101. package/dist/database/entities/password.entity.js.map +1 -0
  102. package/dist/database/entities/refresh-token.entity.d.ts +9 -0
  103. package/dist/database/entities/refresh-token.entity.js +53 -0
  104. package/dist/database/entities/refresh-token.entity.js.map +1 -0
  105. package/dist/database/entities/session.entity.d.ts +20 -0
  106. package/dist/database/entities/session.entity.js +87 -0
  107. package/dist/database/entities/session.entity.js.map +1 -0
  108. package/dist/database/entities/settings.entity.d.ts +7 -0
  109. package/dist/database/entities/settings.entity.js +41 -0
  110. package/dist/database/entities/settings.entity.js.map +1 -0
  111. package/dist/database/entities/sign-in-challenge.entity.d.ts +23 -0
  112. package/dist/database/entities/sign-in-challenge.entity.js +80 -0
  113. package/dist/database/entities/sign-in-challenge.entity.js.map +1 -0
  114. package/dist/database/entities/signing-key.entity.d.ts +11 -0
  115. package/dist/database/entities/signing-key.entity.js +54 -0
  116. package/dist/database/entities/signing-key.entity.js.map +1 -0
  117. package/dist/database/entities/social-account.entity.d.ts +13 -0
  118. package/dist/database/entities/social-account.entity.js +54 -0
  119. package/dist/database/entities/social-account.entity.js.map +1 -0
  120. package/dist/database/entities/two-factor-secret.entity.d.ts +10 -0
  121. package/dist/database/entities/two-factor-secret.entity.js +48 -0
  122. package/dist/database/entities/two-factor-secret.entity.js.map +1 -0
  123. package/dist/database/entities/user.entity.d.ts +34 -0
  124. package/dist/database/entities/user.entity.js +129 -0
  125. package/dist/database/entities/user.entity.js.map +1 -0
  126. package/dist/database/entities/webhook-delivery.entity.d.ts +17 -0
  127. package/dist/database/entities/webhook-delivery.entity.js +79 -0
  128. package/dist/database/entities/webhook-delivery.entity.js.map +1 -0
  129. package/dist/database/entities/webhook.entity.d.ts +12 -0
  130. package/dist/database/entities/webhook.entity.js +58 -0
  131. package/dist/database/entities/webhook.entity.js.map +1 -0
  132. package/dist/database/typeorm-row.d.ts +21 -0
  133. package/dist/database/typeorm-row.js +29 -0
  134. package/dist/database/typeorm-row.js.map +1 -0
  135. package/dist/features/account/account.controller.d.ts +259 -0
  136. package/dist/features/account/account.controller.js +372 -0
  137. package/dist/features/account/account.controller.js.map +1 -0
  138. package/dist/features/account/account.dto.d.ts +16 -0
  139. package/dist/features/account/account.dto.js +61 -0
  140. package/dist/features/account/account.dto.js.map +1 -0
  141. package/dist/features/account/data-export.service.d.ts +85 -0
  142. package/dist/features/account/data-export.service.js +125 -0
  143. package/dist/features/account/data-export.service.js.map +1 -0
  144. package/dist/features/account/health.service.d.ts +30 -0
  145. package/dist/features/account/health.service.js +59 -0
  146. package/dist/features/account/health.service.js.map +1 -0
  147. package/dist/features/admin/admin.controller.d.ts +382 -0
  148. package/dist/features/admin/admin.controller.js +623 -0
  149. package/dist/features/admin/admin.controller.js.map +1 -0
  150. package/dist/features/admin/admin.dto.d.ts +61 -0
  151. package/dist/features/admin/admin.dto.js +253 -0
  152. package/dist/features/admin/admin.dto.js.map +1 -0
  153. package/dist/features/admin/admin.service.d.ts +142 -0
  154. package/dist/features/admin/admin.service.js +227 -0
  155. package/dist/features/admin/admin.service.js.map +1 -0
  156. package/dist/features/api-keys/api-keys.service.d.ts +67 -0
  157. package/dist/features/api-keys/api-keys.service.js +144 -0
  158. package/dist/features/api-keys/api-keys.service.js.map +1 -0
  159. package/dist/features/email-change/email-change.controller.d.ts +38 -0
  160. package/dist/features/email-change/email-change.controller.js +85 -0
  161. package/dist/features/email-change/email-change.controller.js.map +1 -0
  162. package/dist/features/email-change/email-change.dto.d.ts +10 -0
  163. package/dist/features/email-change/email-change.dto.js +43 -0
  164. package/dist/features/email-change/email-change.dto.js.map +1 -0
  165. package/dist/features/email-change/email-change.service.d.ts +74 -0
  166. package/dist/features/email-change/email-change.service.js +179 -0
  167. package/dist/features/email-change/email-change.service.js.map +1 -0
  168. package/dist/features/email-links/email-verification.service.d.ts +46 -0
  169. package/dist/features/email-links/email-verification.service.js +94 -0
  170. package/dist/features/email-links/email-verification.service.js.map +1 -0
  171. package/dist/features/email-links/link-tokens.service.d.ts +44 -0
  172. package/dist/features/email-links/link-tokens.service.js +122 -0
  173. package/dist/features/email-links/link-tokens.service.js.map +1 -0
  174. package/dist/features/events/auth-events.service.d.ts +52 -0
  175. package/dist/features/events/auth-events.service.js +117 -0
  176. package/dist/features/events/auth-events.service.js.map +1 -0
  177. package/dist/features/maintenance/data-retention.service.d.ts +60 -0
  178. package/dist/features/maintenance/data-retention.service.js +182 -0
  179. package/dist/features/maintenance/data-retention.service.js.map +1 -0
  180. package/dist/features/notifications/notifications.service.d.ts +40 -0
  181. package/dist/features/notifications/notifications.service.js +94 -0
  182. package/dist/features/notifications/notifications.service.js.map +1 -0
  183. package/dist/features/one-time-codes/one-time-codes.service.d.ts +40 -0
  184. package/dist/features/one-time-codes/one-time-codes.service.js +102 -0
  185. package/dist/features/one-time-codes/one-time-codes.service.js.map +1 -0
  186. package/dist/features/organizations/organizations.controller.d.ts +220 -0
  187. package/dist/features/organizations/organizations.controller.js +337 -0
  188. package/dist/features/organizations/organizations.controller.js.map +1 -0
  189. package/dist/features/organizations/organizations.dto.d.ts +31 -0
  190. package/dist/features/organizations/organizations.dto.js +100 -0
  191. package/dist/features/organizations/organizations.dto.js.map +1 -0
  192. package/dist/features/organizations/organizations.service.d.ts +274 -0
  193. package/dist/features/organizations/organizations.service.js +481 -0
  194. package/dist/features/organizations/organizations.service.js.map +1 -0
  195. package/dist/features/passkeys/passkeys.controller.d.ts +92 -0
  196. package/dist/features/passkeys/passkeys.controller.js +183 -0
  197. package/dist/features/passkeys/passkeys.controller.js.map +1 -0
  198. package/dist/features/passkeys/passkeys.dto.d.ts +13 -0
  199. package/dist/features/passkeys/passkeys.dto.js +50 -0
  200. package/dist/features/passkeys/passkeys.dto.js.map +1 -0
  201. package/dist/features/passkeys/passkeys.service.d.ts +159 -0
  202. package/dist/features/passkeys/passkeys.service.js +350 -0
  203. package/dist/features/passkeys/passkeys.service.js.map +1 -0
  204. package/dist/features/passwordless/passwordless-auth.service.d.ts +152 -0
  205. package/dist/features/passwordless/passwordless-auth.service.js +304 -0
  206. package/dist/features/passwordless/passwordless-auth.service.js.map +1 -0
  207. package/dist/features/passwordless/passwordless.controller.d.ts +75 -0
  208. package/dist/features/passwordless/passwordless.controller.js +163 -0
  209. package/dist/features/passwordless/passwordless.controller.js.map +1 -0
  210. package/dist/features/passwordless/passwordless.dto.d.ts +29 -0
  211. package/dist/features/passwordless/passwordless.dto.js +101 -0
  212. package/dist/features/passwordless/passwordless.dto.js.map +1 -0
  213. package/dist/features/passwords/password-auth.service.d.ts +154 -0
  214. package/dist/features/passwords/password-auth.service.js +317 -0
  215. package/dist/features/passwords/password-auth.service.js.map +1 -0
  216. package/dist/features/passwords/password-policy.service.d.ts +63 -0
  217. package/dist/features/passwords/password-policy.service.js +175 -0
  218. package/dist/features/passwords/password-policy.service.js.map +1 -0
  219. package/dist/features/passwords/password.controller.d.ts +95 -0
  220. package/dist/features/passwords/password.controller.js +193 -0
  221. package/dist/features/passwords/password.controller.js.map +1 -0
  222. package/dist/features/passwords/password.dto.d.ts +36 -0
  223. package/dist/features/passwords/password.dto.js +140 -0
  224. package/dist/features/passwords/password.dto.js.map +1 -0
  225. package/dist/features/sessions/session.service.d.ts +132 -0
  226. package/dist/features/sessions/session.service.js +308 -0
  227. package/dist/features/sessions/session.service.js.map +1 -0
  228. package/dist/features/sessions/session.types.d.ts +34 -0
  229. package/dist/features/sessions/session.types.js +3 -0
  230. package/dist/features/sessions/session.types.js.map +1 -0
  231. package/dist/features/settings/settings.defaults.d.ts +2 -0
  232. package/dist/features/settings/settings.defaults.js +7 -0
  233. package/dist/features/settings/settings.defaults.js.map +1 -0
  234. package/dist/features/settings/settings.service.d.ts +44 -0
  235. package/dist/features/settings/settings.service.js +112 -0
  236. package/dist/features/settings/settings.service.js.map +1 -0
  237. package/dist/features/settings/settings.types.d.ts +133 -0
  238. package/dist/features/settings/settings.types.js +3 -0
  239. package/dist/features/settings/settings.types.js.map +1 -0
  240. package/dist/features/settings/settings.validation.d.ts +33 -0
  241. package/dist/features/settings/settings.validation.js +614 -0
  242. package/dist/features/settings/settings.validation.js.map +1 -0
  243. package/dist/features/sign-in/sign-in-challenges.service.d.ts +54 -0
  244. package/dist/features/sign-in/sign-in-challenges.service.js +104 -0
  245. package/dist/features/sign-in/sign-in-challenges.service.js.map +1 -0
  246. package/dist/features/sign-in/sign-in.service.d.ts +127 -0
  247. package/dist/features/sign-in/sign-in.service.js +248 -0
  248. package/dist/features/sign-in/sign-in.service.js.map +1 -0
  249. package/dist/features/sign-in/sign-in.types.d.ts +22 -0
  250. package/dist/features/sign-in/sign-in.types.js +3 -0
  251. package/dist/features/sign-in/sign-in.types.js.map +1 -0
  252. package/dist/features/signing-keys/jwks.controller.d.ts +17 -0
  253. package/dist/features/signing-keys/jwks.controller.js +51 -0
  254. package/dist/features/signing-keys/jwks.controller.js.map +1 -0
  255. package/dist/features/signing-keys/signing-keys.service.d.ts +67 -0
  256. package/dist/features/signing-keys/signing-keys.service.js +142 -0
  257. package/dist/features/signing-keys/signing-keys.service.js.map +1 -0
  258. package/dist/features/social/social-auth.service.d.ts +100 -0
  259. package/dist/features/social/social-auth.service.js +232 -0
  260. package/dist/features/social/social-auth.service.js.map +1 -0
  261. package/dist/features/social/social-providers.service.d.ts +97 -0
  262. package/dist/features/social/social-providers.service.js +214 -0
  263. package/dist/features/social/social-providers.service.js.map +1 -0
  264. package/dist/features/social/social.controller.d.ts +54 -0
  265. package/dist/features/social/social.controller.js +124 -0
  266. package/dist/features/social/social.controller.js.map +1 -0
  267. package/dist/features/social/social.dto.d.ts +11 -0
  268. package/dist/features/social/social.dto.js +38 -0
  269. package/dist/features/social/social.dto.js.map +1 -0
  270. package/dist/features/two-factor/two-factor.controller.d.ts +84 -0
  271. package/dist/features/two-factor/two-factor.controller.js +174 -0
  272. package/dist/features/two-factor/two-factor.controller.js.map +1 -0
  273. package/dist/features/two-factor/two-factor.dto.d.ts +10 -0
  274. package/dist/features/two-factor/two-factor.dto.js +37 -0
  275. package/dist/features/two-factor/two-factor.dto.js.map +1 -0
  276. package/dist/features/two-factor/two-factor.service.d.ts +171 -0
  277. package/dist/features/two-factor/two-factor.service.js +348 -0
  278. package/dist/features/two-factor/two-factor.service.js.map +1 -0
  279. package/dist/features/users/sign-in-methods.service.d.ts +40 -0
  280. package/dist/features/users/sign-in-methods.service.js +81 -0
  281. package/dist/features/users/sign-in-methods.service.js.map +1 -0
  282. package/dist/features/users/sign-up-rules.d.ts +12 -0
  283. package/dist/features/users/sign-up-rules.js +29 -0
  284. package/dist/features/users/sign-up-rules.js.map +1 -0
  285. package/dist/features/users/users.service.d.ts +246 -0
  286. package/dist/features/users/users.service.js +509 -0
  287. package/dist/features/users/users.service.js.map +1 -0
  288. package/dist/features/users/users.types.d.ts +24 -0
  289. package/dist/features/users/users.types.js +3 -0
  290. package/dist/features/users/users.types.js.map +1 -0
  291. package/dist/features/webhooks/webhook-delivery.worker.d.ts +55 -0
  292. package/dist/features/webhooks/webhook-delivery.worker.js +175 -0
  293. package/dist/features/webhooks/webhook-delivery.worker.js.map +1 -0
  294. package/dist/features/webhooks/webhook-signature.d.ts +28 -0
  295. package/dist/features/webhooks/webhook-signature.js +85 -0
  296. package/dist/features/webhooks/webhook-signature.js.map +1 -0
  297. package/dist/features/webhooks/webhooks.service.d.ts +144 -0
  298. package/dist/features/webhooks/webhooks.service.js +238 -0
  299. package/dist/features/webhooks/webhooks.service.js.map +1 -0
  300. package/dist/guards/auth.decorators.d.ts +65 -0
  301. package/dist/guards/auth.decorators.js +68 -0
  302. package/dist/guards/auth.decorators.js.map +1 -0
  303. package/dist/guards/auth.guard.d.ts +47 -0
  304. package/dist/guards/auth.guard.js +132 -0
  305. package/dist/guards/auth.guard.js.map +1 -0
  306. package/dist/index.d.ts +38 -0
  307. package/dist/index.js +95 -0
  308. package/dist/index.js.map +1 -0
  309. package/dist/integrations/console-email-sender.d.ts +15 -0
  310. package/dist/integrations/console-email-sender.js +26 -0
  311. package/dist/integrations/console-email-sender.js.map +1 -0
  312. package/dist/integrations/msg91-sms-sender.d.ts +28 -0
  313. package/dist/integrations/msg91-sms-sender.js +46 -0
  314. package/dist/integrations/msg91-sms-sender.js.map +1 -0
  315. package/docs/README.md +39 -0
  316. package/docs/admin-api-keys-webhooks.md +147 -0
  317. package/docs/api-reference.md +175 -0
  318. package/docs/architecture.md +119 -0
  319. package/docs/client-integration.md +192 -0
  320. package/docs/code-walkthrough.md +273 -0
  321. package/docs/configuration.md +229 -0
  322. package/docs/endpoints/README.md +151 -0
  323. package/docs/endpoints/account.md +363 -0
  324. package/docs/endpoints/admin.md +422 -0
  325. package/docs/endpoints/email-password.md +261 -0
  326. package/docs/endpoints/organizations.md +192 -0
  327. package/docs/endpoints/passkeys.md +195 -0
  328. package/docs/endpoints/passwordless.md +214 -0
  329. package/docs/endpoints/social.md +116 -0
  330. package/docs/endpoints/two-factor.md +146 -0
  331. package/docs/error-codes.md +100 -0
  332. package/docs/events.md +83 -0
  333. package/docs/getting-started.md +139 -0
  334. package/docs/manual-testing.md +215 -0
  335. package/docs/organizations.md +86 -0
  336. package/docs/production-checklist.md +60 -0
  337. package/docs/protecting-routes.md +147 -0
  338. package/docs/security.md +61 -0
  339. package/docs/sign-in-flows.md +294 -0
  340. package/package.json +92 -4
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTotpSecret = createTotpSecret;
4
+ exports.buildAuthenticatorAppUrl = buildAuthenticatorAppUrl;
5
+ exports.totpCodeForTimeStep = totpCodeForTimeStep;
6
+ exports.timeStepAt = timeStepAt;
7
+ exports.verifyTotpCode = verifyTotpCode;
8
+ exports.encodeBase32 = encodeBase32;
9
+ exports.decodeBase32 = decodeBase32;
10
+ const node_crypto_1 = require("node:crypto");
11
+ const constants_1 = require("../../constants.js");
12
+ const random_secrets_1 = require("./random-secrets");
13
+ const time_1 = require("../time.js");
14
+ /**
15
+ * Time-based one-time passwords (RFC 6238): the 6-digit codes shown by Google Authenticator,
16
+ * Microsoft Authenticator, 1Password, Authy and others.
17
+ *
18
+ * The user's app and our server share a secret. Every 30 seconds both compute
19
+ * HMAC-SHA1(secret, current 30-second step) and turn it into 6 digits. If they match, the user
20
+ * has the phone that holds the secret.
21
+ */
22
+ /**
23
+ * Creates a new random secret, in the Base32 form authenticator apps expect.
24
+ *
25
+ * @returns The secret, e.g. "JBSWY3DPEHPK3PXP...".
26
+ */
27
+ function createTotpSecret() {
28
+ return encodeBase32((0, node_crypto_1.randomBytes)(constants_1.TOTP_SECRET_BYTES));
29
+ }
30
+ /**
31
+ * Builds the link inside the QR code the user scans:
32
+ * otpauth://totp/Issuer:account?secret=...&issuer=Issuer
33
+ *
34
+ * @param secret - The Base32 secret from `createTotpSecret`.
35
+ * @param accountName - Shown in the app under the issuer, usually the user's email.
36
+ * @param issuerName - Your app's name, shown in the authenticator app.
37
+ * @returns The otpauth:// URL to turn into a QR code.
38
+ */
39
+ function buildAuthenticatorAppUrl(secret, accountName, issuerName) {
40
+ const label = encodeURIComponent(`${issuerName}${constants_1.TOTP_LABEL_SEPARATOR}${accountName}`);
41
+ const parameters = new URLSearchParams({
42
+ secret,
43
+ issuer: issuerName,
44
+ algorithm: constants_1.TOTP_URL_ALGORITHM_NAME,
45
+ digits: String(constants_1.TOTP_DIGITS),
46
+ period: String(constants_1.TOTP_PERIOD_SECONDS),
47
+ });
48
+ return constants_1.AUTHENTICATOR_APP_URL_PREFIX + label + constants_1.URL_QUERY_SEPARATOR + parameters.toString();
49
+ }
50
+ /**
51
+ * Computes the 6-digit code for one 30-second time step (RFC 6238).
52
+ *
53
+ * @param secret - The Base32 secret shared with the authenticator app.
54
+ * @param timeStep - Which 30-second window, from `timeStepAt`.
55
+ * @returns The code as a zero-padded string, e.g. "042913".
56
+ */
57
+ function totpCodeForTimeStep(secret, timeStep) {
58
+ const counter = Buffer.alloc(constants_1.TOTP_COUNTER_BYTES);
59
+ counter.writeBigUInt64BE(BigInt(timeStep));
60
+ const hmac = (0, node_crypto_1.createHmac)(constants_1.HASH_ALGORITHM.sha1, decodeBase32(secret)).update(counter).digest();
61
+ // "Dynamic truncation" from RFC 4226: pick 4 bytes based on the last nibble, keep 31 bits.
62
+ const offset = hmac[hmac.length - 1] & 0x0f;
63
+ const binaryCode = hmac.readUInt32BE(offset) & 0x7fffffff;
64
+ return String(binaryCode % constants_1.DECIMAL_BASE ** constants_1.TOTP_DIGITS).padStart(constants_1.TOTP_DIGITS, constants_1.TOTP_CODE_PAD_CHARACTER);
65
+ }
66
+ /**
67
+ * Converts a time into its 30-second TOTP window number.
68
+ *
69
+ * @param unixTimeSeconds - Seconds since 1970.
70
+ * @returns The time step that contains this moment.
71
+ */
72
+ function timeStepAt(unixTimeSeconds) {
73
+ return Math.floor(unixTimeSeconds / constants_1.TOTP_PERIOD_SECONDS);
74
+ }
75
+ /**
76
+ * Checks a code the user typed from their authenticator app.
77
+ *
78
+ * Codes from one step before or after the current one are also accepted, because phone clocks drift.
79
+ * `lastAcceptedTimeStep` blocks replay: a code that was already used (or an older one) is refused,
80
+ * so someone who sees a code over the user's shoulder cannot use it again.
81
+ *
82
+ * @param secret - The user's Base32 secret.
83
+ * @param typedCode - What the user typed; spaces are ignored.
84
+ * @param lastAcceptedTimeStep - The step of the last code accepted for this user, or null if none.
85
+ * @param unixTimeSeconds - The current time; only passed in tests.
86
+ * @returns The matching time step (save it as the new lastAcceptedTimeStep), or null if wrong.
87
+ */
88
+ function verifyTotpCode(secret, typedCode, lastAcceptedTimeStep, unixTimeSeconds = (0, time_1.currentUnixTimeSeconds)()) {
89
+ const cleanCode = typedCode.replace(constants_1.WHITESPACE_PATTERN, '');
90
+ if (!constants_1.DIGITS_ONLY_PATTERN.test(cleanCode) || cleanCode.length !== constants_1.TOTP_DIGITS)
91
+ return null;
92
+ const currentStep = timeStepAt(unixTimeSeconds);
93
+ for (let drift = -constants_1.TOTP_ALLOWED_DRIFT_STEPS; drift <= constants_1.TOTP_ALLOWED_DRIFT_STEPS; drift++) {
94
+ const step = currentStep + drift;
95
+ const alreadyUsed = lastAcceptedTimeStep !== null && step <= lastAcceptedTimeStep;
96
+ if (!alreadyUsed && (0, random_secrets_1.constantTimeEquals)(totpCodeForTimeStep(secret, step), cleanCode))
97
+ return step;
98
+ }
99
+ return null;
100
+ }
101
+ /**
102
+ * Encodes bytes as Base32 (RFC 4648, no padding), the format authenticator apps use for secrets.
103
+ *
104
+ * @param bytes - The raw bytes.
105
+ * @returns The Base32 text.
106
+ */
107
+ function encodeBase32(bytes) {
108
+ let bits = 0;
109
+ let value = 0;
110
+ let output = '';
111
+ for (const byte of bytes) {
112
+ value = (value << constants_1.BITS_PER_BYTE) | byte;
113
+ bits += constants_1.BITS_PER_BYTE;
114
+ while (bits >= constants_1.BASE32_BITS_PER_CHARACTER) {
115
+ output += constants_1.BASE32_ALPHABET[(value >>> (bits - constants_1.BASE32_BITS_PER_CHARACTER)) & constants_1.BASE32_CHARACTER_MASK];
116
+ bits -= constants_1.BASE32_BITS_PER_CHARACTER;
117
+ }
118
+ }
119
+ if (bits > 0)
120
+ output += constants_1.BASE32_ALPHABET[(value << (constants_1.BASE32_BITS_PER_CHARACTER - bits)) & constants_1.BASE32_CHARACTER_MASK];
121
+ return output;
122
+ }
123
+ /**
124
+ * Decodes Base32 text back into bytes. Lower case and "=" padding are accepted.
125
+ *
126
+ * @param text - The Base32 text.
127
+ * @returns The decoded bytes.
128
+ * @throws If the text contains a character that is not in the Base32 alphabet.
129
+ */
130
+ function decodeBase32(text) {
131
+ const cleanText = text.toUpperCase().replace(constants_1.BASE32_PADDING_PATTERN, '');
132
+ let bits = 0;
133
+ let value = 0;
134
+ const bytes = [];
135
+ for (const character of cleanText) {
136
+ const index = constants_1.BASE32_ALPHABET.indexOf(character);
137
+ if (index === -1)
138
+ throw new Error('Invalid Base32 character');
139
+ value = (value << constants_1.BASE32_BITS_PER_CHARACTER) | index;
140
+ bits += constants_1.BASE32_BITS_PER_CHARACTER;
141
+ if (bits >= constants_1.BITS_PER_BYTE) {
142
+ bytes.push((value >>> (bits - constants_1.BITS_PER_BYTE)) & constants_1.BYTE_MASK);
143
+ bits -= constants_1.BITS_PER_BYTE;
144
+ }
145
+ }
146
+ return Buffer.from(bytes);
147
+ }
148
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sourceRoot":"","sources":["../../../src/common/crypto/totp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { AuthOptions } from '../auth-options.js';
2
+ /**
3
+ * Encrypts secrets we must be able to read back later: the token-signing private key,
4
+ * authenticator-app secrets and webhook signing secrets. (Passwords and tokens are hashed instead.)
5
+ *
6
+ * Stored format: "<method>:<base64 data>", where method is "kms" or "local". Keeping the method
7
+ * with the data means you can move from the local key to KMS later and old values still decrypt.
8
+ *
9
+ * `context` (e.g. { purpose: 'two-factor', userId }) is bound to the ciphertext: a value copied
10
+ * onto another user's row will not decrypt.
11
+ */
12
+ export declare class EncryptionService {
13
+ private readonly kmsClient?;
14
+ private readonly kmsKeyId?;
15
+ private readonly localKey?;
16
+ constructor(options: AuthOptions);
17
+ /**
18
+ * Encrypts a secret with AWS KMS when configured, otherwise with the local AES-256-GCM key.
19
+ *
20
+ * @param plainText - The secret to protect.
21
+ * @param context - Binds the value to its owner, e.g. { purpose: 'two-factor', userId }.
22
+ * The same context must be passed to `decrypt`.
23
+ * @returns The stored form, "<method>:<base64 data>".
24
+ */
25
+ encrypt(plainText: string, context: Record<string, string>): Promise<string>;
26
+ /**
27
+ * Decrypts a value written by `encrypt`. Works for both "kms" and "local" values,
28
+ * whichever method is configured now.
29
+ *
30
+ * @param storedValue - The "<method>:<base64 data>" text from the database.
31
+ * @param context - The same context that was used to encrypt it.
32
+ * @returns The original secret.
33
+ * @throws If the context doesn't match, the data was tampered with, or the needed key is not configured.
34
+ */
35
+ decrypt(storedValue: string, context: Record<string, string>): Promise<string>;
36
+ }
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.EncryptionService = void 0;
16
+ const client_kms_1 = require("@aws-sdk/client-kms");
17
+ const common_1 = require("@nestjs/common");
18
+ const node_crypto_1 = require("node:crypto");
19
+ const constants_1 = require("../constants.js");
20
+ /**
21
+ * Encrypts secrets we must be able to read back later: the token-signing private key,
22
+ * authenticator-app secrets and webhook signing secrets. (Passwords and tokens are hashed instead.)
23
+ *
24
+ * Stored format: "<method>:<base64 data>", where method is "kms" or "local". Keeping the method
25
+ * with the data means you can move from the local key to KMS later and old values still decrypt.
26
+ *
27
+ * `context` (e.g. { purpose: 'two-factor', userId }) is bound to the ciphertext: a value copied
28
+ * onto another user's row will not decrypt.
29
+ */
30
+ let EncryptionService = class EncryptionService {
31
+ kmsClient;
32
+ kmsKeyId;
33
+ localKey;
34
+ constructor(options) {
35
+ if ('awsKmsKeyId' in options.encryption) {
36
+ this.kmsClient = new client_kms_1.KMSClient({ region: options.encryption.awsRegion });
37
+ this.kmsKeyId = options.encryption.awsKmsKeyId;
38
+ // Only for reading values written before the move to KMS.
39
+ if (options.encryption.legacyLocalKey)
40
+ this.localKey = Buffer.from(options.encryption.legacyLocalKey, constants_1.TEXT_ENCODING.base64);
41
+ }
42
+ else {
43
+ this.localKey = Buffer.from(options.encryption.localKey, constants_1.TEXT_ENCODING.base64);
44
+ }
45
+ }
46
+ /**
47
+ * Encrypts a secret with AWS KMS when configured, otherwise with the local AES-256-GCM key.
48
+ *
49
+ * @param plainText - The secret to protect.
50
+ * @param context - Binds the value to its owner, e.g. { purpose: 'two-factor', userId }.
51
+ * The same context must be passed to `decrypt`.
52
+ * @returns The stored form, "<method>:<base64 data>".
53
+ */
54
+ async encrypt(plainText, context) {
55
+ if (this.kmsClient) {
56
+ const result = await this.kmsClient.send(new client_kms_1.EncryptCommand({ KeyId: this.kmsKeyId, Plaintext: Buffer.from(plainText, constants_1.TEXT_ENCODING.utf8), EncryptionContext: context }));
57
+ return `${constants_1.ENCRYPTED_VALUE_METHOD.kms}${constants_1.ENCRYPTED_VALUE_SEPARATOR}${Buffer.from(result.CiphertextBlob).toString(constants_1.TEXT_ENCODING.base64)}`;
58
+ }
59
+ const initializationVector = (0, node_crypto_1.randomBytes)(constants_1.AES_GCM_INITIALIZATION_VECTOR_BYTES);
60
+ const cipher = (0, node_crypto_1.createCipheriv)(constants_1.LOCAL_ENCRYPTION_CIPHER, this.localKey, initializationVector);
61
+ cipher.setAAD(Buffer.from(describeContext(context)));
62
+ const encrypted = Buffer.concat([cipher.update(plainText, constants_1.TEXT_ENCODING.utf8), cipher.final()]);
63
+ const authenticationTag = cipher.getAuthTag();
64
+ return `${constants_1.ENCRYPTED_VALUE_METHOD.local}${constants_1.ENCRYPTED_VALUE_SEPARATOR}${Buffer.concat([initializationVector, authenticationTag, encrypted]).toString(constants_1.TEXT_ENCODING.base64)}`;
65
+ }
66
+ /**
67
+ * Decrypts a value written by `encrypt`. Works for both "kms" and "local" values,
68
+ * whichever method is configured now.
69
+ *
70
+ * @param storedValue - The "<method>:<base64 data>" text from the database.
71
+ * @param context - The same context that was used to encrypt it.
72
+ * @returns The original secret.
73
+ * @throws If the context doesn't match, the data was tampered with, or the needed key is not configured.
74
+ */
75
+ async decrypt(storedValue, context) {
76
+ const separatorIndex = storedValue.indexOf(constants_1.ENCRYPTED_VALUE_SEPARATOR);
77
+ const method = storedValue.slice(0, separatorIndex);
78
+ const data = Buffer.from(storedValue.slice(separatorIndex + 1), constants_1.TEXT_ENCODING.base64);
79
+ if (method === constants_1.ENCRYPTED_VALUE_METHOD.kms) {
80
+ // KMS knows which key encrypted the data, so no key id is needed here.
81
+ const client = this.kmsClient ?? new client_kms_1.KMSClient({});
82
+ const result = await client.send(new client_kms_1.DecryptCommand({ CiphertextBlob: data, EncryptionContext: context }));
83
+ return Buffer.from(result.Plaintext).toString(constants_1.TEXT_ENCODING.utf8);
84
+ }
85
+ if (method === constants_1.ENCRYPTED_VALUE_METHOD.local) {
86
+ if (!this.localKey)
87
+ throw new Error('This value was encrypted with a local key, but no localKey is configured');
88
+ const decipher = (0, node_crypto_1.createDecipheriv)(constants_1.LOCAL_ENCRYPTION_CIPHER, this.localKey, data.subarray(0, constants_1.AES_GCM_INITIALIZATION_VECTOR_BYTES));
89
+ decipher.setAAD(Buffer.from(describeContext(context)));
90
+ decipher.setAuthTag(data.subarray(constants_1.AES_GCM_INITIALIZATION_VECTOR_BYTES, constants_1.AES_GCM_INITIALIZATION_VECTOR_BYTES + constants_1.AES_GCM_AUTH_TAG_BYTES));
91
+ return Buffer.concat([decipher.update(data.subarray(constants_1.AES_GCM_INITIALIZATION_VECTOR_BYTES + constants_1.AES_GCM_AUTH_TAG_BYTES)), decipher.final()]).toString(constants_1.TEXT_ENCODING.utf8);
92
+ }
93
+ throw new Error(`Unknown encryption method "${method}"`);
94
+ }
95
+ };
96
+ exports.EncryptionService = EncryptionService;
97
+ exports.EncryptionService = EncryptionService = __decorate([
98
+ (0, common_1.Injectable)(),
99
+ __param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
100
+ __metadata("design:paramtypes", [Object])
101
+ ], EncryptionService);
102
+ /**
103
+ * Turns the context into one stable string (keys sorted) for AES-GCM's "additional authenticated data".
104
+ *
105
+ * @param context - The encryption context.
106
+ * @returns A JSON string that is the same for the same keys and values in any order.
107
+ */
108
+ function describeContext(context) {
109
+ return JSON.stringify(Object.keys(context).sort().map((key) => [key, context[key]]));
110
+ }
111
+ //# sourceMappingURL=encryption.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"encryption.service.js","sourceRoot":"","sources":["../../src/common/encryption.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Turns what the user typed into the one form we store and look up, so "Asha@Mail.com " and
3
+ * "asha@mail.com" are the same account.
4
+ *
5
+ * @param typedEmail - The raw value from the request.
6
+ * @returns The trimmed, lower-case email.
7
+ * @throws AuthError INVALID_EMAIL if it is not an email address.
8
+ */
9
+ export declare function normalizeEmail(typedEmail: unknown): string;
10
+ /**
11
+ * Turns "+91 98765-43210" into "+919876543210" (E.164). The country code is required,
12
+ * because guessing it would send codes to the wrong country.
13
+ *
14
+ * @param typedPhoneNumber - The raw value from the request.
15
+ * @returns The phone number in E.164 form.
16
+ * @throws AuthError INVALID_PHONE if it is not a valid international number.
17
+ */
18
+ export declare function normalizePhoneNumber(typedPhoneNumber: unknown): string;
19
+ /**
20
+ * Checks the email's domain against an allow-list.
21
+ *
22
+ * @param email - A normalized email.
23
+ * @param allowedDomains - e.g. ["company.com"]; an empty list allows any domain.
24
+ * @returns True when the domain is allowed.
25
+ */
26
+ export declare function isEmailDomainAllowed(email: string, allowedDomains: string[]): boolean;
27
+ /**
28
+ * Checks the phone number's country code against an allow-list.
29
+ *
30
+ * @param phoneNumber - A normalized E.164 phone number.
31
+ * @param allowedCountryCodes - e.g. ["+91", "+1"]; an empty list allows any country.
32
+ * @returns True when the country is allowed.
33
+ */
34
+ export declare function isPhoneCountryAllowed(phoneNumber: string, allowedCountryCodes: string[]): boolean;
35
+ /**
36
+ * Hides most of an email for messages and logs: "asha@mail.com" -> "a***@mail.com".
37
+ *
38
+ * @param email - A normalized email.
39
+ * @returns The masked email.
40
+ */
41
+ export declare function maskEmail(email: string): string;
42
+ /**
43
+ * Hides the middle of a phone number for messages and logs: "+919876543210" -> "+91******3210".
44
+ *
45
+ * @param phoneNumber - A normalized E.164 phone number.
46
+ * @returns The masked phone number.
47
+ */
48
+ export declare function maskPhoneNumber(phoneNumber: string): string;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeEmail = normalizeEmail;
4
+ exports.normalizePhoneNumber = normalizePhoneNumber;
5
+ exports.isEmailDomainAllowed = isEmailDomainAllowed;
6
+ exports.isPhoneCountryAllowed = isPhoneCountryAllowed;
7
+ exports.maskEmail = maskEmail;
8
+ exports.maskPhoneNumber = maskPhoneNumber;
9
+ const constants_1 = require("../constants.js");
10
+ const auth_error_1 = require("./auth-error");
11
+ /**
12
+ * Turns what the user typed into the one form we store and look up, so "Asha@Mail.com " and
13
+ * "asha@mail.com" are the same account.
14
+ *
15
+ * @param typedEmail - The raw value from the request.
16
+ * @returns The trimmed, lower-case email.
17
+ * @throws AuthError INVALID_EMAIL if it is not an email address.
18
+ */
19
+ function normalizeEmail(typedEmail) {
20
+ if (typeof typedEmail !== 'string')
21
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidEmail);
22
+ const email = typedEmail.trim().toLowerCase();
23
+ if (email.length > constants_1.EMAIL_MAX_LENGTH || !constants_1.EMAIL_PATTERN.test(email))
24
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidEmail);
25
+ return email;
26
+ }
27
+ /**
28
+ * Turns "+91 98765-43210" into "+919876543210" (E.164). The country code is required,
29
+ * because guessing it would send codes to the wrong country.
30
+ *
31
+ * @param typedPhoneNumber - The raw value from the request.
32
+ * @returns The phone number in E.164 form.
33
+ * @throws AuthError INVALID_PHONE if it is not a valid international number.
34
+ */
35
+ function normalizePhoneNumber(typedPhoneNumber) {
36
+ if (typeof typedPhoneNumber !== 'string')
37
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidPhone);
38
+ const phoneNumber = typedPhoneNumber.replace(constants_1.PHONE_FORMATTING_CHARACTERS_PATTERN, '');
39
+ if (!constants_1.PHONE_NUMBER_PATTERN.test(phoneNumber))
40
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidPhone);
41
+ return phoneNumber;
42
+ }
43
+ /**
44
+ * Checks the email's domain against an allow-list.
45
+ *
46
+ * @param email - A normalized email.
47
+ * @param allowedDomains - e.g. ["company.com"]; an empty list allows any domain.
48
+ * @returns True when the domain is allowed.
49
+ */
50
+ function isEmailDomainAllowed(email, allowedDomains) {
51
+ if (allowedDomains.length === 0)
52
+ return true;
53
+ const domain = email.slice(email.lastIndexOf(constants_1.EMAIL_MASK.domainSeparator) + 1);
54
+ return allowedDomains.some((allowedDomain) => allowedDomain.toLowerCase() === domain);
55
+ }
56
+ /**
57
+ * Checks the phone number's country code against an allow-list.
58
+ *
59
+ * @param phoneNumber - A normalized E.164 phone number.
60
+ * @param allowedCountryCodes - e.g. ["+91", "+1"]; an empty list allows any country.
61
+ * @returns True when the country is allowed.
62
+ */
63
+ function isPhoneCountryAllowed(phoneNumber, allowedCountryCodes) {
64
+ if (allowedCountryCodes.length === 0)
65
+ return true;
66
+ return allowedCountryCodes.some((countryCode) => phoneNumber.startsWith(countryCode));
67
+ }
68
+ /**
69
+ * Hides most of an email for messages and logs: "asha@mail.com" -> "a***@mail.com".
70
+ *
71
+ * @param email - A normalized email.
72
+ * @returns The masked email.
73
+ */
74
+ function maskEmail(email) {
75
+ const [localPart, domain] = email.split(constants_1.EMAIL_MASK.domainSeparator);
76
+ return `${localPart.slice(0, constants_1.EMAIL_MASK.visibleCharacters)}${constants_1.EMAIL_MASK.hiddenPart}${constants_1.EMAIL_MASK.domainSeparator}${domain}`;
77
+ }
78
+ /**
79
+ * Hides the middle of a phone number for messages and logs: "+919876543210" -> "+91******3210".
80
+ *
81
+ * @param phoneNumber - A normalized E.164 phone number.
82
+ * @returns The masked phone number.
83
+ */
84
+ function maskPhoneNumber(phoneNumber) {
85
+ const visibleEnd = phoneNumber.slice(-constants_1.PHONE_MASK.visibleEnd);
86
+ const visibleStart = phoneNumber.slice(0, constants_1.PHONE_MASK.visibleStart);
87
+ return `${visibleStart}${constants_1.PHONE_MASK.maskCharacter.repeat(Math.max(phoneNumber.length - constants_1.PHONE_MASK.visibleStart - constants_1.PHONE_MASK.visibleEnd, 0))}${visibleEnd}`;
88
+ }
89
+ //# sourceMappingURL=identifiers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identifiers.js","sourceRoot":"","sources":["../../src/common/identifiers.ts"],"names":[],"mappings":";;AAmBA,wCAKC;AAUD,oDAKC;AASD,oDAIC;AASD,sDAGC;AAQD,8BAGC;AAQD,0CAIC;AAvFD,2CAQqB;AACrB,6CAAyC;AAEzC;;;;;;;GAOG;AACH,SAAgB,cAAc,CAAC,UAAmB;IAChD,IAAI,OAAO,UAAU,KAAK,QAAQ;QAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,YAAY,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,IAAI,KAAK,CAAC,MAAM,GAAG,4BAAgB,IAAI,CAAC,yBAAa,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,YAAY,CAAC,CAAC;IACjH,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,oBAAoB,CAAC,gBAAyB;IAC5D,IAAI,OAAO,gBAAgB,KAAK,QAAQ;QAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,YAAY,CAAC,CAAC;IACxF,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,+CAAmC,EAAE,EAAE,CAAC,CAAC;IACtF,IAAI,CAAC,gCAAoB,CAAC,IAAI,CAAC,WAAW,CAAC;QAAE,MAAM,IAAI,sBAAS,CAAC,uBAAW,CAAC,YAAY,CAAC,CAAC;IAC3F,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,oBAAoB,CAAC,KAAa,EAAE,cAAwB;IAC1E,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,sBAAU,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9E,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,aAAa,EAAE,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,CAAC;AACxF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,qBAAqB,CAAC,WAAmB,EAAE,mBAA6B;IACtF,IAAI,mBAAmB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClD,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,WAAW,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;AACxF,CAAC;AAED;;;;;GAKG;AACH,SAAgB,SAAS,CAAC,KAAa;IACrC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,sBAAU,CAAC,eAAe,CAAC,CAAC;IACpE,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAU,CAAC,iBAAiB,CAAC,GAAG,sBAAU,CAAC,UAAU,GAAG,sBAAU,CAAC,eAAe,GAAG,MAAM,EAAE,CAAC;AAC7H,CAAC;AAED;;;;;GAKG;AACH,SAAgB,eAAe,CAAC,WAAmB;IACjD,MAAM,UAAU,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,sBAAU,CAAC,UAAU,CAAC,CAAC;IAC7D,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,sBAAU,CAAC,YAAY,CAAC,CAAC;IACnE,OAAO,GAAG,YAAY,GAAG,sBAAU,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,GAAG,sBAAU,CAAC,YAAY,GAAG,sBAAU,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC;AAC7J,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Checks whether an IP address is private, loopback, link-local, multicast or otherwise not public.
3
+ *
4
+ * @param ipAddress - An IPv4 or IPv6 address.
5
+ * @returns True when the address must not be called (anything that isn't an IP also counts as unsafe).
6
+ */
7
+ export declare function isPrivateIpAddress(ipAddress: string): boolean;
8
+ /**
9
+ * Checks that a URL is safe for the server to call (protection against SSRF):
10
+ * - https only (http allowed for localhost only when NODE_ENV is not "production", for local testing),
11
+ * - no username/password in the URL,
12
+ * - every address the host name resolves to is public.
13
+ *
14
+ * Call it when a webhook is saved AND right before each delivery, because DNS answers can change.
15
+ *
16
+ * @param rawUrl - The URL to check.
17
+ * @returns A reason (from URL_SAFETY_PROBLEM) when unsafe, or null when safe.
18
+ */
19
+ export declare function findUrlSafetyProblem(rawUrl: string): Promise<string | null>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isPrivateIpAddress = isPrivateIpAddress;
4
+ exports.findUrlSafetyProblem = findUrlSafetyProblem;
5
+ const promises_1 = require("node:dns/promises");
6
+ const node_net_1 = require("node:net");
7
+ const constants_1 = require("../constants.js");
8
+ /** All address ranges that outgoing webhook calls must never reach. */
9
+ const blockedAddresses = new node_net_1.BlockList();
10
+ for (const [network, prefixLength] of constants_1.BLOCKED_IPV4_RANGES)
11
+ blockedAddresses.addSubnet(network, prefixLength, constants_1.IP_FAMILY.ipv4);
12
+ for (const [network, prefixLength] of constants_1.BLOCKED_IPV6_RANGES)
13
+ blockedAddresses.addSubnet(network, prefixLength, constants_1.IP_FAMILY.ipv6);
14
+ /**
15
+ * Checks whether an IP address is private, loopback, link-local, multicast or otherwise not public.
16
+ *
17
+ * @param ipAddress - An IPv4 or IPv6 address.
18
+ * @returns True when the address must not be called (anything that isn't an IP also counts as unsafe).
19
+ */
20
+ function isPrivateIpAddress(ipAddress) {
21
+ const version = (0, node_net_1.isIP)(ipAddress);
22
+ if (version === 0)
23
+ return true; // Not an IP at all: treat as unsafe.
24
+ if (version === constants_1.IP_VERSION_6) {
25
+ // "::ffff:10.0.0.1" is an IPv4 address in IPv6 clothing; check the IPv4 part too.
26
+ const mappedIpv4 = constants_1.IPV4_MAPPED_IPV6_PATTERN.exec(ipAddress)?.[1];
27
+ if (mappedIpv4 && blockedAddresses.check(mappedIpv4, constants_1.IP_FAMILY.ipv4))
28
+ return true;
29
+ return blockedAddresses.check(ipAddress, constants_1.IP_FAMILY.ipv6);
30
+ }
31
+ return blockedAddresses.check(ipAddress, constants_1.IP_FAMILY.ipv4);
32
+ }
33
+ /**
34
+ * Checks that a URL is safe for the server to call (protection against SSRF):
35
+ * - https only (http allowed for localhost only when NODE_ENV is not "production", for local testing),
36
+ * - no username/password in the URL,
37
+ * - every address the host name resolves to is public.
38
+ *
39
+ * Call it when a webhook is saved AND right before each delivery, because DNS answers can change.
40
+ *
41
+ * @param rawUrl - The URL to check.
42
+ * @returns A reason (from URL_SAFETY_PROBLEM) when unsafe, or null when safe.
43
+ */
44
+ async function findUrlSafetyProblem(rawUrl) {
45
+ let url;
46
+ try {
47
+ url = new URL(rawUrl);
48
+ }
49
+ catch {
50
+ return constants_1.URL_SAFETY_PROBLEM.invalidUrl;
51
+ }
52
+ if (url.username || url.password)
53
+ return constants_1.URL_SAFETY_PROBLEM.hasCredentials;
54
+ const hostName = url.hostname.replace(constants_1.URL_IPV6_BRACKETS_PATTERN, '');
55
+ const isLocalDevelopmentUrl = process.env.NODE_ENV !== constants_1.PRODUCTION_ENVIRONMENT && url.protocol === constants_1.URL_PROTOCOL.http && constants_1.LOCAL_DEVELOPMENT_HOSTS.includes(hostName);
56
+ if (isLocalDevelopmentUrl)
57
+ return null;
58
+ if (url.protocol !== constants_1.URL_PROTOCOL.https)
59
+ return constants_1.URL_SAFETY_PROBLEM.notHttps;
60
+ try {
61
+ const resolvedAddresses = (0, node_net_1.isIP)(hostName) ? [{ address: hostName }] : await (0, promises_1.lookup)(hostName, { all: true, verbatim: true });
62
+ if (resolvedAddresses.length === 0)
63
+ return constants_1.URL_SAFETY_PROBLEM.doesNotResolve;
64
+ if (resolvedAddresses.some(({ address }) => isPrivateIpAddress(address)))
65
+ return constants_1.URL_SAFETY_PROBLEM.privateAddress;
66
+ }
67
+ catch {
68
+ return constants_1.URL_SAFETY_PROBLEM.doesNotResolve;
69
+ }
70
+ return null;
71
+ }
72
+ //# sourceMappingURL=network-safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"network-safety.js","sourceRoot":"","sources":["../../src/common/network-safety.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import { AuthOptions } from '../auth-options.js';
3
+ import { HealthStatus, RateLimitRule } from '../constants.js';
4
+ /**
5
+ * Counts attempts per key (an IP address, an email, a user id) inside a time window and blocks
6
+ * once there are too many. Counters live in Redis when `redisUrl` is set (shared by every server),
7
+ * otherwise in this server's memory.
8
+ */
9
+ export declare class RateLimiterService implements OnModuleDestroy {
10
+ private readonly logger;
11
+ private readonly redisClient?;
12
+ private readonly countersInMemory;
13
+ constructor(options: AuthOptions);
14
+ /**
15
+ * For the health check: is Redis answering?
16
+ *
17
+ * @returns "ok", "error", or "not_used" when rate limits live in memory.
18
+ */
19
+ checkRedis(): Promise<HealthStatus>;
20
+ /**
21
+ * Closes the Redis connection when the app shuts down, so the process can exit cleanly.
22
+ */
23
+ onModuleDestroy(): Promise<void>;
24
+ /**
25
+ * Records one attempt for `key` and blocks when there have been too many inside the rule's window.
26
+ *
27
+ * @param key - What is being limited, e.g. an IP address, an email or a user id.
28
+ * @param rule - How many attempts are allowed and in how long a window.
29
+ * @throws AuthError TOO_MANY_REQUESTS (with `retryAfterSeconds`) when the limit is exceeded.
30
+ * @example
31
+ * await rateLimiter.consume(`sign-in:ip:${ipAddress}`, RATE_LIMITS.signInPerIp);
32
+ */
33
+ consume(key: string, rule: RateLimitRule): Promise<void>;
34
+ /**
35
+ * Forgets the attempts for `key`, e.g. the per-account sign-in counter after a successful sign-in.
36
+ *
37
+ * @param key - The same key that was passed to `consume`.
38
+ */
39
+ reset(key: string): Promise<void>;
40
+ /**
41
+ * Counts an attempt in Redis (shared by every server).
42
+ * If Redis is down the request is blocked, rather than allowing unlimited password or code guessing.
43
+ *
44
+ * @param key - What is being limited.
45
+ * @param rule - The limit to apply.
46
+ * @returns 0 when allowed, otherwise the seconds until the window resets.
47
+ */
48
+ private countInRedis;
49
+ /**
50
+ * Counts an attempt in this server's memory (used when there is no Redis).
51
+ *
52
+ * @param key - What is being limited.
53
+ * @param rule - The limit to apply.
54
+ * @returns 0 when allowed, otherwise the seconds until the window resets.
55
+ */
56
+ private countInMemory;
57
+ /**
58
+ * Removes finished windows once the map gets large, so memory stays bounded on long-running servers.
59
+ *
60
+ * @param now - The current time in milliseconds.
61
+ */
62
+ private pruneExpiredCounters;
63
+ }