@techelusive/nest-auth 0.0.0-stage → 0.2.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 (340) hide show
  1. package/CHANGELOG.md +71 -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,74 @@
1
+ import { AuthOptions } from '../../auth-options.js';
2
+ import { RateLimiterService } from '../../common/rate-limiter.service.js';
3
+ import { RequestContext } from '../../common/request-context.js';
4
+ import { LinkTokensService } from '../email-links/link-tokens.service.js';
5
+ import { AuthEventsService } from '../events/auth-events.service.js';
6
+ import { NotificationsService } from '../notifications/notifications.service.js';
7
+ import { PasswordPolicyService } from '../passwords/password-policy.service.js';
8
+ import { SettingsService } from '../settings/settings.service.js';
9
+ import { UsersService } from '../users/users.service.js';
10
+ /**
11
+ * Changing the email of an account:
12
+ * - POST /auth/email/change (signed in) checks it's really the owner (current password, or a
13
+ * recent sign-in for accounts without one) and emails a confirmation link to the NEW address.
14
+ * It answers the same whether or not that address already belongs to someone, so it can't be
15
+ * used to find out who has an account.
16
+ * - POST /auth/email/change/confirm (the link) switches the email, marks it verified and tells
17
+ * the OLD address, so an unwanted change gets noticed.
18
+ */
19
+ export declare class EmailChangeService {
20
+ private readonly settingsService;
21
+ private readonly users;
22
+ private readonly linkTokens;
23
+ private readonly notifications;
24
+ private readonly passwordPolicy;
25
+ private readonly events;
26
+ private readonly rateLimiter;
27
+ private readonly database;
28
+ constructor(options: AuthOptions, settingsService: SettingsService, users: UsersService, linkTokens: LinkTokensService, notifications: NotificationsService, passwordPolicy: PasswordPolicyService, events: AuthEventsService, rateLimiter: RateLimiterService);
29
+ /**
30
+ * Starts an email change: checks it's really the owner and emails a confirmation link to the NEW address.
31
+ * Answers the same whether or not that address already belongs to someone.
32
+ * Only one change can be pending: a newer request cancels the older link.
33
+ *
34
+ * @param userId - The signed-in user.
35
+ * @param sessionId - This device's session (used for the "recent sign-in" check).
36
+ * @param typedNewEmail - The new email address.
37
+ * @param currentPassword - Required when the account has a password.
38
+ * @param request - Who is asking (for the audit log).
39
+ * @returns A generic success message.
40
+ * @throws AuthError EMAIL_UNCHANGED when the new email is the current one.
41
+ * @throws AuthError EMAIL_DOMAIN_NOT_ALLOWED when the domain is not in the allowed list.
42
+ * @throws AuthError INVALID_CREDENTIALS or RECENT_SIGN_IN_REQUIRED (see `confirmOwner`).
43
+ */
44
+ requestEmailChange(userId: string, sessionId: string, typedNewEmail: unknown, currentPassword: unknown, request: RequestContext): Promise<{
45
+ success: true;
46
+ message: "If this email can be used, a confirmation link is on its way to it.";
47
+ }>;
48
+ /**
49
+ * Finishes an email change (the emailed link): switches the email, marks it verified, cancels links sent to the old address,
50
+ * and tells the OLD address so an unwanted change gets noticed.
51
+ *
52
+ * @param token - The token from the confirmation link.
53
+ * @param request - Who is asking (for the audit log).
54
+ * @returns A success message and the updated profile.
55
+ * @throws AuthError INVALID_TOKEN when the link is unknown, used or expired.
56
+ * @throws AuthError EMAIL_IN_USE when someone took the address after the link was sent.
57
+ */
58
+ confirmEmailChange(token: unknown, request: RequestContext): Promise<{
59
+ success: true;
60
+ message: "Your email has been changed.";
61
+ user: import("../..").PublicUserProfile;
62
+ }>;
63
+ /**
64
+ * A stolen access token alone must not be enough to take over the account by changing its
65
+ * email. Accounts with a password must type it; others must have signed in in the last minutes.
66
+ *
67
+ * @param user - The signed-in user.
68
+ * @param sessionId - This device's session.
69
+ * @param currentPassword - What the user typed as their current password.
70
+ * @throws AuthError INVALID_CREDENTIALS when the password is wrong.
71
+ * @throws AuthError RECENT_SIGN_IN_REQUIRED when a password-less user signed in too long ago.
72
+ */
73
+ private confirmOwner;
74
+ }
@@ -0,0 +1,179 @@
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.EmailChangeService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("typeorm");
18
+ const auth_error_1 = require("../../common/auth-error.js");
19
+ const identifiers_1 = require("../../common/identifiers.js");
20
+ const rate_limiter_service_1 = require("../../common/rate-limiter.service.js");
21
+ const constants_1 = require("../../constants.js");
22
+ const entities_1 = require("../../database/entities/index.js");
23
+ const link_tokens_service_1 = require("../email-links/link-tokens.service.js");
24
+ const auth_events_service_1 = require("../events/auth-events.service.js");
25
+ const notifications_service_1 = require("../notifications/notifications.service.js");
26
+ const password_policy_service_1 = require("../passwords/password-policy.service.js");
27
+ const settings_service_1 = require("../settings/settings.service.js");
28
+ const users_service_1 = require("../users/users.service.js");
29
+ /** Links sent to the old address stop working once the email changes. */
30
+ const LINKS_FOR_THE_OLD_ADDRESS = [constants_1.LINK_PURPOSE.verifyEmail, constants_1.LINK_PURPOSE.resetPassword, constants_1.LINK_PURPOSE.magicSignIn];
31
+ /**
32
+ * Changing the email of an account:
33
+ * - POST /auth/email/change (signed in) checks it's really the owner (current password, or a
34
+ * recent sign-in for accounts without one) and emails a confirmation link to the NEW address.
35
+ * It answers the same whether or not that address already belongs to someone, so it can't be
36
+ * used to find out who has an account.
37
+ * - POST /auth/email/change/confirm (the link) switches the email, marks it verified and tells
38
+ * the OLD address, so an unwanted change gets noticed.
39
+ */
40
+ let EmailChangeService = class EmailChangeService {
41
+ settingsService;
42
+ users;
43
+ linkTokens;
44
+ notifications;
45
+ passwordPolicy;
46
+ events;
47
+ rateLimiter;
48
+ database;
49
+ constructor(options, settingsService, users, linkTokens, notifications, passwordPolicy, events, rateLimiter) {
50
+ this.settingsService = settingsService;
51
+ this.users = users;
52
+ this.linkTokens = linkTokens;
53
+ this.notifications = notifications;
54
+ this.passwordPolicy = passwordPolicy;
55
+ this.events = events;
56
+ this.rateLimiter = rateLimiter;
57
+ this.database = options.dataSource;
58
+ }
59
+ /**
60
+ * Starts an email change: checks it's really the owner and emails a confirmation link to the NEW address.
61
+ * Answers the same whether or not that address already belongs to someone.
62
+ * Only one change can be pending: a newer request cancels the older link.
63
+ *
64
+ * @param userId - The signed-in user.
65
+ * @param sessionId - This device's session (used for the "recent sign-in" check).
66
+ * @param typedNewEmail - The new email address.
67
+ * @param currentPassword - Required when the account has a password.
68
+ * @param request - Who is asking (for the audit log).
69
+ * @returns A generic success message.
70
+ * @throws AuthError EMAIL_UNCHANGED when the new email is the current one.
71
+ * @throws AuthError EMAIL_DOMAIN_NOT_ALLOWED when the domain is not in the allowed list.
72
+ * @throws AuthError INVALID_CREDENTIALS or RECENT_SIGN_IN_REQUIRED (see `confirmOwner`).
73
+ */
74
+ async requestEmailChange(userId, sessionId, typedNewEmail, currentPassword, request) {
75
+ const settings = await this.settingsService.getSettings();
76
+ await this.rateLimiter.consume(constants_1.RATE_LIMIT_KEY.changeEmailPerUser(userId), constants_1.RATE_LIMITS.changeEmailPerUser);
77
+ const user = await this.users.getUserOrThrow(userId);
78
+ const newEmail = (0, identifiers_1.normalizeEmail)(typedNewEmail);
79
+ if (newEmail === user.email)
80
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.emailUnchanged);
81
+ if (!(0, identifiers_1.isEmailDomainAllowed)(newEmail, settings.registration.allowedEmailDomains))
82
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.emailDomainNotAllowed);
83
+ await this.confirmOwner(user, sessionId, currentPassword);
84
+ // Only one change can be pending: a newer request cancels the older link.
85
+ await this.linkTokens.cancelLinkTokens(constants_1.LINK_PURPOSE.changeEmail, userId);
86
+ const addressIsFree = (await this.users.findUserByEmail(newEmail)) === null;
87
+ if (addressIsFree) {
88
+ const token = await this.linkTokens.createLinkToken(constants_1.LINK_PURPOSE.changeEmail, newEmail, userId);
89
+ await this.notifications.sendRequiredEmail({ type: constants_1.EMAIL_TYPE.confirmEmailChange, to: newEmail, token });
90
+ }
91
+ await this.events.record({
92
+ name: constants_1.AUTH_EVENTS.emailChangeRequested,
93
+ userId,
94
+ request,
95
+ details: { newEmail: (0, identifiers_1.maskEmail)(newEmail) },
96
+ });
97
+ return { success: true, message: constants_1.SUCCESS_MESSAGES.emailChangeRequested };
98
+ }
99
+ /**
100
+ * Finishes an email change (the emailed link): switches the email, marks it verified, cancels links sent to the old address,
101
+ * and tells the OLD address so an unwanted change gets noticed.
102
+ *
103
+ * @param token - The token from the confirmation link.
104
+ * @param request - Who is asking (for the audit log).
105
+ * @returns A success message and the updated profile.
106
+ * @throws AuthError INVALID_TOKEN when the link is unknown, used or expired.
107
+ * @throws AuthError EMAIL_IN_USE when someone took the address after the link was sent.
108
+ */
109
+ async confirmEmailChange(token, request) {
110
+ const settings = await this.settingsService.getSettings();
111
+ const { userId, email: newEmail } = await this.linkTokens.useLinkToken(token, constants_1.LINK_PURPOSE.changeEmail);
112
+ if (!userId)
113
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
114
+ const user = await this.users.getUserOrThrow(userId);
115
+ const previousEmail = user.email;
116
+ try {
117
+ await this.database.getRepository(entities_1.UserEntity).update({ id: userId }, { email: newEmail, isEmailVerified: true });
118
+ }
119
+ catch (error) {
120
+ // Someone else took the address between the request and the click.
121
+ if (error instanceof typeorm_1.QueryFailedError && error.code === constants_1.POSTGRES_UNIQUE_VIOLATION_CODE) {
122
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.emailInUse);
123
+ }
124
+ throw error;
125
+ }
126
+ user.email = newEmail;
127
+ user.isEmailVerified = true;
128
+ for (const purpose of LINKS_FOR_THE_OLD_ADDRESS)
129
+ await this.linkTokens.cancelLinkTokens(purpose, userId);
130
+ if (previousEmail) {
131
+ await this.notifications.sendNoticeEmail({ type: constants_1.EMAIL_TYPE.emailChanged, to: previousEmail, newEmail: (0, identifiers_1.maskEmail)(newEmail) });
132
+ }
133
+ await this.events.record({
134
+ name: constants_1.AUTH_EVENTS.emailChanged,
135
+ userId,
136
+ request,
137
+ details: { previousEmail: previousEmail ? (0, identifiers_1.maskEmail)(previousEmail) : null, newEmail: (0, identifiers_1.maskEmail)(newEmail) },
138
+ });
139
+ // A pending account waiting only for a verified email becomes ACTIVE here.
140
+ const updatedUser = await this.users.activateIfReady(user, settings, request);
141
+ return { success: true, message: constants_1.SUCCESS_MESSAGES.emailChanged, user: this.users.toPublicProfile(updatedUser, settings) };
142
+ }
143
+ /**
144
+ * A stolen access token alone must not be enough to take over the account by changing its
145
+ * email. Accounts with a password must type it; others must have signed in in the last minutes.
146
+ *
147
+ * @param user - The signed-in user.
148
+ * @param sessionId - This device's session.
149
+ * @param currentPassword - What the user typed as their current password.
150
+ * @throws AuthError INVALID_CREDENTIALS when the password is wrong.
151
+ * @throws AuthError RECENT_SIGN_IN_REQUIRED when a password-less user signed in too long ago.
152
+ */
153
+ async confirmOwner(user, sessionId, currentPassword) {
154
+ const storedPassword = await this.database.getRepository(entities_1.PasswordEntity).findOne({ where: { userId: user.id } });
155
+ if (storedPassword) {
156
+ if (!(await this.passwordPolicy.verifyPassword(storedPassword.passwordHash, currentPassword))) {
157
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidCredentials);
158
+ }
159
+ return;
160
+ }
161
+ const session = await this.database.getRepository(entities_1.SessionEntity).findOne({ where: { id: sessionId, userId: user.id } });
162
+ const secondsSinceSignIn = session ? (Date.now() - session.createdAt.getTime()) / constants_1.SECOND_IN_MILLISECONDS : Infinity;
163
+ if (secondsSinceSignIn > constants_1.RECENT_SIGN_IN_SECONDS)
164
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.recentSignInRequired);
165
+ }
166
+ };
167
+ exports.EmailChangeService = EmailChangeService;
168
+ exports.EmailChangeService = EmailChangeService = __decorate([
169
+ (0, common_1.Injectable)(),
170
+ __param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
171
+ __metadata("design:paramtypes", [Object, settings_service_1.SettingsService,
172
+ users_service_1.UsersService,
173
+ link_tokens_service_1.LinkTokensService,
174
+ notifications_service_1.NotificationsService,
175
+ password_policy_service_1.PasswordPolicyService,
176
+ auth_events_service_1.AuthEventsService,
177
+ rate_limiter_service_1.RateLimiterService])
178
+ ], EmailChangeService);
179
+ //# sourceMappingURL=email-change.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-change.service.js","sourceRoot":"","sources":["../../../src/features/email-change/email-change.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { RateLimiterService } from '../../common/rate-limiter.service.js';
2
+ import { RequestContext } from '../../common/request-context.js';
3
+ import { NotificationsService } from '../notifications/notifications.service.js';
4
+ import { SettingsService } from '../settings/settings.service.js';
5
+ import { UsersService } from '../users/users.service.js';
6
+ import { LinkTokensService } from './link-tokens.service';
7
+ /**
8
+ * Proves the user owns their email address by sending them a link with a single-use token.
9
+ * Your app's page reads the token from the link and calls POST /auth/email/verify.
10
+ */
11
+ export declare class EmailVerificationService {
12
+ private readonly settingsService;
13
+ private readonly users;
14
+ private readonly linkTokens;
15
+ private readonly notifications;
16
+ private readonly rateLimiter;
17
+ constructor(settingsService: SettingsService, users: UsersService, linkTokens: LinkTokensService, notifications: NotificationsService, rateLimiter: RateLimiterService);
18
+ /**
19
+ * Sends (or re-sends) the verification link. Answers the same whether or not the email has an account.
20
+ *
21
+ * @param typedEmail - The email from the request.
22
+ * @param request - Who is asking (for rate limits).
23
+ * @returns A generic success message.
24
+ * @throws AuthError TOO_MANY_REQUESTS after too many emails for this IP or address.
25
+ */
26
+ sendVerificationEmail(typedEmail: unknown, request: RequestContext): Promise<{
27
+ success: true;
28
+ message: "If this email needs verifying, a verification link is on its way.";
29
+ }>;
30
+ /**
31
+ * Uses the token from the link. If this was the last verification the settings require, the
32
+ * account becomes ACTIVE and the user can sign in.
33
+ *
34
+ * @param token - The token from the link.
35
+ * @param request - Who is asking (for the audit log).
36
+ * @returns The account's new status and anything still left to verify.
37
+ * @throws AuthError INVALID_TOKEN when the link is unknown, used, expired, or the account's
38
+ * email has changed since it was sent.
39
+ */
40
+ verifyEmail(token: unknown, request: RequestContext): Promise<{
41
+ success: true;
42
+ message: "Your email has been verified.";
43
+ status: import("../../constants.js").AccountStatus;
44
+ pendingVerifications: import("../../constants.js").PendingVerification[];
45
+ }>;
46
+ }
@@ -0,0 +1,94 @@
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
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.EmailVerificationService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const auth_error_1 = require("../../common/auth-error.js");
15
+ const identifiers_1 = require("../../common/identifiers.js");
16
+ const rate_limiter_service_1 = require("../../common/rate-limiter.service.js");
17
+ const constants_1 = require("../../constants.js");
18
+ const notifications_service_1 = require("../notifications/notifications.service.js");
19
+ const settings_service_1 = require("../settings/settings.service.js");
20
+ const users_service_1 = require("../users/users.service.js");
21
+ const link_tokens_service_1 = require("./link-tokens.service");
22
+ /**
23
+ * Proves the user owns their email address by sending them a link with a single-use token.
24
+ * Your app's page reads the token from the link and calls POST /auth/email/verify.
25
+ */
26
+ let EmailVerificationService = class EmailVerificationService {
27
+ settingsService;
28
+ users;
29
+ linkTokens;
30
+ notifications;
31
+ rateLimiter;
32
+ constructor(settingsService, users, linkTokens, notifications, rateLimiter) {
33
+ this.settingsService = settingsService;
34
+ this.users = users;
35
+ this.linkTokens = linkTokens;
36
+ this.notifications = notifications;
37
+ this.rateLimiter = rateLimiter;
38
+ }
39
+ /**
40
+ * Sends (or re-sends) the verification link. Answers the same whether or not the email has an account.
41
+ *
42
+ * @param typedEmail - The email from the request.
43
+ * @param request - Who is asking (for rate limits).
44
+ * @returns A generic success message.
45
+ * @throws AuthError TOO_MANY_REQUESTS after too many emails for this IP or address.
46
+ */
47
+ async sendVerificationEmail(typedEmail, request) {
48
+ const email = (0, identifiers_1.normalizeEmail)(typedEmail);
49
+ await this.rateLimiter.consume(constants_1.RATE_LIMIT_KEY.sendEmailLinkPerIp(request.ipAddress), constants_1.RATE_LIMITS.sendEmailLinkPerIp);
50
+ await this.rateLimiter.consume(constants_1.RATE_LIMIT_KEY.sendEmailLinkPerEmail(email), constants_1.RATE_LIMITS.sendEmailLinkPerEmail);
51
+ const user = await this.users.findUserByEmail(email);
52
+ const needsVerifying = user && !user.isEmailVerified && user.status !== constants_1.ACCOUNT_STATUS.deleted && user.status !== constants_1.ACCOUNT_STATUS.suspended;
53
+ if (needsVerifying) {
54
+ const token = await this.linkTokens.createLinkToken(constants_1.LINK_PURPOSE.verifyEmail, email, user.id);
55
+ await this.notifications.sendRequiredEmail({ type: constants_1.EMAIL_TYPE.verifyEmail, to: email, token });
56
+ }
57
+ return { success: true, message: constants_1.SUCCESS_MESSAGES.verificationEmailSent };
58
+ }
59
+ /**
60
+ * Uses the token from the link. If this was the last verification the settings require, the
61
+ * account becomes ACTIVE and the user can sign in.
62
+ *
63
+ * @param token - The token from the link.
64
+ * @param request - Who is asking (for the audit log).
65
+ * @returns The account's new status and anything still left to verify.
66
+ * @throws AuthError INVALID_TOKEN when the link is unknown, used, expired, or the account's
67
+ * email has changed since it was sent.
68
+ */
69
+ async verifyEmail(token, request) {
70
+ const settings = await this.settingsService.getSettings();
71
+ const usedToken = await this.linkTokens.useLinkToken(token, constants_1.LINK_PURPOSE.verifyEmail);
72
+ const user = usedToken.userId ? await this.users.findUserById(usedToken.userId) : null;
73
+ // The account's email may have changed after this link was sent; then the link proves nothing.
74
+ if (!user || user.email !== usedToken.email || user.status === constants_1.ACCOUNT_STATUS.deleted)
75
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
76
+ const verifiedUser = await this.users.markEmailVerified(user, settings, request);
77
+ return {
78
+ success: true,
79
+ message: constants_1.SUCCESS_MESSAGES.emailVerified,
80
+ status: verifiedUser.status,
81
+ pendingVerifications: this.users.getPendingVerifications(verifiedUser, settings),
82
+ };
83
+ }
84
+ };
85
+ exports.EmailVerificationService = EmailVerificationService;
86
+ exports.EmailVerificationService = EmailVerificationService = __decorate([
87
+ (0, common_1.Injectable)(),
88
+ __metadata("design:paramtypes", [settings_service_1.SettingsService,
89
+ users_service_1.UsersService,
90
+ link_tokens_service_1.LinkTokensService,
91
+ notifications_service_1.NotificationsService,
92
+ rate_limiter_service_1.RateLimiterService])
93
+ ], EmailVerificationService);
94
+ //# sourceMappingURL=email-verification.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"email-verification.service.js","sourceRoot":"","sources":["../../../src/features/email-links/email-verification.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import { AuthOptions } from '../../auth-options.js';
2
+ import { SettingsService } from '../settings/settings.service.js';
3
+ import { LinkTokenPurpose } from '../../database/entities/index.js';
4
+ /** A link token that was valid and has now been used up. */
5
+ export interface UsedLinkToken {
6
+ userId: string | null;
7
+ email: string;
8
+ }
9
+ /**
10
+ * Single-use tokens put in emailed links (verify email, reset password, magic sign-in).
11
+ * Only the hash is stored. Creating a new token of a kind cancels the older ones for that email,
12
+ * so only the newest link in the user's inbox works.
13
+ */
14
+ export declare class LinkTokensService {
15
+ private readonly settingsService;
16
+ private readonly database;
17
+ constructor(options: AuthOptions, settingsService: SettingsService);
18
+ /**
19
+ * Creates a single-use token for an emailed link. Older unused tokens of the same kind for this
20
+ * email stop working, so only the newest link in the inbox works.
21
+ *
22
+ * @param purpose - What the link is for (verify email, reset password, magic sign-in, change email).
23
+ * @param email - The address the link is sent to.
24
+ * @param userId - The account it belongs to, or null (e.g. a magic link before sign-up).
25
+ * @returns The plain token to put in the link. Only its hash is stored.
26
+ */
27
+ createLinkToken(purpose: LinkTokenPurpose, email: string, userId: string | null): Promise<string>;
28
+ /**
29
+ * Uses up a token. Done in one UPDATE so that two clicks at the same moment can't both succeed.
30
+ *
31
+ * @param token - The token from the link.
32
+ * @param purpose - What the link must be for.
33
+ * @returns The user id and email the token was created for.
34
+ * @throws AuthError INVALID_TOKEN for any problem (unknown, used, expired, wrong kind).
35
+ */
36
+ useLinkToken(token: unknown, purpose: LinkTokenPurpose): Promise<UsedLinkToken>;
37
+ /**
38
+ * Cancels every unused link of a kind for a user (e.g. reset links after the password changed).
39
+ *
40
+ * @param purpose - The kind of link.
41
+ * @param userId - The user.
42
+ */
43
+ cancelLinkTokens(purpose: LinkTokenPurpose, userId: string): Promise<void>;
44
+ }
@@ -0,0 +1,122 @@
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.LinkTokensService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const typeorm_1 = require("typeorm");
18
+ const auth_error_1 = require("../../common/auth-error.js");
19
+ const random_secrets_1 = require("../../common/crypto/random-secrets.js");
20
+ const constants_1 = require("../../constants.js");
21
+ const settings_service_1 = require("../settings/settings.service.js");
22
+ const entities_1 = require("../../database/entities/index.js");
23
+ /**
24
+ * How long each kind of link works, from the settings.
25
+ *
26
+ * @param purpose - The kind of link.
27
+ * @param settings - The current settings.
28
+ * @returns The lifetime in seconds.
29
+ */
30
+ function linkLifetimeSeconds(purpose, settings) {
31
+ switch (purpose) {
32
+ case constants_1.LINK_PURPOSE.verifyEmail:
33
+ return settings.emailVerification.linkExpiresInHours * constants_1.HOUR_IN_SECONDS;
34
+ case constants_1.LINK_PURPOSE.resetPassword:
35
+ return settings.passwordReset.linkExpiresInMinutes * constants_1.MINUTE_IN_SECONDS;
36
+ case constants_1.LINK_PURPOSE.magicSignIn:
37
+ return settings.magicLink.expiresInMinutes * constants_1.MINUTE_IN_SECONDS;
38
+ case constants_1.LINK_PURPOSE.changeEmail:
39
+ return settings.emailChange.linkExpiresInMinutes * constants_1.MINUTE_IN_SECONDS;
40
+ }
41
+ }
42
+ /**
43
+ * Single-use tokens put in emailed links (verify email, reset password, magic sign-in).
44
+ * Only the hash is stored. Creating a new token of a kind cancels the older ones for that email,
45
+ * so only the newest link in the user's inbox works.
46
+ */
47
+ let LinkTokensService = class LinkTokensService {
48
+ settingsService;
49
+ database;
50
+ constructor(options, settingsService) {
51
+ this.settingsService = settingsService;
52
+ this.database = options.dataSource;
53
+ }
54
+ /**
55
+ * Creates a single-use token for an emailed link. Older unused tokens of the same kind for this
56
+ * email stop working, so only the newest link in the inbox works.
57
+ *
58
+ * @param purpose - What the link is for (verify email, reset password, magic sign-in, change email).
59
+ * @param email - The address the link is sent to.
60
+ * @param userId - The account it belongs to, or null (e.g. a magic link before sign-up).
61
+ * @returns The plain token to put in the link. Only its hash is stored.
62
+ */
63
+ async createLinkToken(purpose, email, userId) {
64
+ const { prefix } = constants_1.LINK_TOKEN_KINDS[purpose];
65
+ const lifetimeSeconds = linkLifetimeSeconds(purpose, await this.settingsService.getSettings());
66
+ const { plainToken, tokenHash } = (0, random_secrets_1.createRandomToken)(prefix);
67
+ await this.database.transaction(async (transaction) => {
68
+ await transaction.update(entities_1.LinkTokenEntity, { purpose, email, usedAt: (0, typeorm_1.IsNull)() }, { usedAt: new Date() });
69
+ await transaction.insert(entities_1.LinkTokenEntity, {
70
+ purpose,
71
+ email,
72
+ userId,
73
+ tokenHash,
74
+ expiresAt: new Date(Date.now() + lifetimeSeconds * constants_1.SECOND_IN_MILLISECONDS),
75
+ });
76
+ });
77
+ return plainToken;
78
+ }
79
+ /**
80
+ * Uses up a token. Done in one UPDATE so that two clicks at the same moment can't both succeed.
81
+ *
82
+ * @param token - The token from the link.
83
+ * @param purpose - What the link must be for.
84
+ * @returns The user id and email the token was created for.
85
+ * @throws AuthError INVALID_TOKEN for any problem (unknown, used, expired, wrong kind).
86
+ */
87
+ async useLinkToken(token, purpose) {
88
+ if (!(0, random_secrets_1.hasRandomTokenFormat)(token, constants_1.LINK_TOKEN_KINDS[purpose].prefix))
89
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
90
+ const result = await this.database
91
+ .createQueryBuilder()
92
+ .update(entities_1.LinkTokenEntity)
93
+ .set({ usedAt: () => 'now()' })
94
+ .where('token_hash = :tokenHash AND purpose = :purpose AND used_at IS NULL AND expires_at > now()', {
95
+ tokenHash: (0, random_secrets_1.sha256Hex)(token),
96
+ purpose,
97
+ })
98
+ // returning() takes entity property names; the raw rows use column names (user_id).
99
+ .returning(['userId', 'email'])
100
+ .execute();
101
+ const usedRow = result.raw[0];
102
+ if (!usedRow)
103
+ throw new auth_error_1.AuthError(constants_1.AUTH_ERRORS.invalidOrExpiredLink);
104
+ return { userId: usedRow.user_id, email: usedRow.email };
105
+ }
106
+ /**
107
+ * Cancels every unused link of a kind for a user (e.g. reset links after the password changed).
108
+ *
109
+ * @param purpose - The kind of link.
110
+ * @param userId - The user.
111
+ */
112
+ async cancelLinkTokens(purpose, userId) {
113
+ await this.database.getRepository(entities_1.LinkTokenEntity).update({ purpose, userId, usedAt: (0, typeorm_1.IsNull)() }, { usedAt: new Date() });
114
+ }
115
+ };
116
+ exports.LinkTokensService = LinkTokensService;
117
+ exports.LinkTokensService = LinkTokensService = __decorate([
118
+ (0, common_1.Injectable)(),
119
+ __param(0, (0, common_1.Inject)(constants_1.AUTH_OPTIONS_TOKEN)),
120
+ __metadata("design:paramtypes", [Object, settings_service_1.SettingsService])
121
+ ], LinkTokensService);
122
+ //# sourceMappingURL=link-tokens.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-tokens.service.js","sourceRoot":"","sources":["../../../src/features/email-links/link-tokens.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ import { AuthOptions } from '../../auth-options.js';
2
+ import { RequestContext } from '../../common/request-context.js';
3
+ import { AuthEventName } from '../../constants.js';
4
+ import type { JsonObject } from '../../database/entities/json-object.js';
5
+ export interface RecordEventInput {
6
+ name: AuthEventName;
7
+ /** The user the event is about. */
8
+ userId: string | null;
9
+ /** Who did it when not the user themself: "admin:<id>", "system". */
10
+ performedBy?: string | null;
11
+ request?: RequestContext;
12
+ /** Extra facts. Never put passwords, tokens or codes here. */
13
+ details?: JsonObject;
14
+ }
15
+ /**
16
+ * The single place every auth event goes through. One call writes the audit log, then calls
17
+ * your `onEvent` handler, then queues a delivery for every webhook subscribed to the event.
18
+ *
19
+ * Recording an event never breaks the action that caused it: failures here are logged, not thrown.
20
+ */
21
+ export declare class AuthEventsService {
22
+ private readonly options;
23
+ private readonly logger;
24
+ private readonly database;
25
+ constructor(options: AuthOptions);
26
+ /**
27
+ * Records an event: writes the audit log, calls your `onEvent` handler, and queues webhook deliveries.
28
+ * Never throws, so recording an event can't break the action that caused it.
29
+ *
30
+ * @param input - The event name, who it is about, who did it, the request, and extra details.
31
+ * @example
32
+ * await events.record({ name: AUTH_EVENTS.userSignedOut, userId, request, details: { sessionId } });
33
+ */
34
+ record(input: RecordEventInput): Promise<void>;
35
+ /**
36
+ * @param input - The event to write. Failures are logged, not thrown.
37
+ */
38
+ private writeAuditLog;
39
+ /**
40
+ * Calls AuthOptions.onEvent, if set. Failures in your handler are logged, not thrown.
41
+ *
42
+ * @param event - The event to pass on.
43
+ */
44
+ private callAppHandler;
45
+ /**
46
+ * Queues one delivery per enabled webhook that subscribes to this event. The worker sends them.
47
+ * Failures are logged, not thrown.
48
+ *
49
+ * @param event - The event to deliver.
50
+ */
51
+ private queueWebhookDeliveries;
52
+ }