@atproto/pds 0.5.12 → 0.5.15

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 (326) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/api/app/bsky/notification/index.d.ts.map +1 -1
  3. package/dist/api/app/bsky/notification/index.js +2 -0
  4. package/dist/api/app/bsky/notification/index.js.map +1 -1
  5. package/dist/api/app/bsky/notification/unregisterPush.d.ts +4 -0
  6. package/dist/api/app/bsky/notification/unregisterPush.d.ts.map +1 -0
  7. package/dist/api/app/bsky/notification/unregisterPush.js +48 -0
  8. package/dist/api/app/bsky/notification/unregisterPush.js.map +1 -0
  9. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +6 -0
  10. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  11. package/dist/lexicons/app/bsky/actor/defs.defs.js +1 -0
  12. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  13. package/package.json +42 -38
  14. package/build.templates.cjs +0 -22
  15. package/example.env +0 -42
  16. package/jest.config.cjs +0 -27
  17. package/src/account-manager/account-manager.ts +0 -916
  18. package/src/account-manager/db/index.ts +0 -21
  19. package/src/account-manager/db/migrations/001-init.ts +0 -115
  20. package/src/account-manager/db/migrations/002-account-deactivation.ts +0 -17
  21. package/src/account-manager/db/migrations/003-privileged-app-passwords.ts +0 -12
  22. package/src/account-manager/db/migrations/004-oauth.ts +0 -122
  23. package/src/account-manager/db/migrations/005-oauth-account-management.ts +0 -136
  24. package/src/account-manager/db/migrations/006-oauth-permission-sets.ts +0 -20
  25. package/src/account-manager/db/migrations/007-lexicon-failures-index.ts +0 -14
  26. package/src/account-manager/db/migrations/index.ts +0 -17
  27. package/src/account-manager/db/schema/account-device.ts +0 -14
  28. package/src/account-manager/db/schema/account.ts +0 -16
  29. package/src/account-manager/db/schema/actor.ts +0 -17
  30. package/src/account-manager/db/schema/app-password.ts +0 -13
  31. package/src/account-manager/db/schema/authorization-request.ts +0 -30
  32. package/src/account-manager/db/schema/authorized-client.ts +0 -23
  33. package/src/account-manager/db/schema/device.ts +0 -18
  34. package/src/account-manager/db/schema/email-token.ts +0 -19
  35. package/src/account-manager/db/schema/index.ts +0 -43
  36. package/src/account-manager/db/schema/invite-code.ts +0 -24
  37. package/src/account-manager/db/schema/lexicon.ts +0 -15
  38. package/src/account-manager/db/schema/refresh-token.ts +0 -11
  39. package/src/account-manager/db/schema/repo-root.ts +0 -12
  40. package/src/account-manager/db/schema/token.ts +0 -38
  41. package/src/account-manager/db/schema/used-refresh-token.ts +0 -13
  42. package/src/account-manager/helpers/account-device.ts +0 -63
  43. package/src/account-manager/helpers/account.ts +0 -355
  44. package/src/account-manager/helpers/auth.ts +0 -222
  45. package/src/account-manager/helpers/authorization-request.ts +0 -90
  46. package/src/account-manager/helpers/authorized-client.ts +0 -75
  47. package/src/account-manager/helpers/device.ts +0 -47
  48. package/src/account-manager/helpers/email-token.ts +0 -87
  49. package/src/account-manager/helpers/invite.ts +0 -263
  50. package/src/account-manager/helpers/lexicon.ts +0 -67
  51. package/src/account-manager/helpers/password.ts +0 -136
  52. package/src/account-manager/helpers/repo.ts +0 -24
  53. package/src/account-manager/helpers/scrypt.ts +0 -53
  54. package/src/account-manager/helpers/token.ts +0 -158
  55. package/src/account-manager/helpers/used-refresh-token.ts +0 -30
  56. package/src/account-manager/oauth-store.ts +0 -880
  57. package/src/account-manager/scope-reference-getter.ts +0 -106
  58. package/src/actor-store/actor-store-reader.ts +0 -45
  59. package/src/actor-store/actor-store-resources.ts +0 -8
  60. package/src/actor-store/actor-store-transactor.ts +0 -31
  61. package/src/actor-store/actor-store-writer.ts +0 -17
  62. package/src/actor-store/actor-store.ts +0 -210
  63. package/src/actor-store/blob/reader.ts +0 -160
  64. package/src/actor-store/blob/transactor.ts +0 -383
  65. package/src/actor-store/db/index.ts +0 -20
  66. package/src/actor-store/db/migrations/001-init.ts +0 -105
  67. package/src/actor-store/db/migrations/index.ts +0 -5
  68. package/src/actor-store/db/schema/account-pref.ts +0 -11
  69. package/src/actor-store/db/schema/backlink.ts +0 -9
  70. package/src/actor-store/db/schema/blob.ts +0 -14
  71. package/src/actor-store/db/schema/index.ts +0 -23
  72. package/src/actor-store/db/schema/record-blob.ts +0 -8
  73. package/src/actor-store/db/schema/record.ts +0 -14
  74. package/src/actor-store/db/schema/repo-block.ts +0 -10
  75. package/src/actor-store/db/schema/repo-root.ts +0 -10
  76. package/src/actor-store/migrate.ts +0 -39
  77. package/src/actor-store/preference/reader.ts +0 -48
  78. package/src/actor-store/preference/transactor.ts +0 -55
  79. package/src/actor-store/preference/util.ts +0 -39
  80. package/src/actor-store/record/reader.ts +0 -374
  81. package/src/actor-store/record/transactor.ts +0 -111
  82. package/src/actor-store/repo/reader.ts +0 -31
  83. package/src/actor-store/repo/sql-repo-reader.ts +0 -150
  84. package/src/actor-store/repo/sql-repo-transactor.ts +0 -105
  85. package/src/actor-store/repo/transactor.ts +0 -227
  86. package/src/api/app/bsky/actor/getPreferences.ts +0 -57
  87. package/src/api/app/bsky/actor/getProfile.ts +0 -41
  88. package/src/api/app/bsky/actor/getProfiles.ts +0 -51
  89. package/src/api/app/bsky/actor/index.ts +0 -13
  90. package/src/api/app/bsky/actor/putPreferences.ts +0 -62
  91. package/src/api/app/bsky/feed/getActorLikes.ts +0 -63
  92. package/src/api/app/bsky/feed/getAuthorFeed.ts +0 -84
  93. package/src/api/app/bsky/feed/getFeed.ts +0 -47
  94. package/src/api/app/bsky/feed/getPostThread.ts +0 -251
  95. package/src/api/app/bsky/feed/getTimeline.ts +0 -50
  96. package/src/api/app/bsky/feed/index.ts +0 -15
  97. package/src/api/app/bsky/index.ts +0 -11
  98. package/src/api/app/bsky/notification/index.ts +0 -7
  99. package/src/api/app/bsky/notification/registerPush.ts +0 -71
  100. package/src/api/app/bsky/util/resolver.ts +0 -20
  101. package/src/api/com/atproto/admin/deleteAccount.ts +0 -22
  102. package/src/api/com/atproto/admin/disableAccountInvites.ts +0 -18
  103. package/src/api/com/atproto/admin/disableInviteCodes.ts +0 -21
  104. package/src/api/com/atproto/admin/enableAccountInvites.ts +0 -18
  105. package/src/api/com/atproto/admin/getAccountInfo.ts +0 -32
  106. package/src/api/com/atproto/admin/getAccountInfos.ts +0 -34
  107. package/src/api/com/atproto/admin/getInviteCodes.ts +0 -126
  108. package/src/api/com/atproto/admin/getSubjectStatus.ts +0 -76
  109. package/src/api/com/atproto/admin/index.ts +0 -31
  110. package/src/api/com/atproto/admin/sendEmail.ts +0 -47
  111. package/src/api/com/atproto/admin/updateAccountEmail.ts +0 -32
  112. package/src/api/com/atproto/admin/updateAccountHandle.ts +0 -47
  113. package/src/api/com/atproto/admin/updateAccountPassword.ts +0 -34
  114. package/src/api/com/atproto/admin/updateSubjectStatus.ts +0 -68
  115. package/src/api/com/atproto/admin/util.ts +0 -66
  116. package/src/api/com/atproto/identity/getRecommendedDidCredentials.ts +0 -50
  117. package/src/api/com/atproto/identity/index.ts +0 -17
  118. package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +0 -59
  119. package/src/api/com/atproto/identity/resolveHandle.ts +0 -50
  120. package/src/api/com/atproto/identity/signPlcOperation.ts +0 -85
  121. package/src/api/com/atproto/identity/submitPlcOperation.ts +0 -65
  122. package/src/api/com/atproto/identity/updateHandle.ts +0 -66
  123. package/src/api/com/atproto/index.ts +0 -19
  124. package/src/api/com/atproto/moderation/createReport.ts +0 -41
  125. package/src/api/com/atproto/moderation/index.ts +0 -7
  126. package/src/api/com/atproto/repo/applyWrites.ts +0 -226
  127. package/src/api/com/atproto/repo/createRecord.ts +0 -143
  128. package/src/api/com/atproto/repo/deleteRecord.ts +0 -122
  129. package/src/api/com/atproto/repo/describeRepo.ts +0 -43
  130. package/src/api/com/atproto/repo/getRecord.ts +0 -40
  131. package/src/api/com/atproto/repo/importRepo.ts +0 -101
  132. package/src/api/com/atproto/repo/index.ts +0 -25
  133. package/src/api/com/atproto/repo/listMissingBlobs.ts +0 -29
  134. package/src/api/com/atproto/repo/listRecords.ts +0 -36
  135. package/src/api/com/atproto/repo/putRecord.ts +0 -211
  136. package/src/api/com/atproto/repo/uploadBlob.ts +0 -60
  137. package/src/api/com/atproto/server/activateAccount.ts +0 -37
  138. package/src/api/com/atproto/server/checkAccountStatus.ts +0 -51
  139. package/src/api/com/atproto/server/confirmEmail.ts +0 -39
  140. package/src/api/com/atproto/server/createAccount.ts +0 -327
  141. package/src/api/com/atproto/server/createAppPassword.ts +0 -53
  142. package/src/api/com/atproto/server/createInviteCode.ts +0 -38
  143. package/src/api/com/atproto/server/createInviteCodes.ts +0 -42
  144. package/src/api/com/atproto/server/createSession.ts +0 -97
  145. package/src/api/com/atproto/server/deactivateAccount.ts +0 -41
  146. package/src/api/com/atproto/server/deleteAccount.ts +0 -85
  147. package/src/api/com/atproto/server/deleteSession.ts +0 -23
  148. package/src/api/com/atproto/server/describeServer.ts +0 -29
  149. package/src/api/com/atproto/server/getAccountInviteCodes.ts +0 -142
  150. package/src/api/com/atproto/server/getServiceAuth.ts +0 -111
  151. package/src/api/com/atproto/server/getSession.ts +0 -80
  152. package/src/api/com/atproto/server/index.ts +0 -55
  153. package/src/api/com/atproto/server/listAppPasswords.ts +0 -45
  154. package/src/api/com/atproto/server/refreshSession.ts +0 -72
  155. package/src/api/com/atproto/server/requestAccountDelete.ts +0 -66
  156. package/src/api/com/atproto/server/requestEmailConfirmation.ts +0 -68
  157. package/src/api/com/atproto/server/requestEmailUpdate.ts +0 -86
  158. package/src/api/com/atproto/server/requestPasswordReset.ts +0 -52
  159. package/src/api/com/atproto/server/reserveSigningKey.ts +0 -15
  160. package/src/api/com/atproto/server/resetPassword.ts +0 -50
  161. package/src/api/com/atproto/server/revokeAppPassword.ts +0 -42
  162. package/src/api/com/atproto/server/updateEmail.ts +0 -52
  163. package/src/api/com/atproto/server/util.ts +0 -136
  164. package/src/api/com/atproto/sync/deprecated/getCheckout.ts +0 -27
  165. package/src/api/com/atproto/sync/deprecated/getHead.ts +0 -33
  166. package/src/api/com/atproto/sync/getBlob.ts +0 -61
  167. package/src/api/com/atproto/sync/getBlocks.ts +0 -37
  168. package/src/api/com/atproto/sync/getLatestCommit.ts +0 -33
  169. package/src/api/com/atproto/sync/getRecord.ts +0 -49
  170. package/src/api/com/atproto/sync/getRepo.ts +0 -75
  171. package/src/api/com/atproto/sync/getRepoStatus.ts +0 -34
  172. package/src/api/com/atproto/sync/index.ts +0 -27
  173. package/src/api/com/atproto/sync/listBlobs.ts +0 -33
  174. package/src/api/com/atproto/sync/listRepos.ts +0 -74
  175. package/src/api/com/atproto/sync/subscribeRepos.ts +0 -73
  176. package/src/api/com/atproto/sync/util.ts +0 -37
  177. package/src/api/com/atproto/temp/checkSignupQueue.ts +0 -34
  178. package/src/api/com/atproto/temp/index.ts +0 -7
  179. package/src/api/index.ts +0 -10
  180. package/src/api/proxy.ts +0 -44
  181. package/src/app-view.ts +0 -24
  182. package/src/auth-output.ts +0 -52
  183. package/src/auth-routes.ts +0 -64
  184. package/src/auth-scope.ts +0 -40
  185. package/src/auth-verifier.ts +0 -668
  186. package/src/background.ts +0 -65
  187. package/src/basic-routes.ts +0 -52
  188. package/src/bsky-app-view.ts +0 -33
  189. package/src/config/config.ts +0 -553
  190. package/src/config/env.ts +0 -167
  191. package/src/config/index.ts +0 -3
  192. package/src/config/secrets.ts +0 -54
  193. package/src/context.ts +0 -523
  194. package/src/crawlers.ts +0 -46
  195. package/src/db/cast.ts +0 -52
  196. package/src/db/db.ts +0 -140
  197. package/src/db/index.ts +0 -4
  198. package/src/db/migrator.ts +0 -40
  199. package/src/db/pagination.ts +0 -132
  200. package/src/db/tables/moderation.ts +0 -80
  201. package/src/db/util.ts +0 -108
  202. package/src/did-cache/db/index.ts +0 -21
  203. package/src/did-cache/db/migrations.ts +0 -17
  204. package/src/did-cache/db/schema.ts +0 -9
  205. package/src/did-cache/index.ts +0 -131
  206. package/src/disk-blobstore.ts +0 -172
  207. package/src/error.ts +0 -19
  208. package/src/handle/explicit-slurs.ts +0 -22
  209. package/src/handle/index.ts +0 -49
  210. package/src/handle/reserved.ts +0 -1059
  211. package/src/image/image-url-builder.ts +0 -16
  212. package/src/index.ts +0 -189
  213. package/src/lexicons.ts +0 -4
  214. package/src/logger.ts +0 -35
  215. package/src/mailer/index.ts +0 -111
  216. package/src/mailer/moderation.ts +0 -33
  217. package/src/mailer/templates/confirm-email.d.ts +0 -4
  218. package/src/mailer/templates/confirm-email.hbs +0 -158
  219. package/src/mailer/templates/delete-account.d.ts +0 -4
  220. package/src/mailer/templates/delete-account.hbs +0 -156
  221. package/src/mailer/templates/plc-operation.d.ts +0 -4
  222. package/src/mailer/templates/plc-operation.hbs +0 -153
  223. package/src/mailer/templates/reset-password.d.ts +0 -4
  224. package/src/mailer/templates/reset-password.hbs +0 -154
  225. package/src/mailer/templates/update-email.d.ts +0 -4
  226. package/src/mailer/templates/update-email.hbs +0 -153
  227. package/src/mailer/templates.ts +0 -17
  228. package/src/pipethrough.ts +0 -716
  229. package/src/rate-limits.ts +0 -59
  230. package/src/read-after-write/index.ts +0 -3
  231. package/src/read-after-write/types.ts +0 -35
  232. package/src/read-after-write/util.ts +0 -101
  233. package/src/read-after-write/viewer.ts +0 -290
  234. package/src/redis.ts +0 -25
  235. package/src/repo/index.ts +0 -2
  236. package/src/repo/prepare.ts +0 -266
  237. package/src/repo/types.ts +0 -65
  238. package/src/scripts/README.md +0 -40
  239. package/src/scripts/index.ts +0 -20
  240. package/src/scripts/publish-identity.ts +0 -60
  241. package/src/scripts/rebuild-repo.ts +0 -119
  242. package/src/scripts/rotate-keys.ts +0 -146
  243. package/src/scripts/sequencer-recovery/index.ts +0 -23
  244. package/src/scripts/sequencer-recovery/recoverer.ts +0 -297
  245. package/src/scripts/sequencer-recovery/recovery-db.ts +0 -65
  246. package/src/scripts/sequencer-recovery/repair-repos.ts +0 -49
  247. package/src/scripts/sequencer-recovery/user-queues.ts +0 -44
  248. package/src/scripts/util.ts +0 -7
  249. package/src/sequencer/db/index.ts +0 -21
  250. package/src/sequencer/db/migrations/001-init.ts +0 -35
  251. package/src/sequencer/db/migrations/index.ts +0 -5
  252. package/src/sequencer/db/schema.ts +0 -19
  253. package/src/sequencer/events.ts +0 -199
  254. package/src/sequencer/index.ts +0 -3
  255. package/src/sequencer/outbox.ts +0 -123
  256. package/src/sequencer/sequencer.ts +0 -290
  257. package/src/util/compression.ts +0 -16
  258. package/src/util/debug.ts +0 -10
  259. package/src/util/http.ts +0 -31
  260. package/src/util/types.ts +0 -7
  261. package/src/well-known.ts +0 -30
  262. package/test.env +0 -2
  263. package/tests/__snapshots__/takedown-appeal.test.ts.snap +0 -43
  264. package/tests/_oauth_client_assets_middleware.ts +0 -23
  265. package/tests/_puppeteer.ts +0 -147
  266. package/tests/_types.d.ts +0 -16
  267. package/tests/_util.ts +0 -191
  268. package/tests/account-deactivation.test.ts +0 -204
  269. package/tests/account-deletion.test.ts +0 -300
  270. package/tests/account-manager.test.ts +0 -462
  271. package/tests/account-migration.test.ts +0 -221
  272. package/tests/account-status.test.ts +0 -206
  273. package/tests/account.test.ts +0 -595
  274. package/tests/app-passwords.test.ts +0 -262
  275. package/tests/auth.test.ts +0 -405
  276. package/tests/blob-deletes.test.ts +0 -204
  277. package/tests/create-post.test.ts +0 -79
  278. package/tests/crud.test.ts +0 -1595
  279. package/tests/db.test.ts +0 -170
  280. package/tests/email-confirmation.test.ts +0 -227
  281. package/tests/entryway-mock.ts +0 -323
  282. package/tests/entryway.test.ts +0 -145
  283. package/tests/file-uploads.test.ts +0 -301
  284. package/tests/get-service-auth.test.ts +0 -81
  285. package/tests/handle-validation.test.ts +0 -56
  286. package/tests/handles.test.ts +0 -274
  287. package/tests/invite-codes.test.ts +0 -367
  288. package/tests/invites-admin.test.ts +0 -252
  289. package/tests/moderation.test.ts +0 -280
  290. package/tests/moderator-auth.test.ts +0 -177
  291. package/tests/oauth.test.ts +0 -334
  292. package/tests/plc-operations.test.ts +0 -239
  293. package/tests/preferences.test.ts +0 -352
  294. package/tests/proxied/__snapshots__/admin.test.ts.snap +0 -516
  295. package/tests/proxied/__snapshots__/feedgen.test.ts.snap +0 -215
  296. package/tests/proxied/__snapshots__/views.test.ts.snap +0 -4456
  297. package/tests/proxied/admin.test.ts +0 -340
  298. package/tests/proxied/feedgen.test.ts +0 -66
  299. package/tests/proxied/notif.test.ts +0 -85
  300. package/tests/proxied/procedures.test.ts +0 -175
  301. package/tests/proxied/proxy-catchall.test.ts +0 -256
  302. package/tests/proxied/proxy-header.test.ts +0 -239
  303. package/tests/proxied/proxy-oauth-aud.test.ts +0 -182
  304. package/tests/proxied/read-after-write.test.ts +0 -382
  305. package/tests/proxied/views.test.ts +0 -608
  306. package/tests/races.test.ts +0 -89
  307. package/tests/rate-limits.test.ts +0 -70
  308. package/tests/recovery.test.ts +0 -180
  309. package/tests/seeds/basic.ts +0 -165
  310. package/tests/seeds/follows.ts +0 -57
  311. package/tests/seeds/likes.ts +0 -14
  312. package/tests/seeds/reposts.ts +0 -18
  313. package/tests/seeds/thread.ts +0 -40
  314. package/tests/seeds/users-bulk.ts +0 -246
  315. package/tests/seeds/users.ts +0 -67
  316. package/tests/sequencer.test.ts +0 -251
  317. package/tests/server.test.ts +0 -151
  318. package/tests/sync/invertible-ops.test.ts +0 -99
  319. package/tests/sync/list.test.ts +0 -43
  320. package/tests/sync/subscribe-repos.test.ts +0 -672
  321. package/tests/sync/sync.test.ts +0 -316
  322. package/tests/takedown-appeal.test.ts +0 -166
  323. package/tsconfig.build.json +0 -9
  324. package/tsconfig.build.tsbuildinfo +0 -1
  325. package/tsconfig.json +0 -7
  326. package/tsconfig.tests.json +0 -8
