@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,716 +0,0 @@
1
- import { IncomingHttpHeaders, ServerResponse } from 'node:http'
2
- import { PassThrough, Readable, finished } from 'node:stream'
3
- import { Request } from 'express'
4
- import { Agent, Dispatcher, Pool, interceptors } from 'undici'
5
- import {
6
- decodeStream,
7
- getServiceEndpoint,
8
- omit,
9
- streamToNodeBuffer,
10
- } from '@atproto/common'
11
- import { RpcPermissionMatch } from '@atproto/oauth-scopes'
12
- import {
13
- CatchallHandler,
14
- HandlerPipeThroughBuffer,
15
- HandlerPipeThroughStream,
16
- InternalServerError,
17
- InvalidRequestError,
18
- ResponseType,
19
- XRPCError as XRPCServerError,
20
- excludeErrorResult,
21
- parseReqNsid,
22
- } from '@atproto/xrpc-server'
23
- import { isUnicastIp, unicastLookup } from '@atproto-labs/fetch-node'
24
- import { buildProxiedContentEncoding } from '@atproto-labs/xrpc-utils'
25
- import { isAccessPrivileged } from './auth-scope.js'
26
- import { ProxyConfig } from './config/config.js'
27
- import { AppContext } from './context.js'
28
- import { chat, com, tools } from './lexicons/index.js'
29
- import { httpLogger } from './logger.js'
30
-
31
- export function buildProxyAgent(cfg: ProxyConfig): Dispatcher {
32
- const agent = new Agent({
33
- allowH2: cfg.allowHTTP2,
34
- headersTimeout: cfg.headersTimeout,
35
- maxResponseSize: cfg.maxResponseSize,
36
- bodyTimeout: cfg.bodyTimeout,
37
- factory: cfg.disableSsrfProtection
38
- ? undefined
39
- : (origin, opts) => {
40
- const { protocol, hostname } =
41
- origin instanceof URL ? origin : new URL(origin)
42
- if (protocol !== 'https:') {
43
- throw new Error(`Forbidden protocol "${protocol}"`)
44
- }
45
- if (isUnicastIp(hostname) === false) {
46
- throw new Error('Hostname resolved to non-unicast address')
47
- }
48
- return new Pool(origin, opts)
49
- },
50
- connect: {
51
- lookup: cfg.disableSsrfProtection ? undefined : unicastLookup,
52
- },
53
- })
54
-
55
- return agent.compose(
56
- cfg.maxRetries > 0
57
- ? interceptors.retry({
58
- statusCodes: [], // Only retry on socket errors
59
- methods: ['GET', 'HEAD'],
60
- maxRetries: cfg.maxRetries,
61
- })
62
- : (dispatch) => dispatch,
63
- )
64
- }
65
-
66
- export const proxyHandler = (ctx: AppContext): CatchallHandler => {
67
- const performAuth = ctx.authVerifier.authorization<RpcPermissionMatch>({
68
- authorize: (permissions, { params }) => permissions.assertRpc(params),
69
- })
70
-
71
- return async (req, res, next) => {
72
- // /!\ Hot path
73
- try {
74
- if (
75
- req.method !== 'GET' &&
76
- req.method !== 'HEAD' &&
77
- req.method !== 'POST'
78
- ) {
79
- throw new XRPCServerError(
80
- ResponseType.InvalidRequest,
81
- 'XRPC requests only supports GET and POST',
82
- )
83
- }
84
-
85
- const body = req.method === 'POST' ? req : undefined
86
- if (body != null && !body.readable) {
87
- // Body was already consumed by a previous middleware
88
- throw new InternalServerError('Request body is not readable')
89
- }
90
-
91
- const lxm = parseReqNsid(req)
92
- if (PROTECTED_METHODS.has(lxm)) {
93
- throw new InvalidRequestError('Bad token method', 'InvalidToken')
94
- }
95
-
96
- const {
97
- url: origin,
98
- did,
99
- serviceId,
100
- } = await parseProxyInfo(ctx, req, lxm)
101
- // Phase 1 of service auth updates: the scope check sees the combined
102
- // did#serviceId form (so OAuth callers' rpc:?aud=did#service scopes
103
- // match), while the outbound service-auth JWT keeps bare-DID aud
104
- // regardless of session type.
105
- const scopeAud = `${did}#${serviceId}`
106
- const tokenAud = did
107
-
108
- const authResult = await performAuth({
109
- req,
110
- res,
111
- params: { lxm, aud: scopeAud },
112
- })
113
-
114
- const { credentials } = excludeErrorResult(authResult)
115
-
116
- if (
117
- credentials.type === 'access' &&
118
- !isAccessPrivileged(credentials.scope) &&
119
- PRIVILEGED_METHODS.has(lxm)
120
- ) {
121
- throw new InvalidRequestError('Bad token method', 'InvalidToken')
122
- }
123
-
124
- const headers: IncomingHttpHeaders = {
125
- 'accept-encoding': req.headers['accept-encoding'] || 'identity',
126
- 'accept-language': req.headers['accept-language'],
127
- 'atproto-accept-labelers': req.headers['atproto-accept-labelers'],
128
- 'x-bsky-topics': req.headers['x-bsky-topics'],
129
-
130
- 'content-type': body && req.headers['content-type'],
131
- 'content-encoding': body && req.headers['content-encoding'],
132
- 'content-length': body && req.headers['content-length'],
133
-
134
- authorization: `Bearer ${await ctx.serviceAuthJwt(credentials.did, tokenAud, lxm)}`,
135
- }
136
-
137
- const dispatchOptions: Dispatcher.RequestOptions = {
138
- origin,
139
- method: req.method,
140
- path: req.originalUrl,
141
- body,
142
- headers,
143
- }
144
-
145
- await pipethroughStream(ctx, req, dispatchOptions, (upstream) => {
146
- res.status(upstream.statusCode)
147
-
148
- for (const [name, val] of responseHeaders(upstream.headers)) {
149
- res.setHeader(name, val)
150
- }
151
-
152
- // Note that we should not need to manually handle errors here (e.g. by
153
- // destroying the response), as the http server will handle them for us.
154
- res.on('error', logResponseError)
155
-
156
- // Tell undici to write the upstream response directly to the response
157
- return res
158
- })
159
- } catch (err) {
160
- next(err)
161
- }
162
- }
163
- }
164
-
165
- export type PipethroughOptions = {
166
- /**
167
- * Specify the issuer (requester) for service auth. If not provided, no
168
- * authorization headers will be added to the request.
169
- */
170
- iss?: string
171
-
172
- /**
173
- * Override the audience for service auth. If not provided, the audience will
174
- * be determined based on the proxy service.
175
- */
176
- aud?: string
177
-
178
- /**
179
- * Override the lexicon method for service auth. If not provided, the lexicon
180
- * method will be determined based on the request path.
181
- */
182
- lxm?: string
183
- }
184
-
185
- export async function pipethrough(
186
- ctx: AppContext,
187
- req: Request,
188
- options?: PipethroughOptions,
189
- ): Promise<
190
- HandlerPipeThroughStream & {
191
- stream: Readable
192
- headers: Record<string, string>
193
- encoding: string
194
- }
195
- > {
196
- if (req.method !== 'GET' && req.method !== 'HEAD') {
197
- // pipethrough() is used from within xrpcServer handlers, which means that
198
- // the request body either has been parsed or is a readable stream that has
199
- // been piped for decoding & size limiting. Because of this, forwarding the
200
- // request body requires re-encoding it. Since we currently do not use
201
- // pipethrough() with procedures, proxying of request body is not
202
- // implemented.
203
- throw new InternalServerError(
204
- `Proxying of ${req.method} requests is not supported`,
205
- )
206
- }
207
-
208
- const lxm = parseReqNsid(req)
209
-
210
- const { url: origin, did: aud } = await parseProxyInfo(ctx, req, lxm)
211
-
212
- const dispatchOptions: Dispatcher.RequestOptions = {
213
- origin,
214
- method: req.method,
215
- path: req.originalUrl,
216
- headers: {
217
- 'accept-language': req.headers['accept-language'],
218
- 'atproto-accept-labelers': req.headers['atproto-accept-labelers'],
219
- 'x-bsky-topics': req.headers['x-bsky-topics'],
220
-
221
- // Because we sometimes need to interpret the response (e.g. during
222
- // read-after-write, through asPipeThroughBuffer()), we need to ask the
223
- // upstream server for an encoding that both the requester and the PDS can
224
- // understand. Since we might have to do the decoding ourselves, we will
225
- // use our own preferences (and weight) to negotiate the encoding.
226
- 'accept-encoding': buildProxiedContentEncoding(
227
- req.headers['accept-encoding'],
228
- ctx.cfg.proxy.preferCompressed,
229
- ),
230
-
231
- authorization: options?.iss
232
- ? `Bearer ${await ctx.serviceAuthJwt(options.iss, options.aud ?? aud, options.lxm ?? lxm)}`
233
- : undefined,
234
- },
235
-
236
- // Use a high water mark to buffer more data while performing async
237
- // operations before this stream is consumed. This is especially useful
238
- // while processing read-after-write operations.
239
- highWaterMark: 2 * 65536, // twice the default (64KiB)
240
- }
241
-
242
- const { headers, body } = await pipethroughRequest(ctx, req, dispatchOptions)
243
-
244
- return {
245
- encoding: safeString(headers['content-type']) ?? 'application/json',
246
- headers: Object.fromEntries(responseHeaders(headers)),
247
- stream: body,
248
- }
249
- }
250
-
251
- // Request setup/formatting
252
- // -------------------
253
-
254
- export function computeProxyTo(
255
- ctx: AppContext,
256
- req: Request,
257
- lxm: string,
258
- ): string {
259
- const proxyToHeader = req.header('atproto-proxy')
260
- if (proxyToHeader) return proxyToHeader
261
-
262
- const service = defaultService(ctx, lxm)
263
- if (service.serviceInfo) {
264
- return `${service.serviceInfo.did}#${service.serviceId}`
265
- }
266
-
267
- throw new InvalidRequestError(`No service configured for ${lxm}`)
268
- }
269
-
270
- // Bare-DID portion of `proxyTo`, suitable as a service-auth JWT audience
271
- // (Phase 1 of service auth updates).
272
- export function bareDidFromProxyTo(proxyTo: string): string {
273
- const hashIndex = proxyTo.indexOf('#')
274
- return hashIndex === -1 ? proxyTo : proxyTo.slice(0, hashIndex)
275
- }
276
-
277
- export async function parseProxyInfo(
278
- ctx: AppContext,
279
- req: Request,
280
- lxm: string,
281
- ): Promise<{ url: string; did: string; serviceId: string }> {
282
- // /!\ Hot path
283
-
284
- const proxyToHeader = req.header('atproto-proxy')
285
- if (proxyToHeader) return parseProxyHeader(ctx, proxyToHeader)
286
-
287
- const { serviceId, serviceInfo } = defaultService(ctx, lxm)
288
- if (serviceInfo) return { ...serviceInfo, serviceId }
289
-
290
- throw new InvalidRequestError(`No service configured for ${lxm}`)
291
- }
292
-
293
- export const parseProxyHeader = async (
294
- // Using subset of AppContext for testing purposes
295
- ctx: Pick<AppContext, 'cfg' | 'idResolver'>,
296
- proxyTo: string,
297
- ): Promise<{ did: string; url: string; serviceId: string }> => {
298
- // /!\ Hot path
299
-
300
- const hashIndex = proxyTo.indexOf('#')
301
-
302
- if (hashIndex === 0) {
303
- throw new InvalidRequestError('no did specified in proxy header')
304
- }
305
-
306
- if (hashIndex === -1 || hashIndex === proxyTo.length - 1) {
307
- throw new InvalidRequestError('no service id specified in proxy header')
308
- }
309
-
310
- // More than one hash
311
- if (proxyTo.indexOf('#', hashIndex + 1) !== -1) {
312
- throw new InvalidRequestError('invalid proxy header format')
313
- }
314
-
315
- // Basic validation
316
- if (proxyTo.includes(' ')) {
317
- throw new InvalidRequestError('proxy header cannot contain spaces')
318
- }
319
-
320
- const did = proxyTo.slice(0, hashIndex)
321
- const serviceId = proxyTo.slice(hashIndex + 1)
322
-
323
- // Special case a configured appview, while still proxying correctly any other appview
324
- if (
325
- ctx.cfg.bskyAppView &&
326
- proxyTo === `${ctx.cfg.bskyAppView.did}#bsky_appview`
327
- ) {
328
- return { did, url: ctx.cfg.bskyAppView.url, serviceId }
329
- }
330
-
331
- const didDoc = await ctx.idResolver.did.resolve(did)
332
- if (!didDoc) {
333
- throw new InvalidRequestError('could not resolve proxy did')
334
- }
335
-
336
- const url = getServiceEndpoint(didDoc, { id: `#${serviceId}` })
337
- if (!url) {
338
- throw new InvalidRequestError('could not resolve proxy did service url')
339
- }
340
-
341
- return { did, url, serviceId }
342
- }
343
-
344
- /**
345
- * Utility function that wraps the undici stream() function and handles request
346
- * and response errors by wrapping them in XRPCError instances. This function is
347
- * more efficient than "pipethroughRequest" when a writable stream to pipe the
348
- * upstream response to is available.
349
- */
350
- async function pipethroughStream(
351
- ctx: AppContext,
352
- req: Request,
353
- dispatchOptions: Dispatcher.RequestOptions,
354
- successStreamFactory: Dispatcher.StreamFactory,
355
- ): Promise<void> {
356
- return new Promise<void>((resolve, reject) => {
357
- void ctx.proxyAgent
358
- .stream(dispatchOptions, (upstream) => {
359
- if (upstream.statusCode >= 400) {
360
- const passThrough = new PassThrough()
361
-
362
- void tryParsingError(upstream.headers, passThrough).then((parsed) => {
363
- const xrpcError = new PipethroughUpstreamError(upstream, parsed, {
364
- cause: dispatchOptions,
365
- })
366
-
367
- reject(xrpcError)
368
- }, reject)
369
-
370
- return passThrough
371
- }
372
-
373
- const writable = successStreamFactory(upstream)
374
-
375
- // As soon as the control was passed to the writable stream (i.e. by
376
- // returning the writable hereafter), pipethroughStream() is considered
377
- // to have succeeded. Any error occurring while writing upstream data to
378
- // the writable stream should be handled through the stream's error
379
- // state (i.e. successStreamFactory() must ensure that error events on
380
- // the returned writable will be handled).
381
- resolve()
382
-
383
- return writable
384
- })
385
- // The following catch block will be triggered with either network errors
386
- // or writable stream errors. In the latter case, the promise will already
387
- // be resolved, and reject()ing it there after will have no effect. Those
388
- // error would still be logged by the successStreamFactory() function.
389
- .catch(handleUpstreamRequestError.bind(req))
390
- .catch(reject)
391
- })
392
- }
393
-
394
- /**
395
- * Utility function that wraps the undici request() function and handles request
396
- * and response errors by wrapping them in XRPCError instances.
397
- */
398
- async function pipethroughRequest(
399
- ctx: AppContext,
400
- req: Request,
401
- dispatchOptions: Dispatcher.RequestOptions,
402
- ) {
403
- // HandlerPipeThroughStream requires a readable stream to be returned, so we
404
- // use the (less efficient) request() function instead.
405
-
406
- const upstream = await ctx.proxyAgent
407
- .request(dispatchOptions)
408
- .catch(handleUpstreamRequestError.bind(req))
409
-
410
- if (upstream.statusCode >= 400) {
411
- const parsed = await tryParsingError(upstream.headers, upstream.body)
412
-
413
- throw new PipethroughUpstreamError(upstream, parsed, {
414
- cause: dispatchOptions,
415
- })
416
- }
417
-
418
- return upstream
419
- }
420
-
421
- function handleUpstreamRequestError(
422
- this: Request,
423
- err: unknown,
424
- message = 'Upstream service unreachable',
425
- ): never {
426
- const logger = isPinoHttpRequest(this) ? this.log : httpLogger
427
- logger.error({ err }, message)
428
- throw new XRPCServerError(ResponseType.UpstreamFailure, message, undefined, {
429
- cause: err,
430
- })
431
- }
432
-
433
- function isPinoHttpRequest(req: Request): req is Request & {
434
- log: { error: (obj: unknown, msg: string) => void }
435
- } {
436
- return typeof (req as { log?: any }).log?.error === 'function'
437
- }
438
-
439
- // Request parsing/forwarding
440
- // -------------------
441
-
442
- export function isJsonContentType(contentType?: string): boolean | undefined {
443
- if (!contentType) return undefined
444
- return /application\/(?:\w+\+)?json/i.test(contentType)
445
- }
446
-
447
- async function tryParsingError(
448
- headers: IncomingHttpHeaders,
449
- readable: Readable,
450
- ): Promise<{ error?: string; message?: string }> {
451
- if (isJsonContentType(headers['content-type']) === false) {
452
- // We don't known how to parse non JSON content types so we can discard the
453
- // whole response.
454
-
455
- // Since we don't care about the response, we would normally just destroy
456
- // the stream. However, if the underlying HTTP connection is an HTTP/1.1
457
- // connection, this also destroys the underlying (keep-alive) TCP socket. In
458
- // order to avoid destroying the TCP socket, while avoiding the cost of
459
- // consuming too much IO, we give it a chance to finish first.
460
-
461
- // @NOTE we need to listen (and ignore) "error" events, otherwise the
462
- // process could crash (since we drain the stream asynchronously here). This
463
- // is performed through the "finished" call below.
464
-
465
- const to = setTimeout(() => {
466
- readable.destroy()
467
- }, 100)
468
- finished(readable, (_err) => {
469
- clearTimeout(to)
470
- })
471
- readable.resume()
472
-
473
- return {}
474
- }
475
-
476
- try {
477
- const buffer = await bufferUpstreamResponse(
478
- readable,
479
- headers['content-encoding'],
480
- )
481
-
482
- const errInfo: unknown = JSON.parse(buffer.toString('utf8'))
483
- return {
484
- error: safeString(errInfo?.['error']),
485
- message: safeString(errInfo?.['message']),
486
- }
487
- } catch (err) {
488
- // Failed to read, decode, buffer or parse. No big deal.
489
- return {}
490
- }
491
- }
492
-
493
- async function bufferUpstreamResponse(
494
- readable: Readable,
495
- contentEncoding?: string | string[],
496
- ): Promise<Buffer> {
497
- try {
498
- return await streamToNodeBuffer(decodeStream(readable, contentEncoding))
499
- } catch (err) {
500
- if (!readable.destroyed) readable.destroy()
501
-
502
- throw new XRPCServerError(
503
- ResponseType.UpstreamFailure,
504
- err instanceof TypeError ? err.message : 'unable to decode request body',
505
- undefined,
506
- { cause: err },
507
- )
508
- }
509
- }
510
-
511
- export async function asPipeThroughBuffer(
512
- input: HandlerPipeThroughStream,
513
- ): Promise<HandlerPipeThroughBuffer> {
514
- return {
515
- buffer: await bufferUpstreamResponse(
516
- input.stream,
517
- input.headers?.['content-encoding'],
518
- ),
519
- headers: omit(input.headers, ['content-encoding', 'content-length']),
520
- encoding: input.encoding,
521
- }
522
- }
523
-
524
- // Response parsing/forwarding
525
- // -------------------
526
-
527
- const RES_HEADERS_TO_FORWARD = [
528
- 'atproto-repo-rev',
529
- 'atproto-content-labelers',
530
- 'retry-after',
531
- ]
532
-
533
- function* responseHeaders(
534
- headers: IncomingHttpHeaders,
535
- includeContentHeaders = true,
536
- ): Generator<[string, string]> {
537
- if (includeContentHeaders) {
538
- const length = headers['content-length']
539
- if (length) yield ['content-length', length]
540
-
541
- const encoding = headers['content-encoding']
542
- if (encoding) yield ['content-encoding', encoding]
543
-
544
- const type = headers['content-type']
545
- if (type) yield ['content-type', type]
546
-
547
- const language = headers['content-language']
548
- if (language) yield ['content-language', language]
549
- }
550
-
551
- for (let i = 0; i < RES_HEADERS_TO_FORWARD.length; i++) {
552
- const name = RES_HEADERS_TO_FORWARD[i]
553
- const val = headers[name]
554
-
555
- if (val != null) {
556
- const value: string = Array.isArray(val) ? val.join(',') : val
557
- yield [name, value]
558
- }
559
- }
560
- }
561
-
562
- // Utils
563
- // -------------------
564
-
565
- /**
566
- * Performs lexicon method matching on a set of methods,
567
- * taking into account that they are treated case-insensitively.
568
- */
569
- export class LxmSet {
570
- private inner: Set<string>
571
- private original: Iterable<string>
572
- constructor(items: Iterable<string>) {
573
- this.inner = new Set(Array.from(items, normalizeLxm))
574
- this.original = items
575
- }
576
- has(lxm: string) {
577
- return this.inner.has(normalizeLxm(lxm))
578
- }
579
- *[Symbol.iterator](): Iterator<string> {
580
- yield* this.original
581
- }
582
- }
583
-
584
- export function normalizeLxm(lxm: string) {
585
- return lxm.toLowerCase()
586
- }
587
-
588
- export const CHAT_BSKY_METHODS = new LxmSet([
589
- chat.bsky.actor.deleteAccount.$lxm,
590
- chat.bsky.actor.exportAccountData.$lxm,
591
- chat.bsky.convo.deleteMessageForSelf.$lxm,
592
- chat.bsky.convo.getConvo.$lxm,
593
- chat.bsky.convo.getConvoForMembers.$lxm,
594
- chat.bsky.convo.getLog.$lxm,
595
- chat.bsky.convo.getMessages.$lxm,
596
- chat.bsky.convo.leaveConvo.$lxm,
597
- chat.bsky.convo.listConvos.$lxm,
598
- chat.bsky.convo.muteConvo.$lxm,
599
- chat.bsky.convo.sendMessage.$lxm,
600
- chat.bsky.convo.sendMessageBatch.$lxm,
601
- chat.bsky.convo.unmuteConvo.$lxm,
602
- chat.bsky.convo.updateRead.$lxm,
603
- ])
604
-
605
- export const PRIVILEGED_METHODS = new LxmSet([
606
- ...CHAT_BSKY_METHODS,
607
- com.atproto.server.createAccount.$lxm,
608
- ])
609
-
610
- // These endpoints are related to account management and must be used directly,
611
- // not proxied or service-authed. Service auth may be utilized between PDS and
612
- // entryway for these methods.
613
- export const PROTECTED_METHODS = new LxmSet([
614
- com.atproto.admin.sendEmail.$lxm,
615
- com.atproto.identity.requestPlcOperationSignature.$lxm,
616
- com.atproto.identity.signPlcOperation.$lxm,
617
- com.atproto.identity.updateHandle.$lxm,
618
- com.atproto.server.activateAccount.$lxm,
619
- com.atproto.server.confirmEmail.$lxm,
620
- com.atproto.server.createAppPassword.$lxm,
621
- com.atproto.server.deactivateAccount.$lxm,
622
- com.atproto.server.getAccountInviteCodes.$lxm,
623
- com.atproto.server.getSession.$lxm,
624
- com.atproto.server.listAppPasswords.$lxm,
625
- com.atproto.server.requestAccountDelete.$lxm,
626
- com.atproto.server.requestEmailConfirmation.$lxm,
627
- com.atproto.server.requestEmailUpdate.$lxm,
628
- com.atproto.server.revokeAppPassword.$lxm,
629
- com.atproto.server.updateEmail.$lxm,
630
- ])
631
-
632
- const defaultService = (
633
- ctx: AppContext,
634
- nsid: string,
635
- ): {
636
- serviceId: string
637
- serviceInfo: { url: string; did: string } | null
638
- } => {
639
- switch (nsid) {
640
- case tools.ozone.communication.createTemplate.$lxm:
641
- case tools.ozone.communication.deleteTemplate.$lxm:
642
- case tools.ozone.communication.listTemplates.$lxm:
643
- case tools.ozone.communication.updateTemplate.$lxm:
644
- case tools.ozone.moderation.cancelScheduledActions.$lxm:
645
- case tools.ozone.moderation.emitEvent.$lxm:
646
- case tools.ozone.moderation.getAccountTimeline.$lxm:
647
- case tools.ozone.moderation.getEvent.$lxm:
648
- case tools.ozone.moderation.getRecord.$lxm:
649
- case tools.ozone.moderation.getRepo.$lxm:
650
- case tools.ozone.moderation.listScheduledActions.$lxm:
651
- case tools.ozone.moderation.queryEvents.$lxm:
652
- case tools.ozone.moderation.queryStatuses.$lxm:
653
- case tools.ozone.moderation.scheduleAction.$lxm:
654
- case tools.ozone.moderation.searchRepos.$lxm:
655
- case tools.ozone.safelink.addRule.$lxm:
656
- case tools.ozone.safelink.queryEvents.$lxm:
657
- case tools.ozone.safelink.queryRules.$lxm:
658
- case tools.ozone.safelink.removeRule.$lxm:
659
- case tools.ozone.safelink.updateRule.$lxm:
660
- case tools.ozone.team.addMember.$lxm:
661
- case tools.ozone.team.deleteMember.$lxm:
662
- case tools.ozone.team.listMembers.$lxm:
663
- case tools.ozone.team.updateMember.$lxm:
664
- case tools.ozone.verification.grantVerifications.$lxm:
665
- case tools.ozone.verification.listVerifications.$lxm:
666
- case tools.ozone.verification.revokeVerifications.$lxm:
667
- return {
668
- serviceId: 'atproto_labeler',
669
- serviceInfo: ctx.cfg.modService,
670
- }
671
- case com.atproto.moderation.createReport.$lxm:
672
- return {
673
- serviceId: 'atproto_labeler',
674
- serviceInfo: ctx.cfg.reportService,
675
- }
676
- default:
677
- return {
678
- serviceId: 'bsky_appview',
679
- serviceInfo: ctx.cfg.bskyAppView,
680
- }
681
- }
682
- }
683
-
684
- const safeString = (str: unknown): string | undefined => {
685
- return typeof str === 'string' ? str : undefined
686
- }
687
-
688
- function logResponseError(this: ServerResponse, err: unknown): void {
689
- httpLogger.warn({ err }, 'error forwarding upstream response')
690
- }
691
-
692
- export class PipethroughUpstreamError extends XRPCServerError {
693
- constructor(
694
- readonly upstream: {
695
- statusCode: number
696
- headers: IncomingHttpHeaders
697
- },
698
- payload: { message?: string; error?: string },
699
- options?: ErrorOptions,
700
- ) {
701
- const status =
702
- upstream.statusCode === 500
703
- ? ResponseType.UpstreamFailure
704
- : upstream.statusCode
705
-
706
- super(status, payload.message, payload.error, options)
707
- }
708
-
709
- get headers(): Record<string, string> {
710
- return Object.fromEntries(responseHeaders(this.upstream.headers, false))
711
- }
712
-
713
- get error() {
714
- return this.customErrorName ?? this.typeName
715
- }
716
- }