@atproto/pds 0.5.0 → 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.
- package/CHANGELOG.md +27 -0
- package/dist/account-manager/account-manager.d.ts +35 -4
- package/dist/account-manager/account-manager.d.ts.map +1 -1
- package/dist/account-manager/account-manager.js +67 -6
- package/dist/account-manager/account-manager.js.map +1 -1
- package/dist/account-manager/helpers/account.d.ts +1 -1
- package/dist/account-manager/helpers/account.d.ts.map +1 -1
- package/dist/account-manager/helpers/account.js +10 -4
- package/dist/account-manager/helpers/account.js.map +1 -1
- package/dist/account-manager/oauth-store.d.ts +2 -1
- package/dist/account-manager/oauth-store.d.ts.map +1 -1
- package/dist/account-manager/oauth-store.js +61 -12
- package/dist/account-manager/oauth-store.js.map +1 -1
- package/dist/actor-store/record/reader.d.ts +1 -1
- package/dist/actor-store/record/reader.d.ts.map +1 -1
- package/dist/actor-store/record/reader.js.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getPreferences.js +7 -2
- package/dist/api/app/bsky/actor/getPreferences.js.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/putPreferences.js +7 -2
- package/dist/api/app/bsky/actor/putPreferences.js.map +1 -1
- package/dist/api/com/atproto/admin/updateAccountHandle.d.ts.map +1 -1
- package/dist/api/com/atproto/admin/updateAccountHandle.js +33 -43
- package/dist/api/com/atproto/admin/updateAccountHandle.js.map +1 -1
- package/dist/api/com/atproto/identity/updateHandle.d.ts.map +1 -1
- package/dist/api/com/atproto/identity/updateHandle.js +39 -61
- package/dist/api/com/atproto/identity/updateHandle.js.map +1 -1
- package/dist/api/com/atproto/repo/getRecord.js +3 -3
- package/dist/api/com/atproto/repo/getRecord.js.map +1 -1
- package/dist/api/com/atproto/repo/putRecord.js +2 -2
- package/dist/api/com/atproto/repo/putRecord.js.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.d.ts.map +1 -1
- package/dist/api/com/atproto/server/getServiceAuth.js +4 -0
- package/dist/api/com/atproto/server/getServiceAuth.js.map +1 -1
- package/dist/config/config.d.ts +5 -2
- package/dist/config/config.d.ts.map +1 -1
- package/dist/config/config.js +50 -46
- package/dist/config/config.js.map +1 -1
- package/dist/config/env.d.ts +1 -0
- package/dist/config/env.d.ts.map +1 -1
- package/dist/config/env.js +1 -0
- package/dist/config/env.js.map +1 -1
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +3 -3
- package/dist/context.js.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +8 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.js +3 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor/status.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/draft/defs.defs.d.ts +22 -0
- package/dist/lexicons/app/bsky/draft/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/draft/defs.defs.js +11 -0
- package/dist/lexicons/app/bsky/draft/defs.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/embed/gallery.d.ts +3 -0
- package/dist/lexicons/app/bsky/embed/gallery.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts +130 -0
- package/dist/lexicons/app/bsky/embed/gallery.defs.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/embed/gallery.defs.js +47 -0
- package/dist/lexicons/app/bsky/embed/gallery.defs.js.map +1 -0
- package/dist/lexicons/app/bsky/embed/gallery.js +6 -0
- package/dist/lexicons/app/bsky/embed/gallery.js.map +1 -0
- package/dist/lexicons/app/bsky/embed/record.defs.d.ts +2 -1
- package/dist/lexicons/app/bsky/embed/record.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/embed/record.defs.js +2 -0
- package/dist/lexicons/app/bsky/embed/record.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts +13 -12
- package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js +3 -0
- package/dist/lexicons/app/bsky/embed/recordWithMedia.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/embed.d.ts +1 -0
- package/dist/lexicons/app/bsky/embed.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/embed.js +1 -0
- package/dist/lexicons/app/bsky/embed.js.map +1 -1
- package/dist/lexicons/app/bsky/feed/defs.defs.d.ts +2 -1
- package/dist/lexicons/app/bsky/feed/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/defs.defs.js +2 -0
- package/dist/lexicons/app/bsky/feed/defs.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/feed/generator.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/like.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/post.defs.d.ts +12 -11
- package/dist/lexicons/app/bsky/feed/post.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/post.defs.js +2 -0
- package/dist/lexicons/app/bsky/feed/post.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/feed/postgate.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/repost.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/feed/threadgate.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/block.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/follow.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/list.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/listblock.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/listitem.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/starterpack.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/graph/verification.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/labeler/service.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/notification/declaration.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/actor/declaration.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts +2 -0
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.js +1 -0
- package/dist/lexicons/chat/bsky/actor/getStatus.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/authFullChatClient.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/authFullChatClient.defs.js +1 -0
- package/dist/lexicons/chat/bsky/authFullChatClient.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts +53 -14
- package/dist/lexicons/chat/bsky/convo/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/defs.defs.js +33 -5
- package/dist/lexicons/chat/bsky/convo/defs.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js +1 -0
- package/dist/lexicons/chat/bsky/convo/getConvoForMembers.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts +2 -2
- package/dist/lexicons/chat/bsky/convo/getLog.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/convo/getLog.defs.js +3 -0
- package/dist/lexicons/chat/bsky/convo/getLog.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/embed/joinLink.d.ts +3 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts +99 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.js +28 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.js +6 -0
- package/dist/lexicons/chat/bsky/embed/joinLink.js.map +1 -0
- package/dist/lexicons/chat/bsky/embed.d.ts +2 -0
- package/dist/lexicons/chat/bsky/embed.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/embed.js +5 -0
- package/dist/lexicons/chat/bsky/embed.js.map +1 -0
- package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/group/addMembers.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/addMembers.defs.js +1 -0
- package/dist/lexicons/chat/bsky/group/addMembers.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/createGroup.defs.js +1 -0
- package/dist/lexicons/chat/bsky/group/createGroup.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js +1 -1
- package/dist/lexicons/chat/bsky/group/getJoinLinkPreviews.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts +3 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts +20 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js +19 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js +6 -0
- package/dist/lexicons/chat/bsky/group/updateJoinRequestsRead.js.map +1 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts +3 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts +20 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js +18 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js +6 -0
- package/dist/lexicons/chat/bsky/group/withdrawJoinRequest.js.map +1 -0
- package/dist/lexicons/chat/bsky/group.d.ts +2 -0
- package/dist/lexicons/chat/bsky/group.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/group.js +2 -0
- package/dist/lexicons/chat/bsky/group.js.map +1 -1
- package/dist/lexicons/chat/bsky/moderation/defs.d.ts +2 -0
- package/dist/lexicons/chat/bsky/moderation/defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts +58 -0
- package/dist/lexicons/chat/bsky/moderation/defs.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/defs.defs.js +38 -0
- package/dist/lexicons/chat/bsky/moderation/defs.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/defs.js +5 -0
- package/dist/lexicons/chat/bsky/moderation/defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts +3 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts +22 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js +18 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.js +6 -0
- package/dist/lexicons/chat/bsky/moderation/getConvo.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts +3 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts +28 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js +24 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js +6 -0
- package/dist/lexicons/chat/bsky/moderation/getConvoMembers.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts +3 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts +22 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.defs.d.ts.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js +22 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.defs.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.js +6 -0
- package/dist/lexicons/chat/bsky/moderation/getConvos.js.map +1 -0
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts +20 -2
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js +11 -0
- package/dist/lexicons/chat/bsky/moderation/subscribeModEvents.defs.js.map +1 -1
- package/dist/lexicons/chat/bsky/moderation.d.ts +4 -0
- package/dist/lexicons/chat/bsky/moderation.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky/moderation.js +4 -0
- package/dist/lexicons/chat/bsky/moderation.js.map +1 -1
- package/dist/lexicons/chat/bsky.d.ts +1 -0
- package/dist/lexicons/chat/bsky.d.ts.map +1 -1
- package/dist/lexicons/chat/bsky.js +1 -0
- package/dist/lexicons/chat/bsky.js.map +1 -1
- package/dist/lexicons/com/atproto/lexicon/schema.defs.d.ts.map +1 -1
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.d.ts +2 -2
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js +1 -1
- package/dist/lexicons/com/atproto/server/getServiceAuth.defs.js.map +1 -1
- package/dist/lexicons/com/germnetwork/declaration.defs.d.ts.map +1 -1
- package/dist/lexicons/site/standard/document.defs.d.ts.map +1 -1
- package/dist/lexicons/site/standard/graph/recommend.defs.d.ts.map +1 -1
- package/dist/lexicons/site/standard/graph/subscription.defs.d.ts.map +1 -1
- package/dist/lexicons/site/standard/publication.defs.d.ts.map +1 -1
- package/dist/lexicons/site/standard/theme/basic.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts +11 -3
- package/dist/lexicons/tools/ozone/moderation/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/defs.defs.js +9 -0
- package/dist/lexicons/tools/ozone/moderation/defs.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts +2 -2
- package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/queryEvents.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts +2 -2
- package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/queryStatuses.defs.js.map +1 -1
- package/dist/mailer/index.d.ts +3 -3
- package/dist/mailer/index.d.ts.map +1 -1
- package/dist/mailer/index.js +18 -9
- package/dist/mailer/index.js.map +1 -1
- package/dist/mailer/templates/confirm-email.js +11 -3
- package/dist/mailer/templates/confirm-email.js.map +2 -2
- package/dist/mailer/templates/delete-account.js +2 -2
- package/dist/mailer/templates/delete-account.js.map +2 -2
- package/dist/mailer/templates/plc-operation.js +2 -2
- package/dist/mailer/templates/plc-operation.js.map +2 -2
- package/dist/mailer/templates/reset-password.js +2 -2
- package/dist/mailer/templates/reset-password.js.map +2 -2
- package/dist/mailer/templates/update-email.js +2 -2
- package/dist/mailer/templates/update-email.js.map +2 -2
- package/dist/mailer/templates.d.ts +11 -0
- package/dist/mailer/templates.d.ts.map +1 -1
- package/dist/mailer/templates.js.map +1 -1
- package/dist/pipethrough.d.ts +3 -0
- package/dist/pipethrough.d.ts.map +1 -1
- package/dist/pipethrough.js +25 -9
- package/dist/pipethrough.js.map +1 -1
- package/dist/read-after-write/viewer.d.ts +2 -2
- package/package.json +7 -6
- package/src/account-manager/account-manager.ts +105 -7
- package/src/account-manager/helpers/account.ts +15 -7
- package/src/account-manager/oauth-store.ts +76 -18
- package/src/actor-store/record/reader.ts +1 -1
- package/src/api/app/bsky/actor/getPreferences.ts +11 -2
- package/src/api/app/bsky/actor/putPreferences.ts +11 -2
- package/src/api/com/atproto/admin/updateAccountHandle.ts +37 -46
- package/src/api/com/atproto/identity/updateHandle.ts +45 -76
- package/src/api/com/atproto/repo/getRecord.ts +3 -3
- package/src/api/com/atproto/repo/putRecord.ts +2 -2
- package/src/api/com/atproto/server/getServiceAuth.ts +7 -0
- package/src/config/config.ts +69 -57
- package/src/config/env.ts +3 -0
- package/src/context.ts +13 -10
- package/src/mailer/index.ts +25 -9
- package/src/mailer/templates/confirm-email.hbs +18 -17
- package/src/mailer/templates/delete-account.hbs +6 -6
- package/src/mailer/templates/plc-operation.hbs +6 -6
- package/src/mailer/templates/reset-password.hbs +7 -7
- package/src/mailer/templates/update-email.hbs +6 -6
- package/src/mailer/templates.ts +12 -0
- package/src/pipethrough.ts +33 -12
- package/tests/_puppeteer.ts +8 -2
- package/tests/account-manager.test.ts +123 -50
- package/tests/app-passwords.test.ts +5 -5
- package/tests/get-service-auth.test.ts +81 -0
- package/tests/oauth.test.ts +5 -5
- package/tests/proxied/proxy-header.test.ts +1 -0
- package/tests/proxied/proxy-oauth-aud.test.ts +175 -0
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -18,7 +18,7 @@ describe('account manager', () => {
|
|
|
18
18
|
// For debugging:
|
|
19
19
|
// headless: false,
|
|
20
20
|
// devtools: true,
|
|
21
|
-
// slowMo:
|
|
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(`
|
|
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(
|
|
62
|
+
await page.clickOnText('Inscription')
|
|
59
63
|
|
|
60
|
-
await page.
|
|
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(
|
|
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(
|
|
102
|
+
await page.assertTitle('Mon compte Atmosphère')
|
|
97
103
|
|
|
98
104
|
await page.ensureTextVisibility('bob.test', 'span')
|
|
99
105
|
|
|
100
|
-
await
|
|
101
|
-
(
|
|
102
|
-
|
|
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(
|
|
116
|
+
await page.assertTitle('Mon compte Atmosphère')
|
|
116
117
|
|
|
117
|
-
await page.clickOnText('
|
|
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('
|
|
143
|
+
await page.clickOnText('Valider')
|
|
141
144
|
|
|
142
|
-
await page.
|
|
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
|
|
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.
|
|
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('
|
|
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.
|
|
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.
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
.
|
|
195
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
|
|
294
|
+
await page.waitForNetworkIdle()
|
|
220
295
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
token: expect.any(String),
|
|
225
|
-
})
|
|
296
|
+
expect(sendUpdateEmailMock).not.toHaveBeenCalled()
|
|
297
|
+
|
|
298
|
+
await page.clickOnText('Plus tard')
|
|
226
299
|
|
|
227
|
-
await page.
|
|
228
|
-
await page.clickOnText('Soumettre')
|
|
300
|
+
await page.ensureNotification("Modification de l'adresse email réussie")
|
|
229
301
|
|
|
230
|
-
|
|
302
|
+
// The email needs to be verified again
|
|
303
|
+
await page.ensureTextVisibility('Votre adresse email doit être vérifiée.')
|
|
231
304
|
})
|
|
232
305
|
})
|
|
@@ -115,7 +115,7 @@ describe('app_passwords', () => {
|
|
|
115
115
|
it('restricts service auth token methods for non-privileged access tokens', async () => {
|
|
116
116
|
const attemptCaseSensitive = appAgent.api.com.atproto.server.getServiceAuth(
|
|
117
117
|
{
|
|
118
|
-
aud:
|
|
118
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
119
119
|
lxm: 'com.atproto.server.createAccount',
|
|
120
120
|
},
|
|
121
121
|
)
|
|
@@ -124,7 +124,7 @@ describe('app_passwords', () => {
|
|
|
124
124
|
)
|
|
125
125
|
const attemptCaseInsensitive =
|
|
126
126
|
appAgent.api.com.atproto.server.getServiceAuth({
|
|
127
|
-
aud:
|
|
127
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
128
128
|
lxm: 'com.atproto.server.createaccount',
|
|
129
129
|
})
|
|
130
130
|
await expect(attemptCaseInsensitive).rejects.toThrow(
|
|
@@ -134,7 +134,7 @@ describe('app_passwords', () => {
|
|
|
134
134
|
|
|
135
135
|
it('allows privileged service auth token scopes for privileged access tokens', async () => {
|
|
136
136
|
await priviAgent.api.com.atproto.server.getServiceAuth({
|
|
137
|
-
aud:
|
|
137
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
138
138
|
lxm: 'com.atproto.server.createAccount',
|
|
139
139
|
})
|
|
140
140
|
})
|
|
@@ -165,7 +165,7 @@ describe('app_passwords', () => {
|
|
|
165
165
|
|
|
166
166
|
// allows privileged app passwords or higher
|
|
167
167
|
const priviAttempt = appAgent.api.com.atproto.server.getServiceAuth({
|
|
168
|
-
aud:
|
|
168
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
169
169
|
lxm: 'com.atproto.server.createAccount',
|
|
170
170
|
})
|
|
171
171
|
await expect(priviAttempt).rejects.toThrow(
|
|
@@ -211,7 +211,7 @@ describe('app_passwords', () => {
|
|
|
211
211
|
|
|
212
212
|
// allows privileged app passwords or higher
|
|
213
213
|
await priviAgent.api.com.atproto.server.getServiceAuth({
|
|
214
|
-
aud:
|
|
214
|
+
aud: network.pds.ctx.cfg.service.did,
|
|
215
215
|
})
|
|
216
216
|
|
|
217
217
|
// allows only full access auth
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as jose from 'jose'
|
|
2
|
+
import { AtpAgent } from '@atproto/api'
|
|
3
|
+
import { TestNetworkNoAppView } from '@atproto/dev-env'
|
|
4
|
+
|
|
5
|
+
describe('com.atproto.server.getServiceAuth', () => {
|
|
6
|
+
let network: TestNetworkNoAppView
|
|
7
|
+
let agent: AtpAgent
|
|
8
|
+
let aliceDid: string
|
|
9
|
+
let pdsDid: string
|
|
10
|
+
|
|
11
|
+
beforeAll(async () => {
|
|
12
|
+
network = await TestNetworkNoAppView.create({
|
|
13
|
+
dbPostgresSchema: 'get_service_auth',
|
|
14
|
+
})
|
|
15
|
+
pdsDid = network.pds.ctx.cfg.service.did
|
|
16
|
+
agent = network.pds.getAgent()
|
|
17
|
+
const session = await agent.createAccount({
|
|
18
|
+
handle: 'alice.test',
|
|
19
|
+
email: 'alice@test.com',
|
|
20
|
+
password: 'alice-pass',
|
|
21
|
+
})
|
|
22
|
+
aliceDid = session.data.did
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
afterAll(async () => {
|
|
26
|
+
await network.close()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('issues a token whose aud matches a bare-DID input', async () => {
|
|
30
|
+
const res = await agent.api.com.atproto.server.getServiceAuth({
|
|
31
|
+
aud: pdsDid,
|
|
32
|
+
lxm: 'com.atproto.server.describeServer',
|
|
33
|
+
})
|
|
34
|
+
const decoded = jose.decodeJwt(res.data.token)
|
|
35
|
+
expect(decoded.aud).toBe(pdsDid)
|
|
36
|
+
expect(decoded.iss).toBe(aliceDid)
|
|
37
|
+
expect(decoded.lxm).toBe('com.atproto.server.describeServer')
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('issues a token whose aud matches a combined did#serviceId input', async () => {
|
|
41
|
+
const aud = `${pdsDid}#atproto_pds`
|
|
42
|
+
const res = await agent.api.com.atproto.server.getServiceAuth({
|
|
43
|
+
aud,
|
|
44
|
+
lxm: 'com.atproto.server.describeServer',
|
|
45
|
+
})
|
|
46
|
+
const decoded = jose.decodeJwt(res.data.token)
|
|
47
|
+
expect(decoded.aud).toBe(aud)
|
|
48
|
+
expect(decoded.iss).toBe(aliceDid)
|
|
49
|
+
expect(decoded.lxm).toBe('com.atproto.server.describeServer')
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('rejects malformed aud with InvalidRequest', async () => {
|
|
53
|
+
const attempt = agent.api.com.atproto.server.getServiceAuth({
|
|
54
|
+
aud: 'not-a-did',
|
|
55
|
+
lxm: 'com.atproto.server.describeServer',
|
|
56
|
+
})
|
|
57
|
+
await expect(attempt).rejects.toThrow(
|
|
58
|
+
/aud must be a valid atproto DID or did#serviceId reference/,
|
|
59
|
+
)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('rejects an aud with a non-atproto DID method', async () => {
|
|
63
|
+
const attempt = agent.api.com.atproto.server.getServiceAuth({
|
|
64
|
+
aud: 'did:foo:bar',
|
|
65
|
+
lxm: 'com.atproto.server.describeServer',
|
|
66
|
+
})
|
|
67
|
+
await expect(attempt).rejects.toThrow(
|
|
68
|
+
/aud must be a valid atproto DID or did#serviceId reference/,
|
|
69
|
+
)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('rejects an aud with empty fragment', async () => {
|
|
73
|
+
const attempt = agent.api.com.atproto.server.getServiceAuth({
|
|
74
|
+
aud: `${pdsDid}#`,
|
|
75
|
+
lxm: 'com.atproto.server.describeServer',
|
|
76
|
+
})
|
|
77
|
+
await expect(attempt).rejects.toThrow(
|
|
78
|
+
/aud must be a valid atproto DID or did#serviceId reference/,
|
|
79
|
+
)
|
|
80
|
+
})
|
|
81
|
+
})
|
package/tests/oauth.test.ts
CHANGED
|
@@ -25,7 +25,7 @@ describe('oauth', () => {
|
|
|
25
25
|
// For debugging:
|
|
26
26
|
// headless: false,
|
|
27
27
|
// devtools: true,
|
|
28
|
-
// slowMo:
|
|
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(
|
|
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(
|
|
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'
|
|
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(
|
|
115
|
+
await page.assertTitle('Se connecter')
|
|
116
116
|
|
|
117
117
|
// Cancel the OAuth flow:
|
|
118
118
|
await page.navigationClick('Annuler')
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { once } from 'node:events'
|
|
2
|
+
import http from 'node:http'
|
|
3
|
+
import { AddressInfo } from 'node:net'
|
|
4
|
+
import * as plc from '@did-plc/lib'
|
|
5
|
+
import express from 'express'
|
|
6
|
+
import { Keypair } from '@atproto/crypto'
|
|
7
|
+
import { SeedClient, TestNetworkNoAppView, usersSeed } from '@atproto/dev-env'
|
|
8
|
+
import { ScopePermissions } from '@atproto/oauth-scopes'
|
|
9
|
+
import { DidString } from '@atproto/syntax'
|
|
10
|
+
import { AppContext } from '../../src/context.js'
|
|
11
|
+
import { proxyHandler } from '../../src/pipethrough.js'
|
|
12
|
+
|
|
13
|
+
// Regression test for the OAuth service-proxying audience fix.
|
|
14
|
+
//
|
|
15
|
+
// Before the fix, proxyHandler passed a bare DID as `aud` to the rpc scope
|
|
16
|
+
// check. An OAuth caller granted `rpc:<lxm>?aud=<did>#<serviceId>` (the
|
|
17
|
+
// canonical scope shape used in atproto OAuth) had no way to match, so
|
|
18
|
+
// proxied calls failed at the scope check. The fix combines the proxied
|
|
19
|
+
// service id into the scope-check audience as `<did>#<serviceId>`.
|
|
20
|
+
//
|
|
21
|
+
// We use a real PDS AppContext (so every field is real-typed). Only the
|
|
22
|
+
// `authVerifier.authorization` method is replaced, with a thin stub that
|
|
23
|
+
// drives the OAuth path off an `x-test-scope` request header — letting us
|
|
24
|
+
// exercise the rpc scope check without minting a real OAuth token. A
|
|
25
|
+
// separate express app mounts a fresh proxyHandler against the real ctx
|
|
26
|
+
// and forwards to a real upstream ProxyServer.
|
|
27
|
+
|
|
28
|
+
describe('proxy oauth audience', () => {
|
|
29
|
+
let network: TestNetworkNoAppView
|
|
30
|
+
let sc: SeedClient
|
|
31
|
+
let alice: string
|
|
32
|
+
let upstream: ProxyServer
|
|
33
|
+
let server: http.Server
|
|
34
|
+
let serverUrl: string
|
|
35
|
+
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
network = await TestNetworkNoAppView.create({
|
|
38
|
+
dbPostgresSchema: 'proxy_oauth_aud',
|
|
39
|
+
})
|
|
40
|
+
sc = network.getSeedClient()
|
|
41
|
+
await usersSeed(sc)
|
|
42
|
+
alice = sc.dids.alice
|
|
43
|
+
upstream = await ProxyServer.create(
|
|
44
|
+
network.pds.ctx.plcClient,
|
|
45
|
+
network.pds.ctx.plcRotationKey,
|
|
46
|
+
'atproto_test',
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
// Replace only the `authorization` method on the real AuthVerifier so
|
|
50
|
+
// every other ctx field stays real and real-typed. The override's
|
|
51
|
+
// signature is constrained by AuthVerifier['authorization']: an OAuth
|
|
52
|
+
// shape change in the real verifier breaks the body of this stub at
|
|
53
|
+
// compile time.
|
|
54
|
+
const stubAuthorization: typeof network.pds.ctx.authVerifier.authorization =
|
|
55
|
+
({ authorize }) => {
|
|
56
|
+
return async (ctx) => {
|
|
57
|
+
const scopeHeader = ctx.req.headers['x-test-scope'] as
|
|
58
|
+
| string
|
|
59
|
+
| undefined
|
|
60
|
+
const permissions = new ScopePermissions(
|
|
61
|
+
scopeHeader?.split(' ') ?? [],
|
|
62
|
+
)
|
|
63
|
+
await authorize(permissions, ctx)
|
|
64
|
+
return {
|
|
65
|
+
credentials: {
|
|
66
|
+
type: 'oauth',
|
|
67
|
+
did: alice as DidString,
|
|
68
|
+
permissions,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
network.pds.ctx.authVerifier.authorization = stubAuthorization
|
|
74
|
+
|
|
75
|
+
const app = express()
|
|
76
|
+
// dev-env exports AppContext from its built dist/, while we import
|
|
77
|
+
// proxyHandler from src/. Cast at this boundary to bridge the two
|
|
78
|
+
// identical shapes; downstream behavior is real.
|
|
79
|
+
app.all('/xrpc/*', proxyHandler(network.pds.ctx as unknown as AppContext))
|
|
80
|
+
app.use(
|
|
81
|
+
(
|
|
82
|
+
err: Error & { status?: number; statusCode?: number },
|
|
83
|
+
_req: express.Request,
|
|
84
|
+
res: express.Response,
|
|
85
|
+
_next: express.NextFunction,
|
|
86
|
+
) => {
|
|
87
|
+
if (res.headersSent) return
|
|
88
|
+
res.status(err.status ?? err.statusCode ?? 500).end()
|
|
89
|
+
},
|
|
90
|
+
)
|
|
91
|
+
server = app.listen(0)
|
|
92
|
+
await once(server, 'listening')
|
|
93
|
+
serverUrl = `http://localhost:${(server.address() as AddressInfo).port}`
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
afterAll(async () => {
|
|
97
|
+
await new Promise<void>((resolve) => server.close(() => resolve()))
|
|
98
|
+
await upstream.close()
|
|
99
|
+
await network.close()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('matches an OAuth rpc scope granted with combined did#serviceId aud', async () => {
|
|
103
|
+
// Pre-fix this would have rejected because the scope check received
|
|
104
|
+
// bare-DID aud and never matched the combined-form scope. A 200 here
|
|
105
|
+
// implies the scope check ran with combined-form aud.
|
|
106
|
+
const res = await fetch(`${serverUrl}/xrpc/app.bsky.feed.getFeed`, {
|
|
107
|
+
headers: {
|
|
108
|
+
'atproto-proxy': `${upstream.did}#atproto_test`,
|
|
109
|
+
'x-test-scope': `rpc:app.bsky.feed.getFeed?aud=${encodeURIComponent(`${upstream.did}#atproto_test`)}`,
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
expect(res.status).toBe(200)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('rejects an OAuth rpc scope granted for a different service id', async () => {
|
|
116
|
+
// Same DID, different service id — both forms parse as valid scope
|
|
117
|
+
// audiences, but the runtime aud (`upstream.did#atproto_test`) doesn't
|
|
118
|
+
// match the granted aud (`upstream.did#atproto_other`).
|
|
119
|
+
const res = await fetch(`${serverUrl}/xrpc/app.bsky.feed.getFeed`, {
|
|
120
|
+
headers: {
|
|
121
|
+
'atproto-proxy': `${upstream.did}#atproto_test`,
|
|
122
|
+
'x-test-scope': `rpc:app.bsky.feed.getFeed?aud=${encodeURIComponent(`${upstream.did}#atproto_other`)}`,
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
expect([401, 403]).toContain(res.status)
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
class ProxyServer {
|
|
130
|
+
constructor(
|
|
131
|
+
public server: http.Server,
|
|
132
|
+
public url: string,
|
|
133
|
+
public did: string,
|
|
134
|
+
) {}
|
|
135
|
+
|
|
136
|
+
static async create(
|
|
137
|
+
plcClient: plc.Client,
|
|
138
|
+
keypair: Keypair,
|
|
139
|
+
serviceId: string,
|
|
140
|
+
): Promise<ProxyServer> {
|
|
141
|
+
const app = express()
|
|
142
|
+
app.all('*', (_req, res) => res.sendStatus(200))
|
|
143
|
+
|
|
144
|
+
const server = app.listen(0)
|
|
145
|
+
await once(server, 'listening')
|
|
146
|
+
|
|
147
|
+
const { port } = server.address() as AddressInfo
|
|
148
|
+
const url = `http://localhost:${port}`
|
|
149
|
+
const plcOp = await plc.signOperation(
|
|
150
|
+
{
|
|
151
|
+
type: 'plc_operation',
|
|
152
|
+
rotationKeys: [keypair.did()],
|
|
153
|
+
alsoKnownAs: [],
|
|
154
|
+
verificationMethods: {},
|
|
155
|
+
services: {
|
|
156
|
+
[serviceId]: {
|
|
157
|
+
type: 'TestAtprotoService',
|
|
158
|
+
endpoint: url,
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
prev: null,
|
|
162
|
+
},
|
|
163
|
+
keypair,
|
|
164
|
+
)
|
|
165
|
+
const did = await plc.didForCreateOp(plcOp)
|
|
166
|
+
await plcClient.sendOperation(did, plcOp)
|
|
167
|
+
return new ProxyServer(server, url, did)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
close(): Promise<void> {
|
|
171
|
+
return new Promise<void>((resolve) => {
|
|
172
|
+
this.server.close(() => resolve())
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|