@atproto/pds 0.4.59 → 0.4.61

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/account-manager/helpers/account.d.ts +1 -0
  3. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  4. package/dist/account-manager/helpers/account.js +15 -1
  5. package/dist/account-manager/helpers/account.js.map +1 -1
  6. package/dist/account-manager/helpers/invite.d.ts +1 -1
  7. package/dist/account-manager/helpers/invite.d.ts.map +1 -1
  8. package/dist/account-manager/helpers/invite.js +20 -9
  9. package/dist/account-manager/helpers/invite.js.map +1 -1
  10. package/dist/account-manager/index.d.ts +2 -0
  11. package/dist/account-manager/index.d.ts.map +1 -1
  12. package/dist/account-manager/index.js +8 -1
  13. package/dist/account-manager/index.js.map +1 -1
  14. package/dist/api/app/bsky/actor/getProfile.d.ts.map +1 -1
  15. package/dist/api/app/bsky/actor/getProfile.js +2 -9
  16. package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
  17. package/dist/api/app/bsky/actor/getProfiles.d.ts.map +1 -1
  18. package/dist/api/app/bsky/actor/getProfiles.js +2 -6
  19. package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
  20. package/dist/api/app/bsky/feed/getActorLikes.d.ts.map +1 -1
  21. package/dist/api/app/bsky/feed/getActorLikes.js +2 -9
  22. package/dist/api/app/bsky/feed/getActorLikes.js.map +1 -1
  23. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  24. package/dist/api/app/bsky/feed/getAuthorFeed.js +2 -9
  25. package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
  26. package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
  27. package/dist/api/app/bsky/feed/getFeed.js +2 -1
  28. package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
  29. package/dist/api/app/bsky/feed/getPostThread.d.ts.map +1 -1
  30. package/dist/api/app/bsky/feed/getPostThread.js +12 -14
  31. package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
  32. package/dist/api/app/bsky/feed/getTimeline.d.ts.map +1 -1
  33. package/dist/api/app/bsky/feed/getTimeline.js +2 -6
  34. package/dist/api/app/bsky/feed/getTimeline.js.map +1 -1
  35. package/dist/api/com/atproto/admin/getAccountInfo.d.ts.map +1 -1
  36. package/dist/api/com/atproto/admin/getAccountInfo.js +6 -14
  37. package/dist/api/com/atproto/admin/getAccountInfo.js.map +1 -1
  38. package/dist/api/com/atproto/admin/getAccountInfos.d.ts +4 -0
  39. package/dist/api/com/atproto/admin/getAccountInfos.d.ts.map +1 -0
  40. package/dist/api/com/atproto/admin/getAccountInfos.js +32 -0
  41. package/dist/api/com/atproto/admin/getAccountInfos.js.map +1 -0
  42. package/dist/api/com/atproto/admin/index.d.ts.map +1 -1
  43. package/dist/api/com/atproto/admin/index.js +2 -0
  44. package/dist/api/com/atproto/admin/index.js.map +1 -1
  45. package/dist/api/com/atproto/admin/util.d.ts +17 -0
  46. package/dist/api/com/atproto/admin/util.d.ts.map +1 -1
  47. package/dist/api/com/atproto/admin/util.js +27 -1
  48. package/dist/api/com/atproto/admin/util.js.map +1 -1
  49. package/dist/api/com/atproto/repo/getRecord.d.ts.map +1 -1
  50. package/dist/api/com/atproto/repo/getRecord.js +2 -2
  51. package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
  52. package/dist/api/com/atproto/server/requestPasswordReset.js +1 -1
  53. package/dist/api/com/atproto/server/requestPasswordReset.js.map +1 -1
  54. package/dist/config/config.d.ts +17 -0
  55. package/dist/config/config.d.ts.map +1 -1
  56. package/dist/config/config.js +11 -1
  57. package/dist/config/config.js.map +1 -1
  58. package/dist/config/env.d.ts +7 -1
  59. package/dist/config/env.d.ts.map +1 -1
  60. package/dist/config/env.js +9 -1
  61. package/dist/config/env.js.map +1 -1
  62. package/dist/context.d.ts +6 -2
  63. package/dist/context.d.ts.map +1 -1
  64. package/dist/context.js +55 -11
  65. package/dist/context.js.map +1 -1
  66. package/dist/index.d.ts.map +1 -1
  67. package/dist/index.js +1 -0
  68. package/dist/index.js.map +1 -1
  69. package/dist/lexicon/index.d.ts +4 -0
  70. package/dist/lexicon/index.d.ts.map +1 -1
  71. package/dist/lexicon/index.js +8 -0
  72. package/dist/lexicon/index.js.map +1 -1
  73. package/dist/lexicon/lexicons.d.ts +118 -0
  74. package/dist/lexicon/lexicons.d.ts.map +1 -1
  75. package/dist/lexicon/lexicons.js +135 -3
  76. package/dist/lexicon/lexicons.js.map +1 -1
  77. package/dist/lexicon/types/app/bsky/actor/defs.d.ts +2 -0
  78. package/dist/lexicon/types/app/bsky/actor/defs.d.ts.map +1 -1
  79. package/dist/lexicon/types/app/bsky/actor/defs.js.map +1 -1
  80. package/dist/lexicon/types/app/bsky/actor/profile.d.ts +1 -0
  81. package/dist/lexicon/types/app/bsky/actor/profile.d.ts.map +1 -1
  82. package/dist/lexicon/types/app/bsky/actor/profile.js.map +1 -1
  83. package/dist/lexicon/types/app/bsky/feed/defs.d.ts +13 -2
  84. package/dist/lexicon/types/app/bsky/feed/defs.d.ts.map +1 -1
  85. package/dist/lexicon/types/app/bsky/feed/defs.js +21 -1
  86. package/dist/lexicon/types/app/bsky/feed/defs.js.map +1 -1
  87. package/dist/lexicon/types/app/bsky/feed/getAuthorFeed.d.ts +1 -0
  88. package/dist/lexicon/types/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  89. package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts +2 -0
  90. package/dist/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
  91. package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts +2 -0
  92. package/dist/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.d.ts.map +1 -1
  93. package/dist/lexicon/types/com/atproto/repo/getRecord.d.ts +1 -0
  94. package/dist/lexicon/types/com/atproto/repo/getRecord.d.ts.map +1 -1
  95. package/dist/lexicon/types/tools/ozone/moderation/getRecords.d.ts +39 -0
  96. package/dist/lexicon/types/tools/ozone/moderation/getRecords.d.ts.map +1 -0
  97. package/dist/lexicon/types/tools/ozone/moderation/getRecords.js +3 -0
  98. package/dist/lexicon/types/tools/ozone/moderation/getRecords.js.map +1 -0
  99. package/dist/lexicon/types/tools/ozone/moderation/getRepos.d.ts +39 -0
  100. package/dist/lexicon/types/tools/ozone/moderation/getRepos.d.ts.map +1 -0
  101. package/dist/lexicon/types/tools/ozone/moderation/getRepos.js +3 -0
  102. package/dist/lexicon/types/tools/ozone/moderation/getRepos.js.map +1 -0
  103. package/dist/mailer/index.d.ts +1 -1
  104. package/dist/mailer/index.d.ts.map +1 -1
  105. package/dist/mailer/index.js.map +1 -1
  106. package/dist/mailer/templates/confirm-email.js +1 -1
  107. package/dist/mailer/templates/confirm-email.js.map +2 -2
  108. package/dist/mailer/templates/delete-account.js +1 -1
  109. package/dist/mailer/templates/delete-account.js.map +2 -2
  110. package/dist/mailer/templates/plc-operation.js +1 -1
  111. package/dist/mailer/templates/plc-operation.js.map +2 -2
  112. package/dist/mailer/templates/reset-password.js +1 -1
  113. package/dist/mailer/templates/reset-password.js.map +2 -2
  114. package/dist/mailer/templates/update-email.js +1 -1
  115. package/dist/mailer/templates/update-email.js.map +2 -2
  116. package/dist/pipethrough.d.ts +26 -26
  117. package/dist/pipethrough.d.ts.map +1 -1
  118. package/dist/pipethrough.js +360 -228
  119. package/dist/pipethrough.js.map +1 -1
  120. package/dist/read-after-write/util.d.ts +13 -5
  121. package/dist/read-after-write/util.d.ts.map +1 -1
  122. package/dist/read-after-write/util.js +37 -22
  123. package/dist/read-after-write/util.js.map +1 -1
  124. package/package.json +15 -14
  125. package/src/account-manager/helpers/account.ts +22 -0
  126. package/src/account-manager/helpers/invite.ts +19 -9
  127. package/src/account-manager/index.ts +13 -1
  128. package/src/api/app/bsky/actor/getProfile.ts +3 -17
  129. package/src/api/app/bsky/actor/getProfiles.ts +3 -15
  130. package/src/api/app/bsky/feed/getActorLikes.ts +3 -19
  131. package/src/api/app/bsky/feed/getAuthorFeed.ts +3 -17
  132. package/src/api/app/bsky/feed/getFeed.ts +3 -1
  133. package/src/api/app/bsky/feed/getPostThread.ts +16 -23
  134. package/src/api/app/bsky/feed/getTimeline.ts +3 -14
  135. package/src/api/com/atproto/admin/getAccountInfo.ts +6 -13
  136. package/src/api/com/atproto/admin/getAccountInfos.ts +33 -0
  137. package/src/api/com/atproto/admin/index.ts +2 -0
  138. package/src/api/com/atproto/admin/util.ts +38 -0
  139. package/src/api/com/atproto/repo/getRecord.ts +5 -2
  140. package/src/api/com/atproto/server/requestPasswordReset.ts +1 -1
  141. package/src/config/config.ts +31 -1
  142. package/src/config/env.ts +22 -2
  143. package/src/context.ts +62 -17
  144. package/src/index.ts +1 -0
  145. package/src/lexicon/index.ts +24 -0
  146. package/src/lexicon/lexicons.ts +137 -3
  147. package/src/lexicon/types/app/bsky/actor/defs.ts +2 -0
  148. package/src/lexicon/types/app/bsky/actor/profile.ts +1 -0
  149. package/src/lexicon/types/app/bsky/feed/defs.ts +38 -2
  150. package/src/lexicon/types/app/bsky/feed/getAuthorFeed.ts +1 -0
  151. package/src/lexicon/types/app/bsky/graph/getSuggestedFollowsByActor.ts +2 -0
  152. package/src/lexicon/types/app/bsky/unspecced/getSuggestionsSkeleton.ts +2 -0
  153. package/src/lexicon/types/com/atproto/repo/getRecord.ts +1 -0
  154. package/src/lexicon/types/tools/ozone/moderation/getRecords.ts +50 -0
  155. package/src/lexicon/types/tools/ozone/moderation/getRepos.ts +50 -0
  156. package/src/mailer/index.ts +1 -1
  157. package/src/mailer/templates/confirm-email.hbs +106 -336
  158. package/src/mailer/templates/delete-account.hbs +110 -346
  159. package/src/mailer/templates/plc-operation.hbs +107 -338
  160. package/src/mailer/templates/reset-password.d.ts +1 -1
  161. package/src/mailer/templates/reset-password.hbs +108 -344
  162. package/src/mailer/templates/update-email.hbs +107 -337
  163. package/src/pipethrough.ts +528 -233
  164. package/src/read-after-write/util.ts +58 -32
  165. package/tests/account-deletion.test.ts +1 -1
  166. package/tests/account.test.ts +2 -2
  167. package/tests/email-confirmation.test.ts +2 -2
  168. package/tests/plc-operations.test.ts +1 -1
  169. package/tests/proxied/proxy-catchall.test.ts +255 -0
  170. package/tests/proxied/proxy-header.test.ts +31 -1
  171. package/tests/proxied/read-after-write.test.ts +77 -0