@@ -1,916 +0,0 @@
1
- import assert from 'node:assert'
2
- import { KeyObject } from 'node:crypto'
3
- import { Client as PlcClient } from '@did-plc/lib'
4
- import { isEmailValid } from '@hapi/address'
5
- import { isDisposableEmail } from 'disposable-email-domains-js'
6
- import { HOUR, wait } from '@atproto/common'
7
- import { Keypair } from '@atproto/crypto'
8
- import { IdResolver } from '@atproto/identity'
9
- import {
10
- AtIdentifierString,
11
- DidString,
12
- HandleString,
13
- isAtIdentifierString,
14
- } from '@atproto/lex'
15
- import { Cid } from '@atproto/lex-data'
16
- import {
17
- INVALID_HANDLE,
18
- currentDatetimeString,
19
- isValidTld,
20
- } from '@atproto/syntax'
21
- import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
22
- import { ActorStore } from '../actor-store/actor-store.js'
23
- import { assertValidDidDocumentForService } from '../api/com/atproto/server/util.js'
24
- import { AuthScope } from '../auth-scope.js'
25
- import { ServerConfig } from '../config/config.js'
26
- import { softDeleted } from '../db/index.js'
27
- import { hasExplicitSlur } from '../handle/explicit-slurs.js'
28
- import {
29
- baseNormalizeAndValidate,
30
- ensureHandleServiceConstraints,
31
- isServiceDomain,
32
- } from '../handle/index.js'
33
- import { com } from '../lexicons/index.js'
34
- import { httpLogger } from '../logger.js'
35
- import { ServerMailer } from '../mailer/index.js'
36
- import { Sequencer } from '../sequencer/index.js'
37
- import { AccountDb, EmailTokenPurpose, getDb, getMigrator } from './db/index.js'
38
- import * as accountHelpers from './helpers/account.js'
39
- import { AccountStatus, ActorAccount } from './helpers/account.js'
40
- import * as auth from './helpers/auth.js'
41
- import * as authorizedClientHelper from './helpers/authorized-client.js'
42
- import * as emailToken from './helpers/email-token.js'
43
- import * as invite from './helpers/invite.js'
44
- import * as password from './helpers/password.js'
45
- import * as repo from './helpers/repo.js'
46
- import * as scrypt from './helpers/scrypt.js'
47
- import * as token from './helpers/token.js'
48
-
49
- export { AccountStatus, formatAccountStatus } from './helpers/account.js'
50
-
51
- /**
52
- * Thrown by {@link AccountManager.login} when the identifier resolved to a
53
- * known account but the supplied credentials (account password / app
54
- * password) did not match. The matched `did` is attached so downstream
55
- * callers can distinguish "identifier known, credentials wrong" from
56
- * "identifier unknown" (which continues to throw a plain
57
- * {@link AuthRequiredError}).
58
- *
59
- * Callers should take care that remote clients *cannot* distinguish the above,
60
- * to prevent enumeration attacks. (Tested for in
61
- * packages/pds/tests/auth.test.ts)
62
- */
63
- export class InvalidPasswordError extends AuthRequiredError {
64
- constructor(
65
- public readonly did: DidString,
66
- errorMessage = 'Invalid identifier or password',
67
- ) {
68
- super(errorMessage)
69
- }
70
- }
71
-
72
- export type AccountManagerDbConfig = {
73
- accountDbLoc: string
74
- disableWalAutoCheckpoint: boolean
75
- }
76
-
77
- export class AccountManager {
78
- readonly db: AccountDb
79
-
80
- constructor(
81
- readonly cfg: ServerConfig,
82
- readonly actorStore: ActorStore,
83
- readonly idResolver: IdResolver,
84
- readonly jwtKey: KeyObject,
85
- readonly mailer: ServerMailer,
86
- readonly sequencer: Sequencer,
87
- readonly plcClient: PlcClient,
88
- readonly plcRotationKey: Keypair,
89
- ) {
90
- this.db = getDb(cfg.db.accountDbLoc, cfg.db.disableWalAutoCheckpoint)
91
- }
92
-
93
- get serviceDid(): DidString {
94
- return this.cfg.service.did
95
- }
96
-
97
- get serviceHandleDomains(): string[] {
98
- return this.cfg.identity.serviceHandleDomains
99
- }
100
-
101
- async migrateOrThrow() {
102
- await this.db.ensureWal()
103
- await getMigrator(this.db).migrateToLatestOrThrow()
104
- }
105
-
106
- close() {
107
- this.db.close()
108
- }
109
-
110
- // Account
111
- // ----------
112
-
113
- async getAccount(
114
- handleOrDid: AtIdentifierString,
115
- flags?: accountHelpers.AvailabilityFlags,
116
- ): Promise<ActorAccount | null> {
117
- return accountHelpers.getAccount(this.db, handleOrDid, flags)
118
- }
119
-
120
- async getAccounts(
121
- dids: DidString[],
122
- flags?: accountHelpers.AvailabilityFlags,
123
- ): Promise<Map<string, ActorAccount>> {
124
- return accountHelpers.getAccounts(this.db, dids, flags)
125
- }
126
-
127
- async getAccountByEmail(
128
- email: string,
129
- flags?: accountHelpers.AvailabilityFlags,
130
- ): Promise<ActorAccount | null> {
131
- return accountHelpers.getAccountByEmail(this.db, email, flags)
132
- }
133
-
134
- async isAccountActivated(did: DidString): Promise<boolean> {
135
- const account = await this.getAccount(did, { includeDeactivated: true })
136
- if (!account) return false
137
- return !account.deactivatedAt
138
- }
139
-
140
- async getDidForActor(
141
- handleOrDid: AtIdentifierString,
142
- flags?: accountHelpers.AvailabilityFlags,
143
- ): Promise<DidString | null> {
144
- const got = await this.getAccount(handleOrDid, flags)
145
- return got?.did ?? null
146
- }
147
-
148
- async getAccountStatus(handleOrDid: AtIdentifierString) {
149
- const got = await this.getAccount(handleOrDid, {
150
- includeDeactivated: true,
151
- includeTakenDown: true,
152
- })
153
-
154
- const { active, status = active ? AccountStatus.Active : undefined } =
155
- accountHelpers.formatAccountStatus(got)
156
- assert(status != null)
157
- return { status, account: got } as
158
- | { status: AccountStatus.Deleted; account: null }
159
- | { status: AccountStatus.Takendown; account: ActorAccount }
160
- | { status: AccountStatus.Deactivated; account: ActorAccount }
161
- | { status: AccountStatus.Active; account: ActorAccount }
162
- }
163
-
164
- async normalizeAndValidateHandle(
165
- handle: string,
166
- {
167
- did,
168
- allowAnyValid = false,
169
- }: {
170
- did?: string
171
- allowAnyValid?: boolean
172
- } = {},
173
- ): Promise<HandleString> {
174
- const normalized = baseNormalizeAndValidate(handle)
175
-
176
- // tld validation
177
- if (!isValidTld(normalized)) {
178
- throw new InvalidRequestError(
179
- 'Handle TLD is invalid or disallowed',
180
- 'InvalidHandle',
181
- )
182
- }
183
- // slur check
184
- if (!allowAnyValid && hasExplicitSlur(normalized)) {
185
- throw new InvalidRequestError(
186
- 'Inappropriate language in handle',
187
- 'InvalidHandle',
188
- )
189
- }
190
- if (isServiceDomain(normalized, this.serviceHandleDomains)) {
191
- // verify constraints on a service domain
192
- ensureHandleServiceConstraints(
193
- normalized,
194
- this.serviceHandleDomains,
195
- allowAnyValid,
196
- )
197
- } else {
198
- // When creating an account (no did yet), we require the handle to be a
199
- // local service domain. Updating to a custom handle will be possible once
200
- // the account was created.
201
- if (did == null) {
202
- throw new InvalidRequestError(
203
- 'Not a supported handle domain',
204
- 'UnsupportedDomain',
205
- )
206
- }
207
-
208
- // verify resolution of a non-service domain
209
- const resolvedDid = await this.idResolver.handle.resolve(normalized)
210
- if (resolvedDid !== did) {
211
- // @TODO This should use a distinct error code
212
- throw new InvalidRequestError('External handle did not resolve to DID')
213
- }
214
- }
215
-
216
- return normalized
217
- }
218
-
219
- async createAccount({
220
- did,
221
- handle,
222
- email,
223
- password,
224
- repoCid,
225
- repoRev,
226
- inviteCode,
227
- deactivated,
228
- refreshJwt,
229
- }: {
230
- did: DidString
231
- handle: HandleString
232
- email?: string
233
- password?: string
234
- repoCid: Cid
235
- repoRev: string
236
- inviteCode?: string
237
- deactivated?: boolean
238
- refreshJwt?: string
239
- }) {
240
- if (password && password.length > scrypt.NEW_PASSWORD_MAX_LENGTH) {
241
- throw new InvalidRequestError('Password too long')
242
- }
243
-
244
- const passwordScrypt =
245
- email && password ? await scrypt.genSaltAndHash(password) : undefined
246
-
247
- const now = currentDatetimeString()
248
- return this.db.transaction(async (dbTxn) => {
249
- if (inviteCode) {
250
- await invite.ensureInviteIsAvailable(dbTxn, inviteCode)
251
- }
252
-
253
- await accountHelpers.registerActor(dbTxn, { did, handle, deactivated })
254
-
255
- if (email && passwordScrypt) {
256
- await accountHelpers.registerAccount(dbTxn, {
257
- did,
258
- email,
259
- passwordScrypt,
260
- })
261
- }
262
-
263
- await invite.recordInviteUse(dbTxn, {
264
- did,
265
- inviteCode,
266
- now,
267
- })
268
-
269
- if (refreshJwt) {
270
- await auth.storeRefreshToken(
271
- dbTxn,
272
- auth.decodeRefreshToken(refreshJwt),
273
- null,
274
- )
275
- }
276
-
277
- await repo.updateRoot(dbTxn, did, repoCid, repoRev)
278
- })
279
- }
280
-
281
- async createAccountAndSession(opts: {
282
- did: DidString
283
- handle: HandleString
284
- email?: string
285
- password?: string
286
- repoCid: Cid
287
- repoRev: string
288
- inviteCode?: string
289
- deactivated?: boolean
290
- }) {
291
- const { accessJwt, refreshJwt } = await auth.createTokens({
292
- did: opts.did,
293
- jwtKey: this.jwtKey,
294
- serviceDid: this.serviceDid,
295
- scope: AuthScope.Access,
296
- })
297
-
298
- await this.createAccount({ ...opts, refreshJwt })
299
-
300
- return { accessJwt, refreshJwt }
301
- }
302
-
303
- /**
304
- * Validates the requested handle, updates the PLC document if needed, persists
305
- * the new handle locally, and emits an identity event.
306
- *
307
- * @throws {InvalidRequestError} when the handle is invalid, taken by another
308
- * account, or cannot be resolved for non-service domains.
309
- *
310
- * @see {@link AccountManager.updateAccountHandle} for behavior when the PLC update fails.
311
- */
312
- async updateHandle(
313
- did: DidString,
314
- newHandle: string,
315
- options?: { allowAnyValid?: boolean },
316
- ): Promise<ActorAccount & { handle: HandleString }> {
317
- const { account, handle } = await this.validateHandleUpdate(
318
- did,
319
- newHandle,
320
- options,
321
- )
322
-
323
- if (did.startsWith('did:plc:')) {
324
- // @TODO We should verify the status before issuing a PLC update.
325
- await this.plcClient.updateHandle(did, this.plcRotationKey, handle)
326
- } else {
327
- const resolved = await this.idResolver.did.resolveAtprotoData(did, true)
328
- if (resolved.handle !== handle) {
329
- throw new InvalidRequestError(
330
- 'DID is not properly configured for handle',
331
- )
332
- }
333
- }
334
-
335
- // @NOTE If the next line fails (for any reason), we don't "rollback" the
336
- // PLC update above. The caller can just call this method again.
337
- await this.updateAccountHandle(did, handle)
338
-
339
- return { ...account, handle }
340
- }
341
-
342
- async validateHandleUpdate(
343
- did: DidString,
344
- newHandle: string,
345
- options?: { allowAnyValid?: boolean },
346
- ): Promise<{
347
- did: DidString
348
- handle: HandleString
349
- // Returned for convenience
350
- account: ActorAccount
351
- }> {
352
- const account = await this.getAccount(did, { includeDeactivated: true })
353
- if (!account) {
354
- throw new InvalidRequestError('Account not found')
355
- }
356
-
357
- const handle = await this.normalizeAndValidateHandle(newHandle, {
358
- allowAnyValid: options?.allowAnyValid,
359
- did,
360
- })
361
-
362
- // Pessimistic check to handle spam: also enforced by updateAccountHandle() and the db.
363
- const existing = await this.getAccount(handle, {
364
- includeDeactivated: true,
365
- includeTakenDown: true,
366
- })
367
-
368
- if (existing && existing.did !== did) {
369
- throw new InvalidRequestError(
370
- `Handle already taken: ${handle}`,
371
- 'HandleNotAvailable',
372
- )
373
- }
374
-
375
- return { did, handle, account }
376
- }
377
-
378
- /**
379
- * @note Failure to emit the identity event will silently be ignored. Users
380
- * can emit the event again by updating their handle to the same value.
381
- */
382
- async updateAccountHandle(
383
- did: DidString,
384
- handle: HandleString,
385
- ): Promise<void> {
386
- await accountHelpers.updateHandle(this.db, did, handle)
387
-
388
- try {
389
- await this.sequencer.sequenceIdentity(did, handle)
390
- } catch (err) {
391
- httpLogger.error({ err, did, handle }, 'failed to sequence handle update')
392
- }
393
- }
394
-
395
- async deleteAccount(did: DidString) {
396
- return accountHelpers.deleteAccount(this.db, did)
397
- }
398
-
399
- async takedownAccount(
400
- did: DidString,
401
- takedown: com.atproto.admin.defs.StatusAttr,
402
- ) {
403
- await this.db.transaction(async (dbTxn) => {
404
- await accountHelpers.updateAccountTakedownStatus(dbTxn, did, takedown)
405
- await auth.revokeRefreshTokensByDid(dbTxn, did)
406
- await token.removeByDid(dbTxn, did)
407
- })
408
-
409
- await this.sequenceAccountStatus(did)
410
- }
411
-
412
- async getAccountAdminStatus(did: DidString) {
413
- return accountHelpers.getAccountAdminStatus(this.db, did)
414
- }
415
-
416
- async updateRepoRoot(did: DidString, cid: Cid, rev: string) {
417
- return repo.updateRoot(this.db, did, cid, rev)
418
- }
419
-
420
- async deactivateAccount(
421
- did: DidString,
422
- options?: {
423
- deleteCredentials?: boolean
424
- deleteAfter?: string | null
425
- },
426
- ) {
427
- const wasUpdated = await this.db.transaction(async (dbTxn) => {
428
- if (options?.deleteCredentials) {
429
- await token.removeByDid(dbTxn, did)
430
- await authorizedClientHelper.deleteAllAuthorizedClients(dbTxn, did)
431
- await password.deleteAllAppPasswords(dbTxn, did)
432
- }
433
-
434
- return accountHelpers.deactivateAccount(
435
- dbTxn,
436
- did,
437
- options?.deleteAfter ?? null,
438
- )
439
- })
440
-
441
- if (!wasUpdated) {
442
- throw new InvalidRequestError('Account not found')
443
- }
444
-
445
- const accountStatus = await this.getAccountStatus(did)
446
-
447
- // Account is likely soft-deleted (takendown)
448
- if (accountStatus.status === AccountStatus.Deleted) {
449
- throw new InvalidRequestError('Account not found')
450
- }
451
-
452
- await this.sequencer.sequenceAccount(did, accountStatus.status)
453
-
454
- return accountStatus
455
- }
456
-
457
- async activateAccount(did: DidString) {
458
- await assertValidDidDocumentForService(this, did)
459
-
460
- const found = await accountHelpers.activateAccount(this.db, did, {
461
- // We cannot activate a takendown account
462
- includeTakenDown: false,
463
- includeDeactivated: true,
464
- })
465
- if (!found) {
466
- throw new InvalidRequestError('user not found', 'AccountNotFound')
467
- }
468
-
469
- const accountStatus = await this.getAccountStatus(did)
470
-
471
- const { account, status } = accountStatus
472
-
473
- if (status === AccountStatus.Deleted) {
474
- // A concurrent operation deleted the account
475
- throw new InvalidRequestError('user not found', 'AccountNotFound')
476
- }
477
-
478
- const syncData = await this.actorStore.read(did, (store) => {
479
- return store.repo.getSyncEventData()
480
- })
481
-
482
- await this.sequencer.sequenceAccountActivation(
483
- did,
484
- account.handle ?? INVALID_HANDLE,
485
- status,
486
- syncData,
487
- )
488
-
489
- return accountStatus
490
- }
491
-
492
- async sequenceAccountStatus(did: DidString) {
493
- const { status } = await this.getAccountStatus(did)
494
- await this.sequencer.sequenceAccount(did, status)
495
- }
496
-
497
- // Auth
498
- // ----------
499
-
500
- async createSession(
501
- did: DidString,
502
- appPassword: password.AppPassDescript | null,
503
- isSoftDeleted = false,
504
- ) {
505
- const { accessJwt, refreshJwt } = await auth.createTokens({
506
- did,
507
- jwtKey: this.jwtKey,
508
- serviceDid: this.serviceDid,
509
- scope: auth.formatScope(appPassword, isSoftDeleted),
510
- })
511
- // For soft deleted accounts don't store refresh token so that it can't be rotated.
512
- if (!isSoftDeleted) {
513
- const refreshPayload = auth.decodeRefreshToken(refreshJwt)
514
- await auth.storeRefreshToken(this.db, refreshPayload, appPassword)
515
- }
516
- return { accessJwt, refreshJwt }
517
- }
518
-
519
- async rotateRefreshToken(id: string) {
520
- const token = await auth.getRefreshToken(this.db, id)
521
- if (!token) return null
522
-
523
- const now = new Date()
524
-
525
- // take the chance to tidy all of a user's expired tokens
526
- // does not need to be transactional since this is just best-effort
527
- await auth.deleteExpiredRefreshTokens(this.db, token.did, now.toISOString())
528
-
529
- // Shorten the refresh token lifespan down from its
530
- // original expiration time to its revocation grace period.
531
- const prevExpiresAt = new Date(token.expiresAt)
532
- const REFRESH_GRACE_MS = 2 * HOUR
533
- const graceExpiresAt = new Date(now.getTime() + REFRESH_GRACE_MS)
534
-
535
- const expiresAt =
536
- graceExpiresAt < prevExpiresAt ? graceExpiresAt : prevExpiresAt
537
-
538
- if (expiresAt <= now) {
539
- return null
540
- }
541
-
542
- // Determine the next refresh token id: upon refresh token
543
- // reuse you always receive a refresh token with the same id.
544
- const nextId = token.nextId ?? auth.getRefreshTokenId()
545
-
546
- const { accessJwt, refreshJwt } = await auth.createTokens({
547
- did: token.did,
548
- jwtKey: this.jwtKey,
549
- serviceDid: this.serviceDid,
550
- scope: auth.formatScope(token.appPassword),
551
- jti: nextId,
552
- })
553
-
554
- const refreshPayload = auth.decodeRefreshToken(refreshJwt)
555
- try {
556
- await this.db.transaction((dbTxn) =>
557
- Promise.all([
558
- auth.addRefreshGracePeriod(dbTxn, {
559
- id,
560
- expiresAt: expiresAt.toISOString(),
561
- nextId,
562
- }),
563
- auth.storeRefreshToken(dbTxn, refreshPayload, token.appPassword),
564
- ]),
565
- )
566
- } catch (err) {
567
- if (err instanceof auth.ConcurrentRefreshError) {
568
- return this.rotateRefreshToken(id)
569
- }
570
- throw err
571
- }
572
- return { accessJwt, refreshJwt }
573
- }
574
-
575
- async revokeRefreshToken(id: string) {
576
- return auth.revokeRefreshToken(this.db, id)
577
- }
578
-
579
- // Login
580
- // ----------
581
-
582
- async login({
583
- identifier,
584
- password,
585
- }: {
586
- identifier: string
587
- password: string
588
- }): Promise<{
589
- user: ActorAccount
590
- appPassword: password.AppPassDescript | null
591
- isSoftDeleted: boolean
592
- }> {
593
- const start = Date.now()
594
- try {
595
- const identifierNormalized = identifier.toLowerCase()
596
-
597
- const user = identifierNormalized.includes('@')
598
- ? await this.getAccountByEmail(identifierNormalized, {
599
- includeDeactivated: true,
600
- includeTakenDown: true,
601
- })
602
- : isAtIdentifierString(identifierNormalized)
603
- ? await this.getAccount(identifierNormalized, {
604
- includeDeactivated: true,
605
- includeTakenDown: true,
606
- })
607
- : null
608
-
609
- if (!user) {
610
- throw new AuthRequiredError('Invalid identifier or password')
611
- }
612
- const isSoftDeleted = softDeleted(user)
613
-
614
- let appPassword: password.AppPassDescript | null = null
615
- const validAccountPass = await this.verifyAccountPassword(
616
- user.did,
617
- password,
618
- )
619
- if (!validAccountPass) {
620
- // takendown/suspended accounts cannot login with app password
621
- if (isSoftDeleted) {
622
- throw new InvalidPasswordError(user.did)
623
- }
624
- appPassword = await this.verifyAppPassword(user.did, password)
625
- if (appPassword === null) {
626
- throw new InvalidPasswordError(user.did)
627
- }
628
- }
629
-
630
- return { user, appPassword, isSoftDeleted }
631
- } finally {
632
- // Mitigate timing attacks
633
- await wait(350 - (Date.now() - start))
634
- }
635
- }
636
-
637
- // Passwords
638
- // ----------
639
-
640
- async createAppPassword(did: DidString, name: string, privileged: boolean) {
641
- return password.createAppPassword(this.db, did, name, privileged)
642
- }
643
-
644
- async listAppPasswords(did: DidString) {
645
- return password.listAppPasswords(this.db, did)
646
- }
647
-
648
- async verifyAccountPassword(
649
- did: DidString,
650
- passwordStr: string,
651
- ): Promise<boolean> {
652
- if (passwordStr.length > scrypt.OLD_PASSWORD_MAX_LENGTH) {
653
- // @NOTE Avoid throwing from here to avoid leaking account email validity
654
- // through error messages.
655
- return false
656
- }
657
-
658
- return password.verifyAccountPassword(this.db, did, passwordStr)
659
- }
660
-
661
- async verifyAppPassword(
662
- did: DidString,
663
- passwordStr: string,
664
- ): Promise<password.AppPassDescript | null> {
665
- return password.verifyAppPassword(this.db, did, passwordStr)
666
- }
667
-
668
- async revokeAppPassword(did: DidString, name: string) {
669
- await this.db.transaction(async (dbTxn) =>
670
- Promise.all([
671
- password.deleteAppPassword(dbTxn, did, name),
672
- auth.revokeAppPasswordRefreshToken(dbTxn, did, name),
673
- ]),
674
- )
675
- }
676
-
677
- // Invites
678
- // ----------
679
-
680
- async ensureInviteIsAvailable(code: string) {
681
- return invite.ensureInviteIsAvailable(this.db, code)
682
- }
683
-
684
- async createInviteCodes(
685
- toCreate: { account: string; codes: string[] }[],
686
- useCount: number,
687
- ) {
688
- return invite.createInviteCodes(this.db, toCreate, useCount)
689
- }
690
-
691
- async createAccountInviteCodes(
692
- forAccount: string,
693
- codes: string[],
694
- expectedTotal: number,
695
- disabled: 0 | 1,
696
- ) {
697
- return invite.createAccountInviteCodes(
698
- this.db,
699
- forAccount,
700
- codes,
701
- expectedTotal,
702
- disabled,
703
- )
704
- }
705
-
706
- async getAccountInvitesCodes(did: DidString) {
707
- const inviteCodes = await invite.getAccountsInviteCodes(this.db, [did])
708
- return inviteCodes.get(did) ?? []
709
- }
710
-
711
- async getAccountsInvitesCodes(dids: DidString[]) {
712
- return invite.getAccountsInviteCodes(this.db, dids)
713
- }
714
-
715
- async getInvitedByForAccounts(dids: DidString[]) {
716
- return invite.getInvitedByForAccounts(this.db, dids)
717
- }
718
-
719
- async getInviteCodesUses(codes: string[]) {
720
- return invite.getInviteCodesUses(this.db, codes)
721
- }
722
-
723
- async setAccountInvitesDisabled(did: DidString, disabled: boolean) {
724
- return invite.setAccountInvitesDisabled(this.db, did, disabled)
725
- }
726
-
727
- async disableInviteCodes(opts: { codes: string[]; accounts: string[] }) {
728
- return invite.disableInviteCodes(this.db, opts)
729
- }
730
-
731
- // Email Tokens
732
- // ----------
733
-
734
- async createEmailToken(did: DidString, purpose: EmailTokenPurpose) {
735
- return emailToken.createEmailToken(this.db, did, purpose)
736
- }
737
-
738
- async assertValidEmailToken(
739
- did: DidString,
740
- purpose: EmailTokenPurpose,
741
- token: string,
742
- ) {
743
- return emailToken.assertValidToken(this.db, did, purpose, token)
744
- }
745
-
746
- async assertValidEmailTokenAndCleanup(
747
- did: DidString,
748
- purpose: EmailTokenPurpose,
749
- token: string,
750
- ) {
751
- await emailToken.assertValidToken(this.db, did, purpose, token)
752
- await emailToken.deleteEmailToken(this.db, did, purpose)
753
- }
754
-
755
- async requestEmailConfirmation(did: DidString, opts?: { locale?: string }) {
756
- const account = await this.getAccount(did, {
757
- includeDeactivated: true,
758
- includeTakenDown: true,
759
- })
760
-
761
- if (!account) {
762
- throw new InvalidRequestError('account not found')
763
- }
764
-
765
- if (!account.email) {
766
- throw new InvalidRequestError('account does not have an email address')
767
- }
768
-
769
- const locale = opts?.locale
770
- const token = await this.createEmailToken(did, 'confirm_email')
771
-
772
- await this.mailer.sendConfirmEmail({ token, locale }, { to: account.email })
773
- }
774
-
775
- async confirmEmail(did: DidString, email: string, token: string) {
776
- const user = await this.getAccount(did, {
777
- includeDeactivated: true,
778
- includeTakenDown: true,
779
- })
780
-
781
- if (!user) {
782
- throw new InvalidRequestError('user not found', 'AccountNotFound')
783
- }
784
-
785
- if (user.email !== email.toLowerCase()) {
786
- throw new InvalidRequestError('invalid email', 'InvalidEmail')
787
- }
788
-
789
- await emailToken.assertValidToken(this.db, did, 'confirm_email', token)
790
- const now = currentDatetimeString()
791
- await this.db.transaction(async (dbTxn) => {
792
- await emailToken.deleteEmailToken(dbTxn, did, 'confirm_email')
793
- await accountHelpers.setEmailConfirmedAt(dbTxn, did, now)
794
- })
795
-
796
- user.emailConfirmedAt = now
797
-
798
- return user
799
- }
800
-
801
- async requestEmailUpdate(
802
- did: DidString,
803
- opts?: { locale?: string },
804
- ): Promise<{ tokenRequired: boolean }> {
805
- const account = await this.getAccount(did, {
806
- includeDeactivated: true,
807
- includeTakenDown: true,
808
- })
809
-
810
- if (!account) {
811
- throw new InvalidRequestError('account not found')
812
- }
813
-
814
- if (!account.email) {
815
- throw new InvalidRequestError('account does not have an email address')
816
- }
817
-
818
- const token = account.emailConfirmedAt
819
- ? await this.createEmailToken(did, 'update_email')
820
- : null
821
-
822
- if (token) {
823
- await this.mailer.sendUpdateEmail(
824
- { token, locale: opts?.locale },
825
- { to: account.email },
826
- )
827
- }
828
-
829
- return { tokenRequired: !!token }
830
- }
831
-
832
- /**
833
- * @throws UserAlreadyExistsError if the new email is already in use by another account
834
- */
835
- async updateEmail(
836
- did: DidString,
837
- email: string,
838
- token?: string,
839
- opts?: { locale?: string; sendConfirmationEmail?: boolean },
840
- ): Promise<ActorAccount> {
841
- if (!isEmailValid(email) || isDisposableEmail(email)) {
842
- throw new InvalidRequestError(
843
- 'This email address is not supported, please use a different email.',
844
- )
845
- }
846
-
847
- const account = await this.getAccount(did, {
848
- includeDeactivated: true,
849
- includeTakenDown: true,
850
- })
851
-
852
- if (!account) {
853
- throw new InvalidRequestError('account not found')
854
- }
855
-
856
- const tokenRequired = !!account.emailConfirmedAt
857
-
858
- // require a token if account email is confirmed
859
- if (!token && tokenRequired) {
860
- throw new InvalidRequestError(
861
- 'confirmation token required',
862
- 'TokenRequired',
863
- )
864
- }
865
-
866
- if (token) {
867
- await this.assertValidEmailToken(did, 'update_email', token)
868
- }
869
-
870
- await this.updateAccountEmail({ did, email })
871
-
872
- account.email = email
873
- account.emailConfirmedAt = null
874
-
875
- // Proactively send a confirmation email so that the user can confirm the
876
- // new email immediately.
877
- if (opts?.sendConfirmationEmail) {
878
- const token = await this.createEmailToken(did, 'confirm_email')
879
- const locale = opts.locale
880
- await this.mailer.sendConfirmEmail({ token, locale }, { to: email })
881
- }
882
-
883
- return account
884
- }
885
-
886
- async updateAccountEmail(opts: { did: DidString; email: string }) {
887
- const { did, email } = opts
888
- await this.db.transaction(async (dbTxn) => {
889
- await accountHelpers.updateEmail(dbTxn, did, email)
890
- await emailToken.deleteAllEmailTokens(dbTxn, did)
891
- })
892
- }
893
-
894
- async resetPassword(opts: { password: string; token: string }) {
895
- const did = await emailToken.assertValidTokenAndFindDid(
896
- this.db,
897
- 'reset_password',
898
- opts.token,
899
- )
900
- await this.updateAccountPassword({ did, password: opts.password })
901
-
902
- return did
903
- }
904
-
905
- async updateAccountPassword(opts: { did: DidString; password: string }) {
906
- const { did } = opts
907
- const passwordScrypt = await scrypt.genSaltAndHash(opts.password)
908
- await this.db.transaction(async (dbTxn) =>
909
- Promise.all([
910
- password.updateUserPassword(dbTxn, { did, passwordScrypt }),
911
- emailToken.deleteEmailToken(dbTxn, did, 'reset_password'),
912
- auth.revokeRefreshTokensByDid(dbTxn, did),
913
- ]),
914
- )
915
- }
916
- }