@atproto/pds 0.5.1 → 0.5.2

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 (217) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/account-manager/account-manager.d.ts +35 -4
  3. package/dist/account-manager/account-manager.d.ts.map +1 -1
  4. package/dist/account-manager/account-manager.js +67 -6
  5. package/dist/account-manager/account-manager.js.map +1 -1
  6. package/dist/account-manager/helpers/account.d.ts +1 -1
  7. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  8. package/dist/account-manager/helpers/account.js +10 -4
  9. package/dist/account-manager/helpers/account.js.map +1 -1
  10. package/dist/account-manager/oauth-store.d.ts +2 -1
  11. package/dist/account-manager/oauth-store.d.ts.map +1 -1
  12. package/dist/account-manager/oauth-store.js +61 -12
  13. package/dist/account-manager/oauth-store.js.map +1 -1
  14. package/dist/actor-store/record/reader.d.ts +1 -1
  15. package/dist/actor-store/record/reader.d.ts.map +1 -1
  16. package/dist/actor-store/record/reader.js.map +1 -1
  17. package/dist/api/com/atproto/admin/updateAccountHandle.d.ts.map +1 -1
  18. package/dist/api/com/atproto/admin/updateAccountHandle.js +33 -43
  19. package/dist/api/com/atproto/admin/updateAccountHandle.js.map +1 -1
  20. package/dist/api/com/atproto/identity/updateHandle.d.ts.map +1 -1
  21. package/dist/api/com/atproto/identity/updateHandle.js +39 -61
  22. package/dist/api/com/atproto/identity/updateHandle.js.map +1 -1
  23. package/dist/api/com/atproto/repo/getRecord.js +3 -3
  24. package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
  25. package/dist/api/com/atproto/repo/putRecord.js +2 -2
  26. package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
  27. package/dist/context.d.ts.map +1 -1
  28. package/dist/context.js +2 -2
  29. package/dist/context.js.map +1 -1
  30. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +8 -0
  31. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  32. package/dist/lexicons/app/bsky/actor/defs.defs.js +3 -0
  33. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  34. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
  35. package/dist/lexicons/app/bsky/actor/status.defs.d.ts.map +1 -1
  36. package/dist/lexicons/app/bsky/draft/defs.defs.d.ts +22 -0
  37. package/dist/lexicons/app/bsky/draft/defs.defs.d.ts.map +1 -1
  38. package/dist/lexicons/app/bsky/draft/defs.defs.js +11 -0
  39. package/dist/lexicons/app/bsky/draft/defs.defs.js.map +1 -1
  40. package/dist/lexicons/app/bsky/embed/gallery.d.ts +3 -0
  41. package/dist/lexicons/app/bsky/embed/gallery.d.ts.map +1 -0
  42. package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts +130 -0
  43. package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts.map +1 -0
  44. package/dist/lexicons/app/bsky/embed/gallery.defs.js +47 -0
  45. package/dist/lexicons/app/bsky/embed/gallery.defs.js.map +1 -0
  46. package/dist/lexicons/app/bsky/embed/gallery.js +6 -0
  47. package/dist/lexicons/app/bsky/embed/gallery.js.map +1 -0
  48. package/dist/lexicons/app/bsky/embed/record.defs.d.ts +2 -1
  49. package/dist/lexicons/app/bsky/embed/record.defs.d.ts.map +1 -1
  50. package/dist/lexicons/app/bsky/embed/record.defs.js +2 -0
  51. package/dist/lexicons/app/bsky/embed/record.defs.js.map +1 -1
  52. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts +13 -12
  53. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts.map +1 -1
  54. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js +3 -0
  55. package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js.map +1 -1
  56. package/dist/lexicons/app/bsky/embed.d.ts +1 -0
  57. package/dist/lexicons/app/bsky/embed.d.ts.map +1 -1
  58. package/dist/lexicons/app/bsky/embed.js +1 -0
  59. package/dist/lexicons/app/bsky/embed.js.map +1 -1
  60. package/dist/lexicons/app/bsky/feed/defs.defs.d.ts +2 -1
  61. package/dist/lexicons/app/bsky/feed/defs.defs.d.ts.map +1 -1
  62. package/dist/lexicons/app/bsky/feed/defs.defs.js +2 -0
  63. package/dist/lexicons/app/bsky/feed/defs.defs.js.map +1 -1
  64. package/dist/lexicons/app/bsky/feed/generator.defs.d.ts.map +1 -1
  65. package/dist/lexicons/app/bsky/feed/like.defs.d.ts.map +1 -1
  66. package/dist/lexicons/app/bsky/feed/post.defs.d.ts +12 -11
  67. package/dist/lexicons/app/bsky/feed/post.defs.d.ts.map +1 -1
  68. package/dist/lexicons/app/bsky/feed/post.defs.js +2 -0
  69. package/dist/lexicons/app/bsky/feed/post.defs.js.map +1 -1
  70. package/dist/lexicons/app/bsky/feed/postgate.defs.d.ts.map +1 -1
  71. package/dist/lexicons/app/bsky/feed/repost.defs.d.ts.map +1 -1
  72. package/dist/lexicons/app/bsky/feed/threadgate.defs.d.ts.map +1 -1
  73. package/dist/lexicons/app/bsky/graph/block.defs.d.ts.map +1 -1
  74. package/dist/lexicons/app/bsky/graph/follow.defs.d.ts.map +1 -1
  75. package/dist/lexicons/app/bsky/graph/list.defs.d.ts.map +1 -1
  76. package/dist/lexicons/app/bsky/graph/listblock.defs.d.ts.map +1 -1
  77. package/dist/lexicons/app/bsky/graph/listitem.defs.d.ts.map +1 -1
  78. package/dist/lexicons/app/bsky/graph/starterpack.defs.d.ts.map +1 -1
  79. package/dist/lexicons/app/bsky/graph/verification.defs.d.ts.map +1 -1
  80. package/dist/lexicons/app/bsky/labeler/service.defs.d.ts.map +1 -1
  81. package/dist/lexicons/app/bsky/notification/declaration.defs.d.ts.map +1 -1
  82. package/dist/lexicons/chat/bsky/actor/declaration.defs.d.ts.map +1 -1
  83. package/dist/lexicons/chat/bsky/authFullChatClient.defs.d.ts.map +1 -1
  84. package/dist/lexicons/chat/bsky/authFullChatClient.defs.js +1 -0
  85. package/dist/lexicons/chat/bsky/authFullChatClient.defs.js.map +1 -1
  86. package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +53 -14
  87. package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
  88. package/dist/lexicons/chat/bsky/convo/defs.defs.js +33 -5
  89. package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
  90. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts +1 -1
  91. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts.map +1 -1
  92. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js +1 -0
  93. package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js.map +1 -1
  94. package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts +2 -2
  95. package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts.map +1 -1
  96. package/dist/lexicons/chat/bsky/convo/getLog.defs.js +3 -0
  97. package/dist/lexicons/chat/bsky/convo/getLog.defs.js.map +1 -1
  98. package/dist/lexicons/chat/bsky/embed/joinLink.d.ts +3 -0
  99. package/dist/lexicons/chat/bsky/embed/joinLink.d.ts.map +1 -0
  100. package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts +99 -0
  101. package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts.map +1 -0
  102. package/dist/lexicons/chat/bsky/embed/joinLink.defs.js +28 -0
  103. package/dist/lexicons/chat/bsky/embed/joinLink.defs.js.map +1 -0
  104. package/dist/lexicons/chat/bsky/embed/joinLink.js +6 -0
  105. package/dist/lexicons/chat/bsky/embed/joinLink.js.map +1 -0
  106. package/dist/lexicons/chat/bsky/embed.d.ts +2 -0
  107. package/dist/lexicons/chat/bsky/embed.d.ts.map +1 -0
  108. package/dist/lexicons/chat/bsky/embed.js +5 -0
  109. package/dist/lexicons/chat/bsky/embed.js.map +1 -0
  110. package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts +1 -1
  111. package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts.map +1 -1
  112. package/dist/lexicons/chat/bsky/group/addMembers.defs.js +1 -0
  113. package/dist/lexicons/chat/bsky/group/addMembers.defs.js.map +1 -1
  114. package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts +1 -1
  115. package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts.map +1 -1
  116. package/dist/lexicons/chat/bsky/group/createGroup.defs.js +1 -0
  117. package/dist/lexicons/chat/bsky/group/createGroup.defs.js.map +1 -1
  118. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts +1 -1
  119. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts.map +1 -1
  120. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js +1 -1
  121. package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js.map +1 -1
  122. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts +3 -0
  123. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts.map +1 -0
  124. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts +20 -0
  125. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts.map +1 -0
  126. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js +19 -0
  127. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js.map +1 -0
  128. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js +6 -0
  129. package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js.map +1 -0
  130. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts +3 -0
  131. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts.map +1 -0
  132. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts +20 -0
  133. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts.map +1 -0
  134. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js +18 -0
  135. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js.map +1 -0
  136. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js +6 -0
  137. package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js.map +1 -0
  138. package/dist/lexicons/chat/bsky/group.d.ts +2 -0
  139. package/dist/lexicons/chat/bsky/group.d.ts.map +1 -1
  140. package/dist/lexicons/chat/bsky/group.js +2 -0
  141. package/dist/lexicons/chat/bsky/group.js.map +1 -1
  142. package/dist/lexicons/chat/bsky/moderation/defs.d.ts +2 -0
  143. package/dist/lexicons/chat/bsky/moderation/defs.d.ts.map +1 -0
  144. package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts +58 -0
  145. package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts.map +1 -0
  146. package/dist/lexicons/chat/bsky/moderation/defs.defs.js +38 -0
  147. package/dist/lexicons/chat/bsky/moderation/defs.defs.js.map +1 -0
  148. package/dist/lexicons/chat/bsky/moderation/defs.js +5 -0
  149. package/dist/lexicons/chat/bsky/moderation/defs.js.map +1 -0
  150. package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts +3 -0
  151. package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts.map +1 -0
  152. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts +22 -0
  153. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts.map +1 -0
  154. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js +18 -0
  155. package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js.map +1 -0
  156. package/dist/lexicons/chat/bsky/moderation/getConvo.js +6 -0
  157. package/dist/lexicons/chat/bsky/moderation/getConvo.js.map +1 -0
  158. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts +3 -0
  159. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts.map +1 -0
  160. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts +28 -0
  161. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts.map +1 -0
  162. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js +24 -0
  163. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js.map +1 -0
  164. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js +6 -0
  165. package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js.map +1 -0
  166. package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts +3 -0
  167. package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts.map +1 -0
  168. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts +22 -0
  169. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts.map +1 -0
  170. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js +22 -0
  171. package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js.map +1 -0
  172. package/dist/lexicons/chat/bsky/moderation/getConvos.js +6 -0
  173. package/dist/lexicons/chat/bsky/moderation/getConvos.js.map +1 -0
  174. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts +20 -2
  175. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts.map +1 -1
  176. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js +11 -0
  177. package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js.map +1 -1
  178. package/dist/lexicons/chat/bsky/moderation.d.ts +4 -0
  179. package/dist/lexicons/chat/bsky/moderation.d.ts.map +1 -1
  180. package/dist/lexicons/chat/bsky/moderation.js +4 -0
  181. package/dist/lexicons/chat/bsky/moderation.js.map +1 -1
  182. package/dist/lexicons/chat/bsky.d.ts +1 -0
  183. package/dist/lexicons/chat/bsky.d.ts.map +1 -1
  184. package/dist/lexicons/chat/bsky.js +1 -0
  185. package/dist/lexicons/chat/bsky.js.map +1 -1
  186. package/dist/lexicons/com/atproto/lexicon/schema.defs.d.ts.map +1 -1
  187. package/dist/lexicons/com/germnetwork/declaration.defs.d.ts.map +1 -1
  188. package/dist/lexicons/site/standard/document.defs.d.ts.map +1 -1
  189. package/dist/lexicons/site/standard/graph/recommend.defs.d.ts.map +1 -1
  190. package/dist/lexicons/site/standard/graph/subscription.defs.d.ts.map +1 -1
  191. package/dist/lexicons/site/standard/publication.defs.d.ts.map +1 -1
  192. package/dist/lexicons/site/standard/theme/basic.defs.d.ts.map +1 -1
  193. package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts +11 -3
  194. package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts.map +1 -1
  195. package/dist/lexicons/tools/ozone/moderation/defs.defs.js +9 -0
  196. package/dist/lexicons/tools/ozone/moderation/defs.defs.js.map +1 -1
  197. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts +2 -2
  198. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts.map +1 -1
  199. package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.js.map +1 -1
  200. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts +2 -2
  201. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts.map +1 -1
  202. package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.js.map +1 -1
  203. package/dist/read-after-write/viewer.d.ts +2 -2
  204. package/package.json +10 -10
  205. package/src/account-manager/account-manager.ts +105 -7
  206. package/src/account-manager/helpers/account.ts +15 -7
  207. package/src/account-manager/oauth-store.ts +76 -18
  208. package/src/actor-store/record/reader.ts +1 -1
  209. package/src/api/com/atproto/admin/updateAccountHandle.ts +37 -46
  210. package/src/api/com/atproto/identity/updateHandle.ts +45 -76
  211. package/src/api/com/atproto/repo/getRecord.ts +3 -3
  212. package/src/api/com/atproto/repo/putRecord.ts +2 -2
  213. package/src/context.ts +12 -9
  214. package/tests/_puppeteer.ts +8 -2
  215. package/tests/account-manager.test.ts +123 -50
  216. package/tests/oauth.test.ts +5 -5
  217. package/tsconfig.build.tsbuildinfo +1 -1
