@atproto/pds 0.4.104 → 0.4.106

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 (209) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/account-manager/{index.d.ts → account-manager.d.ts} +26 -35
  3. package/dist/account-manager/account-manager.d.ts.map +1 -0
  4. package/dist/account-manager/{index.js → account-manager.js} +52 -207
  5. package/dist/account-manager/account-manager.js.map +1 -0
  6. package/dist/account-manager/helpers/account.d.ts +3 -3
  7. package/dist/account-manager/helpers/device-account.d.ts +15 -15
  8. package/dist/account-manager/helpers/device-account.d.ts.map +1 -1
  9. package/dist/account-manager/helpers/device-account.js +2 -1
  10. package/dist/account-manager/helpers/device-account.js.map +1 -1
  11. package/dist/account-manager/helpers/token.d.ts +98 -98
  12. package/dist/account-manager/oauth-store.d.ts +58 -0
  13. package/dist/account-manager/oauth-store.d.ts.map +1 -0
  14. package/dist/account-manager/oauth-store.js +417 -0
  15. package/dist/account-manager/oauth-store.js.map +1 -0
  16. package/dist/actor-store/record/reader.d.ts +3 -3
  17. package/dist/actor-store/repo/reader.d.ts +2 -0
  18. package/dist/actor-store/repo/reader.d.ts.map +1 -1
  19. package/dist/actor-store/repo/reader.js +9 -0
  20. package/dist/actor-store/repo/reader.js.map +1 -1
  21. package/dist/actor-store/repo/sql-repo-reader.d.ts +1 -1
  22. package/dist/actor-store/repo/transactor.d.ts.map +1 -1
  23. package/dist/actor-store/repo/transactor.js +13 -4
  24. package/dist/actor-store/repo/transactor.js.map +1 -1
  25. package/dist/api/com/atproto/admin/deleteAccount.d.ts.map +1 -1
  26. package/dist/api/com/atproto/admin/deleteAccount.js +2 -3
  27. package/dist/api/com/atproto/admin/deleteAccount.js.map +1 -1
  28. package/dist/api/com/atproto/admin/updateAccountHandle.d.ts.map +1 -1
  29. package/dist/api/com/atproto/admin/updateAccountHandle.js +2 -6
  30. package/dist/api/com/atproto/admin/updateAccountHandle.js.map +1 -1
  31. package/dist/api/com/atproto/identity/resolveHandle.d.ts.map +1 -1
  32. package/dist/api/com/atproto/identity/resolveHandle.js +2 -36
  33. package/dist/api/com/atproto/identity/resolveHandle.js.map +1 -1
  34. package/dist/api/com/atproto/identity/updateHandle.d.ts.map +1 -1
  35. package/dist/api/com/atproto/identity/updateHandle.js +1 -7
  36. package/dist/api/com/atproto/identity/updateHandle.js.map +1 -1
  37. package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
  38. package/dist/api/com/atproto/server/activateAccount.js +2 -18
  39. package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
  40. package/dist/api/com/atproto/server/createAccount.d.ts.map +1 -1
  41. package/dist/api/com/atproto/server/createAccount.js +7 -7
  42. package/dist/api/com/atproto/server/createAccount.js.map +1 -1
  43. package/dist/api/com/atproto/server/createSession.js +1 -1
  44. package/dist/api/com/atproto/server/createSession.js.map +1 -1
  45. package/dist/api/com/atproto/server/deleteAccount.d.ts.map +1 -1
  46. package/dist/api/com/atproto/server/deleteAccount.js +2 -3
  47. package/dist/api/com/atproto/server/deleteAccount.js.map +1 -1
  48. package/dist/api/com/atproto/server/getSession.js +1 -1
  49. package/dist/api/com/atproto/server/getSession.js.map +1 -1
  50. package/dist/api/com/atproto/server/refreshSession.js +1 -1
  51. package/dist/api/com/atproto/server/refreshSession.js.map +1 -1
  52. package/dist/api/com/atproto/sync/getRecord.d.ts.map +1 -1
  53. package/dist/api/com/atproto/sync/getRecord.js.map +1 -1
  54. package/dist/api/com/atproto/sync/getRepoStatus.js +1 -1
  55. package/dist/api/com/atproto/sync/getRepoStatus.js.map +1 -1
  56. package/dist/api/com/atproto/sync/listRepos.js +1 -1
  57. package/dist/api/com/atproto/sync/listRepos.js.map +1 -1
  58. package/dist/api/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
  59. package/dist/api/com/atproto/sync/subscribeRepos.js +2 -10
  60. package/dist/api/com/atproto/sync/subscribeRepos.js.map +1 -1
  61. package/dist/app-view.d.ts +14 -0
  62. package/dist/app-view.d.ts.map +1 -0
  63. package/dist/app-view.js +36 -0
  64. package/dist/app-view.js.map +1 -0
  65. package/dist/auth-routes.d.ts +1 -1
  66. package/dist/auth-routes.d.ts.map +1 -1
  67. package/dist/auth-routes.js +9 -3
  68. package/dist/auth-routes.js.map +1 -1
  69. package/dist/auth-verifier.d.ts +1 -1
  70. package/dist/auth-verifier.d.ts.map +1 -1
  71. package/dist/config/config.d.ts +3 -2
  72. package/dist/config/config.d.ts.map +1 -1
  73. package/dist/config/config.js +17 -7
  74. package/dist/config/config.js.map +1 -1
  75. package/dist/config/env.d.ts +4 -0
  76. package/dist/config/env.d.ts.map +1 -1
  77. package/dist/config/env.js +5 -0
  78. package/dist/config/env.js.map +1 -1
  79. package/dist/context.d.ts +4 -4
  80. package/dist/context.d.ts.map +1 -1
  81. package/dist/context.js +24 -18
  82. package/dist/context.js.map +1 -1
  83. package/dist/handle/index.d.ts +0 -7
  84. package/dist/handle/index.d.ts.map +1 -1
  85. package/dist/handle/index.js +4 -58
  86. package/dist/handle/index.js.map +1 -1
  87. package/dist/image/image-url.d.ts +8 -0
  88. package/dist/image/image-url.d.ts.map +1 -0
  89. package/dist/image/image-url.js +26 -0
  90. package/dist/image/image-url.js.map +1 -0
  91. package/dist/lexicon/index.d.ts +6 -0
  92. package/dist/lexicon/index.d.ts.map +1 -1
  93. package/dist/lexicon/index.js +12 -0
  94. package/dist/lexicon/index.js.map +1 -1
  95. package/dist/lexicon/lexicons.d.ts +310 -130
  96. package/dist/lexicon/lexicons.d.ts.map +1 -1
  97. package/dist/lexicon/lexicons.js +171 -67
  98. package/dist/lexicon/lexicons.js.map +1 -1
  99. package/dist/lexicon/types/app/bsky/embed/video.d.ts +1 -0
  100. package/dist/lexicon/types/app/bsky/embed/video.d.ts.map +1 -1
  101. package/dist/lexicon/types/app/bsky/embed/video.js.map +1 -1
  102. package/dist/lexicon/types/com/atproto/identity/defs.d.ts +17 -0
  103. package/dist/lexicon/types/com/atproto/identity/defs.d.ts.map +1 -0
  104. package/dist/lexicon/types/com/atproto/identity/defs.js +16 -0
  105. package/dist/lexicon/types/com/atproto/identity/defs.js.map +1 -0
  106. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts +39 -0
  107. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.d.ts.map +1 -0
  108. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js +7 -0
  109. package/dist/lexicon/types/com/atproto/identity/refreshIdentity.js.map +1 -0
  110. package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts +40 -0
  111. package/dist/lexicon/types/com/atproto/identity/resolveDid.d.ts.map +1 -0
  112. package/dist/lexicon/types/com/atproto/identity/resolveDid.js +7 -0
  113. package/dist/lexicon/types/com/atproto/identity/resolveDid.js.map +1 -0
  114. package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts +1 -0
  115. package/dist/lexicon/types/com/atproto/identity/resolveHandle.d.ts.map +1 -1
  116. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts +36 -0
  117. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.d.ts.map +1 -0
  118. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js +7 -0
  119. package/dist/lexicon/types/com/atproto/identity/resolveIdentity.js.map +1 -0
  120. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts +1 -30
  121. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.d.ts.map +1 -1
  122. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js +0 -27
  123. package/dist/lexicon/types/com/atproto/sync/subscribeRepos.js.map +1 -1
  124. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts +1 -0
  125. package/dist/lexicon/types/tools/ozone/team/listMembers.d.ts.map +1 -1
  126. package/dist/mailer/index.d.ts +5 -5
  127. package/dist/mailer/index.d.ts.map +1 -1
  128. package/dist/mailer/index.js +6 -5
  129. package/dist/mailer/index.js.map +1 -1
  130. package/dist/read-after-write/viewer.d.ts +1 -1
  131. package/dist/read-after-write/viewer.d.ts.map +1 -1
  132. package/dist/repo/types.d.ts +6 -2
  133. package/dist/repo/types.d.ts.map +1 -1
  134. package/dist/repo/types.js.map +1 -1
  135. package/dist/scripts/rebuild-repo.d.ts.map +1 -1
  136. package/dist/scripts/rebuild-repo.js +2 -1
  137. package/dist/scripts/rebuild-repo.js.map +1 -1
  138. package/dist/sequencer/db/schema.d.ts +1 -1
  139. package/dist/sequencer/db/schema.d.ts.map +1 -1
  140. package/dist/sequencer/events.d.ts +27 -38
  141. package/dist/sequencer/events.d.ts.map +1 -1
  142. package/dist/sequencer/events.js +40 -58
  143. package/dist/sequencer/events.js.map +1 -1
  144. package/dist/sequencer/sequencer.d.ts +2 -3
  145. package/dist/sequencer/sequencer.d.ts.map +1 -1
  146. package/dist/sequencer/sequencer.js +5 -17
  147. package/dist/sequencer/sequencer.js.map +1 -1
  148. package/package.json +15 -15
  149. package/src/account-manager/{index.ts → account-manager.ts} +107 -307
  150. package/src/account-manager/helpers/device-account.ts +1 -0
  151. package/src/account-manager/oauth-store.ts +494 -0
  152. package/src/actor-store/repo/reader.ts +11 -0
  153. package/src/actor-store/repo/transactor.ts +15 -4
  154. package/src/api/com/atproto/admin/deleteAccount.ts +2 -3
  155. package/src/api/com/atproto/admin/updateAccountHandle.ts +7 -8
  156. package/src/api/com/atproto/identity/resolveHandle.ts +2 -11
  157. package/src/api/com/atproto/identity/updateHandle.ts +4 -7
  158. package/src/api/com/atproto/server/activateAccount.ts +4 -18
  159. package/src/api/com/atproto/server/createAccount.ts +15 -11
  160. package/src/api/com/atproto/server/createSession.ts +1 -1
  161. package/src/api/com/atproto/server/deleteAccount.ts +2 -3
  162. package/src/api/com/atproto/server/getSession.ts +1 -1
  163. package/src/api/com/atproto/server/refreshSession.ts +1 -1
  164. package/src/api/com/atproto/sync/getRecord.ts +0 -1
  165. package/src/api/com/atproto/sync/getRepoStatus.ts +1 -1
  166. package/src/api/com/atproto/sync/listRepos.ts +1 -1
  167. package/src/api/com/atproto/sync/subscribeRepos.ts +2 -9
  168. package/src/app-view.ts +24 -0
  169. package/src/auth-routes.ts +9 -3
  170. package/src/auth-verifier.ts +1 -1
  171. package/src/config/config.ts +25 -13
  172. package/src/config/env.ts +12 -0
  173. package/src/context.ts +44 -24
  174. package/src/handle/index.ts +6 -52
  175. package/src/image/image-url.ts +16 -0
  176. package/src/lexicon/index.ts +36 -0
  177. package/src/lexicon/lexicons.ts +186 -67
  178. package/src/lexicon/types/app/bsky/embed/video.ts +1 -0
  179. package/src/lexicon/types/com/atproto/identity/defs.ts +30 -0
  180. package/src/lexicon/types/com/atproto/identity/refreshIdentity.ts +52 -0
  181. package/src/lexicon/types/com/atproto/identity/resolveDid.ts +52 -0
  182. package/src/lexicon/types/com/atproto/identity/resolveHandle.ts +1 -0
  183. package/src/lexicon/types/com/atproto/identity/resolveIdentity.ts +48 -0
  184. package/src/lexicon/types/com/atproto/sync/subscribeRepos.ts +0 -59
  185. package/src/lexicon/types/tools/ozone/team/listMembers.ts +1 -0
  186. package/src/mailer/index.ts +7 -5
  187. package/src/read-after-write/viewer.ts +1 -1
  188. package/src/repo/types.ts +7 -2
  189. package/src/scripts/rebuild-repo.ts +4 -1
  190. package/src/sequencer/db/schema.ts +1 -8
  191. package/src/sequencer/events.ts +47 -75
  192. package/src/sequencer/sequencer.ts +9 -23
  193. package/tests/account-deletion.test.ts +3 -5
  194. package/tests/oauth.test.ts +286 -71
  195. package/tests/sequencer.test.ts +20 -29
  196. package/tests/sync/subscribe-repos.test.ts +89 -45
  197. package/tsconfig.build.tsbuildinfo +1 -1
  198. package/dist/account-manager/index.d.ts.map +0 -1
  199. package/dist/account-manager/index.js.map +0 -1
  200. package/dist/actor-store/repo/util.d.ts +0 -5
  201. package/dist/actor-store/repo/util.d.ts.map +0 -1
  202. package/dist/actor-store/repo/util.js +0 -25
  203. package/dist/actor-store/repo/util.js.map +0 -1
  204. package/dist/oauth/provider.d.ts +0 -10
  205. package/dist/oauth/provider.d.ts.map +0 -1
  206. package/dist/oauth/provider.js +0 -38
  207. package/dist/oauth/provider.js.map +0 -1
  208. package/src/actor-store/repo/util.ts +0 -22
  209. package/src/oauth/provider.ts +0 -59
