@atproto/pds 0.4.25 → 0.4.26

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 (85) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/account-manager/db/migrations/003-privileged-app-passwords.d.ts +4 -0
  3. package/dist/account-manager/db/migrations/003-privileged-app-passwords.d.ts.map +1 -0
  4. package/dist/account-manager/db/migrations/003-privileged-app-passwords.js +15 -0
  5. package/dist/account-manager/db/migrations/003-privileged-app-passwords.js.map +1 -0
  6. package/dist/account-manager/db/migrations/index.d.ts +2 -0
  7. package/dist/account-manager/db/migrations/index.d.ts.map +1 -1
  8. package/dist/account-manager/db/migrations/index.js +2 -0
  9. package/dist/account-manager/db/migrations/index.js.map +1 -1
  10. package/dist/account-manager/db/schema/app-password.d.ts +1 -0
  11. package/dist/account-manager/db/schema/app-password.d.ts.map +1 -1
  12. package/dist/account-manager/db/schema/app-password.js.map +1 -1
  13. package/dist/account-manager/helpers/auth.d.ts +9 -4
  14. package/dist/account-manager/helpers/auth.d.ts.map +1 -1
  15. package/dist/account-manager/helpers/auth.js +30 -5
  16. package/dist/account-manager/helpers/auth.js.map +1 -1
  17. package/dist/account-manager/helpers/password.d.ts +7 -2
  18. package/dist/account-manager/helpers/password.d.ts.map +1 -1
  19. package/dist/account-manager/helpers/password.js +17 -4
  20. package/dist/account-manager/helpers/password.js.map +1 -1
  21. package/dist/account-manager/index.d.ts +5 -3
  22. package/dist/account-manager/index.d.ts.map +1 -1
  23. package/dist/account-manager/index.js +7 -7
  24. package/dist/account-manager/index.js.map +1 -1
  25. package/dist/api/chat/index.js +14 -14
  26. package/dist/api/chat/index.js.map +1 -1
  27. package/dist/api/com/atproto/identity/requestPlcOperationSignature.js +1 -1
  28. package/dist/api/com/atproto/identity/requestPlcOperationSignature.js.map +1 -1
  29. package/dist/api/com/atproto/identity/signPlcOperation.js +1 -1
  30. package/dist/api/com/atproto/identity/signPlcOperation.js.map +1 -1
  31. package/dist/api/com/atproto/repo/importRepo.js +1 -1
  32. package/dist/api/com/atproto/repo/importRepo.js.map +1 -1
  33. package/dist/api/com/atproto/server/activateAccount.js +1 -1
  34. package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
  35. package/dist/api/com/atproto/server/createAppPassword.d.ts.map +1 -1
  36. package/dist/api/com/atproto/server/createAppPassword.js +2 -2
  37. package/dist/api/com/atproto/server/createAppPassword.js.map +1 -1
  38. package/dist/api/com/atproto/server/createSession.d.ts.map +1 -1
  39. package/dist/api/com/atproto/server/createSession.js +4 -4
  40. package/dist/api/com/atproto/server/createSession.js.map +1 -1
  41. package/dist/api/com/atproto/server/deactivateAccount.js +1 -1
  42. package/dist/api/com/atproto/server/deactivateAccount.js.map +1 -1
  43. package/dist/api/com/atproto/server/getAccountInviteCodes.js +1 -1
  44. package/dist/api/com/atproto/server/getAccountInviteCodes.js.map +1 -1
  45. package/dist/api/com/atproto/server/getServiceAuth.js +1 -1
  46. package/dist/api/com/atproto/server/getServiceAuth.js.map +1 -1
  47. package/dist/api/com/atproto/server/updateEmail.js +1 -1
  48. package/dist/api/com/atproto/server/updateEmail.js.map +1 -1
  49. package/dist/auth-verifier.d.ts +3 -1
  50. package/dist/auth-verifier.d.ts.map +1 -1
  51. package/dist/auth-verifier.js +16 -1
  52. package/dist/auth-verifier.js.map +1 -1
  53. package/dist/lexicon/lexicons.d.ts +10 -0
  54. package/dist/lexicon/lexicons.d.ts.map +1 -1
  55. package/dist/lexicon/lexicons.js +10 -0
  56. package/dist/lexicon/lexicons.js.map +1 -1
  57. package/dist/lexicon/types/com/atproto/server/createAppPassword.d.ts +3 -0
  58. package/dist/lexicon/types/com/atproto/server/createAppPassword.d.ts.map +1 -1
  59. package/dist/lexicon/types/com/atproto/server/createAppPassword.js.map +1 -1
  60. package/dist/lexicon/types/com/atproto/server/listAppPasswords.d.ts +1 -0
  61. package/dist/lexicon/types/com/atproto/server/listAppPasswords.d.ts.map +1 -1
  62. package/dist/lexicon/types/com/atproto/server/listAppPasswords.js.map +1 -1
  63. package/package.json +3 -3
  64. package/src/account-manager/db/migrations/003-privileged-app-passwords.ts +12 -0
  65. package/src/account-manager/db/migrations/index.ts +2 -0
  66. package/src/account-manager/db/schema/app-password.ts +1 -0
  67. package/src/account-manager/helpers/auth.ts +32 -4
  68. package/src/account-manager/helpers/password.ts +23 -5
  69. package/src/account-manager/index.ts +11 -9
  70. package/src/api/chat/index.ts +14 -14
  71. package/src/api/com/atproto/identity/requestPlcOperationSignature.ts +1 -1
  72. package/src/api/com/atproto/identity/signPlcOperation.ts +1 -1
  73. package/src/api/com/atproto/repo/importRepo.ts +1 -1
  74. package/src/api/com/atproto/server/activateAccount.ts +1 -1
  75. package/src/api/com/atproto/server/createAppPassword.ts +3 -1
  76. package/src/api/com/atproto/server/createSession.ts +5 -4
  77. package/src/api/com/atproto/server/deactivateAccount.ts +1 -1
  78. package/src/api/com/atproto/server/getAccountInviteCodes.ts +1 -1
  79. package/src/api/com/atproto/server/getServiceAuth.ts +1 -1
  80. package/src/api/com/atproto/server/updateEmail.ts +1 -1
  81. package/src/auth-verifier.ts +12 -1
  82. package/src/lexicon/lexicons.ts +11 -0
  83. package/src/lexicon/types/com/atproto/server/createAppPassword.ts +3 -0
  84. package/src/lexicon/types/com/atproto/server/listAppPasswords.ts +1 -0
  85. package/tests/app-passwords.test.ts +108 -7