@@ -1,97 +1,66 @@
1
1
  import { DAY, MINUTE } from '@atproto/common'
2
- import { InvalidRequestError, Server } from '@atproto/xrpc-server'
2
+ import { MethodRateLimit, Server } from '@atproto/xrpc-server'
3
+ import { AccessOutput, OAuthOutput } from '../../../../auth-output.js'
3
4
  import { AppContext } from '../../../../context.js'
4
5
  import { com } from '../../../../lexicons/index.js'
5
- import { httpLogger } from '../../../../logger.js'
6
6
 
7
7
  export default function (server: Server, ctx: AppContext) {
8
- server.add(com.atproto.identity.updateHandle, {
9
- auth: ctx.authVerifier.authorization({
10
- checkTakedown: true,
11
- authorize: (permissions) => {
12
- permissions.assertIdentity({ attr: 'handle' })
13
- },
14
- }),
15
- rateLimit: [
16
- {
17
- durationMs: 5 * MINUTE,
18
- points: 10,
19
- calcKey: ({ auth }) => auth.credentials.did,
20
- },
21
- {
22
- durationMs: DAY,
23
- points: 50,
24
- calcKey: ({ auth }) => auth.credentials.did,
25
- },
26
- ],
27
- handler: async ({ auth, input, req }) => {
28
- const requester = auth.credentials.did
8
+ const { entrywayClient } = ctx
9
+
10
+ const auth = ctx.authVerifier.authorization({
11
+ checkTakedown: true,
12
+ authorize: (permissions) => {
13
+ permissions.assertIdentity({ attr: 'handle' })
14
+ },
15
+ })
29
16
 
30
- if (ctx.entrywayClient) {
17
+ const rateLimit: MethodRateLimit<AccessOutput | OAuthOutput> = [
18
+ {
19
+ durationMs: 5 * MINUTE,
20
+ points: 10,
21
+ calcKey: ({ auth }) => auth.credentials.did,
22
+ },
23
+ {
24
+ durationMs: DAY,
25
+ points: 50,
26
+ calcKey: ({ auth }) => auth.credentials.did,
27
+ },
28
+ ]
29
+
30
+ if (entrywayClient) {
31
+ server.add(com.atproto.identity.updateHandle, {
32
+ auth,
33
+ rateLimit,
34
+ handler: async ({ auth, input, req }) => {
31
35
  const { headers } = await ctx.entrywayAuthHeaders(
32
36
  req,
33
37
  auth.credentials.did,
34
38
  com.atproto.identity.updateHandle.$lxm,
35
39
  )
36
- // the full flow is:
40
+
41
+ // The full flow is:
37
42
  // -> entryway(identity.updateHandle) [update handle, submit plc op]
38
43
  // -> pds(admin.updateAccountHandle) [track handle, sequence handle update]
39
- await ctx.entrywayClient.xrpc(com.atproto.identity.updateHandle, {
44
+ await entrywayClient.xrpc(com.atproto.identity.updateHandle, {
40
45
  headers,
41
46
  body: {
42
47
  handle: input.body.handle,
43
48
  // @ts-expect-error "did" is not in the schema
44
- did: requester,
49
+ did: auth.credentials.did,
45
50
  },
46
51
  })
47
- return
48
- }
49
-
50
- const handle = await ctx.accountManager.normalizeAndValidateHandle(
51
- input.body.handle,
52
- { did: requester },
53
- )
54
-
55
- // Pessimistic check to handle spam: also enforced by updateHandle() and the db.
56
- const account = await ctx.accountManager.getAccount(handle, {
57
- includeDeactivated: true,
58
- })
59
-
60
- if (!account) {
61
- if (requester.startsWith('did:plc:')) {
62
- await ctx.plcClient.updateHandle(
63
- requester,
64
- ctx.plcRotationKey,
65
- handle,
66
- )
67
- } else {
68
- const resolved = await ctx.idResolver.did.resolveAtprotoData(
69
- requester,
70
- true,
71
- )
72
- if (resolved.handle !== handle) {
73
- throw new InvalidRequestError(
74
- 'DID is not properly configured for handle',
75
- )
76
- }
77
- }
78
- await ctx.accountManager.updateHandle(requester, handle)
79
- } else {
80
- // if we found an account with matching handle, check if it is the same as requester
81
- // if so emit an identity event, otherwise error.
82
- if (account.did !== requester) {
83
- throw new InvalidRequestError(`Handle already taken: ${handle}`)
84
- }
85
- }
86
-
87
- try {
88
- await ctx.sequencer.sequenceIdentityEvt(requester, handle)
89
- } catch (err) {
90
- httpLogger.error(
91
- { err, did: requester, handle },
92
- 'failed to sequence handle update',
52
+ },
53
+ })
54
+ } else {
55
+ server.add(com.atproto.identity.updateHandle, {
56
+ auth,
57
+ rateLimit,
58
+ handler: async ({ auth, input }) => {
59
+ await ctx.accountManager.updateHandle(
60
+ auth.credentials.did,
61
+ input.body.handle,
93
62
  )
94
- }
95
- },
96
- })
63
+ },
64
+ })
65
+ }
97
66
  }
@@ -1,4 +1,4 @@
1
- import { AtUri } from '@atproto/syntax'
1
+ import { atUri } from '@atproto/lex'
2
2
  import { InvalidRequestError, Server } from '@atproto/xrpc-server'
3
3
  import { AppContext } from '../../../../context.js'
4
4
  import { com } from '../../../../lexicons/index.js'
@@ -11,7 +11,7 @@ export default function (server: Server, ctx: AppContext) {
11
11
 
12
12
  // fetch from pds if available, if not then fetch from appview
13
13
  if (did) {
14
- const uri = AtUri.make(did, collection, rkey)
14
+ const uri = atUri(did, collection, rkey)
15
15
  const record = await ctx.actorStore.read(did, (store) =>
16
16
  store.record.getRecord(uri, cid ?? null),
17
17
  )
@@ -24,7 +24,7 @@ export default function (server: Server, ctx: AppContext) {
24
24
  return {
25
25
  encoding: 'application/json' as const,
26
26
  body: {
27
- uri: uri.toString(),
27
+ uri,
28
28
  cid: record.cid,
29
29
  value: record.value,
30
30
  },
@@ -1,3 +1,4 @@
1
+ import { atUri } from '@atproto/lex'
1
2
  import {
2
3
  LegacyBlobRef,
3
4
  LexMap,
@@ -5,7 +6,6 @@ import {
5
6
  isLegacyBlobRef,
6
7
  parseCid,
7
8
  } from '@atproto/lex-data'
8
- import { AtUri } from '@atproto/syntax'
9
9
  import {
10
10
  AuthRequiredError,
11
11
  InvalidRequestError,
@@ -90,7 +90,7 @@ export default function (server: Server, ctx: AppContext) {
90
90
  })
91
91
  }
92
92
 
93
- const uri = AtUri.make(did, collection, rkey)
93
+ const uri = atUri(did, collection, rkey)
94
94
  const swapCommitCid = swapCommit ? parseCid(swapCommit) : undefined
95
95
  const swapRecordCid =
96
96
  typeof swapRecord === 'string' ? parseCid(swapRecord) : swapRecord
package/src/context.ts CHANGED
@@ -267,25 +267,28 @@ export class AppContext {
267
267
  backgroundQueue,
268
268
  })
269
269
 
270
+ const plcRotationKey =
271
+ secrets.plcRotationKey.provider === 'kms'
272
+ ? await KmsKeypair.load({
273
+ keyId: secrets.plcRotationKey.keyId,
274
+ })
275
+ : await crypto.Secp256k1Keypair.import(
276
+ secrets.plcRotationKey.privateKeyHex,
277
+ )
278
+
270
279
  const accountManager = new AccountManager(
271
280
  idResolver,
272
281
  jwtSecretKey,
273
282
  mailer,
283
+ sequencer,
284
+ plcClient,
285
+ plcRotationKey,
274
286
  cfg.service.did,
275
287
  cfg.identity.serviceHandleDomains,
276
288
  cfg.db,
277
289
  )
278
290
  await accountManager.migrateOrThrow()
279
291
 
280
- const plcRotationKey =
281
- secrets.plcRotationKey.provider === 'kms'
282
- ? await KmsKeypair.load({
283
- keyId: secrets.plcRotationKey.keyId,
284
- })
285
- : await crypto.Secp256k1Keypair.import(
286
- secrets.plcRotationKey.privateKeyHex,
287
- )
288
-
289
292
  const localViewer = LocalViewer.creator(
290
293
  accountManager,
291
294
  imageUrlBuilder,
@@ -85,6 +85,8 @@ export class PageHelper implements AsyncDisposable {
85
85
  async typeIn(selector: string, text: string) {
86
86
  const elementHandle = await this.getVisibleElement(selector)
87
87
  elementHandle.focus()
88
+ await elementHandle.click({ clickCount: 3 }) // Select all existing text
89
+ await elementHandle.press('Backspace') // Clear the input
88
90
  await elementHandle.type(text)
89
91
  return elementHandle
90
92
  }
@@ -93,16 +95,20 @@ export class PageHelper implements AsyncDisposable {
93
95
  return this.typeIn(`input[name=${JSON.stringify(name)}]`, text)
94
96
  }
95
97
 
96
- async ensureTextVisibility(text: string, tag = 'p') {
98
+ async ensureTextVisibility(text: string, tag = 'p', timeout = 5_000) {
97
99
  await this.page.waitForSelector(
98
100
  `${tag}::-p-text(${JSON.stringify(text)})`,
99
101
  {
100
102
  visible: true,
101
- timeout: 5_000,
103
+ timeout,
102
104
  },
103
105
  )
104
106
  }
105
107
 
108
+ async ensureNotification(text: string) {
109
+ return this.ensureTextVisibility(text, 'div')
110
+ }
111
+
106
112
  protected async getVisibleElement(selector: string) {
107
113
  const elementHandle = await this.page.waitForSelector(selector, {
108
114
  visible: true,
@@ -18,7 +18,7 @@ describe('account manager', () => {
18
18
  // For debugging:
19
19
  // headless: false,
20
20
  // devtools: true,
21
- // slowMo: 150,
21
+ // slowMo: 25,
22
22
  })
23
23
 
24
24
  network = await TestNetworkNoAppView.create({
@@ -34,6 +34,10 @@ describe('account manager', () => {
34
34
  })
35
35
  })
36
36
 
37
+ afterEach(async () => {
38
+ await network.processAll()
39
+ })
40
+
37
41
  afterAll(async () => {
38
42
  await network?.close()
39
43
  await browser?.close()
@@ -44,7 +48,7 @@ describe('account manager', () => {
44
48
 
45
49
  await page.goto(new URL('/account', network.pds.url))
46
50
 
47
- await page.assertTitle(`S'identifier`)
51
+ await page.assertTitle(`Se connecter`)
48
52
 
49
53
  await page.clickOnText('Créer un nouveau compte')
50
54
 
@@ -55,12 +59,14 @@ describe('account manager', () => {
55
59
  await page.typeInInput('email', 'bob@test.com')
56
60
  await page.typeInInput('password', 'bob-pass')
57
61
 
58
- await page.clickOnText("S'inscrire")
62
+ await page.clickOnText('Inscription')
59
63
 
60
- await page.assertTitle(`Compte utilisateur`)
64
+ await page.waitForNetworkIdle()
61
65
 
62
66
  await page.ensureTextVisibility('bob.test', 'span')
63
67
  await page.ensureTextVisibility('Votre compte Atmosphère est hébergé chez')
68
+
69
+ await page.assertTitle('Mon compte Atmosphère')
64
70
  })
65
71
 
66
72
  it('allows switching accounts', async () => {
@@ -68,7 +74,7 @@ describe('account manager', () => {
68
74
 
69
75
  await page.goto(new URL('/account', network.pds.url))
70
76
 
71
- await page.assertTitle(`Compte utilisateur`)
77
+ await page.assertTitle('Mon compte Atmosphère')
72
78
 
73
79
  await page.clickOnAriaLabel(`Sélecteur de compte`)
74
80
  await page.clickOnText('Sélectionner un autre compte')
@@ -93,18 +99,13 @@ describe('account manager', () => {
93
99
 
94
100
  await page.goto(new URL('/account', network.pds.url))
95
101
 
96
- await page.assertTitle(`Compte utilisateur`)
102
+ await page.assertTitle('Mon compte Atmosphère')
97
103
 
98
104
  await page.ensureTextVisibility('bob.test', 'span')
99
105
 
100
- await page.ensureTextVisibility('alice.test', 'span').then(
101
- () => {
102
- throw new Error('Should not be visible')
103
- },
104
- (err) => {
105
- expect(err).toBeInstanceOf(Error)
106
- },
107
- )
106
+ await expect(async () => {
107
+ await page.ensureTextVisibility('alice.test', 'span', 500)
108
+ }).rejects.toThrow('Waiting for selector')
108
109
  })
109
110
 
110
111
  it('allows changing the password', async () => {
@@ -112,9 +113,11 @@ describe('account manager', () => {
112
113
 
113
114
  await page.goto(new URL('/account', network.pds.url))
114
115
 
115
- await page.assertTitle(`Compte utilisateur`)
116
+ await page.assertTitle('Mon compte Atmosphère')
116
117
 
117
- await page.clickOnText('Mot de passe', 'a')
118
+ await page.clickOnText('Compte utilisateur', 'a')
119
+
120
+ await page.clickOnText('Mot de passe')
118
121
 
119
122
  using sendResetPasswordMock = jest
120
123
  .spyOn(network.pds.ctx.mailer, 'sendResetPassword')
@@ -122,7 +125,7 @@ describe('account manager', () => {
122
125
  // noop
123
126
  })
124
127
 
125
- await page.clickOnText('Envoyer le code')
128
+ await page.clickOnText('Envoyer le code de vérification')
126
129
 
127
130
  await page.waitForNetworkIdle()
128
131
 
@@ -137,20 +140,23 @@ describe('account manager', () => {
137
140
  await page.typeInInput('code', params.token)
138
141
  await page.typeInInput('password', 'bob-new-pass')
139
142
 
140
- await page.clickOnText('Soumettre')
143
+ await page.clickOnText('Valider')
141
144
 
142
- await page.ensureTextVisibility(
143
- 'Réinitialisation du mot de passe réussie',
144
- 'div',
145
- )
145
+ await page.ensureNotification('Réinitialisation du mot de passe réussie')
146
146
  })
147
147
 
148
- it('allows validating the email address', async () => {
148
+ it('allows verifying the email address', async () => {
149
149
  await using page = await PageHelper.from(browser, { languages })
150
150
 
151
151
  await page.goto(new URL('/account', network.pds.url))
152
152
 
153
- await page.clickOnText('Vérifier maintenant', 'button')
153
+ await page.assertTitle('Mon compte Atmosphère')
154
+
155
+ await page.clickOnText('Compte utilisateur', 'a')
156
+
157
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
158
+
159
+ await page.clickOnText('Vérifier')
154
160
 
155
161
  using sendConfirmEmailMock = jest
156
162
  .spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
@@ -158,7 +164,7 @@ describe('account manager', () => {
158
164
  // noop
159
165
  })
160
166
 
161
- await page.clickOnText('Envoyer le code', 'button')
167
+ await page.clickOnText('Envoyer le code de vérification', 'button')
162
168
 
163
169
  await page.waitForNetworkIdle()
164
170
 
@@ -171,9 +177,31 @@ describe('account manager', () => {
171
177
  })
172
178
 
173
179
  await page.typeInInput('code', params.token)
174
- await page.clickOnText('Soumettre')
180
+ await page.clickOnText('Valider')
181
+
182
+ await page.ensureNotification('Adresse email vérifiée')
183
+ })
184
+
185
+ it('allows changing the username', async () => {
186
+ await using page = await PageHelper.from(browser, { languages })
187
+
188
+ await page.goto(new URL('/account', network.pds.url))
189
+
190
+ await page.assertTitle('Mon compte Atmosphère')
175
191
 
176
- await page.ensureTextVisibility('Adresse email vérifiée', 'div')
192
+ await page.clickOnText('Compte utilisateur', 'a')
193
+
194
+ await page.clickOnText("Nom d'utilisateur")
195
+
196
+ await page.clickOnText("Utiliser un nom d'utilisateur par défaut")
197
+
198
+ await page.typeInInput('handle', 'bob-renamed')
199
+
200
+ await page.clickOnText('Valider')
201
+
202
+ await page.waitForNetworkIdle()
203
+
204
+ await page.ensureTextVisibility('bob-renamed.test', 'span')
177
205
  })
178
206
 
179
207
  it('allows changing the email address', async () => {
@@ -181,7 +209,11 @@ describe('account manager', () => {
181
209
 
182
210
  await page.goto(new URL('/account', network.pds.url))
183
211
 
184
- await page.clickOnText('Email', 'a')
212
+ await page.assertTitle('Mon compte Atmosphère')
213
+
214
+ await page.clickOnText('Compte utilisateur', 'a')
215
+
216
+ await page.clickOnText('Adresse email')
185
217
 
186
218
  using sendUpdateEmailMock = jest
187
219
  .spyOn(network.pds.ctx.mailer, 'sendUpdateEmail')
@@ -189,13 +221,11 @@ describe('account manager', () => {
189
221
  // noop
190
222
  })
191
223
 
192
- using sendConfirmEmailMock = jest
193
- .spyOn(network.pds.ctx.mailer, 'sendConfirmEmail')
194
- .mockImplementation(async () => {
195
- // noop
196
- })
197
-
198
- await page.clickOnText('Envoyer le code', 'button')
224
+ const emailInput = await page.typeInInput(
225
+ 'email',
226
+ 'bob-new-email@example.com',
227
+ )
228
+ emailInput.press('Enter')
199
229
 
200
230
  await page.waitForNetworkIdle()
201
231
 
@@ -207,26 +237,69 @@ describe('account manager', () => {
207
237
  token: expect.any(String),
208
238
  })
209
239
 
210
- await page.typeInInput('code', updateParams.token)
211
- await page.typeInInput('email', 'bob-new-email@example.com')
212
- await page.clickOnText('Soumettre')
240
+ const codeInput = await page.typeInInput('code', updateParams.token)
241
+ codeInput.press('Enter')
242
+
243
+ await page.ensureNotification("Modification de l'adresse email réussie")
213
244
 
214
- await page.ensureTextVisibility(
215
- "Modification de l'adresse email réussie",
216
- 'div',
245
+ // The email needs to be verified again
246
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
247
+ })
248
+
249
+ it('allows signing out & signing back in', async () => {
250
+ await using page = await PageHelper.from(browser, { languages })
251
+
252
+ await page.goto(new URL('/account', network.pds.url))
253
+
254
+ await page.assertTitle('Mon compte Atmosphère')
255
+
256
+ await page.clickOnAriaLabel(`Sélecteur de compte`)
257
+ await page.clickOnText('Se déconnecter')
258
+
259
+ await page.assertTitle(`Se connecter`)
260
+ await page.clickOnText('Se connecter')
261
+
262
+ await page.clickOnText('Se souvenir de ce compte sur cet appareil', 'label')
263
+ await page.typeInInput('username', 'bob-new-email@example.com')
264
+ const input = await page.typeInInput('password', 'bob-new-pass')
265
+
266
+ input.press('Enter')
267
+
268
+ await page.ensureTextVisibility('bob-renamed.test', 'span')
269
+ })
270
+
271
+ it('does not ask for a token when changing a non-verified email', async () => {
272
+ await using page = await PageHelper.from(browser, { languages })
273
+
274
+ await page.goto(new URL('/account', network.pds.url))
275
+
276
+ await page.assertTitle('Mon compte Atmosphère')
277
+
278
+ await page.clickOnText('Compte utilisateur', 'a')
279
+
280
+ await page.clickOnText('Adresse email')
281
+
282
+ using sendUpdateEmailMock = jest
283
+ .spyOn(network.pds.ctx.mailer, 'sendUpdateEmail')
284
+ .mockImplementation(async () => {
285
+ // noop
286
+ })
287
+
288
+ const emailInput = await page.typeInInput(
289
+ 'email',
290
+ 'bob-new-email@example.com',
217
291
  )
292
+ emailInput.press('Enter')
218
293
 
219
- expect(sendConfirmEmailMock).toHaveBeenCalledTimes(1)
294
+ await page.waitForNetworkIdle()
220
295
 
221
- const [confirmParams] = sendConfirmEmailMock.mock.lastCall!
222
- expect(confirmParams).toEqual({
223
- locale: 'fr',
224
- token: expect.any(String),
225
- })
296
+ expect(sendUpdateEmailMock).not.toHaveBeenCalled()
297
+
298
+ await page.clickOnText('Plus tard')
226
299
 
227
- await page.typeInInput('code', confirmParams.token)
228
- await page.clickOnText('Soumettre')
300
+ await page.ensureNotification("Modification de l'adresse email réussie")
229
301
 
230
- await page.ensureTextVisibility('Adresse email vérifiée', 'div')
302
+ // The email needs to be verified again
303
+ await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
231
304
  })
232
305
  })
@@ -25,7 +25,7 @@ describe('oauth', () => {
25
25
  // For debugging:
26
26
  // headless: false,
27
27
  // devtools: true,
28
- // slowMo: 250,
28
+ // slowMo: 25,
29
29
  })
30
30
 
31
31
  network = await TestNetworkNoAppView.create({
@@ -71,7 +71,7 @@ describe('oauth', () => {
71
71
 
72
72
  await page.navigationClick(`Sign up with ${new URL(network.pds.url).host}`)
73
73
 
74
- await page.assertTitle("S'inscrire")
74
+ await page.assertTitle('Inscription')
75
75
 
76
76
  await page.typeInInput('handle', 'bob')
77
77
 
@@ -80,10 +80,10 @@ describe('oauth', () => {
80
80
  await page.typeInInput('email', 'bob@test.com')
81
81
  await page.typeInInput('password', 'bob-pass')
82
82
 
83
- await page.clickOnText("S'inscrire")
83
+ await page.clickOnText('Inscription')
84
84
 
85
85
  await page.ensureTextVisibility(
86
- `L'application demande un contrôle total sur votre identité, ce qui signifie qu'elle pourrait casser de façon permanente, ou même usurper, votre compte. N'authorisez l'accès qu'aux applications auxquelles vous faites vraiment confiance.`,
86
+ `L'application demande un contrôle total sur votre identité, ce qui signifie qu'elle pourrait casser de façon permanente, ou même usurper, votre compte. N'autorisez l'accès qu'aux applications auxquelles vous faites vraiment confiance.`,
87
87
  )
88
88
 
89
89
  // Make sure the new account is propagated to the PLC directory, allowing
@@ -112,7 +112,7 @@ describe('oauth', () => {
112
112
 
113
113
  await page.navigationClick(`Login with ${new URL(network.pds.url).host}`)
114
114
 
115
- await page.assertTitle("S'identifier")
115
+ await page.assertTitle('Se connecter')
116
116
 
117
117
  // Cancel the OAuth flow:
118
118
  await page.navigationClick('Annuler')