@@ -1,4 +1,3 @@
1
- import { CidSet } from '@atproto/repo'
2
1
  import { INVALID_HANDLE } from '@atproto/syntax'
3
2
  import { InvalidRequestError } from '@atproto/xrpc-server'
4
3
  import { AppContext } from '../../../../context'
@@ -31,22 +30,9 @@ export default function (server: Server, ctx: AppContext) {
31
30
 
32
31
  await ctx.accountManager.activateAccount(requester)
33
32
 
34
- const commitData = await ctx.actorStore.read(requester, async (store) => {
35
- const root = await store.repo.storage.getRootDetailed()
36
- const blocks = await store.repo.storage.getBlocks([root.cid])
37
- return {
38
- cid: root.cid,
39
- rev: root.rev,
40
- since: null,
41
- prev: null,
42
- newBlocks: blocks.blocks,
43
- relevantBlocks: blocks.blocks,
44
- removedCids: new CidSet(),
45
- ops: [],
46
- blobs: new CidSet(),
47
- prevData: null,
48
- }
49
- })
33
+ const syncData = await ctx.actorStore.read(requester, (store) =>
34
+ store.repo.getSyncEventData(),
35
+ )
50
36
 
51
37
  // @NOTE: we're over-emitting for now for backwards compatibility, can reduce this in the future
52
38
  const status = await ctx.accountManager.getAccountStatus(requester)
@@ -55,7 +41,7 @@ export default function (server: Server, ctx: AppContext) {
55
41
  requester,
56
42
  account.handle ?? INVALID_HANDLE,
57
43
  )
58
- await ctx.sequencer.sequenceCommit(requester, commitData)
44
+ await ctx.sequencer.sequenceSyncEvt(requester, syncData)
59
45
  },