@@ -13,6 +13,8 @@ export interface QueryParams {}
13
13
  export interface InputSchema {
14
14
  /** A short name for the App Password, to help distinguish them. */
15
15
  name: string
16
+ /** If an app password has 'privileged' access to possibly sensitive account state. Meant for use with trusted clients. */
17
+ privileged?: boolean
16
18
  [k: string]: unknown
17
19
  }
18
20
 
@@ -51,6 +53,7 @@ export interface AppPassword {
51
53
  name: string
52
54
  password: string
53
55
  createdAt: string
56
+ privileged?: boolean
54
57
  [k: string]: unknown
55
58
  }
56
59
 
@@ -46,6 +46,7 @@ export type Handler<HA extends HandlerAuth = never> = (
46
46
  export interface AppPassword {
47
47
  name: string
48
48
  createdAt: string
49
+ privileged?: boolean
49
50
  [k: string]: unknown
50
51
  }
51
52
 
@@ -6,6 +6,7 @@ describe('app_passwords', () => {
6
6
  let network: TestNetworkNoAppView
7
7
  let accntAgent: AtpAgent
8
8
  let appAgent: AtpAgent
9
+ let priviAgent: AtpAgent
9
10
 
10
11
  beforeAll(async () => {
11
12
  network = await TestNetworkNoAppView.create({
@@ -13,6 +14,7 @@ describe('app_passwords', () => {
13
14
  })
14
15
  accntAgent = network.pds.getClient()
15
16
  appAgent = network.pds.getClient()
17
+ priviAgent = network.pds.getClient()
16
18
 
17
19
  await accntAgent.createAccount({
18
20
  handle: 'alice.test',
@@ -26,26 +28,46 @@ describe('app_passwords', () => {
26
28
  })
27
29
 
28
30
  let appPass: string
31
+ let privilegedAppPass: string
29
32
 
30
33
  it('creates an app-specific password', async () => {
31
34
  const res = await accntAgent.api.com.atproto.server.createAppPassword({
32
35
  name: 'test-pass',
33
36
  })
34
37
  expect(res.data.name).toBe('test-pass')
38
+ expect(res.data.privileged).toBe(false)
35
39
  appPass = res.data.password
36
40
  })
37
41
 
42
+ it('creates a privileged app-specific password', async () => {
43
+ const res = await accntAgent.api.com.atproto.server.createAppPassword({
44
+ name: 'privi-pass',
45
+ privileged: true,
46
+ })
47
+ expect(res.data.name).toBe('privi-pass')
48
+ expect(res.data.privileged).toBe(true)
49
+ privilegedAppPass = res.data.password
50
+ })
51
+
38
52
  it('creates a session with an app-specific password', async () => {
39
- const res = await appAgent.login({
53
+ const res1 = await appAgent.login({
40
54
  identifier: 'alice.test',
41
55
  password: appPass,
42
56
  })
43
- expect(res.data.did).toEqual(accntAgent.session?.did)
57
+ expect(res1.data.did).toEqual(accntAgent.session?.did)
58
+ const res2 = await priviAgent.login({
59
+ identifier: 'alice.test',
60
+ password: privilegedAppPass,
61
+ })
62
+ expect(res2.data.did).toEqual(accntAgent.session?.did)
44
63
  })
45
64
 
46
65
  it('creates an access token for an app with a restricted scope', () => {
47
66
  const decoded = jose.decodeJwt(appAgent.session?.accessJwt ?? '')
48
67
  expect(decoded?.scope).toEqual('com.atproto.appPass')
68
+
69
+ const decodedPrivi = jose.decodeJwt(priviAgent.session?.accessJwt ?? '')
70
+ expect(decodedPrivi?.scope).toEqual('com.atproto.appPassPrivileged')
49
71
  })
50
72
 
51
73
  it('allows actions to be performed from app', async () => {
@@ -58,15 +80,41 @@ describe('app_passwords', () => {
58
80
  createdAt: new Date().toISOString(),
59
81
  },
60
82
  )
83
+ await priviAgent.api.app.bsky.feed.post.create(
84
+ {
85
+ repo: priviAgent.session?.did,
86
+ },
87
+ {
88
+ text: 'testing again',
89
+ createdAt: new Date().toISOString(),
90
+ },
91
+ )
61
92
  })
62
93
 
63
- it('restricts certain actions', async () => {
64
- const attempt = appAgent.api.com.atproto.server.createAppPassword({
94
+ it('restricts full access actions', async () => {
95
+ const attempt1 = appAgent.api.com.atproto.server.createAppPassword({
65
96
  name: 'another-one',
66
97
  })
98
+ await expect(attempt1).rejects.toThrow('Bad token scope')
99
+ const attempt2 = priviAgent.api.com.atproto.server.createAppPassword({
100
+ name: 'another-one',
101
+ })
102
+ await expect(attempt2).rejects.toThrow('Bad token scope')
103
+ })
104
+
105
+ it('restricts privileged app password actions', async () => {
106
+ const attempt = appAgent.api.com.atproto.server.getServiceAuth({
107
+ aud: 'did:example:test',
108
+ })
67
109
  await expect(attempt).rejects.toThrow('Bad token scope')
68
110
  })
69
111
 
112
+ it('allows privileged actions with a privileged app password', async () => {
113
+ await priviAgent.api.com.atproto.server.getServiceAuth({
114
+ aud: 'did:example:test',
115
+ })
116
+ })
117
+
70
118
  it('persists scope across refreshes', async () => {
71
119
  const session = await appAgent.api.com.atproto.server.refreshSession(
72
120
  undefined,
@@ -77,6 +125,7 @@ describe('app_passwords', () => {
77
125
  },
78
126
  )
79
127
 
128
+ // allows any access auth
80
129
  await appAgent.api.app.bsky.feed.post.create(
81
130
  {
82
131
  repo: appAgent.session?.did,
@@ -90,7 +139,56 @@ describe('app_passwords', () => {
90
139
  },
91
140
  )
92
141
 
93
- const attempt = appAgent.api.com.atproto.server.createAppPassword(
142
+ // allows privileged app passwords or higher
143
+ const priviAttempt = appAgent.api.com.atproto.server.getServiceAuth({
144
+ aud: 'did:example:test',
145
+ })
146
+ await expect(priviAttempt).rejects.toThrow('Bad token scope')
147
+
148
+ // allows only full access auth
149
+ const fullAttempt = appAgent.api.com.atproto.server.createAppPassword(
150
+ {
151
+ name: 'another-one',
152
+ },
153
+ {
154
+ encoding: 'application/json',
155
+ headers: { authorization: `Bearer ${session.data.accessJwt}` },
156
+ },
157
+ )
158
+ await expect(fullAttempt).rejects.toThrow('Bad token scope')
159
+ })
160
+
161
+ it('persists privileged scope across refreshes', async () => {
162
+ const session = await priviAgent.api.com.atproto.server.refreshSession(
163
+ undefined,
164
+ {
165
+ headers: {
166
+ authorization: `Bearer ${priviAgent.session?.refreshJwt}`,
167
+ },
168
+ },
169
+ )
170
+
171
+ // allows any access auth
172
+ await priviAgent.api.app.bsky.feed.post.create(
173
+ {
174
+ repo: priviAgent.session?.did,
175
+ },
176
+ {
177
+ text: 'Testing testing',
178
+ createdAt: new Date().toISOString(),
179
+ },
180
+ {
181
+ authorization: `Bearer ${session.data.accessJwt}`,
182
+ },
183
+ )
184
+
185
+ // allows privileged app passwords or higher
186
+ await priviAgent.api.com.atproto.server.getServiceAuth({
187
+ aud: 'did:example:test',
188
+ })
189
+
190
+ // allows only full access auth
191
+ const attempt = priviAgent.api.com.atproto.server.createAppPassword(
94
192
  {
95
193
  name: 'another-one',
96
194
  },
@@ -104,8 +202,11 @@ describe('app_passwords', () => {
104
202
 
105
203
  it('lists available app-specific passwords', async () => {
106
204
  const res = await appAgent.api.com.atproto.server.listAppPasswords()
107
- expect(res.data.passwords.length).toBe(1)
108
- expect(res.data.passwords[0].name).toEqual('test-pass')
205
+ expect(res.data.passwords.length).toBe(2)
206
+ expect(res.data.passwords[0].name).toEqual('privi-pass')
207
+ expect(res.data.passwords[0].privileged).toEqual(true)
208
+ expect(res.data.passwords[1].name).toEqual('test-pass')
209
+ expect(res.data.passwords[1].privileged).toEqual(false)
109
210
  })
110
211
 
111
212
  it('revokes an app-specific password', async () => {