@@ -0,0 +1,33 @@
1
+ import { Server } from '../../../../lexicon'
2
+ import AppContext from '../../../../context'
3
+ import { formatAccountInfo } from './util'
4
+
5
+ export default function (server: Server, ctx: AppContext) {
6
+ server.com.atproto.admin.getAccountInfos({
7
+ auth: ctx.authVerifier.moderator,
8
+ handler: async ({ params }) => {
9
+ const [accounts, invites, invitedBy] = await Promise.all([
10
+ ctx.accountManager.getAccounts(params.dids, {
11
+ includeDeactivated: true,
12
+ includeTakenDown: true,
13
+ }),
14
+ ctx.accountManager.getAccountsInvitesCodes(params.dids),
15
+ ctx.accountManager.getInvitedByForAccounts(params.dids),
16
+ ])
17
+
18
+ const managesOwnInvites = !ctx.cfg.entryway
19
+ const infos = Array.from(accounts.values()).map((account) => {
20
+ return formatAccountInfo(account, {
21
+ managesOwnInvites,
22
+ invitedBy,
23
+ invites,
24
+ })
25
+ })
26
+
27
+ return {
28
+ encoding: 'application/json',
29
+ body: { infos },
30
+ }
31
+ },
32
+ })
33
+ }
@@ -12,11 +12,13 @@ import updateAccountEmail from './updateAccountEmail'
12
12
  import updateAccountPassword from './updateAccountPassword'