60
46
  })
61
47
  }
@@ -5,14 +5,12 @@ import { DidDocument, MINUTE, check } from '@atproto/common'
5
5
  import { ExportableKeypair, Keypair, Secp256k1Keypair } from '@atproto/crypto'
6
6
  import { AtprotoData, ensureAtpDocument } from '@atproto/identity'
7
7
  import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
8
- import { AccountStatus } from '../../../../account-manager'
8
+ import { AccountStatus } from '../../../../account-manager/account-manager'
9
9
  import { AppContext } from '../../../../context'
10
- import {
11
- baseNormalizeAndValidate,
12
- normalizeAndValidateHandle,
13
- } from '../../../../handle'
10
+ import { baseNormalizeAndValidate } from '../../../../handle'
14
11
  import { Server } from '../../../../lexicon'
15
12
  import { InputSchema as CreateAccountInput } from '../../../../lexicon/types/com/atproto/server/createAccount'
13
+ import { syncEvtDataFromCommit } from '../../../../sequencer'
16
14
  import { safeResolveDidDoc } from './util'
17
15
 
18
16
  export default function (server: Server, ctx: AppContext) {
@@ -23,6 +21,9 @@ export default function (server: Server, ctx: AppContext) {
23
21
  },
24
22
  auth: ctx.authVerifier.userServiceAuthOptional,
25
23
  handler: async ({ input, auth, req }) => {
24
+ // @NOTE Until this code and the OAuthStore's `createAccount` are
25
+ // refactored together, any change made here must be reflected over there.
26
+
26
27
  const requester = auth.credentials?.did ?? null
27
28
  const {
28
29
  did,
@@ -60,7 +61,7 @@ export default function (server: Server, ctx: AppContext) {
60
61
 
61
62
  didDoc = await safeResolveDidDoc(ctx, did, true)
62
63
 
63
- creds = await ctx.accountManager.createAccount({
64
+ creds = await ctx.accountManager.createAccountAndSession({
64
65
  did,
65
66
  handle,
66
67
  email,
@@ -75,6 +76,10 @@ export default function (server: Server, ctx: AppContext) {
75
76
  await ctx.sequencer.sequenceIdentityEvt(did, handle)
76
77
  await ctx.sequencer.sequenceAccountEvt(did, AccountStatus.Active)
77
78
  await ctx.sequencer.sequenceCommit(did, commit)
79
+ await ctx.sequencer.sequenceSyncEvt(
80
+ did,
81
+ syncEvtDataFromCommit(commit),
82
+ )
78
83
  }
79
84
  await ctx.accountManager.updateRepoRoot(did, commit.cid, commit.rev)
80
85
  await ctx.actorStore.clearReservedKeypair(signingKey.did(), did)
@@ -183,11 +188,10 @@ const validateInputsForLocalPds = async (
183
188
  }
184
189
 
185
190
  // normalize & ensure valid handle
186
- const handle = await normalizeAndValidateHandle({
187
- ctx,
188
- handle: input.handle,
189
- did: input.did,
190
- })
191
+ const handle = await ctx.accountManager.normalizeAndValidateHandle(
192
+ input.handle,
193
+ { did: input.did },
194
+ )
191
195
 
192
196
  // check that the invite code still has uses
193
197
  if (ctx.cfg.invites.required && inviteCode) {
@@ -1,7 +1,7 @@
1
1
  import { DAY, MINUTE } from '@atproto/common'
2
2
  import { INVALID_HANDLE } from '@atproto/syntax'
3
3
  import { AuthRequiredError } from '@atproto/xrpc-server'
4
- import { formatAccountStatus } from '../../../../account-manager'
4
+ import { formatAccountStatus } from '../../../../account-manager/account-manager'
5
5
  import { AppContext } from '../../../../context'
6
6
  import { Server } from '../../../../lexicon'
7
7
  import { resultPassthru } from '../../../proxy'
@@ -1,6 +1,6 @@
1
1
  import { MINUTE } from '@atproto/common'
2
2
  import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
3
- import { AccountStatus } from '../../../../account-manager'
3
+ import { AccountStatus } from '../../../../account-manager/account-manager'
4
4
  import { AppContext } from '../../../../context'
5
5
  import { Server } from '../../../../lexicon'
6
6
 
@@ -48,8 +48,7 @@ export default function (server: Server, ctx: AppContext) {
48
48
  did,
49
49
  AccountStatus.Deleted,
50
50
  )
51
- const tombstoneSeq = await ctx.sequencer.sequenceTombstone(did)
52
- await ctx.sequencer.deleteAllForUser(did, [accountSeq, tombstoneSeq])
51
+ await ctx.sequencer.deleteAllForUser(did, [accountSeq])
53
52
  },
54
53
  })
55
54
  }
@@ -1,6 +1,6 @@
1
1
  import { INVALID_HANDLE } from '@atproto/syntax'
2
2
  import { InvalidRequestError } from '@atproto/xrpc-server'
3
- import { formatAccountStatus } from '../../../../account-manager'
3
+ import { formatAccountStatus } from '../../../../account-manager/account-manager'
4
4
  import { AuthScope } from '../../../../auth-verifier'
5
5
  import { AppContext } from '../../../../context'
6
6
  import { Server } from '../../../../lexicon'
@@ -1,6 +1,6 @@
1
1
  import { INVALID_HANDLE } from '@atproto/syntax'
2
2
  import { AuthRequiredError, InvalidRequestError } from '@atproto/xrpc-server'
3
- import { formatAccountStatus } from '../../../../account-manager'
3
+ import { formatAccountStatus } from '../../../../account-manager/account-manager'
4
4
  import { AppContext } from '../../../../context'
5
5
  import { softDeleted } from '../../../../db/util'
6
6
  import { Server } from '../../../../lexicon'
@@ -1,5 +1,4 @@
1
1
  import stream from 'node:stream'
2
- import { CID } from 'multiformats/cid'
3
2
  import { byteIterableToStream } from '@atproto/common'
4
3
  import * as repo from '@atproto/repo'
5
4
  import { InvalidRequestError } from '@atproto/xrpc-server'
@@ -1,4 +1,4 @@
1
- import { formatAccountStatus } from '../../../../account-manager'
1
+ import { formatAccountStatus } from '../../../../account-manager/account-manager'
2
2
  import { AppContext } from '../../../../context'
3
3
  import { Server } from '../../../../lexicon'
4
4
  import { assertRepoAvailability } from './util'
@@ -1,5 +1,5 @@
1
1
  import { InvalidRequestError } from '@atproto/xrpc-server'
2
- import { formatAccountStatus } from '../../../../account-manager'
2
+ import { formatAccountStatus } from '../../../../account-manager/account-manager'
3
3
  import { AppContext } from '../../../../context'
4
4
  import { Cursor, GenericKeyset, paginate } from '../../../../db/pagination'
5
5
  import { Server } from '../../../../lexicon'
@@ -45,9 +45,9 @@ export default function (server: Server, ctx: AppContext) {
45
45
  time: evt.time,
46
46
  ...evt.evt,
47
47
  }
48
- } else if (evt.type === 'handle') {
48
+ } else if (evt.type === 'sync') {
49
49
  yield {
50
- $type: '#handle',
50
+ $type: '#sync',
51
51
  seq: evt.seq,
52
52
  time: evt.time,
53
53
  ...evt.evt,
@@ -66,13 +66,6 @@ export default function (server: Server, ctx: AppContext) {
66
66
  time: evt.time,
67
67
  ...evt.evt,
68
68
  }
69
- } else if (evt.type === 'tombstone') {
70
- yield {
71
- $type: '#tombstone',
72
- seq: evt.seq,
73
- time: evt.time,
74
- ...evt.evt,
75
- }
76
69
  }
77
70
  }
78
71
  })
@@ -0,0 +1,24 @@
1
+ import { format } from 'node:util'
2
+ import { AtpAgent } from '@atproto/api'
3
+
4
+ export type AppViewOptions = {
5
+ url: string
6
+ did: string
7
+ cdnUrlPattern?: string
8
+ }
9
+
10
+ export class AppView {
11
+ public did: string
12
+ public agent: AtpAgent
13
+ private cdnUrlPattern?: string
14
+
15
+ constructor(options: AppViewOptions) {
16
+ this.did = options.did
17
+ this.agent = new AtpAgent({ service: options.url })
18
+ this.cdnUrlPattern = options.cdnUrlPattern
19
+ }
20
+
21
+ getImageUrl(pattern: string, did: string, cid: string): string | undefined {
22
+ if (this.cdnUrlPattern) return format(this.cdnUrlPattern, pattern, did, cid)
23
+ }
24
+ }
@@ -1,8 +1,9 @@
1
1
  import { Router } from 'express'
2
2
  import { oauthProtectedResourceMetadataSchema } from '@atproto/oauth-provider'
3
3
  import { AppContext } from './context'
4
+ import { oauthLogger } from './logger'
4
5
 
5
- export const createRouter = ({ authProvider, cfg }: AppContext): Router => {
6
+ export const createRouter = ({ oauthProvider, cfg }: AppContext): Router => {
6
7
  const router = Router()
7
8
 
8
9
  const oauthProtectedResourceMetadata =
@@ -28,8 +29,13 @@ export const createRouter = ({ authProvider, cfg }: AppContext): Router => {
28
29
  res.status(200).json(oauthProtectedResourceMetadata)
29
30
  })
30
31
 
31
- if (authProvider) {
32
- router.use(authProvider.createRouter())
32
+ if (oauthProvider) {
33
+ const oauthMiddleware = oauthProvider.httpHandler({
34
+ onError: (req, res, err, message) => {
35
+ oauthLogger.error({ err, req }, message)
36
+ },
37
+ })
38
+ router.use(oauthMiddleware)
33
39
  }
34
40
 
35
41
  return router
@@ -19,7 +19,7 @@ import {
19
19
  parseReqNsid,
20
20
  verifyJwt as verifyServiceJwt,
21
21
  } from '@atproto/xrpc-server'
22
- import { AccountManager } from './account-manager'
22
+ import { AccountManager } from './account-manager/account-manager'
23
23
  import { softDeleted } from './db'
24
24
 
25
25
  type ReqCtx = AuthVerifierContext | StreamAuthVerifierContext
@@ -1,7 +1,7 @@
1
1
  import assert from 'node:assert'
2
2
  import path from 'node:path'
3
3
  import { DAY, HOUR, SECOND } from '@atproto/common'
4
- import { Customization } from '@atproto/oauth-provider'
4
+ import { BrandingInput, HcaptchaConfig } from '@atproto/oauth-provider'
5
5
  import { ServerEnvironment } from './env'
6
6
 
7
7
  // off-config but still from env:
@@ -261,38 +261,49 @@ export const envToCfg = (env: ServerEnvironment): ServerConfig => {
261
261
  : {
262
262
  issuer: serviceCfg.publicUrl,
263
263
  provider: {
264
- customization: {
264
+ hcaptcha:
265
+ env.hcaptchaSiteKey &&
266
+ env.hcaptchaSecretKey &&
267
+ env.hcaptchaTokenSalt
268
+ ? {
269
+ siteKey: env.hcaptchaSiteKey,
270
+ secretKey: env.hcaptchaSecretKey,
271
+ tokenSalt: env.hcaptchaTokenSalt,
272
+ }
273
+ : undefined,
274
+ branding: {
265
275
  name: env.serviceName ?? 'Personal PDS',
266
276
  logo: env.logoUrl,
267
277
  colors: {
268
278
  brand: env.brandColor,
269
279
  error: env.errorColor,
280
+ success: env.successColor,
270
281
  warning: env.warningColor,
271
282
  },
272
283
  links: [
273
284
  {
274
- title: 'Home',
285
+ title: { en: 'Home', fr: 'Accueil' },
275
286
  href: env.homeUrl,
276
- rel: 'bookmark',
287
+ rel: 'canonical' as const, // Prevents login page from being indexed
277
288
  },
278
289
  {
279
- title: 'Terms of Service',
290
+ title: { en: 'Terms of Service' },
280
291
  href: env.termsOfServiceUrl,
281
- rel: 'terms-of-service',
292
+ rel: 'terms-of-service' as const,
282
293
  },
283
294
  {
284
- title: 'Privacy Policy',
295
+ title: { en: 'Privacy Policy' },
285
296
  href: env.privacyPolicyUrl,
286
- rel: 'privacy-policy',
297
+ rel: 'privacy-policy' as const,
287
298
  },
288
299
  {
289
- title: 'Support',
300
+ title: { en: 'Support' },
290
301
  href: env.supportUrl,
291
- rel: 'help',
302
+ rel: 'help' as const,
292
303
  },
293
304
  ].filter(
294
- (f): f is typeof f & { href: NonNullable<(typeof f)['href']> } =>
295
- f.href != null,
305
+ <T extends { href?: string }>(f: T): f is T & { href: string } =>
306
+ f.href != null && f.href !== '',
296
307
  ),
297
308
  },
298
309
  },
@@ -435,7 +446,8 @@ export type OAuthConfig = {
435
446
  provider:
436
447
  | false
437
448
  | {
438
- customization: Customization
449
+ hcaptcha?: HcaptchaConfig
450
+ branding: BrandingInput
439
451
  }
440
452
  }
441
453
 
package/src/config/env.ts CHANGED
@@ -18,10 +18,16 @@ export const readEnv = (): ServerEnvironment => {
18
18
  blobUploadLimit: envInt('PDS_BLOB_UPLOAD_LIMIT'),
19
19
  devMode: envBool('PDS_DEV_MODE'),
20
20
 
21
+ // OAuth
22
+ hcaptchaSiteKey: envStr('PDS_HCAPTCHA_SITE_KEY'),
23
+ hcaptchaSecretKey: envStr('PDS_HCAPTCHA_SECRET_KEY'),
24
+ hcaptchaTokenSalt: envStr('PDS_HCAPTCHA_TOKEN_SALT'),
25
+
21
26
  // branding
22
27
  brandColor: envStr('PDS_PRIMARY_COLOR'),
23
28
  errorColor: envStr('PDS_ERROR_COLOR'),
24
29
  warningColor: envStr('PDS_WARNING_COLOR'),
30
+ successColor: envStr('PDS_SUCCESS_COLOR'),
25
31
 
26
32
  // database
27
33
  dataDirectory: envStr('PDS_DATA_DIRECTORY'),
@@ -150,10 +156,16 @@ export type ServerEnvironment = {
150
156
  blobUploadLimit?: number
151
157
  devMode?: boolean
152
158
 
159
+ // OAuth
160
+ hcaptchaSiteKey?: string
161
+ hcaptchaSecretKey?: string
162
+ hcaptchaTokenSalt?: string
163
+
153
164
  // branding
154
165
  brandColor?: string
155
166
  errorColor?: string
156
167
  warningColor?: string
168
+ successColor?: string
157
169
 
158
170
  // database
159
171
  dataDirectory?: string
package/src/context.ts CHANGED
@@ -8,7 +8,12 @@ import { AtpAgent } from '@atproto/api'
8
8
  import { KmsKeypair, S3BlobStore } from '@atproto/aws'
9
9
  import * as crypto from '@atproto/crypto'
10
10
  import { IdResolver } from '@atproto/identity'
11
- import { JoseKey, OAuthVerifier } from '@atproto/oauth-provider'
11
+ import {
12
+ AccessTokenType,
13
+ JoseKey,
14
+ OAuthProvider,
15
+ OAuthVerifier,
16
+ } from '@atproto/oauth-provider'
12
17
  import { BlobStore } from '@atproto/repo'
13
18
  import {
14
19
  RateLimiter,
@@ -24,7 +29,8 @@ import {
24
29
  safeFetchWrap,
25
30
  unicastLookup,
26
31
  } from '@atproto-labs/fetch-node'
27
- import { AccountManager } from './account-manager'
32
+ import { AccountManager } from './account-manager/account-manager'
33
+ import { OAuthStore } from './account-manager/oauth-store'
28
34
  import { ActorStore } from './actor-store/actor-store'
29
35
  import { authPassthru, forwardedFor } from './api/proxy'
30
36
  import {
@@ -42,7 +48,6 @@ import { ImageUrlBuilder } from './image/image-url-builder'
42
48
  import { fetchLogger } from './logger'
43
49
  import { ServerMailer } from './mailer'
44
50
  import { ModerationMailer } from './mailer/moderation'
45
- import { PdsOAuthProvider } from './oauth/provider'
46
51
  import { LocalViewer, LocalViewerCreator } from './read-after-write/viewer'
47
52
  import { getRedisClient } from './redis'
48
53
  import { Sequencer } from './sequencer'
@@ -68,7 +73,7 @@ export type AppContextOptions = {
68
73
  entrywayAgent?: AtpAgent
69
74
  proxyAgent: undici.Dispatcher
70
75
  safeFetch: Fetch
71
- authProvider?: PdsOAuthProvider
76
+ oauthProvider?: OAuthProvider
72
77
  authVerifier: AuthVerifier
73
78
  plcRotationKey: crypto.Keypair
74
79
  cfg: ServerConfig
@@ -96,7 +101,7 @@ export class AppContext {
96
101
  public proxyAgent: undici.Dispatcher
97
102
  public safeFetch: Fetch
98
103
  public authVerifier: AuthVerifier
99
- public authProvider?: PdsOAuthProvider
104
+ public oauthProvider?: OAuthProvider
100
105
  public plcRotationKey: crypto.Keypair
101
106
  public cfg: ServerConfig
102
107
 
@@ -122,7 +127,7 @@ export class AppContext {
122
127
  this.proxyAgent = opts.proxyAgent
123
128
  this.safeFetch = opts.safeFetch
124
129
  this.authVerifier = opts.authVerifier
125
- this.authProvider = opts.authProvider
130
+ this.oauthProvider = opts.oauthProvider
126
131
  this.plcRotationKey = opts.plcRotationKey
127
132
  this.cfg = opts.cfg
128
133
  }
@@ -247,13 +252,11 @@ export class AppContext {
247
252
  })
248
253
 
249
254
  const accountManager = new AccountManager(
250
- actorStore,
251
- imageUrlBuilder,
252
- backgroundQueue,
253
- cfg.db.accountDbLoc,
255
+ idResolver,
254
256
  jwtSecretKey,
255
257
  cfg.service.did,
256
- cfg.db.disableWalAutoCheckpoint,
258
+ cfg.identity.serviceHandleDomains,
259
+ cfg.db,
257
260
  )
258
261
  await accountManager.migrateOrThrow()
259
262
 
@@ -323,26 +326,43 @@ export class AppContext {
323
326
  logError: false,
324
327
  })
325
328
 
326
- const authProvider = cfg.oauth.provider
327
- ? new PdsOAuthProvider({
329
+ const oauthProvider = cfg.oauth.provider
330
+ ? new OAuthProvider({
328
331
  issuer: cfg.oauth.issuer,
329
- keyset: [
330
- // Note: OpenID compatibility would require an RS256 private key in this list
331
- await JoseKey.fromKeyLike(jwtSecretKey, undefined, 'HS256'),
332
- ],
333
- accountManager,
332
+ keyset: [await JoseKey.fromKeyLike(jwtSecretKey, undefined, 'HS256')],
333
+ store: new OAuthStore(
334
+ accountManager,
335
+ actorStore,
336
+ imageUrlBuilder,
337
+ backgroundQueue,
338
+ mailer,
339
+ sequencer,
340
+ plcClient,
341
+ plcRotationKey,
342
+ cfg.service.publicUrl,
343
+ cfg.identity.recoveryDidKey,
344
+ ),
334
345
  redis: redisScratch,
335
346
  dpopSecret: secrets.dpopSecret,
336
- customization: cfg.oauth.provider.customization,
347
+ inviteCodeRequired: cfg.invites.required,
348
+ availableUserDomains: cfg.identity.serviceHandleDomains,
349
+ hcaptcha: cfg.oauth.provider.hcaptcha,
350
+ branding: cfg.oauth.provider.branding,
337
351
  safeFetch,
338
- // @TODO: Make this configurable. The legacy implementation used to
339
- // blindly trust the X-Forwarded-For header.
340
- trustProxy: (_addr: string, _i: number) => true,
352
+ metadata: {
353
+ protected_resources: [new URL(cfg.oauth.issuer).origin],
354
+ scopes_supported: ['transition:generic', 'transition:chat.bsky'],
355
+ },
356
+ // If the PDS is both an authorization server & resource server (no
357
+ // entryway), there is no need to use JWTs as access tokens. Instead,
358
+ // the PDS can use tokenId as access tokens. This allows the PDS to
359
+ // always use up-to-date token data from the token store.
360
+ accessTokenType: AccessTokenType.id,
341
361
  })
342
362
  : undefined
343
363
 
344
364
  const oauthVerifier: OAuthVerifier =
345
- authProvider ?? // OAuthProvider extends OAuthVerifier
365
+ oauthProvider ?? // OAuthProvider extends OAuthVerifier
346
366
  new OAuthVerifier({
347
367
  issuer: cfg.oauth.issuer,
348
368
  keyset: [await JoseKey.fromKeyLike(jwtPublicKey!, undefined, 'ES256K')],
@@ -388,7 +408,7 @@ export class AppContext {
388
408
  proxyAgent,
389
409
  safeFetch,
390
410
  authVerifier,
391
- authProvider,
411
+ oauthProvider,
392
412
  plcRotationKey,
393
413
  cfg,
394
414
  ...(overrides ?? {}),
@@ -1,61 +1,15 @@
1
- import * as ident from '@atproto/syntax'
1
+ import {
2
+ InvalidHandleError,
3
+ normalizeAndEnsureValidHandle,
4
+ } from '@atproto/syntax'
2
5
  import { InvalidRequestError } from '@atproto/xrpc-server'
3
- import { AppContext } from '../context'
4
- import { hasExplicitSlur } from './explicit-slurs'
5
6
  import { reservedSubdomains } from './reserved'
6
7
 
7
- export const normalizeAndValidateHandle = async (opts: {
8
- ctx: AppContext
9
- handle: string
10
- did?: string
11
- allowReserved?: boolean
12
- }): Promise<string> => {
13
- const { ctx, did, allowReserved } = opts
14
- // base formatting validation
15
- const handle = baseNormalizeAndValidate(opts.handle)
16
- // tld validation
17
- if (!ident.isValidTld(handle)) {
18
- throw new InvalidRequestError(
19
- 'Handle TLD is invalid or disallowed',
20
- 'InvalidHandle',
21
- )
22
- }
23
- // slur check
24
- if (hasExplicitSlur(handle)) {
25
- throw new InvalidRequestError(
26
- 'Inappropriate language in handle',
27
- 'InvalidHandle',
28
- )
29
- }
30
- if (isServiceDomain(handle, ctx.cfg.identity.serviceHandleDomains)) {
31
- // verify constraints on a service domain
32
- ensureHandleServiceConstraints(
33
- handle,
34
- ctx.cfg.identity.serviceHandleDomains,
35
- allowReserved,
36
- )
37
- } else {
38
- if (opts.did === undefined) {
39
- throw new InvalidRequestError(
40
- 'Not a supported handle domain',
41
- 'UnsupportedDomain',
42
- )
43
- }
44
- // verify resolution of a non-service domain
45
- const resolvedDid = await ctx.idResolver.handle.resolve(handle)
46
- if (resolvedDid !== did) {
47
- throw new InvalidRequestError('External handle did not resolve to DID')
48
- }
49
- }
50
- return handle
51
- }
52
-
53
8
  export const baseNormalizeAndValidate = (handle: string) => {
54
9
  try {
55
- const normalized = ident.normalizeAndEnsureValidHandle(handle)
56
- return normalized
10
+ return normalizeAndEnsureValidHandle(handle)
57
11
  } catch (err) {
58
- if (err instanceof ident.InvalidHandleError) {
12
+ if (err instanceof InvalidHandleError) {
59
13
  throw new InvalidRequestError(err.message, 'InvalidHandle')
60
14
  }
61
15
  throw err
@@ -0,0 +1,16 @@
1
+ import { AppView } from '../app-view'
2
+ import { ids } from '../lexicon/lexicons'
3
+
4
+ export class ImageUrlBuilder {
5
+ constructor(
6
+ readonly pdsHostname: string,
7
+ readonly appview?: AppView,
8
+ ) {}
9
+
10
+ build(pattern: string, did: string, cid: string): string {
11
+ return (
12
+ this.appview?.getImageUrl(pattern, did, cid) ??
13
+ `https://${this.pdsHostname}/xrpc/${ids.ComAtprotoSyncGetBlob}?did=${did}&cid=${cid}`
14
+ )
15
+ }
16
+ }
@@ -24,8 +24,11 @@ import * as ComAtprotoAdminUpdateAccountHandle from './types/com/atproto/admin/u
24
24
  import * as ComAtprotoAdminUpdateAccountPassword from './types/com/atproto/admin/updateAccountPassword.js'
25
25
  import * as ComAtprotoAdminUpdateSubjectStatus from './types/com/atproto/admin/updateSubjectStatus.js'
26
26
  import * as ComAtprotoIdentityGetRecommendedDidCredentials from './types/com/atproto/identity/getRecommendedDidCredentials.js'
27
+ import * as ComAtprotoIdentityRefreshIdentity from './types/com/atproto/identity/refreshIdentity.js'
27
28
  import * as ComAtprotoIdentityRequestPlcOperationSignature from './types/com/atproto/identity/requestPlcOperationSignature.js'
29
+ import * as ComAtprotoIdentityResolveDid from './types/com/atproto/identity/resolveDid.js'
28
30
  import * as ComAtprotoIdentityResolveHandle from './types/com/atproto/identity/resolveHandle.js'
31
+ import * as ComAtprotoIdentityResolveIdentity from './types/com/atproto/identity/resolveIdentity.js'
29
32
  import * as ComAtprotoIdentitySignPlcOperation from './types/com/atproto/identity/signPlcOperation.js'
30
33
  import * as ComAtprotoIdentitySubmitPlcOperation from './types/com/atproto/identity/submitPlcOperation.js'
31
34
  import * as ComAtprotoIdentityUpdateHandle from './types/com/atproto/identity/updateHandle.js'
@@ -480,6 +483,17 @@ export class ComAtprotoIdentityNS {
480
483
  return this._server.xrpc.method(nsid, cfg)
481
484
  }
482
485
 
486
+ refreshIdentity<AV extends AuthVerifier>(
487
+ cfg: ConfigOf<
488
+ AV,
489
+ ComAtprotoIdentityRefreshIdentity.Handler<ExtractAuth<AV>>,
490
+ ComAtprotoIdentityRefreshIdentity.HandlerReqCtx<ExtractAuth<AV>>
491
+ >,
492
+ ) {
493
+ const nsid = 'com.atproto.identity.refreshIdentity' // @ts-ignore
494
+ return this._server.xrpc.method(nsid, cfg)
495
+ }
496
+
483
497
  requestPlcOperationSignature<AV extends AuthVerifier>(
484
498
  cfg: ConfigOf<
485
499
  AV,
@@ -493,6 +507,17 @@ export class ComAtprotoIdentityNS {
493
507
  return this._server.xrpc.method(nsid, cfg)
494
508
  }
495
509
 
510
+ resolveDid<AV extends AuthVerifier>(
511
+ cfg: ConfigOf<
512
+ AV,
513
+ ComAtprotoIdentityResolveDid.Handler<ExtractAuth<AV>>,
514
+ ComAtprotoIdentityResolveDid.HandlerReqCtx<ExtractAuth<AV>>
515
+ >,
516
+ ) {
517
+ const nsid = 'com.atproto.identity.resolveDid' // @ts-ignore
518
+ return this._server.xrpc.method(nsid, cfg)
519
+ }
520
+
496
521
  resolveHandle<AV extends AuthVerifier>(
497
522
  cfg: ConfigOf<
498
523
  AV,
@@ -504,6 +529,17 @@ export class ComAtprotoIdentityNS {
504
529
  return this._server.xrpc.method(nsid, cfg)
505
530
  }
506
531
 
532
+ resolveIdentity<AV extends AuthVerifier>(
533
+ cfg: ConfigOf<
534
+ AV,
535
+ ComAtprotoIdentityResolveIdentity.Handler<ExtractAuth<AV>>,
536
+ ComAtprotoIdentityResolveIdentity.HandlerReqCtx<ExtractAuth<AV>>
537
+ >,
538
+ ) {
539
+ const nsid = 'com.atproto.identity.resolveIdentity' // @ts-ignore
540
+ return this._server.xrpc.method(nsid, cfg)
541
+ }
542
+
507
543
  signPlcOperation<AV extends AuthVerifier>(
508
544
  cfg: ConfigOf<
509
545
  AV,