@atproto/pds 0.5.5 → 0.5.7

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 (256) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/account-manager/account-manager.d.ts +51 -13
  3. package/dist/account-manager/account-manager.d.ts.map +1 -1
  4. package/dist/account-manager/account-manager.js +86 -26
  5. package/dist/account-manager/account-manager.js.map +1 -1
  6. package/dist/account-manager/db/migrations/005-oauth-account-management.d.ts.map +1 -1
  7. package/dist/account-manager/db/migrations/005-oauth-account-management.js +3 -4
  8. package/dist/account-manager/db/migrations/005-oauth-account-management.js.map +1 -1
  9. package/dist/account-manager/db/schema/authorization-request.d.ts +2 -2
  10. package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -1
  11. package/dist/account-manager/db/schema/authorization-request.js.map +1 -1
  12. package/dist/account-manager/db/schema/authorized-client.d.ts +2 -2
  13. package/dist/account-manager/db/schema/authorized-client.d.ts.map +1 -1
  14. package/dist/account-manager/db/schema/authorized-client.js.map +1 -1
  15. package/dist/account-manager/db/schema/token.d.ts +2 -2
  16. package/dist/account-manager/db/schema/token.d.ts.map +1 -1
  17. package/dist/account-manager/db/schema/token.js.map +1 -1
  18. package/dist/account-manager/helpers/account-device.d.ts +23 -196
  19. package/dist/account-manager/helpers/account-device.d.ts.map +1 -1
  20. package/dist/account-manager/helpers/account-device.js +3 -3
  21. package/dist/account-manager/helpers/account-device.js.map +1 -1
  22. package/dist/account-manager/helpers/account.d.ts +14 -4
  23. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  24. package/dist/account-manager/helpers/account.js +17 -11
  25. package/dist/account-manager/helpers/account.js.map +1 -1
  26. package/dist/account-manager/helpers/auth.js +1 -1
  27. package/dist/account-manager/helpers/auth.js.map +1 -1
  28. package/dist/account-manager/helpers/authorization-request.d.ts +83 -5
  29. package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -1
  30. package/dist/account-manager/helpers/authorization-request.js +8 -8
  31. package/dist/account-manager/helpers/authorization-request.js.map +1 -1
  32. package/dist/account-manager/helpers/authorized-client.d.ts +5 -4
  33. package/dist/account-manager/helpers/authorized-client.d.ts.map +1 -1
  34. package/dist/account-manager/helpers/authorized-client.js +3 -0
  35. package/dist/account-manager/helpers/authorized-client.js.map +1 -1
  36. package/dist/account-manager/helpers/device.d.ts +9 -3
  37. package/dist/account-manager/helpers/device.d.ts.map +1 -1
  38. package/dist/account-manager/helpers/device.js +4 -4
  39. package/dist/account-manager/helpers/device.js.map +1 -1
  40. package/dist/account-manager/helpers/invite.d.ts +35 -2
  41. package/dist/account-manager/helpers/invite.d.ts.map +1 -1
  42. package/dist/account-manager/helpers/lexicon.d.ts.map +1 -1
  43. package/dist/account-manager/helpers/lexicon.js +6 -0
  44. package/dist/account-manager/helpers/lexicon.js.map +1 -1
  45. package/dist/account-manager/helpers/password.d.ts +1 -0
  46. package/dist/account-manager/helpers/password.d.ts.map +1 -1
  47. package/dist/account-manager/helpers/password.js +3 -0
  48. package/dist/account-manager/helpers/password.js.map +1 -1
  49. package/dist/account-manager/helpers/token.d.ts +72 -1031
  50. package/dist/account-manager/helpers/token.d.ts.map +1 -1
  51. package/dist/account-manager/helpers/token.js +13 -10
  52. package/dist/account-manager/helpers/token.js.map +1 -1
  53. package/dist/account-manager/helpers/used-refresh-token.d.ts +6 -2
  54. package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -1
  55. package/dist/account-manager/oauth-store.d.ts +17 -13
  56. package/dist/account-manager/oauth-store.d.ts.map +1 -1
  57. package/dist/account-manager/oauth-store.js +89 -39
  58. package/dist/account-manager/oauth-store.js.map +1 -1
  59. package/dist/actor-store/blob/reader.d.ts.map +1 -1
  60. package/dist/actor-store/blob/reader.js +2 -3
  61. package/dist/actor-store/blob/reader.js.map +1 -1
  62. package/dist/actor-store/record/reader.js +3 -3
  63. package/dist/actor-store/record/reader.js.map +1 -1
  64. package/dist/actor-store/repo/sql-repo-reader.d.ts +5 -1
  65. package/dist/actor-store/repo/sql-repo-reader.d.ts.map +1 -1
  66. package/dist/actor-store/repo/sql-repo-reader.js.map +1 -1
  67. package/dist/api/com/atproto/admin/updateSubjectStatus.d.ts.map +1 -1
  68. package/dist/api/com/atproto/admin/updateSubjectStatus.js +12 -4
  69. package/dist/api/com/atproto/admin/updateSubjectStatus.js.map +1 -1
  70. package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
  71. package/dist/api/com/atproto/server/activateAccount.js +25 -31
  72. package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
  73. package/dist/api/com/atproto/server/deactivateAccount.d.ts.map +1 -1
  74. package/dist/api/com/atproto/server/deactivateAccount.js +3 -4
  75. package/dist/api/com/atproto/server/deactivateAccount.js.map +1 -1
  76. package/dist/api/com/atproto/server/deleteAccount.d.ts.map +1 -1
  77. package/dist/api/com/atproto/server/deleteAccount.js +51 -37
  78. package/dist/api/com/atproto/server/deleteAccount.js.map +1 -1
  79. package/dist/api/com/atproto/server/util.d.ts +25 -6
  80. package/dist/api/com/atproto/server/util.d.ts.map +1 -1
  81. package/dist/api/com/atproto/server/util.js.map +1 -1
  82. package/dist/background.d.ts +12 -6
  83. package/dist/background.d.ts.map +1 -1
  84. package/dist/background.js +32 -13
  85. package/dist/background.js.map +1 -1
  86. package/dist/config/config.d.ts +2 -1
  87. package/dist/config/config.d.ts.map +1 -1
  88. package/dist/config/config.js +4 -1
  89. package/dist/config/config.js.map +1 -1
  90. package/dist/context.d.ts.map +1 -1
  91. package/dist/context.js +5 -32
  92. package/dist/context.js.map +1 -1
  93. package/dist/db/migrator.d.ts +4 -3
  94. package/dist/db/migrator.d.ts.map +1 -1
  95. package/dist/db/migrator.js +1 -1
  96. package/dist/db/migrator.js.map +1 -1
  97. package/dist/db/pagination.d.ts +2 -1
  98. package/dist/db/pagination.d.ts.map +1 -1
  99. package/dist/db/pagination.js +2 -2
  100. package/dist/db/pagination.js.map +1 -1
  101. package/dist/db/util.d.ts +3 -3
  102. package/dist/db/util.d.ts.map +1 -1
  103. package/dist/db/util.js.map +1 -1
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +33 -11
  107. package/dist/index.js.map +1 -1
  108. package/dist/lexicons/app/bsky/notification/defs.defs.d.ts +5 -0
  109. package/dist/lexicons/app/bsky/notification/defs.defs.d.ts.map +1 -1
  110. package/dist/lexicons/app/bsky/notification/defs.defs.js +1 -0
  111. package/dist/lexicons/app/bsky/notification/defs.defs.js.map +1 -1
  112. package/dist/lexicons/chat/bsky/notification/defs.d.ts +2 -0
  113. package/dist/lexicons/chat/bsky/notification/defs.d.ts.map +1 -0
  114. package/dist/lexicons/chat/bsky/notification/defs.defs.d.ts +20 -0
  115. package/dist/lexicons/chat/bsky/notification/defs.defs.d.ts.map +1 -0
  116. package/dist/lexicons/chat/bsky/notification/defs.defs.js +19 -0
  117. package/dist/lexicons/chat/bsky/notification/defs.defs.js.map +1 -0
  118. package/dist/lexicons/chat/bsky/notification/defs.js +5 -0
  119. package/dist/lexicons/chat/bsky/notification/defs.js.map +1 -0
  120. package/dist/lexicons/chat/bsky/notification/getPreferences.d.ts +3 -0
  121. package/dist/lexicons/chat/bsky/notification/getPreferences.d.ts.map +1 -0
  122. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.d.ts +18 -0
  123. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.d.ts.map +1 -0
  124. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.js +16 -0
  125. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.js.map +1 -0
  126. package/dist/lexicons/chat/bsky/notification/getPreferences.js +6 -0
  127. package/dist/lexicons/chat/bsky/notification/getPreferences.js.map +1 -0
  128. package/dist/lexicons/chat/bsky/notification/putPreferences.d.ts +3 -0
  129. package/dist/lexicons/chat/bsky/notification/putPreferences.d.ts.map +1 -0
  130. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.d.ts +27 -0
  131. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.d.ts.map +1 -0
  132. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.js +22 -0
  133. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.js.map +1 -0
  134. package/dist/lexicons/chat/bsky/notification/putPreferences.js +6 -0
  135. package/dist/lexicons/chat/bsky/notification/putPreferences.js.map +1 -0
  136. package/dist/lexicons/chat/bsky/notification.d.ts +4 -0
  137. package/dist/lexicons/chat/bsky/notification.d.ts.map +1 -0
  138. package/dist/lexicons/chat/bsky/notification.js +7 -0
  139. package/dist/lexicons/chat/bsky/notification.js.map +1 -0
  140. package/dist/lexicons/chat/bsky.d.ts +1 -0
  141. package/dist/lexicons/chat/bsky.d.ts.map +1 -1
  142. package/dist/lexicons/chat/bsky.js +1 -0
  143. package/dist/lexicons/chat/bsky.js.map +1 -1
  144. package/dist/lexicons/tools/ozone/report/defs.defs.d.ts +4 -0
  145. package/dist/lexicons/tools/ozone/report/defs.defs.d.ts.map +1 -1
  146. package/dist/lexicons/tools/ozone/report/defs.defs.js +2 -0
  147. package/dist/lexicons/tools/ozone/report/defs.defs.js.map +1 -1
  148. package/dist/lexicons/tools/ozone/report/queryActivities.d.ts +3 -0
  149. package/dist/lexicons/tools/ozone/report/queryActivities.d.ts.map +1 -0
  150. package/dist/lexicons/tools/ozone/report/queryActivities.defs.d.ts +42 -0
  151. package/dist/lexicons/tools/ozone/report/queryActivities.defs.d.ts.map +1 -0
  152. package/dist/lexicons/tools/ozone/report/queryActivities.defs.js +31 -0
  153. package/dist/lexicons/tools/ozone/report/queryActivities.defs.js.map +1 -0
  154. package/dist/lexicons/tools/ozone/report/queryActivities.js +6 -0
  155. package/dist/lexicons/tools/ozone/report/queryActivities.js.map +1 -0
  156. package/dist/lexicons/tools/ozone/report.d.ts +1 -0
  157. package/dist/lexicons/tools/ozone/report.d.ts.map +1 -1
  158. package/dist/lexicons/tools/ozone/report.js +1 -0
  159. package/dist/lexicons/tools/ozone/report.js.map +1 -1
  160. package/dist/mailer/index.d.ts +2 -0
  161. package/dist/mailer/index.d.ts.map +1 -1
  162. package/dist/mailer/index.js +2 -0
  163. package/dist/mailer/index.js.map +1 -1
  164. package/dist/pipethrough.d.ts +3 -0
  165. package/dist/pipethrough.d.ts.map +1 -1
  166. package/dist/pipethrough.js +32 -0
  167. package/dist/pipethrough.js.map +1 -1
  168. package/dist/scripts/sequencer-recovery/recovery-db.js.map +1 -1
  169. package/dist/sequencer/events.d.ts.map +1 -1
  170. package/dist/sequencer/events.js +6 -13
  171. package/dist/sequencer/events.js.map +1 -1
  172. package/dist/sequencer/sequencer.d.ts +1 -1
  173. package/dist/sequencer/sequencer.d.ts.map +1 -1
  174. package/dist/sequencer/sequencer.js.map +1 -1
  175. package/package.json +14 -14
  176. package/src/account-manager/account-manager.ts +135 -36
  177. package/src/account-manager/db/migrations/005-oauth-account-management.ts +6 -5
  178. package/src/account-manager/db/schema/authorization-request.ts +2 -1
  179. package/src/account-manager/db/schema/authorized-client.ts +6 -2
  180. package/src/account-manager/db/schema/token.ts +2 -2
  181. package/src/account-manager/helpers/account-device.ts +5 -11
  182. package/src/account-manager/helpers/account.ts +39 -16
  183. package/src/account-manager/helpers/auth.ts +2 -2
  184. package/src/account-manager/helpers/authorization-request.ts +12 -8
  185. package/src/account-manager/helpers/authorized-client.ts +12 -6
  186. package/src/account-manager/helpers/device.ts +4 -4
  187. package/src/account-manager/helpers/lexicon.ts +8 -3
  188. package/src/account-manager/helpers/password.ts +6 -0
  189. package/src/account-manager/helpers/token.ts +17 -11
  190. package/src/account-manager/oauth-store.ts +129 -61
  191. package/src/actor-store/blob/reader.ts +8 -5
  192. package/src/actor-store/record/reader.ts +3 -3
  193. package/src/actor-store/repo/sql-repo-reader.ts +1 -1
  194. package/src/api/com/atproto/admin/updateSubjectStatus.ts +16 -4
  195. package/src/api/com/atproto/server/activateAccount.ts +27 -49
  196. package/src/api/com/atproto/server/deactivateAccount.ts +3 -7
  197. package/src/api/com/atproto/server/deleteAccount.ts +60 -43
  198. package/src/api/com/atproto/server/util.ts +24 -7
  199. package/src/background.ts +49 -18
  200. package/src/config/config.ts +7 -2
  201. package/src/context.ts +6 -40
  202. package/src/db/migrator.ts +2 -1
  203. package/src/db/pagination.ts +7 -7
  204. package/src/db/util.ts +5 -2
  205. package/src/index.ts +31 -13
  206. package/src/mailer/index.ts +4 -2
  207. package/src/pipethrough.ts +38 -1
  208. package/src/scripts/sequencer-recovery/recovery-db.ts +1 -1
  209. package/src/sequencer/events.ts +8 -13
  210. package/src/sequencer/sequencer.ts +1 -3
  211. package/tests/_util.ts +8 -25
  212. package/tests/account-deactivation.test.ts +1 -1
  213. package/tests/account-deletion.test.ts +1 -1
  214. package/tests/account-manager.test.ts +79 -0
  215. package/tests/account-migration.test.ts +2 -2
  216. package/tests/account-status.test.ts +206 -0
  217. package/tests/account.test.ts +1 -1
  218. package/tests/app-passwords.test.ts +1 -1
  219. package/tests/auth.test.ts +1 -1
  220. package/tests/blob-deletes.test.ts +1 -1
  221. package/tests/create-post.test.ts +1 -1
  222. package/tests/crud.test.ts +1 -1
  223. package/tests/db.test.ts +4 -4
  224. package/tests/email-confirmation.test.ts +1 -1
  225. package/tests/file-uploads.test.ts +2 -2
  226. package/tests/get-service-auth.test.ts +1 -1
  227. package/tests/handles.test.ts +1 -1
  228. package/tests/invite-codes.test.ts +1 -1
  229. package/tests/invites-admin.test.ts +1 -1
  230. package/tests/moderation.test.ts +1 -1
  231. package/tests/moderator-auth.test.ts +1 -1
  232. package/tests/oauth.test.ts +91 -0
  233. package/tests/plc-operations.test.ts +1 -1
  234. package/tests/preferences.test.ts +1 -1
  235. package/tests/proxied/admin.test.ts +1 -1
  236. package/tests/proxied/feedgen.test.ts +1 -1
  237. package/tests/proxied/notif.test.ts +6 -13
  238. package/tests/proxied/procedures.test.ts +1 -1
  239. package/tests/proxied/proxy-catchall.test.ts +9 -8
  240. package/tests/proxied/proxy-header.test.ts +12 -8
  241. package/tests/proxied/proxy-oauth-aud.test.ts +17 -10
  242. package/tests/proxied/read-after-write.test.ts +4 -5
  243. package/tests/proxied/views.test.ts +1 -1
  244. package/tests/races.test.ts +1 -1
  245. package/tests/rate-limits.test.ts +1 -1
  246. package/tests/recovery.test.ts +1 -1
  247. package/tests/seeds/basic.ts +2 -2
  248. package/tests/seeds/users.ts +4 -1
  249. package/tests/sequencer.test.ts +1 -1
  250. package/tests/server.test.ts +9 -12
  251. package/tests/sync/invertible-ops.test.ts +1 -1
  252. package/tests/sync/list.test.ts +1 -1
  253. package/tests/sync/subscribe-repos.test.ts +1 -1
  254. package/tests/sync/sync.test.ts +1 -1
  255. package/tests/takedown-appeal.test.ts +1 -1
  256. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert'