13
13
  import sendEmail from './sendEmail'
14
14
  import deleteAccount from './deleteAccount'
15
+ import getAccountInfos from './getAccountInfos'
15
16
 
16
17
  export default function (server: Server, ctx: AppContext) {
17
18
  updateSubjectStatus(server, ctx)
18
19
  getSubjectStatus(server, ctx)
19
20
  getAccountInfo(server, ctx)
21
+ getAccountInfos(server, ctx)
20
22
  enableAccountInvites(server, ctx)
21
23
  disableAccountInvites(server, ctx)
22
24
  disableInviteCodes(server, ctx)
@@ -1,4 +1,7 @@
1
1
  import express from 'express'
2
+ import { ActorAccount } from '../../../../account-manager/helpers/account'
3
+ import { INVALID_HANDLE } from '@atproto/syntax'
4
+ import { CodeDetail } from '../../../../account-manager/helpers/invite'
2
5
 
3
6
  // Output designed to passed as second arg to AtpAgent methods.
4
7
  // The encoding field here is a quirk of the AtpAgent.
@@ -22,3 +25,38 @@ export function authPassthru(req: express.Request, withEncoding?: boolean) {
22
25
  }
23
26
  }
24
27
  }
28
+
29
+ export function formatAccountInfo(
30
+ account: ActorAccount,
31
+ {
32
+ managesOwnInvites,
33
+ invitedBy,
34
+ invites,
35
+ }: {
36
+ managesOwnInvites: boolean
37
+ invites: Map<string, CodeDetail[]> | CodeDetail[]
38
+ invitedBy: Record<string, CodeDetail>
39
+ },
40
+ ) {
41
+ let invitesResults: CodeDetail[] | undefined
42
+ if (managesOwnInvites) {
43
+ if (Array.isArray(invites)) {
44
+ invitesResults = invites
45
+ } else {
46
+ invitesResults = invites.get(account.did) || []
47
+ }
48
+ }
49
+ return {
50
+ did: account.did,
51
+ handle: account.handle ?? INVALID_HANDLE,
52
+ email: account.email ?? undefined,
53
+ indexedAt: account.createdAt,
54
+ emailConfirmedAt: account.emailConfirmedAt ?? undefined,
55
+ invitedBy: managesOwnInvites ? invitedBy[account.did] : undefined,
56
+ invites: invitesResults,
57
+ invitesDisabled: managesOwnInvites
58
+ ? account.invitesDisabled === 1
59
+ : undefined,
60
+ deactivatedAt: account.deactivatedAt ?? undefined,
61
+ }
62
+ }
@@ -16,7 +16,10 @@ export default function (server: Server, ctx: AppContext) {
16
16
  store.record.getRecord(uri, cid ?? null),
17
17
  )
