@atproto/pds 0.5.11 → 0.5.14

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 (322) hide show
  1. package/CHANGELOG.md +46 -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/package.json +42 -38
  10. package/build.templates.cjs +0 -22
  11. package/example.env +0 -42
  12. package/jest.config.cjs +0 -27
  13. package/src/account-manager/account-manager.ts +0 -916
  14. package/src/account-manager/db/index.ts +0 -21
  15. package/src/account-manager/db/migrations/001-init.ts +0 -115
  16. package/src/account-manager/db/migrations/002-account-deactivation.ts +0 -17
  17. package/src/account-manager/db/migrations/003-privileged-app-passwords.ts +0 -12
  18. package/src/account-manager/db/migrations/004-oauth.ts +0 -122
  19. package/src/account-manager/db/migrations/005-oauth-account-management.ts +0 -136
  20. package/src/account-manager/db/migrations/006-oauth-permission-sets.ts +0 -20
  21. package/src/account-manager/db/migrations/007-lexicon-failures-index.ts +0 -14
  22. package/src/account-manager/db/migrations/index.ts +0 -17
  23. package/src/account-manager/db/schema/account-device.ts +0 -14
  24. package/src/account-manager/db/schema/account.ts +0 -16
  25. package/src/account-manager/db/schema/actor.ts +0 -17
  26. package/src/account-manager/db/schema/app-password.ts +0 -13
  27. package/src/account-manager/db/schema/authorization-request.ts +0 -30
  28. package/src/account-manager/db/schema/authorized-client.ts +0 -23
  29. package/src/account-manager/db/schema/device.ts +0 -18
  30. package/src/account-manager/db/schema/email-token.ts +0 -19
  31. package/src/account-manager/db/schema/index.ts +0 -43
  32. package/src/account-manager/db/schema/invite-code.ts +0 -24
  33. package/src/account-manager/db/schema/lexicon.ts +0 -15
  34. package/src/account-manager/db/schema/refresh-token.ts +0 -11
  35. package/src/account-manager/db/schema/repo-root.ts +0 -12
  36. package/src/account-manager/db/schema/token.ts +0 -38
  37. package/src/account-manager/db/schema/used-refresh-token.ts +0 -13
  38. package/src/account-manager/helpers/account-device.ts +0 -63
  39. package/src/account-manager/helpers/account.ts +0 -355
  40. package/src/account-manager/helpers/auth.ts +0 -222
  41. package/src/account-manager/helpers/authorization-request.ts +0 -90
  42. package/src/account-manager/helpers/authorized-client.ts +0 -75
  43. package/src/account-manager/helpers/device.ts +0 -47
  44. package/src/account-manager/helpers/email-token.ts +0 -87
  45. package/src/account-manager/helpers/invite.ts +0 -263
  46. package/src/account-manager/helpers/lexicon.ts +0 -67
  47. package/src/account-manager/helpers/password.ts +0 -136
  48. package/src/account-manager/helpers/repo.ts +0 -24
  49. package/src/account-manager/helpers/scrypt.ts +0 -53
  50. package/src/account-manager/helpers/token.ts +0 -158
  51. package/src/account-manager/helpers/used-refresh-token.ts +0 -30
  52. package/src/account-manager/oauth-store.ts +0 -880
  53. package/src/account-manager/scope-reference-getter.ts +0 -106
  54. package/src/actor-store/actor-store-reader.ts +0 -45
  55. package/src/actor-store/actor-store-resources.ts +0 -8
  56. package/src/actor-store/actor-store-transactor.ts +0 -31
  57. package/src/actor-store/actor-store-writer.ts +0 -17
  58. package/src/actor-store/actor-store.ts +0 -210
  59. package/src/actor-store/blob/reader.ts +0 -160
  60. package/src/actor-store/blob/transactor.ts +0 -383
  61. package/src/actor-store/db/index.ts +0 -20
  62. package/src/actor-store/db/migrations/001-init.ts +0 -105
  63. package/src/actor-store/db/migrations/index.ts +0 -5
  64. package/src/actor-store/db/schema/account-pref.ts +0 -11
  65. package/src/actor-store/db/schema/backlink.ts +0 -9
  66. package/src/actor-store/db/schema/blob.ts +0 -14
  67. package/src/actor-store/db/schema/index.ts +0 -23
  68. package/src/actor-store/db/schema/record-blob.ts +0 -8
  69. package/src/actor-store/db/schema/record.ts +0 -14
  70. package/src/actor-store/db/schema/repo-block.ts +0 -10
  71. package/src/actor-store/db/schema/repo-root.ts +0 -10
  72. package/src/actor-store/migrate.ts +0 -39
  73. package/src/actor-store/preference/reader.ts +0 -48
  74. package/src/actor-store/preference/transactor.ts +0 -55
  75. package/src/actor-store/preference/util.ts +0 -39
  76. package/src/actor-store/record/reader.ts +0 -374
  77. package/src/actor-store/record/transactor.ts +0 -111
  78. package/src/actor-store/repo/reader.ts +0 -31
  79. package/src/actor-store/repo/sql-repo-reader.ts +0 -150
  80. package/src/actor-store/repo/sql-repo-transactor.ts +0 -105
  81. package/src/actor-store/repo/transactor.ts +0 -227
  82. package/src/api/app/bsky/actor/getPreferences.ts +0 -57
  83. package/src/api/app/bsky/actor/getProfile.ts +0 -41
  84. package/src/api/app/bsky/actor/getProfiles.ts +0 -51
  85. package/src/api/app/bsky/actor/index.ts +0 -13
  86. package/src/api/app/bsky/actor/putPreferences.ts +0 -62
  87. package/src/api/app/bsky/feed/getActorLikes.ts +0 -63
  88. package/src/api/app/bsky/feed/getAuthorFeed.ts +0 -84
  89. package/src/api/app/bsky/feed/getFeed.ts +0 -47
  90. package/src/api/app/bsky/feed/getPostThread.ts +0 -251
  91. package/src/api/app/bsky/feed/getTimeline.ts +0 -50
  92. package/src/api/app/bsky/feed/index.ts +0 -15
  93. package/src/api/app/bsky/index.ts +0 -11
  94. package/src/api/app/bsky/notification/index.ts +0 -7
  95. package/src/api/app/bsky/notification/registerPush.ts +0 -71
  96. package/src/api/app/bsky/util/resolver.ts +0 -20
  97. package/src/api/com/atproto/admin/deleteAccount.ts +0 -22
  98. package/src/api/com/atproto/admin/disableAccountInvites.ts +0 -18
  99. package/src/api/com/atproto/admin/disableInviteCodes.ts +0 -21
  100. package/src/api/com/atproto/admin/enableAccountInvites.ts +0 -18
  101. package/src/api/com/atproto/admin/getAccountInfo.ts +0 -32
  102. package/src/api/com/atproto/admin/getAccountInfos.ts +0 -34
  103. package/src/api/com/atproto/admin/getInviteCodes.ts +0 -126
  104. package/src/api/com/atproto/admin/getSubjectStatus.ts +0 -76
  105. package/src/api/com/atproto/admin/index.ts +0 -31
  106. package/src/api/com/atproto/admin/sendEmail.ts +0 -47
  107. package/src/api/com/atproto/admin/updateAccountEmail.ts +0 -32
  108. package/src/api/com/atproto/admin/updateAccountHandle.ts +0 -47
  109. package/src/api/com/atproto/admin/updateAccountPassword.ts +0 -34
  110. package/src/api/com/atproto/admin/updateSubjectStatus.ts +0 -68
  111. package/src/api/com/atproto/admin/util.ts +0 -66
  112. package/src/api/com/atproto/identity/getRecommendedDidCredentials.ts +0 -50
  113. package/src/api/com/atproto/identity/index.ts +0 -17
  114. package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +0 -59
  115. package/src/api/com/atproto/identity/resolveHandle.ts +0 -50
  116. package/src/api/com/atproto/identity/signPlcOperation.ts +0 -85
  117. package/src/api/com/atproto/identity/submitPlcOperation.ts +0 -65
  118. package/src/api/com/atproto/identity/updateHandle.ts +0 -66
  119. package/src/api/com/atproto/index.ts +0 -19
  120. package/src/api/com/atproto/moderation/createReport.ts +0 -41
  121. package/src/api/com/atproto/moderation/index.ts +0 -7
  122. package/src/api/com/atproto/repo/applyWrites.ts +0 -226
  123. package/src/api/com/atproto/repo/createRecord.ts +0 -143
  124. package/src/api/com/atproto/repo/deleteRecord.ts +0 -122
  125. package/src/api/com/atproto/repo/describeRepo.ts +0 -43
  126. package/src/api/com/atproto/repo/getRecord.ts +0 -40
  127. package/src/api/com/atproto/repo/importRepo.ts +0 -101
  128. package/src/api/com/atproto/repo/index.ts +0 -25
  129. package/src/api/com/atproto/repo/listMissingBlobs.ts +0 -29
  130. package/src/api/com/atproto/repo/listRecords.ts +0 -36
  131. package/src/api/com/atproto/repo/putRecord.ts +0 -211
  132. package/src/api/com/atproto/repo/uploadBlob.ts +0 -60
  133. package/src/api/com/atproto/server/activateAccount.ts +0 -37
  134. package/src/api/com/atproto/server/checkAccountStatus.ts +0 -51
  135. package/src/api/com/atproto/server/confirmEmail.ts +0 -39
  136. package/src/api/com/atproto/server/createAccount.ts +0 -327
  137. package/src/api/com/atproto/server/createAppPassword.ts +0 -53
  138. package/src/api/com/atproto/server/createInviteCode.ts +0 -38
  139. package/src/api/com/atproto/server/createInviteCodes.ts +0 -42
  140. package/src/api/com/atproto/server/createSession.ts +0 -97
  141. package/src/api/com/atproto/server/deactivateAccount.ts +0 -41
  142. package/src/api/com/atproto/server/deleteAccount.ts +0 -85
  143. package/src/api/com/atproto/server/deleteSession.ts +0 -23
  144. package/src/api/com/atproto/server/describeServer.ts +0 -29
  145. package/src/api/com/atproto/server/getAccountInviteCodes.ts +0 -142
  146. package/src/api/com/atproto/server/getServiceAuth.ts +0 -111
  147. package/src/api/com/atproto/server/getSession.ts +0 -80
  148. package/src/api/com/atproto/server/index.ts +0 -55
  149. package/src/api/com/atproto/server/listAppPasswords.ts +0 -45
  150. package/src/api/com/atproto/server/refreshSession.ts +0 -72
  151. package/src/api/com/atproto/server/requestAccountDelete.ts +0 -66
  152. package/src/api/com/atproto/server/requestEmailConfirmation.ts +0 -68
  153. package/src/api/com/atproto/server/requestEmailUpdate.ts +0 -86
  154. package/src/api/com/atproto/server/requestPasswordReset.ts +0 -52
  155. package/src/api/com/atproto/server/reserveSigningKey.ts +0 -15
  156. package/src/api/com/atproto/server/resetPassword.ts +0 -50
  157. package/src/api/com/atproto/server/revokeAppPassword.ts +0 -42
  158. package/src/api/com/atproto/server/updateEmail.ts +0 -52
  159. package/src/api/com/atproto/server/util.ts +0 -136
  160. package/src/api/com/atproto/sync/deprecated/getCheckout.ts +0 -27
  161. package/src/api/com/atproto/sync/deprecated/getHead.ts +0 -33
  162. package/src/api/com/atproto/sync/getBlob.ts +0 -61
  163. package/src/api/com/atproto/sync/getBlocks.ts +0 -37
  164. package/src/api/com/atproto/sync/getLatestCommit.ts +0 -33
  165. package/src/api/com/atproto/sync/getRecord.ts +0 -49
  166. package/src/api/com/atproto/sync/getRepo.ts +0 -75
  167. package/src/api/com/atproto/sync/getRepoStatus.ts +0 -34
  168. package/src/api/com/atproto/sync/index.ts +0 -27
  169. package/src/api/com/atproto/sync/listBlobs.ts +0 -33
  170. package/src/api/com/atproto/sync/listRepos.ts +0 -74
  171. package/src/api/com/atproto/sync/subscribeRepos.ts +0 -73
  172. package/src/api/com/atproto/sync/util.ts +0 -37
  173. package/src/api/com/atproto/temp/checkSignupQueue.ts +0 -34
  174. package/src/api/com/atproto/temp/index.ts +0 -7
  175. package/src/api/index.ts +0 -10
  176. package/src/api/proxy.ts +0 -44
  177. package/src/app-view.ts +0 -24
  178. package/src/auth-output.ts +0 -52
  179. package/src/auth-routes.ts +0 -64
  180. package/src/auth-scope.ts +0 -40
  181. package/src/auth-verifier.ts +0 -668
  182. package/src/background.ts +0 -65
  183. package/src/basic-routes.ts +0 -52
  184. package/src/bsky-app-view.ts +0 -33
  185. package/src/config/config.ts +0 -553
  186. package/src/config/env.ts +0 -167
  187. package/src/config/index.ts +0 -3
  188. package/src/config/secrets.ts +0 -54
  189. package/src/context.ts +0 -523
  190. package/src/crawlers.ts +0 -46
  191. package/src/db/cast.ts +0 -52
  192. package/src/db/db.ts +0 -140
  193. package/src/db/index.ts +0 -4
  194. package/src/db/migrator.ts +0 -40
  195. package/src/db/pagination.ts +0 -132
  196. package/src/db/tables/moderation.ts +0 -80
  197. package/src/db/util.ts +0 -108
  198. package/src/did-cache/db/index.ts +0 -21
  199. package/src/did-cache/db/migrations.ts +0 -17
  200. package/src/did-cache/db/schema.ts +0 -9
  201. package/src/did-cache/index.ts +0 -131
  202. package/src/disk-blobstore.ts +0 -172
  203. package/src/error.ts +0 -19
  204. package/src/handle/explicit-slurs.ts +0 -22
  205. package/src/handle/index.ts +0 -49
  206. package/src/handle/reserved.ts +0 -1059
  207. package/src/image/image-url-builder.ts +0 -16
  208. package/src/index.ts +0 -189
  209. package/src/lexicons.ts +0 -4
  210. package/src/logger.ts +0 -35
  211. package/src/mailer/index.ts +0 -111
  212. package/src/mailer/moderation.ts +0 -33
  213. package/src/mailer/templates/confirm-email.d.ts +0 -4
  214. package/src/mailer/templates/confirm-email.hbs +0 -158
  215. package/src/mailer/templates/delete-account.d.ts +0 -4
  216. package/src/mailer/templates/delete-account.hbs +0 -156
  217. package/src/mailer/templates/plc-operation.d.ts +0 -4
  218. package/src/mailer/templates/plc-operation.hbs +0 -153
  219. package/src/mailer/templates/reset-password.d.ts +0 -4
  220. package/src/mailer/templates/reset-password.hbs +0 -154
  221. package/src/mailer/templates/update-email.d.ts +0 -4
  222. package/src/mailer/templates/update-email.hbs +0 -153
  223. package/src/mailer/templates.ts +0 -17
  224. package/src/pipethrough.ts +0 -716
  225. package/src/rate-limits.ts +0 -59
  226. package/src/read-after-write/index.ts +0 -3
  227. package/src/read-after-write/types.ts +0 -35
  228. package/src/read-after-write/util.ts +0 -101
  229. package/src/read-after-write/viewer.ts +0 -290
  230. package/src/redis.ts +0 -25
  231. package/src/repo/index.ts +0 -2
  232. package/src/repo/prepare.ts +0 -266
  233. package/src/repo/types.ts +0 -65
  234. package/src/scripts/README.md +0 -40
  235. package/src/scripts/index.ts +0 -20
  236. package/src/scripts/publish-identity.ts +0 -60
  237. package/src/scripts/rebuild-repo.ts +0 -119
  238. package/src/scripts/rotate-keys.ts +0 -146
  239. package/src/scripts/sequencer-recovery/index.ts +0 -23
  240. package/src/scripts/sequencer-recovery/recoverer.ts +0 -297
  241. package/src/scripts/sequencer-recovery/recovery-db.ts +0 -65
  242. package/src/scripts/sequencer-recovery/repair-repos.ts +0 -49
  243. package/src/scripts/sequencer-recovery/user-queues.ts +0 -44
  244. package/src/scripts/util.ts +0 -7
  245. package/src/sequencer/db/index.ts +0 -21
  246. package/src/sequencer/db/migrations/001-init.ts +0 -35
  247. package/src/sequencer/db/migrations/index.ts +0 -5
  248. package/src/sequencer/db/schema.ts +0 -19
  249. package/src/sequencer/events.ts +0 -199
  250. package/src/sequencer/index.ts +0 -3
  251. package/src/sequencer/outbox.ts +0 -123
  252. package/src/sequencer/sequencer.ts +0 -290
  253. package/src/util/compression.ts +0 -16
  254. package/src/util/debug.ts +0 -10
  255. package/src/util/http.ts +0 -31
  256. package/src/util/types.ts +0 -7
  257. package/src/well-known.ts +0 -30
  258. package/test.env +0 -2
  259. package/tests/__snapshots__/takedown-appeal.test.ts.snap +0 -43
  260. package/tests/_oauth_client_assets_middleware.ts +0 -23
  261. package/tests/_puppeteer.ts +0 -147
  262. package/tests/_types.d.ts +0 -16
  263. package/tests/_util.ts +0 -191
  264. package/tests/account-deactivation.test.ts +0 -204
  265. package/tests/account-deletion.test.ts +0 -300
  266. package/tests/account-manager.test.ts +0 -462
  267. package/tests/account-migration.test.ts +0 -221
  268. package/tests/account-status.test.ts +0 -206
  269. package/tests/account.test.ts +0 -595
  270. package/tests/app-passwords.test.ts +0 -262
  271. package/tests/auth.test.ts +0 -405
  272. package/tests/blob-deletes.test.ts +0 -204
  273. package/tests/create-post.test.ts +0 -79
  274. package/tests/crud.test.ts +0 -1595
  275. package/tests/db.test.ts +0 -170
  276. package/tests/email-confirmation.test.ts +0 -227
  277. package/tests/entryway-mock.ts +0 -323
  278. package/tests/entryway.test.ts +0 -145
  279. package/tests/file-uploads.test.ts +0 -301
  280. package/tests/get-service-auth.test.ts +0 -81
  281. package/tests/handle-validation.test.ts +0 -56
  282. package/tests/handles.test.ts +0 -274
  283. package/tests/invite-codes.test.ts +0 -367
  284. package/tests/invites-admin.test.ts +0 -252
  285. package/tests/moderation.test.ts +0 -280
  286. package/tests/moderator-auth.test.ts +0 -177
  287. package/tests/oauth.test.ts +0 -334
  288. package/tests/plc-operations.test.ts +0 -239
  289. package/tests/preferences.test.ts +0 -352
  290. package/tests/proxied/__snapshots__/admin.test.ts.snap +0 -516
  291. package/tests/proxied/__snapshots__/feedgen.test.ts.snap +0 -215
  292. package/tests/proxied/__snapshots__/views.test.ts.snap +0 -4456
  293. package/tests/proxied/admin.test.ts +0 -340
  294. package/tests/proxied/feedgen.test.ts +0 -66
  295. package/tests/proxied/notif.test.ts +0 -85
  296. package/tests/proxied/procedures.test.ts +0 -175
  297. package/tests/proxied/proxy-catchall.test.ts +0 -256
  298. package/tests/proxied/proxy-header.test.ts +0 -239
  299. package/tests/proxied/proxy-oauth-aud.test.ts +0 -182
  300. package/tests/proxied/read-after-write.test.ts +0 -382
  301. package/tests/proxied/views.test.ts +0 -608
  302. package/tests/races.test.ts +0 -89
  303. package/tests/rate-limits.test.ts +0 -70
  304. package/tests/recovery.test.ts +0 -180
  305. package/tests/seeds/basic.ts +0 -165
  306. package/tests/seeds/follows.ts +0 -57
  307. package/tests/seeds/likes.ts +0 -14
  308. package/tests/seeds/reposts.ts +0 -18
  309. package/tests/seeds/thread.ts +0 -40
  310. package/tests/seeds/users-bulk.ts +0 -246
  311. package/tests/seeds/users.ts +0 -67
  312. package/tests/sequencer.test.ts +0 -251
  313. package/tests/server.test.ts +0 -151
  314. package/tests/sync/invertible-ops.test.ts +0 -99
  315. package/tests/sync/list.test.ts +0 -43
  316. package/tests/sync/subscribe-repos.test.ts +0 -672
  317. package/tests/sync/sync.test.ts +0 -316
  318. package/tests/takedown-appeal.test.ts +0 -166
  319. package/tsconfig.build.json +0 -9
  320. package/tsconfig.build.tsbuildinfo +0 -1
  321. package/tsconfig.json +0 -7
  322. 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
- }