@atproto/pds 0.4.25 → 0.4.26

Sign up to get free protection for your applications and to get access to all the features.
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 () => {