18
18
  if (!record || record.takedownRef !== null) {
19
- throw new InvalidRequestError(`Could not locate record: ${uri}`)
19
+ throw new InvalidRequestError(
20
+ `Could not locate record: ${uri}`,
21
+ 'RecordNotFound',
22
+ )
20
23
  }
21
24
  return {
22
25
  encoding: 'application/json',
@@ -32,6 +35,6 @@ export default function (server: Server, ctx: AppContext) {
32
35
  throw new InvalidRequestError(`Could not locate record`)
33
36
  }
34
37
 
35
- return await pipethrough(ctx, req, null)
38
+ return pipethrough(ctx, req)
36
39
  })
37
40
  }
@@ -40,7 +40,7 @@ export default function (server: Server, ctx: AppContext) {
40
40
  'reset_password',
41
41
  )
42
42
  await ctx.mailer.sendResetPassword(
43
- { identifier: account.handle ?? account.email, token },
43
+ { handle: account.handle ?? account.email, token },
44
44
  { to: account.email },
45
45
  )
46
46
  },
@@ -236,7 +236,17 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
236
236
  const crawlersCfg: ServerConfig['crawlers'] = env.crawlers ?? []
237
237
 
238
238
  const fetchCfg: ServerConfig['fetch'] = {
239
- disableSsrfProtection: env.fetchDisableSsrfProtection ?? false,
239
+ disableSsrfProtection: env.disableSsrfProtection ?? env.devMode ?? false,
240
+ maxResponseSize: env.fetchMaxResponseSize ?? 512 * 1024, // 512kb
241
+ }
242
+
243
+ const proxyCfg: ServerConfig['proxy'] = {
244
+ disableSsrfProtection: env.disableSsrfProtection ?? env.devMode ?? false,
245
+ allowHTTP2: env.proxyAllowHTTP2 ?? false,
246
+ headersTimeout: env.proxyHeadersTimeout ?? 10e3,
247
+ bodyTimeout: env.proxyBodyTimeout ?? 30e3,
248
+ maxResponseSize: env.proxyMaxResponseSize ?? 10 * 1024 * 1024, // 10mb
249
+ preferCompressed: env.proxyPreferCompressed ?? false,
240
250
  }
241
251
 
242
252
  const oauthCfg: ServerConfig['oauth'] = entrywayCfg
@@ -302,6 +312,7 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
302
312
  rateLimits: rateLimitsCfg,
303
313
  crawlers: crawlersCfg,
304
314
  fetch: fetchCfg,
315
+ proxy: proxyCfg,
305
316
  oauth: oauthCfg,
306
317
  }
307
318
  }
@@ -324,6 +335,7 @@ export type ServerConfig = {
324
335
  rateLimits: RateLimitsConfig
325
336
  crawlers: string[]
326
337
  fetch: FetchConfig
338
+ proxy: ProxyConfig
327
339
  oauth: OAuthConfig
328
340
  }
329
341
 
@@ -393,6 +405,24 @@ export type EntrywayConfig = {
393
405
 
394
406
  export type FetchConfig = {
395
407
  disableSsrfProtection: boolean
408
+ maxResponseSize: number
409
+ }
410
+
411
+ export type ProxyConfig = {
412
+ disableSsrfProtection: boolean
413
+ allowHTTP2: boolean
414
+ headersTimeout: number
415
+ bodyTimeout: number
416
+ maxResponseSize: number
417
+
418
+ /**
419
+ * When proxying requests that might get intercepted (for read-after-write) we
420
+ * negotiate the encoding based on the client's preferences. We will however
421
+ * use or own weights in order to be able to better control if the PDS will
422
+ * need to perform content decoding. This settings allows to prefer compressed
423
+ * content over uncompressed one.
424
+ */
425
+ preferCompressed: boolean
396
426
  }
397
427
 
398
428
  export type OAuthConfig = {
package/src/config/env.ts CHANGED
@@ -117,8 +117,18 @@ export const readEnv = (): ServerEnvironment => {
117
117
  'PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX',
118
118
  ),
119
119
 
120
+ // user provided url http requests
121
+ disableSsrfProtection: envBool('PDS_DISABLE_SSRF_PROTECTION'),
122
+
120
123
  // fetch
121
- fetchDisableSsrfProtection: envBool('PDS_DISABLE_SSRF_PROTECTION'),
124
+ fetchMaxResponseSize: envInt('PDS_FETCH_MAX_RESPONSE_SIZE'),
125
+
126
+ // proxy
127
+ proxyAllowHTTP2: envBool('PDS_PROXY_ALLOW_HTTP2'),
128
+ proxyHeadersTimeout: envInt('PDS_PROXY_HEADERS_TIMEOUT'),
129
+ proxyBodyTimeout: envInt('PDS_PROXY_BODY_TIMEOUT'),
130
+ proxyMaxResponseSize: envInt('PDS_PROXY_MAX_RESPONSE_SIZE'),
131
+ proxyPreferCompressed: envBool('PDS_PROXY_PREFER_COMPRESSED'),
122
132
  }
123
133
  }
