@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,6 +1,7 @@
1
1
  import { Selectable } from 'kysely'
2
2
  import {
3
3
  Code,
4
+ Did,
4
5
  NewTokenData,
5
6
  RefreshToken,
6
7
  TokenData,
@@ -18,7 +19,7 @@ export function toTokenData(row: Selectable<Token>): TokenData {
18
19
  clientId: row.clientId,
19
20
  clientAuth: fromJson(row.clientAuth),
20
21
  deviceId: row.deviceId,
21
- sub: row.did,
22
+ did: row.did,
22
23
  parameters: fromJson(row.parameters),
23
24
  code: row.code,
24
25
  scope: row.scope,
@@ -60,7 +61,7 @@ export const createQB = (
60
61
  clientId: data.clientId,
61
62
  clientAuth: toJson(data.clientAuth),
62
63
  deviceId: data.deviceId,
63
- did: data.sub,
64
+ did: data.did,
64
65
  parameters: toJson(data.parameters),
65
66
  details: data.details ? toJson(data.details) : null,
66
67
  code: data.code,
@@ -80,7 +81,7 @@ export const findByQB = (
80
81
  db: AccountDb,
81
82
  search: {
82
83
  id?: number
83
- did?: string
84
+ did?: Did
84
85
  code?: Code
85
86
  tokenId?: TokenId
86
87
  currentRefreshToken?: RefreshToken
@@ -98,33 +99,38 @@ export const findByQB = (
98
99
  }
99
100
 
100
101
  return selectTokenInfoQB(db)
101
- .if(search.id !== undefined, (qb) =>
102
+ .$if(search.id !== undefined, (qb) =>
102
103
  // uses primary key index
103
104
  qb.where('token.id', '=', search.id!),
104
105
  )
105
- .if(search.did !== undefined, (qb) =>
106
+ .$if(search.did !== undefined, (qb) =>
106
107
  // uses "token_did_idx" index
107
108
  qb.where('token.did', '=', search.did!),
108
109
  )
109
- .if(search.code !== undefined, (qb) =>
110
+ .$if(search.code !== undefined, (qb) =>
110
111
  // uses "token_code_idx" partial index (hence the null check)
111
112
  qb
112
113
  .where('token.code', '=', search.code!)
113
114
  .where('token.code', 'is not', null),
114
115
  )
115
- .if(search.tokenId !== undefined, (qb) =>
116
+ .$if(search.tokenId !== undefined, (qb) =>
116
117
  // uses "token_token_id_idx"
117
118
  qb.where('token.tokenId', '=', search.tokenId!),
118
119
  )
119
- .if(search.currentRefreshToken !== undefined, (qb) =>
120
+ .$if(search.currentRefreshToken !== undefined, (qb) =>
120
121
  // uses "token_refresh_token_unique_idx"
121
122
  qb.where('token.currentRefreshToken', '=', search.currentRefreshToken!),
122
123
  )
123
124
  }
124
125
 
125
- export const removeByDidQB = (db: AccountDb, did: string) =>
126
- // uses "token_did_idx" index
127
- db.db.deleteFrom('token').where('did', '=', did)
126
+ export const removeByDid = async (db: AccountDb, did: Did) => {
127
+ await db.executeWithRetry(
128
+ db.db
129
+ .deleteFrom('token')
130
+ // uses "token_did_idx" index
131
+ .where('did', '=', did),
132
+ )
133
+ }
128
134
 
129
135
  export const rotateQB = (
130
136
  db: AccountDb,
@@ -2,14 +2,7 @@ import assert from 'node:assert'
2
2
  import { Client, createOp as createPlcOp } from '@did-plc/lib'
3
3
  import { Selectable } from 'kysely'
4
4
  import { Keypair, Secp256k1Keypair } from '@atproto/crypto'
5
- import {
6
- DidString,
7
- HandleString,
8
- asAtIdentifierString,
9
- getBlobCidString,
10
- isDidString,
11
- isHandleString,
12
- } from '@atproto/lex'
5
+ import { DidString, HandleString, getBlobCidString } from '@atproto/lex'
13
6
  import {
14
7
  Account,
15
8
  AccountStore,
@@ -18,10 +11,14 @@ import {
18
11
  AuthorizedClients,
19
12
  ClientId,
20
13
  Code,
14
+ DeactivateAccountData,
15
+ DeleteAccountConfirmInput,
16
+ DeleteAccountRequestInput,
21
17
  DeviceAccount,
22
18
  DeviceData,
23
19
  DeviceId,
24
20
  DeviceStore,
21
+ Did,
25
22
  FoundRequestResult,
26
23
  HandleUnavailableError,
27
24
  HandleUnavailableReason,
@@ -31,6 +28,7 @@ import {
31
28
  LexiconData,
32
29
  LexiconStore,
33
30
  NewTokenData,
31
+ ReactivateAccountData,
34
32
  RefreshToken,
35
33
  RequestData,
36
34
  RequestId,
@@ -38,7 +36,6 @@ import {
38
36
  ResetPasswordConfirmInput,
39
37
  ResetPasswordRequestInput,
40
38
  SignUpData,
41
- Sub,
42
39
  TokenData,
43
40
  TokenId,
44
41
  TokenInfo,
@@ -140,8 +137,6 @@ export class OAuthStore
140
137
  // @TODO Send an account creation confirmation email (+verification link) to the user (in their locale)
141
138
  // @NOTE Password strength & length already enforced by the OAuthProvider
142
139
 
143
- assert(isHandleString(handle), 'Handle must be a valid HandleString')
144
-
145
140
  await Promise.all([
146
141
  this.verifyEmailAvailability(email),
147
142
  this.verifyHandleAvailability(handle),
@@ -281,32 +276,28 @@ export class OAuthStore
281
276
  }
282
277
 
283
278
  async setAuthorizedClient(
284
- sub: Sub,
279
+ did: Did,
285
280
  clientId: ClientId,
286
281
  data: AuthorizedClientData,
287
282
  ): Promise<void> {
288
- await authorizedClientHelper.upsert(this.db, sub, clientId, data)
283
+ await authorizedClientHelper.upsert(this.db, did, clientId, data)
289
284
  }
290
285
 
291
- async getAccount(sub: Sub): Promise<{
286
+ async getAccount(did: Did): Promise<{
292
287
  account: Account
293
288
  authorizedClients: AuthorizedClients
294
289
  }> {
295
- const accountRow = await this.accountManager.getAccount(
296
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
297
- asAtIdentifierString(sub),
298
- {
299
- includeDeactivated: true,
300
- includeTakenDown: false,
301
- },
302
- )
290
+ const accountRow = await this.accountManager.getAccount(did, {
291
+ includeDeactivated: true,
292
+ includeTakenDown: false,
293
+ })
303
294
 
304
295
  assert(accountRow, 'Account not found')
305
296
 
306
297
  const account = await this.buildAccount(accountRow)
307
298
  const authorizedClients = await authorizedClientHelper.getAuthorizedClients(
308
299
  this.db,
309
- sub,
300
+ did,
310
301
  )
311
302
 
312
303
  return { account, authorizedClients }
@@ -320,10 +311,10 @@ export class OAuthStore
320
311
 
321
312
  async getDeviceAccount(
322
313
  deviceId: DeviceId,
323
- sub: string,
314
+ did: Did,
324
315
  ): Promise<DeviceAccount | null> {
325
316
  const row = await accountDeviceHelper
326
- .selectQB(this.db, { deviceId, sub })
317
+ .selectQB(this.db, { deviceId, did })
327
318
  .executeTakeFirst()
328
319
 
329
320
  if (!row) return null
@@ -334,30 +325,30 @@ export class OAuthStore
334
325
  account: await this.buildAccount(row),
335
326
  authorizedClients: await authorizedClientHelper.getAuthorizedClients(
336
327
  this.db,
337
- sub,
328
+ did,
338
329
  ),
339
330
  createdAt: fromDateISO(row.adCreatedAt),
340
331
  updatedAt: fromDateISO(row.adUpdatedAt),
341
332
  }
342
333
  }
343
334
 
344
- async removeDeviceAccount(deviceId: DeviceId, sub: Sub): Promise<void> {
335
+ async removeDeviceAccount(deviceId: DeviceId, did: Did): Promise<void> {
345
336
  await this.db.executeWithRetry(
346
- accountDeviceHelper.removeQB(this.db, deviceId, sub),
337
+ accountDeviceHelper.removeQB(this.db, deviceId, did),
347
338
  )
348
339
  }
349
340
 
350
341
  async listDeviceAccounts(
351
- filter: { sub: Sub } | { deviceId: DeviceId },
342
+ filter: { did: Did } | { deviceId: DeviceId },
352
343
  ): Promise<DeviceAccount[]> {
353
344
  const rows = await accountDeviceHelper.selectQB(this.db, filter).execute()
354
345
 
355
- const uniqueDids: string[] = [...new Set(rows.map((row) => row.did))]
346
+ const uniqueDids = [...new Set(rows.map((row) => row.did))]
356
347
 
357
348
  // Enrich all distinct account with their profile data
358
349
  const accounts = new Map(
359
350
  await Promise.all(
360
- Array.from(uniqueDids, async (did): Promise<[Sub, Account]> => {
351
+ Array.from(uniqueDids, async (did): Promise<[Did, Account]> => {
361
352
  const row = rows.find((r) => r.did === did)!
362
353
  return [did, await this.buildAccount(row)]
363
354
  }),
@@ -381,8 +372,8 @@ export class OAuthStore
381
372
  }
382
373
 
383
374
  async resetPasswordRequest({
384
- locale: _locale,
385
375
  email,
376
+ locale,
386
377
  }: ResetPasswordRequestInput): Promise<Account | null> {
387
378
  const account = await this.accountManager.getAccountByEmail(email, {
388
379
  includeDeactivated: true,
@@ -397,9 +388,8 @@ export class OAuthStore
397
388
  'reset_password',
398
389
  )
399
390
 
400
- // @TODO Use the locale to send the email in the right language
401
391
  await this.mailer.sendResetPassword(
402
- { handle, token },
392
+ { handle, token, locale },
403
393
  { to: account.email },
404
394
  )
405
395
 
@@ -557,8 +547,8 @@ export class OAuthStore
557
547
  })
558
548
  }
559
549
 
560
- async listAccountTokens(sub: Sub): Promise<TokenInfo[]> {
561
- const rows = await tokenHelper.findByQB(this.db, { did: sub }).execute()
550
+ async listAccountTokens(did: Did): Promise<TokenInfo[]> {
551
+ const rows = await tokenHelper.findByQB(this.db, { did }).execute()
562
552
  return Promise.all(rows.map((row) => this.toTokenInfo(row)))
563
553
  }
564
554
 
@@ -629,12 +619,9 @@ export class OAuthStore
629
619
  }
630
620
 
631
621
  async verifyEmailRequest({
632
- sub: did,
622
+ did,
633
623
  locale,
634
624
  }: VerifyEmailRequestInput): Promise<void> {
635
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
636
- assert(isDidString(did), 'sub must be a valid DID string')
637
-
638
625
  try {
639
626
  await this.accountManager.requestEmailConfirmation(did, { locale })
640
627
  } catch (err) {
@@ -647,13 +634,10 @@ export class OAuthStore
647
634
  }
648
635
 
649
636
  async verifyEmailConfirm({
650
- sub: did,
637
+ did,
651
638
  email,
652
639
  token,
653
640
  }: VerifyEmailConfirmInput): Promise<Account | null> {
654
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
655
- assert(isDidString(did), 'sub must be a valid DID string')
656
-
657
641
  try {
658
642
  const account = await this.accountManager.confirmEmail(did, email, token)
659
643
 
@@ -668,24 +652,18 @@ export class OAuthStore
668
652
  }
669
653
 
670
654
  async updateEmailRequest({
671
- sub: did,
655
+ did,
672
656
  locale,
673
657
  }: UpdateEmailRequestInput): Promise<UpdateEmailRequestOutput> {
674
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
675
- assert(isDidString(did), 'sub must be a valid DID string')
676
-
677
658
  return this.accountManager.requestEmailUpdate(did, { locale })
678
659
  }
679
660
 
680
661
  async updateEmailConfirm({
681
- sub: did,
662
+ did,
682
663
  token,
683
664
  email,
684
665
  locale,
685
666
  }: UpdateEmailConfirmInput): Promise<Account | null> {
686
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
687
- assert(isDidString(did), 'sub must be a valid DID string')
688
-
689
667
  try {
690
668
  const account = await this.accountManager.updateEmail(did, email, token, {
691
669
  sendConfirmationEmail: true,
@@ -702,10 +680,7 @@ export class OAuthStore
702
680
  }
703
681
  }
704
682
 
705
- async updateHandle({ sub: did, handle }: UpdateHandleData): Promise<Account> {
706
- // @TODO @atproto/oauth-provider should strongly type `Sub` as `DidString`
707
- assert(isDidString(did), 'sub must be a valid DID string')
708
-
683
+ async updateHandle({ did, handle }: UpdateHandleData): Promise<Account> {
709
684
  try {
710
685
  const account = await this.accountManager.updateHandle(did, handle)
711
686
 
@@ -715,6 +690,98 @@ export class OAuthStore
715
690
  }
716
691
  }
717
692
 
693
+ async deactivateAccount({ did }: DeactivateAccountData): Promise<Account> {
694
+ const { account } = await this.accountManager.deactivateAccount(did, {
695
+ deleteCredentials: true,
696
+ })
697
+
698
+ return this.buildAccount(account)
699
+ }
700
+
701
+ async reactivateAccount({ did }: ReactivateAccountData): Promise<Account> {
702
+ try {
703
+ const { account } = await this.accountManager.activateAccount(did)
704
+
705
+ return this.buildAccount(account)
706
+ } catch (err) {
707
+ if (err instanceof XrpcInvalidRequestError) {
708
+ throw new InvalidRequestError(err.message, err)
709
+ }
710
+
711
+ throw err
712
+ }
713
+ }
714
+
715
+ async deleteAccountRequest({
716
+ did,
717
+ locale,
718
+ }: DeleteAccountRequestInput): Promise<void> {
719
+ // Mirror the XRPC `com.atproto.server.requestAccountDelete` flow
720
+ // (no-entryway path): generate an email confirmation token and dispatch
721
+ // it to the account's email address.
722
+ const account = await this.accountManager.getAccount(did, {
723
+ includeDeactivated: true,
724
+ includeTakenDown: true,
725
+ })
726
+ if (!account) {
727
+ throw new InvalidRequestError('Account not found')
728
+ }
729
+ if (!account.email) {
730
+ throw new InvalidRequestError('Account does not have an email address')
731
+ }
732
+
733
+ const token = await this.accountManager.createEmailToken(
734
+ did,
735
+ 'delete_account',
736
+ )
737
+ await this.mailer.sendAccountDelete(
738
+ { token, locale },
739
+ { to: account.email },
740
+ )
741
+ }
742
+
743
+ async deleteAccountConfirm({
744
+ did,
745
+ token,
746
+ password,
747
+ }: DeleteAccountConfirmInput): Promise<void> {
748
+ // Mirror the XRPC `com.atproto.server.deleteAccount` flow (no-entryway
749
+ // path): verify the password, validate the email confirmation token,
750
+ // destroy the actor store, delete the account row, and emit the
751
+ // tombstone account event.
752
+ const account = await this.accountManager.getAccount(did, {
753
+ includeDeactivated: true,
754
+ includeTakenDown: true,
755
+ })
756
+ if (!account) {
757
+ throw new InvalidRequestError('Account not found')
758
+ }
759
+
760
+ const validPass = await this.accountManager.verifyAccountPassword(
761
+ did,
762
+ password,
763
+ )
764
+ if (!validPass) {
765
+ throw new InvalidCredentialsError('Invalid did or password', did)
766
+ }
767
+
768
+ await this.accountManager.assertValidEmailToken(
769
+ did,
770
+ 'delete_account',
771
+ token,
772
+ )
773
+
774
+ // @NOTE Order matters here: first "unlink" the account by removing it
775
+ // from the account manager database ("source of truth"), then notify the
776
+ // sequencer, and finally cleanup files from the file system.
777
+ await this.accountManager.deleteAccount(did)
778
+ try {
779
+ await this.sequencer.sequenceAccountDeletion(did)
780
+ } finally {
781
+ await this.actorStore.destroy(did)
782
+ }
783
+ }
784
+
718
785
  private async toTokenInfo(
719
786
  row: ActorAccount & Selectable<schemas.Token>,
720
787
  ): Promise<TokenInfo> {
@@ -728,15 +795,16 @@ export class OAuthStore
728
795
 
729
796
  private async buildAccount(row: ActorAccount): Promise<Account> {
730
797
  const account: Account = {
731
- sub: row.did,
732
- aud: this.serviceDid,
798
+ did: row.did,
799
+ pds: this.serviceDid,
733
800
  email: row.email || undefined,
734
- email_verified: row.email ? row.emailConfirmedAt != null : undefined,
735
- preferred_username: row.handle || undefined,
801
+ emailVerified: row.email ? row.emailConfirmedAt != null : undefined,
802
+ handle: row.handle || undefined,
803
+ deactivated: row.deactivatedAt != null,
736
804
  }
737
805
 
738
806
  if (!account.name || !account.picture) {
739
- const did = account.sub
807
+ const { did } = account
740
808
 
741
809
  const profile = await this.actorStore
742
810
  .read(did, async (store) => {
@@ -130,11 +130,14 @@ export class BlobReader {
130
130
  const { cursor, limit } = opts
131
131
  let builder = this.db.db
132
132
  .selectFrom('record_blob')
133
- .whereNotExists((qb) =>
134
- qb
135
- .selectFrom('blob')
136
- .selectAll()
137
- .whereRef('blob.cid', '=', 'record_blob.blobCid'),
133
+ .where(({ not, exists, selectFrom }) =>
134
+ not(
135
+ exists(
136
+ selectFrom('blob')
137
+ .selectAll()
138
+ .whereRef('blob.cid', '=', 'record_blob.blobCid'),
139
+ ),
140
+ ),
138
141
  )
139
142
  .selectAll()
140
143
  .orderBy('blobCid', 'asc')
@@ -88,7 +88,7 @@ export class RecordReader {
88
88
  .selectFrom('record')
89
89
  .innerJoin('repo_block', 'repo_block.cid', 'record.cid')
90
90
  .where('record.collection', '=', collection)
91
- .if(!includeSoftDeleted, (qb) =>
91
+ .$if(!includeSoftDeleted, (qb) =>
92
92
  qb.where(notSoftDeletedClause(ref('record'))),
93
93
  )
94
94
  .orderBy('record.rkey', reverse ? 'asc' : 'desc')
@@ -135,7 +135,7 @@ export class RecordReader {
135
135
  .innerJoin('repo_block', 'repo_block.cid', 'record.cid')
136
136
  .where('record.uri', '=', uri.toString())
137
137
  .selectAll()
138
- .if(!includeSoftDeleted, (qb) =>
138
+ .$if(!includeSoftDeleted, (qb) =>
139
139
  qb.where(notSoftDeletedClause(ref('record'))),
140
140
  )
141
141
  if (cid) {
@@ -162,7 +162,7 @@ export class RecordReader {
162
162
  .selectFrom('record')
163
163
  .select('uri')
164
164
  .where('record.uri', '=', uri.toString())
165
- .if(!includeSoftDeleted, (qb) =>
165
+ .$if(!includeSoftDeleted, (qb) =>
166
166
  qb.where(notSoftDeletedClause(ref('record'))),
167
167
  )
168
168
  if (cid) {
@@ -118,7 +118,7 @@ export class SqlRepoReader extends ReadableBlockstore {
118
118
  if (cursor) {
119
119
  // use this syntax to ensure we hit the index
120
120
  builder = builder.where(
121
- sql`((${ref('repoRev')}, ${ref('cid')}) < (${
121
+ sql<boolean>`((${ref('repoRev')}, ${ref('cid')}) < (${
122
122
  cursor.rev
123
123
  }, ${cursor.cid.toString()}))`,
124
124
  )
@@ -9,6 +9,13 @@ export default function (server: Server, ctx: AppContext) {
9
9
  auth: ctx.authVerifier.moderator,
10
10
  handler: async ({ input }) => {
11
11
  const { subject, takedown, deactivated } = input.body
12
+
13
+ if (takedown?.applied && deactivated != null && !deactivated.applied) {
14
+ throw new InvalidRequestError(
15
+ `Cannot activate and takedown an account at the same time`,
16
+ )
17
+ }
18
+
12
19
  if (takedown) {
13
20
  if (com.atproto.admin.defs.repoRef.$isTypeOf(subject)) {
14
21
  await ctx.accountManager.takedownAccount(subject.did, takedown)
@@ -32,16 +39,21 @@ export default function (server: Server, ctx: AppContext) {
32
39
  if (deactivated) {
33
40
  if (com.atproto.admin.defs.repoRef.$isTypeOf(subject)) {
34
41
  if (deactivated.applied) {
35
- await ctx.accountManager.deactivateAccount(subject.did, null)
42
+ await ctx.accountManager.deactivateAccount(subject.did)
36
43
  } else {
37
44
  await ctx.accountManager.activateAccount(subject.did)
38
45
  }
39
46
  }
40
47
  }
41
48
 
42
- if (com.atproto.admin.defs.repoRef.$isTypeOf(subject)) {
43
- const status = await ctx.accountManager.getAccountStatus(subject.did)
44
- await ctx.sequencer.sequenceAccount(subject.did, status)
49
+ // @NOTE accountManager will sequence an account status when updating the
50
+ // status, so we don't *need* to sequence the account status here.
51
+ // However, this endpoint historically has always sequenced the account
52
+ // status.
53
+ if (!takedown && !deactivated) {
54
+ if (com.atproto.admin.defs.repoRef.$isTypeOf(subject)) {
55
+ await ctx.accountManager.sequenceAccountStatus(subject.did)
56
+ }
45
57
  }
46
58
 
47
59
  return {
@@ -1,59 +1,37 @@
1
- import { INVALID_HANDLE } from '@atproto/syntax'
2
- import {
3
- ForbiddenError,
4
- InvalidRequestError,
5
- Server,
6
- } from '@atproto/xrpc-server'
1
+ import { ForbiddenError, Server } from '@atproto/xrpc-server'
7
2
  import { ACCESS_FULL } from '../../../../auth-scope.js'
8
3
  import { AppContext } from '../../../../context.js'
9
4
  import { com } from '../../../../lexicons/index.js'
10
- import { assertValidDidDocumentForService } from './util.js'
11
5
 
12
6
  export default function (server: Server, ctx: AppContext) {
13
- server.add(com.atproto.server.activateAccount, {
14
- auth: ctx.authVerifier.authorization({
15
- scopes: ACCESS_FULL,
16
- authorize: () => {
17
- throw new ForbiddenError(
18
- 'OAuth credentials are not supported for this endpoint',
19
- )
20
- },
21
- }),
22
- handler: async ({ req, auth }) => {
23
- // in the case of entryway, the full flow is activateAccount (PDS) -> activateAccount (Entryway) -> updateSubjectStatus(PDS)
24
- if (ctx.entrywayClient) {
25
- const { headers } = ctx.entrywayPassthruHeaders(req)
26
- await ctx.entrywayClient.xrpc(com.atproto.server.activateAccount, {
27
- headers,
28
- })
29
- return
30
- }
31
-
32
- const requester = auth.credentials.did
33
-
34
- await assertValidDidDocumentForService(ctx, requester)
35
-
36
- const account = await ctx.accountManager.getAccount(requester, {
37
- includeDeactivated: true,
38
- })
39
- if (!account) {
40
- throw new InvalidRequestError('user not found', 'AccountNotFound')
41
- }
42
-
43
- await ctx.accountManager.activateAccount(requester)
7
+ const { entrywayClient } = ctx
44
8
 
45
- const syncData = await ctx.actorStore.read(requester, (store) =>
46
- store.repo.getSyncEventData(),
47
- )
48
-
49
- // @NOTE: we're over-emitting for now for backwards compatibility, can reduce this in the future
50
- const status = await ctx.accountManager.getAccountStatus(requester)
51
- await ctx.sequencer.sequenceAccountActivation(
52
- requester,
53
- account.handle ?? INVALID_HANDLE,
54
- status,
55
- syncData,
9
+ const auth = ctx.authVerifier.authorization({
10
+ scopes: ACCESS_FULL,
11
+ authorize: () => {
12
+ throw new ForbiddenError(
13
+ 'OAuth credentials are not supported for this endpoint',
56
14
  )
57
15
  },
58
16
  })
17
+
18
+ if (entrywayClient) {
19
+ // in the case of entryway, the full flow is activateAccount (PDS) -> activateAccount (Entryway) -> updateSubjectStatus(PDS)
20
+ server.add(com.atproto.server.activateAccount, {
21
+ auth,
22
+ handler: async ({ req }) => {
23
+ const { headers } = ctx.entrywayPassthruHeaders(req)
24
+ await entrywayClient.xrpc(com.atproto.server.activateAccount, {
25
+ headers,
26
+ })
27
+ },
28
+ })
29
+ } else {
30
+ server.add(com.atproto.server.activateAccount, {
31
+ auth,
32
+ handler: async ({ auth }) => {
33
+ await ctx.accountManager.activateAccount(auth.credentials.did)
34
+ },
35
+ })
36
+ }
59
37
  }
@@ -32,13 +32,9 @@ export default function (server: Server, ctx: AppContext) {
32
32
  server.add(com.atproto.server.deactivateAccount, {
33
33
  auth,
34
34
  handler: async ({ input: { body }, auth }) => {
35
- const requester = auth.credentials.did
36
- await ctx.accountManager.deactivateAccount(
37
- requester,
38
- body.deleteAfter ?? null,
39
- )
40
- const status = await ctx.accountManager.getAccountStatus(requester)
41
- await ctx.sequencer.sequenceAccount(requester, status)
35
+ await ctx.accountManager.deactivateAccount(auth.credentials.did, {
36
+ deleteAfter: body.deleteAfter ?? null,
37
+ })
42
38
  },
43
39
  })
44
40
  }