1
2
  import { KeyObject } from 'node:crypto'
2
3
  import { Client as PlcClient } from '@did-plc/lib'
3
4
  import { isEmailValid } from '@hapi/address'
@@ -12,9 +13,16 @@ import {
12
13
  isAtIdentifierString,
13
14
  } from '@atproto/lex'
14
15
  import { Cid } from '@atproto/lex-data'
15
- import { currentDatetimeString, isValidTld } from '@atproto/syntax'
16
+ import {
17
+ INVALID_HANDLE,
18
+ currentDatetimeString,
19
+ isValidTld,
20
+ } from '@atproto/syntax'
16
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'
17
24
  import { AuthScope } from '../auth-scope.js'
25
+ import { ServerConfig } from '../config/config.js'
18
26
  import { softDeleted } from '../db/index.js'
19
27
  import { hasExplicitSlur } from '../handle/explicit-slurs.js'
20
28
  import {
@@ -27,9 +35,10 @@ import { httpLogger } from '../logger.js'
27
35
  import { ServerMailer } from '../mailer/index.js'
28
36
  import { Sequencer } from '../sequencer/index.js'
29
37
  import { AccountDb, EmailTokenPurpose, getDb, getMigrator } from './db/index.js'
30
- import * as account from './helpers/account.js'
38
+ import * as accountHelpers from './helpers/account.js'
31
39
  import { AccountStatus, ActorAccount } from './helpers/account.js'
32
40
  import * as auth from './helpers/auth.js'
41
+ import * as authorizedClientHelper from './helpers/authorized-client.js'
33
42
  import * as emailToken from './helpers/email-token.js'
34
43
  import * as invite from './helpers/invite.js'
35
44
  import * as password from './helpers/password.js'
@@ -53,7 +62,7 @@ export { AccountStatus, formatAccountStatus } from './helpers/account.js'
53
62
  */
54
63
  export class InvalidPasswordError extends AuthRequiredError {
55
64
  constructor(
56
- public readonly did: string,
65
+ public readonly did: DidString,
57
66
  errorMessage = 'Invalid identifier or password',
58
67
  ) {
59
68
  super(errorMessage)
@@ -69,17 +78,24 @@ export class AccountManager {
69
78
  readonly db: AccountDb
70
79
 
71
80
  constructor(
81
+ readonly cfg: ServerConfig,
82
+ readonly actorStore: ActorStore,
72
83
  readonly idResolver: IdResolver,
73
84
  readonly jwtKey: KeyObject,
74
85
  readonly mailer: ServerMailer,
75
86
  readonly sequencer: Sequencer,
76
87
  readonly plcClient: PlcClient,
77
88
  readonly plcRotationKey: Keypair,
78
- readonly serviceDid: string,
79
- readonly serviceHandleDomains: string[],
80
- db: AccountManagerDbConfig,
81
89
  ) {
82
- this.db = getDb(db.accountDbLoc, db.disableWalAutoCheckpoint)
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
83
99
  }
84
100
 
85
101
  async migrateOrThrow() {
@@ -96,23 +112,23 @@ export class AccountManager {
96
112
 
97
113
  async getAccount(
98
114
  handleOrDid: AtIdentifierString,
99
- flags?: account.AvailabilityFlags,
115
+ flags?: accountHelpers.AvailabilityFlags,
100
116
  ): Promise<ActorAccount | null> {
101
- return account.getAccount(this.db, handleOrDid, flags)
117
+ return accountHelpers.getAccount(this.db, handleOrDid, flags)
102
118
  }
103
119
 
104
120
  async getAccounts(
105
121
  dids: DidString[],
106
- flags?: account.AvailabilityFlags,
122
+ flags?: accountHelpers.AvailabilityFlags,
107
123
  ): Promise<Map<string, ActorAccount>> {
108
- return account.getAccounts(this.db, dids, flags)
124
+ return accountHelpers.getAccounts(this.db, dids, flags)
109
125
  }
110
126
 
111
127
  async getAccountByEmail(
112
128
  email: string,
113
- flags?: account.AvailabilityFlags,
129
+ flags?: accountHelpers.AvailabilityFlags,
114
130
  ): Promise<ActorAccount | null> {
115
- return account.getAccountByEmail(this.db, email, flags)
131
+ return accountHelpers.getAccountByEmail(this.db, email, flags)
116
132
  }
117
133
 
118
134
  async isAccountActivated(did: DidString): Promise<boolean> {
@@ -123,22 +139,26 @@ export class AccountManager {
123
139
 
124
140
  async getDidForActor(
125
141
  handleOrDid: AtIdentifierString,
126
- flags?: account.AvailabilityFlags,
142
+ flags?: accountHelpers.AvailabilityFlags,
127
143
  ): Promise<DidString | null> {
128
144
  const got = await this.getAccount(handleOrDid, flags)
129
145
  return got?.did ?? null
130
146
  }
131
147
 
132
- async getAccountStatus(
133
- handleOrDid: AtIdentifierString,
134
- ): Promise<AccountStatus> {
148
+ async getAccountStatus(handleOrDid: AtIdentifierString) {
135
149
  const got = await this.getAccount(handleOrDid, {
136
150
  includeDeactivated: true,
137
151
  includeTakenDown: true,
138
152
  })
139
153
 
140
- const res = account.formatAccountStatus(got)
141
- return res.active ? AccountStatus.Active : res.status
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 }
142
162
  }
143
163
 
144
164
  async normalizeAndValidateHandle(
@@ -230,10 +250,14 @@ export class AccountManager {
230
250
  await invite.ensureInviteIsAvailable(dbTxn, inviteCode)
231
251
  }
232
252
 
233
- await account.registerActor(dbTxn, { did, handle, deactivated })
253
+ await accountHelpers.registerActor(dbTxn, { did, handle, deactivated })
234
254
 
235
255
  if (email && passwordScrypt) {
236
- await account.registerAccount(dbTxn, { did, email, passwordScrypt })
256
+ await accountHelpers.registerAccount(dbTxn, {
257
+ did,
258
+ email,
259
+ passwordScrypt,
260
+ })
237
261
  }
238
262
 
239
263
  await invite.recordInviteUse(dbTxn, {
@@ -359,7 +383,7 @@ export class AccountManager {
359
383
  did: DidString,
360
384
  handle: HandleString,
361
385
  ): Promise<void> {
362
- await account.updateHandle(this.db, did, handle)
386
+ await accountHelpers.updateHandle(this.db, did, handle)
363
387
 
364
388
  try {
365
389
  await this.sequencer.sequenceIdentity(did, handle)
@@ -369,36 +393,105 @@ export class AccountManager {
369
393
  }
370
394
 
371
395
  async deleteAccount(did: DidString) {
372
- return account.deleteAccount(this.db, did)
396
+ return accountHelpers.deleteAccount(this.db, did)
373
397
  }
374
398
 
375
399
  async takedownAccount(
376
400
  did: DidString,
377
401
  takedown: com.atproto.admin.defs.StatusAttr,
378
402
  ) {
379
- await this.db.transaction(async (dbTxn) =>
380
- Promise.all([
381
- account.updateAccountTakedownStatus(dbTxn, did, takedown),
382
- auth.revokeRefreshTokensByDid(dbTxn, did),
383
- token.removeByDidQB(dbTxn, did).execute(),
384
- ]),
385
- )
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)
386
410
  }
387
411
 
388
412
  async getAccountAdminStatus(did: DidString) {
389
- return account.getAccountAdminStatus(this.db, did)
413
+ return accountHelpers.getAccountAdminStatus(this.db, did)
390
414
  }
391
415
 
392
416
  async updateRepoRoot(did: DidString, cid: Cid, rev: string) {
393
417
  return repo.updateRoot(this.db, did, cid, rev)
394
418
  }
395
419
 
396
- async deactivateAccount(did: DidString, deleteAfter: string | null) {
397
- return account.deactivateAccount(this.db, did, deleteAfter)
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
398
455
  }
399
456
 
400
457
  async activateAccount(did: DidString) {
401
- return account.activateAccount(this.db, did)
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)
402
495
  }
403
496
 
404
497
  // Auth
@@ -556,6 +649,12 @@ export class AccountManager {
556
649
  did: DidString,
557
650
  passwordStr: string,
558
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
+
559
658
  return password.verifyAccountPassword(this.db, did, passwordStr)
560
659
  }
561
660
 
@@ -691,7 +790,7 @@ export class AccountManager {
691
790
  const now = currentDatetimeString()
692
791
  await this.db.transaction(async (dbTxn) => {
693
792
  await emailToken.deleteEmailToken(dbTxn, did, 'confirm_email')
694
- await account.setEmailConfirmedAt(dbTxn, did, now)
793
+ await accountHelpers.setEmailConfirmedAt(dbTxn, did, now)
695
794
  })
696
795
 
697
796
  user.emailConfirmedAt = now
@@ -787,7 +886,7 @@ export class AccountManager {
787
886
  async updateAccountEmail(opts: { did: DidString; email: string }) {
788
887
  const { did, email } = opts
789
888
  await this.db.transaction(async (dbTxn) => {
790
- await account.updateEmail(dbTxn, did, email)
889
+ await accountHelpers.updateEmail(dbTxn, did, email)
791
890
  await emailToken.deleteAllEmailTokens(dbTxn, did)
792
891
  })
793
892
  }
@@ -85,13 +85,14 @@ export async function up(
85
85
  .select('authenticatedAt as createdAt') // Best we can do
86
86
  .select('authenticatedAt as updatedAt')
87
87
  .where('remember', '=', 1)
88
- .whereExists((qb) =>
88
+ .where(({ exists, selectFrom }) =>
89
89
  // device_account does not have fkey on account.did,
90
90
  // so we satisfy account_device_did_fk with this condition.
91
- qb
92
- .selectFrom('account')
93
- .selectAll()
94
- .whereRef('account.did', '=', 'device_account.did'),
91
+ exists(
92
+ selectFrom('account')
93
+ .selectAll()
94
+ .whereRef('account.did', '=', 'device_account.did'),
95
+ ),
95
96
  ),
96
97
  )
97
98
  .onConflict((oc) => oc.doNothing())
@@ -4,6 +4,7 @@ import {
4
4
  ClientAuthLegacy,
5
5
  Code,
6
6
  DeviceId,
7
+ Did,
7
8
  OAuthAuthorizationRequestParameters,
8
9
  OAuthClientId,
9
10
  RequestId,
@@ -12,7 +13,7 @@ import { DateISO, JsonEncoded } from '../../../db/index.js'
12
13
 
13
14
  export interface AuthorizationRequest {
14
15
  id: RequestId
15
- did: string | null
16
+ did: Did | null
16
17
  deviceId: DeviceId | null
17
18
 
18
19
  clientId: OAuthClientId
@@ -1,9 +1,13 @@
1
1
  import { Selectable } from 'kysely'
2
- import { AuthorizedClientData, OAuthClientId } from '@atproto/oauth-provider'
2
+ import {
3
+ AuthorizedClientData,
4
+ Did,
5
+ OAuthClientId,
6
+ } from '@atproto/oauth-provider'
3
7
  import { DateISO, JsonEncoded } from '../../../db/index.js'
4
8
 
5
9
  export interface AuthorizedClient {
6
- did: string
10
+ did: Did
7
11
  clientId: OAuthClientId
8
12
 
9
13
  createdAt: DateISO
@@ -4,18 +4,18 @@ import {
4
4
  ClientAuthLegacy,
5
5
  Code,
6
6
  DeviceId,
7
+ Did,
7
8
  OAuthAuthorizationDetails,
8
9
  OAuthAuthorizationRequestParameters,
9
10
  OAuthClientId,
10
11
  RefreshToken,
11
- Sub,
12
12
  TokenId,
13
13
  } from '@atproto/oauth-provider'
14
14
  import { DateISO, JsonEncoded } from '../../../db/cast.js'
15
15
 
16
16
  export interface Token {
17
17
  id: Generated<number>
18
- did: Sub
18
+ did: Did
19
19
 
20
20
  tokenId: TokenId
21
21
  createdAt: DateISO
@@ -1,6 +1,5 @@
1
1
  import assert from 'node:assert'
2
- import { DeviceId } from '@atproto/oauth-provider'
3
- import { DidString } from '@atproto/syntax'
2
+ import { DeviceId, Did } from '@atproto/oauth-provider'
4
3
  import { toDateISO } from '../../db/index.js'
5
4
  import { AccountDb } from '../db/index.js'
6
5
  import { selectAccountQB } from './account.js'
@@ -26,13 +25,10 @@ export function upsertQB(db: AccountDb, deviceId: DeviceId, did: string) {
26
25
 
27
26
  export function selectQB(
28
27
  db: AccountDb,
29
- filter: {
30
- sub?: string
31
- deviceId?: DeviceId
32
- },
28
+ filter: { did?: Did; deviceId?: DeviceId },
33
29
  ) {
34
30
  assert(
35
- filter.sub != null || filter.deviceId != null,
31
+ filter.did != null || filter.deviceId != null,
36
32
  'Either sub or deviceId must be provided',
37
33
  )
38
34
 
@@ -52,10 +48,8 @@ export function selectQB(
52
48
  'device.ipAddress',
53
49
  'device.lastSeenAt',
54
50
  ])
55
- .if(filter.sub != null, (qb) =>
56
- qb.where('actor.did', '=', filter.sub! as DidString),
57
- )
58
- .if(filter.deviceId != null, (qb) =>
51
+ .$if(filter.did != null, (qb) => qb.where('actor.did', '=', filter.did!))
52
+ .$if(filter.deviceId != null, (qb) =>
59
53
  qb.where('account_device.deviceId', '=', filter.deviceId!),
60
54
  )
61
55
  )
@@ -46,8 +46,10 @@ export const selectAccountQB = (db: AccountDb, flags?: AvailabilityFlags) => {
46
46
  return db.db
47
47
  .selectFrom('actor')
48
48
  .leftJoin('account', 'actor.did', 'account.did')
49
- .if(!includeTakenDown, (qb) => qb.where(notSoftDeletedClause(ref('actor'))))
50
- .if(!includeDeactivated, (qb) =>
49
+ .$if(!includeTakenDown, (qb) =>
50
+ qb.where(notSoftDeletedClause(ref('actor'))),
51
+ )
52
+ .$if(!includeDeactivated, (qb) =>
51
53
  qb.where('actor.deactivatedAt', 'is', null),
52
54
  )
53
55
  .select([
@@ -69,11 +71,11 @@ export const getAccount = async (
69
71
  flags?: AvailabilityFlags,
70
72
  ): Promise<ActorAccount | null> => {
71
73
  const found = await selectAccountQB(db, flags)
72
- .where((qb) => {
74
+ .where((eb) => {
73
75
  if (isDidIdentifier(handleOrDid)) {
74
- return qb.where('actor.did', '=', handleOrDid)
76
+ return eb('actor.did', '=', handleOrDid)
75
77
  } else {
76
- return qb.where('actor.handle', '=', handleOrDid)
78
+ return eb('actor.handle', '=', handleOrDid)
77
79
  }
78
80
  })
79
81
  .executeTakeFirst()
@@ -202,12 +204,16 @@ export const updateHandle = async (
202
204
  .updateTable('actor')
203
205
  .set({ handle })
204
206
  .where('did', '=', did)
205
- .whereNotExists(
206
- db.db
207
- .selectFrom('actor')
208
- .where('handle', '=', handle)
209
- .where('did', '!=', did)
210
- .selectAll(),
207
+ .where(({ not, exists }) =>
208
+ not(
209
+ exists(
210
+ db.db
211
+ .selectFrom('actor')
212
+ .where('handle', '=', handle)
213
+ .where('did', '!=', did)
214
+ .selectAll(),
215
+ ),
216
+ ),
211
217
  ),
212
218
  )
213
219
  if (res.numUpdatedRows < 1) {
@@ -290,8 +296,8 @@ export const deactivateAccount = async (
290
296
  db: AccountDb,
291
297
  did: DidString,
292
298
  deleteAfter: string | null,
293
- ) => {
294
- await db.executeWithRetry(
299
+ ): Promise<boolean> => {
300
+ const [res] = await db.executeWithRetry(
295
301
  db.db
296
302
  .updateTable('actor')
297
303
  .set({
@@ -300,18 +306,35 @@ export const deactivateAccount = async (
300
306
  })
301
307
  .where('did', '=', did),
302
308
  )
309
+
310
+ return res.numUpdatedRows > 0
303
311
  }
304
312
 
305
- export const activateAccount = async (db: AccountDb, did: DidString) => {
306
- await db.executeWithRetry(
313
+ export const activateAccount = async (
314
+ db: AccountDb,
315
+ did: DidString,
316
+ flags?: AvailabilityFlags,
317
+ ): Promise<boolean> => {
318
+ const { includeTakenDown = false, includeDeactivated = true } = flags ?? {}
319
+ const { ref } = db.db.dynamic
320
+
321
+ const [res] = await db.executeWithRetry(
307
322
  db.db
308
323
  .updateTable('actor')
309
324
  .set({
310
325
  deactivatedAt: null,
311
326
  deleteAfter: null,
312
327
  })
313
- .where('did', '=', did),
328
+ .where('did', '=', did)
329
+ .$if(!includeTakenDown, (q) =>
330
+ q.where(notSoftDeletedClause(ref('actor'))),
331
+ )
332
+ .$if(!includeDeactivated, (q) =>
333
+ q.where('actor.deactivatedAt', 'is not', null),
334
+ ),
314
335
  )
336
+
337
+ return res.numUpdatedRows > 0
315
338
  }
316
339
 
317
340
  export const formatAccountStatus = (
@@ -164,8 +164,8 @@ export const addRefreshGracePeriod = async (
164
164
  db.db
165
165
  .updateTable('refresh_token')
166
166
  .where('id', '=', id)
167
- .where((inner) =>
168
- inner.where('nextId', 'is', null).orWhere('nextId', '=', nextId),
167
+ .where((eb) =>
168
+ eb.or([eb('nextId', 'is', null), eb('nextId', '=', nextId)]),
169
169
  )
170
170
  .set({ expiresAt, nextId })
171
171
  .returningAll(),
@@ -18,7 +18,7 @@ export const rowToRequestData = (
18
18
  parameters: fromJson(row.parameters),
19
19
  expiresAt: fromDateISO(row.expiresAt),
20
20
  deviceId: row.deviceId,
21
- sub: row.did,
21
+ did: row.did,
22
22
  code: row.code,
23
23
  })
24
24
 
@@ -34,7 +34,7 @@ const requestDataToRow = (
34
34
  data: RequestData,
35
35
  ): Insertable<AuthorizationRequest> => ({
36
36
  id,
37
- did: data.sub,
37
+ did: data.did,
38
38
  deviceId: data.deviceId,
39
39
 
40
40
  clientId: data.clientId,
@@ -53,16 +53,20 @@ export const readQB = (db: AccountDb, id: RequestId) =>
53
53
  export const updateQB = (
54
54
  db: AccountDb,
55
55
  id: RequestId,
56
- { code, sub, deviceId, expiresAt, parameters, ...rest }: UpdateRequestData,
56
+ { code, did, deviceId, expiresAt, parameters, ...rest }: UpdateRequestData,
57
57
  ) => {
58
58
  assert(!Object.keys(rest).length, 'Unexpected fields in UpdateRequestData')
59
59
  return db.db
60
60
  .updateTable('authorization_request')
61
- .if(code !== undefined, (qb) => qb.set({ code }))
62
- .if(sub !== undefined, (qb) => qb.set({ did: sub }))
63
- .if(deviceId !== undefined, (qb) => qb.set({ deviceId }))
64
- .if(expiresAt != null, (qb) => qb.set({ expiresAt: toDateISO(expiresAt!) }))
65
- .if(parameters != null, (qb) => qb.set({ parameters: toJson(parameters!) }))
61
+ .$if(code !== undefined, (qb) => qb.set({ code }))
62
+ .$if(did !== undefined, (qb) => qb.set({ did }))
63
+ .$if(deviceId !== undefined, (qb) => qb.set({ deviceId }))
64
+ .$if(expiresAt != null, (qb) =>
65
+ qb.set({ expiresAt: toDateISO(expiresAt!) }),
66
+ )
67
+ .$if(parameters != null, (qb) =>
68
+ qb.set({ parameters: toJson(parameters!) }),
69
+ )
66
70
  .where('id', '=', id)
67
71
  }
68
72
 
@@ -2,14 +2,14 @@ import {
2
2
  AuthorizedClientData,
3
3
  AuthorizedClients,
4
4
  ClientId,
5
- Sub,
5
+ Did,
6
6
  } from '@atproto/oauth-provider'
7
7
  import { fromJson, toDateISO, toJson } from '../../db/index.js'
8
8
  import { AccountDb } from '../db/index.js'
9
9
 
10
10
  export async function upsert(
11
11
  db: AccountDb,
12
- did: string,
12
+ did: Did,
13
13
  clientId: ClientId,
14
14
  data: AuthorizedClientData,
15
15
  ) {
@@ -36,17 +36,23 @@ export async function upsert(
36
36
 
37
37
  export async function getAuthorizedClients(
38
38
  db: AccountDb,
39
- did: string,
39
+ did: Did,
40
40
  ): Promise<AuthorizedClients> {
41
41
  return (await getAuthorizedClientsMulti(db, [did])).get(did)!
42
42
  }
43
43
 
44
+ export async function deleteAllAuthorizedClients(db: AccountDb, did: Did) {
45
+ await db.executeWithRetry(
46
+ db.db.deleteFrom('authorized_client').where('did', '=', did),
47
+ )
48
+ }
49
+
44
50
  export async function getAuthorizedClientsMulti(
45
51
  db: AccountDb,
46
- dids: Iterable<string>,
47
- ): Promise<Map<Sub, AuthorizedClients>> {
52
+ dids: Iterable<Did>,
53
+ ): Promise<Map<Did, AuthorizedClients>> {
48
54
  // Using a Map will ensure unicity of dids (through unicity of keys)
49
- const map = new Map<Sub, AuthorizedClients>(
55
+ const map = new Map<Did, AuthorizedClients>(
50
56
  Array.from(dids, (did) => [did, new Map()]),
51
57
  )
52
58
 
@@ -35,10 +35,10 @@ export const updateQB = (
35
35
  ) =>
36
36
  db.db
37
37
  .updateTable('device')
38
- .if(sessionId != null, (qb) => qb.set({ sessionId }))
39
- .if(userAgent != null, (qb) => qb.set({ userAgent }))
40
- .if(ipAddress != null, (qb) => qb.set({ ipAddress }))
41
- .if(lastSeenAt != null, (qb) =>
38
+ .$if(sessionId != null, (qb) => qb.set({ sessionId }))
39
+ .$if(userAgent != null, (qb) => qb.set({ userAgent }))
40
+ .$if(ipAddress != null, (qb) => qb.set({ ipAddress }))
41
+ .$if(lastSeenAt != null, (qb) =>
42
42
  qb.set({ lastSeenAt: toDateISO(lastSeenAt!) }),
43
43
  )
44
44
  .where('id', '=', deviceId)
@@ -1,10 +1,15 @@
1
- import { Insertable } from 'kysely'
2
1
  import { LEXICON_REFRESH_FREQUENCY, LexiconData } from '@atproto/oauth-provider'
3
2
  import { fromDateISO, fromJson, toDateISO, toJson } from '../../db/index.js'
4
- import { AccountDb, Lexicon } from '../db/index.js'
3
+ import { AccountDb } from '../db/index.js'
5
4
 
6
5
  export async function upsert(db: AccountDb, nsid: string, data: LexiconData) {
7
- const updates: Omit<Insertable<Lexicon>, 'nsid'> = {
6
+ // @TODO not annotated as `Omit<Insertable<Lexicon>, 'nsid'>`. Insertable's
7
+ // nullable/non-nullable key partition evaluates `IsNullable<InsertType<…>>`
8
+ // per column, which for the recursive `JsonEncoded<LexiconDocument>` column
9
+ // overflows the tsgo (TS7) checker's instantiation depth (TS2589). The older
10
+ // tsc handled it; the `.values()`/`.doUpdateSet()` calls below still
11
+ // type-check this object against the insert type regardless.
12
+ const updates = {
8
13
  ...data,
9
14
  createdAt: toDateISO(data.createdAt),
10
15
  updatedAt: toDateISO(data.updatedAt),
@@ -128,3 +128,9 @@ export const deleteAppPassword = async (
128
128
  .where('name', '=', name),
129
129
  )
130
130
  }
131
+
132
+ export const deleteAllAppPasswords = async (db: AccountDb, did: string) => {
133
+ await db.executeWithRetry(
134
+ db.db.deleteFrom('app_password').where('did', '=', did),
135
+ )
136
+ }