124
134
 
@@ -233,6 +243,16 @@ export type ServerEnvironment = {
233
243
  plcRotationKeyKmsKeyId?: string
234
244
  plcRotationKeyK256PrivateKeyHex?: string
235
245
 
246
+ // user provided url http requests
247
+ disableSsrfProtection?: boolean
248
+
236
249
  // fetch
237
- fetchDisableSsrfProtection?: boolean
250
+ fetchMaxResponseSize?: number
251
+
252
+ // proxy
253
+ proxyAllowHTTP2?: boolean
254
+ proxyHeadersTimeout?: number
255
+ proxyBodyTimeout?: number
256
+ proxyMaxResponseSize?: number
257
+ proxyPreferCompressed?: boolean
238
258
  }
package/src/context.ts CHANGED
@@ -1,23 +1,28 @@
1
1
  import assert from 'node:assert'
2
+ import * as undici from 'undici'
2
3
  import * as nodemailer from 'nodemailer'
3
4
  import { Redis } from 'ioredis'
4
5
  import * as plc from '@did-plc/lib'
6
+ import {
7
+ Fetch,
8
+ isUnicastIp,
9
+ loggedFetch,
10
+ safeFetchWrap,
11
+ unicastLookup,
12
+ } from '@atproto-labs/fetch-node'
5
13
  import * as crypto from '@atproto/crypto'
6
14
  import { IdResolver } from '@atproto/identity'
7
15
  import { AtpAgent } from '@atproto/api'
8
16
  import { KmsKeypair, S3BlobStore } from '@atproto/aws'
17
+ import { JoseKey, OAuthVerifier } from '@atproto/oauth-provider'
18
+ import { BlobStore } from '@atproto/repo'
9
19
  import {
10
20
  RateLimiter,
11
21
  RateLimiterCreator,
12
22
  RateLimiterOpts,
13
23
  createServiceAuthHeaders,
24
+ createServiceJwt,
14
25
  } from '@atproto/xrpc-server'
15
- import {
16
- JoseKey,
17
- Fetch,
18
- safeFetchWrap,
19
- OAuthVerifier,
20
- } from '@atproto/oauth-provider'
21
26
 
22
27
  import { ServerConfig, ServerSecrets } from './config'
23
28
  import { PdsOAuthProvider } from './oauth/provider'
@@ -29,7 +34,6 @@ import {
29
34
  import { fetchLogger } from './logger'
30
35
  import { ServerMailer } from './mailer'
31
36
  import { ModerationMailer } from './mailer/moderation'
32
- import { BlobStore } from '@atproto/repo'
33
37
  import { AccountManager } from './account-manager'
34
38
  import { Sequencer } from './sequencer'
35
39
  import { BackgroundQueue } from './background'
@@ -59,6 +63,7 @@ export type AppContextOptions = {
59
63
  moderationAgent?: AtpAgent
60
64
  reportingAgent?: AtpAgent
61
65
  entrywayAgent?: AtpAgent
66
+ proxyAgent: undici.Agent
62
67
  safeFetch: Fetch
63
68
  authProvider?: PdsOAuthProvider
64
69
  authVerifier: AuthVerifier
@@ -85,6 +90,7 @@ export class AppContext {
85
90
  public moderationAgent: AtpAgent | undefined
86
91
  public reportingAgent: AtpAgent | undefined
87
92
  public entrywayAgent: AtpAgent | undefined
93
+ public proxyAgent: undici.Agent
88
94
  public safeFetch: Fetch
89
95
  public authVerifier: AuthVerifier
90
96
  public authProvider?: PdsOAuthProvider
@@ -110,6 +116,7 @@ export class AppContext {
110
116
  this.moderationAgent = opts.moderationAgent
111
117
  this.reportingAgent = opts.reportingAgent
112
118
  this.entrywayAgent = opts.entrywayAgent
119
+ this.proxyAgent = opts.proxyAgent
113
120
  this.safeFetch = opts.safeFetch
114
121
  this.authVerifier = opts.authVerifier
115
122
  this.authProvider = opts.authProvider
@@ -256,20 +263,47 @@ export class AppContext {
256
263
  appviewCdnUrlPattern: cfg.bskyAppView?.cdnUrlPattern,
257
264
  })
258
265
 
266
+ // An agent for performing HTTP requests based on user provided URLs.
267
+ const proxyAgent = new undici.Agent({
268
+ allowH2: cfg.proxy.allowHTTP2, // This is experimental
269
+ headersTimeout: cfg.proxy.headersTimeout,
270
+ maxResponseSize: cfg.proxy.maxResponseSize,
271
+ bodyTimeout: cfg.proxy.bodyTimeout,
272
+ factory: cfg.proxy.disableSsrfProtection
273
+ ? undefined
274
+ : (origin, opts) => {
275
+ const { protocol, hostname } =
276
+ origin instanceof URL ? origin : new URL(origin)
277
+ if (protocol !== 'https:') {
278
+ throw new Error(`Forbidden protocol "${protocol}"`)
279
+ }
280
+ if (isUnicastIp(hostname) === false) {
281
+ throw new Error('Hostname resolved to non-unicast address')
282
+ }
283
+ return new undici.Pool(origin, opts)
284
+ },
285
+ connect: {
286
+ lookup: cfg.proxy.disableSsrfProtection ? undefined : unicastLookup,
287
+ },
288
+ })
289
+
259
290
  // A fetch() function that protects against SSRF attacks, large responses &
260
291
  // known bad domains. This function can safely be used to fetch user
261
292
  // provided URLs (unless "disableSsrfProtection" is true, of course).
262
- const safeFetch = safeFetchWrap({
263
- allowHttp: cfg.fetch.disableSsrfProtection,
264
- responseMaxSize: 512 * 1024, // 512kB
265
- ssrfProtection: !cfg.fetch.disableSsrfProtection,
266
- fetch: async (input, init) => {
267
- const request = input instanceof Request ? input : null
268
- const method = init?.method ?? request?.method ?? 'GET'
269
- const uri = request?.url ?? String(input)
270
- fetchLogger.debug({ method, uri }, 'fetch')
271
- return globalThis.fetch(input, init)
293
+ const safeFetch = loggedFetch({
294
+ fetch: safeFetchWrap({
295
+ // Using globalThis.fetch allows safeFetchWrap to use keep-alive. See
296
+ // unicastFetchWrap().
297
+ fetch: globalThis.fetch,
298
+ allowIpHost: false,
299
+ responseMaxSize: cfg.fetch.maxResponseSize,
300
+ ssrfProtection: !cfg.fetch.disableSsrfProtection,
301
+ }),
302
+ logRequest: ({ method, url }) => {
303
+ fetchLogger.debug({ method, uri: url }, 'fetch')
272
304
  },
305
+ logResponse: false,
306
+ logError: false,
273
307
  })
274
308
 
275
309
  const authProvider = cfg.oauth.provider
@@ -333,6 +367,7 @@ export class AppContext {
333
367
  moderationAgent,
334
368
  reportingAgent,
335
369
  entrywayAgent,
370
+ proxyAgent,
336
371
  safeFetch,
337
372
  authVerifier,
338
373
  authProvider,
@@ -356,6 +391,16 @@ export class AppContext {
356
391
  keypair,
357
392
  })
358
393
  }
394
+
395
+ async serviceAuthJwt(did: string, aud: string, lxm: string) {
396
+ const keypair = await this.actorStore.keypair(did)
397
+ return createServiceJwt({
398
+ iss: did,
399
+ aud,
400
+ lxm,
401
+ keypair,
402
+ })
403
+ }
359
404
  }
360
405
 
361
406
  export default AppContext
package/src/index.ts CHANGED
@@ -127,6 +127,7 @@ export class PDS {
127
127
  await this.ctx.backgroundQueue.destroy()
128
128
  await this.ctx.accountManager.close()
129
129
  await this.ctx.redisScratch?.quit()
130
+ await this.ctx.proxyAgent.destroy()
130
131
  clearInterval(this.dbStatsInterval)
131
132
  clearInterval(this.sequencerStatsInterval)
132
133
  }
@@ -166,7 +166,9 @@ import * as ToolsOzoneCommunicationUpdateTemplate from './types/tools/ozone/comm
166
166
  import * as ToolsOzoneModerationEmitEvent from './types/tools/ozone/moderation/emitEvent'
167
167
  import * as ToolsOzoneModerationGetEvent from './types/tools/ozone/moderation/getEvent'
168
168
  import * as ToolsOzoneModerationGetRecord from './types/tools/ozone/moderation/getRecord'
169
+ import * as ToolsOzoneModerationGetRecords from './types/tools/ozone/moderation/getRecords'
169
170
  import * as ToolsOzoneModerationGetRepo from './types/tools/ozone/moderation/getRepo'
171
+ import * as ToolsOzoneModerationGetRepos from './types/tools/ozone/moderation/getRepos'
170
172
  import * as ToolsOzoneModerationQueryEvents from './types/tools/ozone/moderation/queryEvents'
171
173
  import * as ToolsOzoneModerationQueryStatuses from './types/tools/ozone/moderation/queryStatuses'
172
174
  import * as ToolsOzoneModerationSearchRepos from './types/tools/ozone/moderation/searchRepos'
@@ -2262,6 +2264,17 @@ export class ToolsOzoneModerationNS {
2262
2264
  return this._server.xrpc.method(nsid, cfg)
2263
2265
  }
2264
2266
 
2267
+ getRecords<AV extends AuthVerifier>(
2268
+ cfg: ConfigOf<
2269
+ AV,
2270
+ ToolsOzoneModerationGetRecords.Handler<ExtractAuth<AV>>,
2271
+ ToolsOzoneModerationGetRecords.HandlerReqCtx<ExtractAuth<AV>>
2272
+ >,
2273
+ ) {
2274
+ const nsid = 'tools.ozone.moderation.getRecords' // @ts-ignore
2275
+ return this._server.xrpc.method(nsid, cfg)
2276
+ }
2277
+
2265
2278
  getRepo<AV extends AuthVerifier>(
2266
2279
  cfg: ConfigOf<
2267
2280
  AV,
@@ -2273,6 +2286,17 @@ export class ToolsOzoneModerationNS {
2273
2286
  return this._server.xrpc.method(nsid, cfg)
2274
2287
  }
2275
2288
 
2289
+ getRepos<AV extends AuthVerifier>(
2290
+ cfg: ConfigOf<
2291
+ AV,
2292
+ ToolsOzoneModerationGetRepos.Handler<ExtractAuth<AV>>,
2293
+ ToolsOzoneModerationGetRepos.HandlerReqCtx<ExtractAuth<AV>>
2294
+ >,
2295
+ ) {
2296
+ const nsid = 'tools.ozone.moderation.getRepos' // @ts-ignore
2297
+ return this._server.xrpc.method(nsid, cfg)
2298
+ }
2299
+
2276
2300
  queryEvents<AV extends AuthVerifier>(
2277
2301
  cfg: ConfigOf<
2278
2302
  AV,
@@ -1686,6 +1686,11 @@ export const schemaDict = {
1686
1686
  },
1687
1687
  },
1688
1688
  },
1689
+ errors: [
1690
+ {
1691
+ name: 'RecordNotFound',
1692
+ },
1693
+ ],
1689
1694
  },
1690
1695
  },
1691
1696
  },
@@ -4190,6 +4195,10 @@ export const schemaDict = {
4190
4195
  ref: 'lex:com.atproto.label.defs#label',
4191
4196
  },
4192
4197
  },
4198
+ pinnedPost: {
4199
+ type: 'ref',
4200
+ ref: 'lex:com.atproto.repo.strongRef',
4201
+ },
4193
4202
  },
4194
4203
  },
4195
4204
  profileAssociated: {
@@ -4812,6 +4821,10 @@ export const schemaDict = {
4812
4821
  type: 'ref',
4813
4822
  ref: 'lex:com.atproto.repo.strongRef',
4814
4823
  },
4824
+ pinnedPost: {
4825
+ type: 'ref',
4826
+ ref: 'lex:com.atproto.repo.strongRef',
4827
+ },
4815
4828
  createdAt: {
4816
4829
  type: 'string',
4817
4830
  format: 'datetime',
@@ -5468,6 +5481,9 @@ export const schemaDict = {
5468
5481
  embeddingDisabled: {
5469
5482
  type: 'boolean',
5470
5483
  },
5484
+ pinned: {
5485
+ type: 'boolean',
5486
+ },
5471
5487
  },
5472
5488
  },
5473
5489
  feedViewPost: {
@@ -5484,7 +5500,10 @@ export const schemaDict = {
5484
5500
  },
5485
5501
  reason: {
5486
5502
  type: 'union',
5487
- refs: ['lex:app.bsky.feed.defs#reasonRepost'],
5503
+ refs: [
5504
+ 'lex:app.bsky.feed.defs#reasonRepost',
5505
+ 'lex:app.bsky.feed.defs#reasonPin',
5506
+ ],
5488
5507
  },
5489
5508
  feedContext: {
5490
5509
  type: 'string',
@@ -5536,6 +5555,10 @@ export const schemaDict = {
5536
5555
  },
5537
5556
  },
5538
5557
  },
5558
+ reasonPin: {
5559
+ type: 'object',
5560
+ properties: {},
5561
+ },
5539
5562
  threadViewPost: {
5540
5563
  type: 'object',
5541
5564
  required: ['post'],
@@ -5693,7 +5716,10 @@ export const schemaDict = {
5693
5716
  },
5694
5717
  reason: {
5695
5718
  type: 'union',
5696
- refs: ['lex:app.bsky.feed.defs#skeletonReasonRepost'],
5719
+ refs: [
5720
+ 'lex:app.bsky.feed.defs#skeletonReasonRepost',
5721
+ 'lex:app.bsky.feed.defs#skeletonReasonPin',
5722
+ ],
5697
5723
  },
5698
5724
  feedContext: {
5699
5725
  type: 'string',
@@ -5713,6 +5739,10 @@ export const schemaDict = {
5713
5739
  },
5714
5740
  },
5715
5741
  },
5742
+ skeletonReasonPin: {
5743
+ type: 'object',
5744
+ properties: {},
5745
+ },
5716
5746
  threadgateView: {
5717
5747
  type: 'object',
5718
5748
  properties: {
@@ -6078,6 +6108,10 @@ export const schemaDict = {
6078
6108
  ],
6079
6109
  default: 'posts_with_replies',
6080
6110
  },
6111
+ includePins: {
6112
+ type: 'boolean',
6113
+ default: false,
6114
+ },
6081
6115
  },
6082
6116
  },
6083
6117
  output: {
@@ -7162,7 +7196,7 @@ export const schemaDict = {
7162
7196
  type: 'record',
7163
7197
  key: 'tid',
7164
7198
  description:
7165
- "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository..",
7199
+ "Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository.",
7166
7200
  record: {
7167
7201
  type: 'object',
7168
7202
  required: ['post', 'createdAt'],
@@ -8236,6 +8270,12 @@ export const schemaDict = {
8236
8270
  ref: 'lex:app.bsky.actor.defs#profileView',
8237
8271
  },
8238
8272
  },
8273
+ isFallback: {
8274
+ type: 'boolean',
8275
+ description:
8276
+ 'If true, response has fallen-back to generic results, and is not scoped using relativeToDid',
8277
+ default: false,
8278
+ },
8239
8279
  },
8240
8280
  },
8241
8281
  },
@@ -9192,6 +9232,12 @@ export const schemaDict = {
9192
9232
  ref: 'lex:app.bsky.unspecced.defs#skeletonSearchActor',
9193
9233
  },
9194
9234
  },
9235
+ relativeToDid: {
9236
+ type: 'string',
9237
+ format: 'did',
9238
+ description:
9239
+ 'DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer.',
9240
+ },
9195
9241
  },
9196
9242
  },
9197
9243
  },
@@ -11602,6 +11648,49 @@ export const schemaDict = {
11602
11648
  },
11603
11649
  },
11604
11650
  },
11651
+ ToolsOzoneModerationGetRecords: {
11652
+ lexicon: 1,
11653
+ id: 'tools.ozone.moderation.getRecords',
11654
+ defs: {
11655
+ main: {
11656
+ type: 'query',
11657
+ description: 'Get details about some records.',
11658
+ parameters: {
11659
+ type: 'params',
11660
+ required: ['uris'],
11661
+ properties: {
11662
+ uris: {
11663
+ type: 'array',
11664
+ maxLength: 100,
11665
+ items: {
11666
+ type: 'string',
11667
+ format: 'at-uri',
11668
+ },
11669
+ },
11670
+ },
11671
+ },
11672
+ output: {
11673
+ encoding: 'application/json',
11674
+ schema: {
11675
+ type: 'object',
11676
+ required: ['records'],
11677
+ properties: {
11678
+ records: {
11679
+ type: 'array',
11680
+ items: {
11681
+ type: 'union',
11682
+ refs: [
11683
+ 'lex:tools.ozone.moderation.defs#recordViewDetail',
11684
+ 'lex:tools.ozone.moderation.defs#recordViewNotFound',
11685
+ ],
11686
+ },
11687
+ },
11688
+ },
11689
+ },
11690
+ },
11691
+ },
11692
+ },
11693
+ },
11605
11694
  ToolsOzoneModerationGetRepo: {
11606
11695
  lexicon: 1,
11607
11696
  id: 'tools.ozone.moderation.getRepo',
@@ -11634,6 +11723,49 @@ export const schemaDict = {
11634
11723
  },
11635
11724
  },
11636
11725
  },
11726
+ ToolsOzoneModerationGetRepos: {
11727
+ lexicon: 1,
11728
+ id: 'tools.ozone.moderation.getRepos',
11729
+ defs: {
11730
+ main: {
11731
+ type: 'query',
11732
+ description: 'Get details about some repositories.',
11733
+ parameters: {
11734
+ type: 'params',
11735
+ required: ['dids'],
11736
+ properties: {
11737
+ dids: {
11738
+ type: 'array',
11739
+ maxLength: 100,
11740
+ items: {
11741
+ type: 'string',
11742
+ format: 'did',
11743
+ },
11744
+ },
11745
+ },
11746
+ },
11747
+ output: {
11748
+ encoding: 'application/json',
11749
+ schema: {
11750
+ type: 'object',
11751
+ required: ['repos'],
11752
+ properties: {
11753
+ repos: {
11754
+ type: 'array',
11755
+ items: {
11756
+ type: 'union',
11757
+ refs: [
11758
+ 'lex:tools.ozone.moderation.defs#repoViewDetail',
11759
+ 'lex:tools.ozone.moderation.defs#repoViewNotFound',
11760
+ ],
11761
+ },
11762
+ },
11763
+ },
11764
+ },
11765
+ },
11766
+ },
11767
+ },
11768
+ },
11637
11769
  ToolsOzoneModerationQueryEvents: {
11638
11770
  lexicon: 1,
11639
11771
  id: 'tools.ozone.moderation.queryEvents',
@@ -12450,7 +12582,9 @@ export const ids = {
12450
12582
  ToolsOzoneModerationEmitEvent: 'tools.ozone.moderation.emitEvent',
12451
12583
  ToolsOzoneModerationGetEvent: 'tools.ozone.moderation.getEvent',
12452
12584
  ToolsOzoneModerationGetRecord: 'tools.ozone.moderation.getRecord',
12585
+ ToolsOzoneModerationGetRecords: 'tools.ozone.moderation.getRecords',
12453
12586
  ToolsOzoneModerationGetRepo: 'tools.ozone.moderation.getRepo',
12587
+ ToolsOzoneModerationGetRepos: 'tools.ozone.moderation.getRepos',
12454
12588
  ToolsOzoneModerationQueryEvents: 'tools.ozone.moderation.queryEvents',
12455
12589
  ToolsOzoneModerationQueryStatuses: 'tools.ozone.moderation.queryStatuses',
12456
12590
  ToolsOzoneModerationSearchRepos: 'tools.ozone.moderation.searchRepos',
@@ -7,6 +7,7 @@ import { isObj, hasProp } from '../../../../util'
7
7
  import { CID } from 'multiformats/cid'
8
8
  import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs'
9
9
  import * as AppBskyGraphDefs from '../graph/defs'
10
+ import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef'
10
11
 
11
12
  export interface ProfileViewBasic {
12
13
  did: string
@@ -74,6 +75,7 @@ export interface ProfileViewDetailed {
74
75
  createdAt?: string
75
76
  viewer?: ViewerState
76
77
  labels?: ComAtprotoLabelDefs.Label[]
78
+ pinnedPost?: ComAtprotoRepoStrongRef.Main
77
79
  [k: string]: unknown
78
80
  }
79
81