@atproto/pds 0.5.5 → 0.5.7

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 (256) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/account-manager/account-manager.d.ts +51 -13
  3. package/dist/account-manager/account-manager.d.ts.map +1 -1
  4. package/dist/account-manager/account-manager.js +86 -26
  5. package/dist/account-manager/account-manager.js.map +1 -1
  6. package/dist/account-manager/db/migrations/005-oauth-account-management.d.ts.map +1 -1
  7. package/dist/account-manager/db/migrations/005-oauth-account-management.js +3 -4
  8. package/dist/account-manager/db/migrations/005-oauth-account-management.js.map +1 -1
  9. package/dist/account-manager/db/schema/authorization-request.d.ts +2 -2
  10. package/dist/account-manager/db/schema/authorization-request.d.ts.map +1 -1
  11. package/dist/account-manager/db/schema/authorization-request.js.map +1 -1
  12. package/dist/account-manager/db/schema/authorized-client.d.ts +2 -2
  13. package/dist/account-manager/db/schema/authorized-client.d.ts.map +1 -1
  14. package/dist/account-manager/db/schema/authorized-client.js.map +1 -1
  15. package/dist/account-manager/db/schema/token.d.ts +2 -2
  16. package/dist/account-manager/db/schema/token.d.ts.map +1 -1
  17. package/dist/account-manager/db/schema/token.js.map +1 -1
  18. package/dist/account-manager/helpers/account-device.d.ts +23 -196
  19. package/dist/account-manager/helpers/account-device.d.ts.map +1 -1
  20. package/dist/account-manager/helpers/account-device.js +3 -3
  21. package/dist/account-manager/helpers/account-device.js.map +1 -1
  22. package/dist/account-manager/helpers/account.d.ts +14 -4
  23. package/dist/account-manager/helpers/account.d.ts.map +1 -1
  24. package/dist/account-manager/helpers/account.js +17 -11
  25. package/dist/account-manager/helpers/account.js.map +1 -1
  26. package/dist/account-manager/helpers/auth.js +1 -1
  27. package/dist/account-manager/helpers/auth.js.map +1 -1
  28. package/dist/account-manager/helpers/authorization-request.d.ts +83 -5
  29. package/dist/account-manager/helpers/authorization-request.d.ts.map +1 -1
  30. package/dist/account-manager/helpers/authorization-request.js +8 -8
  31. package/dist/account-manager/helpers/authorization-request.js.map +1 -1
  32. package/dist/account-manager/helpers/authorized-client.d.ts +5 -4
  33. package/dist/account-manager/helpers/authorized-client.d.ts.map +1 -1
  34. package/dist/account-manager/helpers/authorized-client.js +3 -0
  35. package/dist/account-manager/helpers/authorized-client.js.map +1 -1
  36. package/dist/account-manager/helpers/device.d.ts +9 -3
  37. package/dist/account-manager/helpers/device.d.ts.map +1 -1
  38. package/dist/account-manager/helpers/device.js +4 -4
  39. package/dist/account-manager/helpers/device.js.map +1 -1
  40. package/dist/account-manager/helpers/invite.d.ts +35 -2
  41. package/dist/account-manager/helpers/invite.d.ts.map +1 -1
  42. package/dist/account-manager/helpers/lexicon.d.ts.map +1 -1
  43. package/dist/account-manager/helpers/lexicon.js +6 -0
  44. package/dist/account-manager/helpers/lexicon.js.map +1 -1
  45. package/dist/account-manager/helpers/password.d.ts +1 -0
  46. package/dist/account-manager/helpers/password.d.ts.map +1 -1
  47. package/dist/account-manager/helpers/password.js +3 -0
  48. package/dist/account-manager/helpers/password.js.map +1 -1
  49. package/dist/account-manager/helpers/token.d.ts +72 -1031
  50. package/dist/account-manager/helpers/token.d.ts.map +1 -1
  51. package/dist/account-manager/helpers/token.js +13 -10
  52. package/dist/account-manager/helpers/token.js.map +1 -1
  53. package/dist/account-manager/helpers/used-refresh-token.d.ts +6 -2
  54. package/dist/account-manager/helpers/used-refresh-token.d.ts.map +1 -1
  55. package/dist/account-manager/oauth-store.d.ts +17 -13
  56. package/dist/account-manager/oauth-store.d.ts.map +1 -1
  57. package/dist/account-manager/oauth-store.js +89 -39
  58. package/dist/account-manager/oauth-store.js.map +1 -1
  59. package/dist/actor-store/blob/reader.d.ts.map +1 -1
  60. package/dist/actor-store/blob/reader.js +2 -3
  61. package/dist/actor-store/blob/reader.js.map +1 -1
  62. package/dist/actor-store/record/reader.js +3 -3
  63. package/dist/actor-store/record/reader.js.map +1 -1
  64. package/dist/actor-store/repo/sql-repo-reader.d.ts +5 -1
  65. package/dist/actor-store/repo/sql-repo-reader.d.ts.map +1 -1
  66. package/dist/actor-store/repo/sql-repo-reader.js.map +1 -1
  67. package/dist/api/com/atproto/admin/updateSubjectStatus.d.ts.map +1 -1
  68. package/dist/api/com/atproto/admin/updateSubjectStatus.js +12 -4
  69. package/dist/api/com/atproto/admin/updateSubjectStatus.js.map +1 -1
  70. package/dist/api/com/atproto/server/activateAccount.d.ts.map +1 -1
  71. package/dist/api/com/atproto/server/activateAccount.js +25 -31
  72. package/dist/api/com/atproto/server/activateAccount.js.map +1 -1
  73. package/dist/api/com/atproto/server/deactivateAccount.d.ts.map +1 -1
  74. package/dist/api/com/atproto/server/deactivateAccount.js +3 -4
  75. package/dist/api/com/atproto/server/deactivateAccount.js.map +1 -1
  76. package/dist/api/com/atproto/server/deleteAccount.d.ts.map +1 -1
  77. package/dist/api/com/atproto/server/deleteAccount.js +51 -37
  78. package/dist/api/com/atproto/server/deleteAccount.js.map +1 -1
  79. package/dist/api/com/atproto/server/util.d.ts +25 -6
  80. package/dist/api/com/atproto/server/util.d.ts.map +1 -1
  81. package/dist/api/com/atproto/server/util.js.map +1 -1
  82. package/dist/background.d.ts +12 -6
  83. package/dist/background.d.ts.map +1 -1
  84. package/dist/background.js +32 -13
  85. package/dist/background.js.map +1 -1
  86. package/dist/config/config.d.ts +2 -1
  87. package/dist/config/config.d.ts.map +1 -1
  88. package/dist/config/config.js +4 -1
  89. package/dist/config/config.js.map +1 -1
  90. package/dist/context.d.ts.map +1 -1
  91. package/dist/context.js +5 -32
  92. package/dist/context.js.map +1 -1
  93. package/dist/db/migrator.d.ts +4 -3
  94. package/dist/db/migrator.d.ts.map +1 -1
  95. package/dist/db/migrator.js +1 -1
  96. package/dist/db/migrator.js.map +1 -1
  97. package/dist/db/pagination.d.ts +2 -1
  98. package/dist/db/pagination.d.ts.map +1 -1
  99. package/dist/db/pagination.js +2 -2
  100. package/dist/db/pagination.js.map +1 -1
  101. package/dist/db/util.d.ts +3 -3
  102. package/dist/db/util.d.ts.map +1 -1
  103. package/dist/db/util.js.map +1 -1
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js +33 -11
  107. package/dist/index.js.map +1 -1
  108. package/dist/lexicons/app/bsky/notification/defs.defs.d.ts +5 -0
  109. package/dist/lexicons/app/bsky/notification/defs.defs.d.ts.map +1 -1
  110. package/dist/lexicons/app/bsky/notification/defs.defs.js +1 -0
  111. package/dist/lexicons/app/bsky/notification/defs.defs.js.map +1 -1
  112. package/dist/lexicons/chat/bsky/notification/defs.d.ts +2 -0
  113. package/dist/lexicons/chat/bsky/notification/defs.d.ts.map +1 -0
  114. package/dist/lexicons/chat/bsky/notification/defs.defs.d.ts +20 -0
  115. package/dist/lexicons/chat/bsky/notification/defs.defs.d.ts.map +1 -0
  116. package/dist/lexicons/chat/bsky/notification/defs.defs.js +19 -0
  117. package/dist/lexicons/chat/bsky/notification/defs.defs.js.map +1 -0
  118. package/dist/lexicons/chat/bsky/notification/defs.js +5 -0
  119. package/dist/lexicons/chat/bsky/notification/defs.js.map +1 -0
  120. package/dist/lexicons/chat/bsky/notification/getPreferences.d.ts +3 -0
  121. package/dist/lexicons/chat/bsky/notification/getPreferences.d.ts.map +1 -0
  122. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.d.ts +18 -0
  123. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.d.ts.map +1 -0
  124. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.js +16 -0
  125. package/dist/lexicons/chat/bsky/notification/getPreferences.defs.js.map +1 -0
  126. package/dist/lexicons/chat/bsky/notification/getPreferences.js +6 -0
  127. package/dist/lexicons/chat/bsky/notification/getPreferences.js.map +1 -0
  128. package/dist/lexicons/chat/bsky/notification/putPreferences.d.ts +3 -0
  129. package/dist/lexicons/chat/bsky/notification/putPreferences.d.ts.map +1 -0
  130. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.d.ts +27 -0
  131. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.d.ts.map +1 -0
  132. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.js +22 -0
  133. package/dist/lexicons/chat/bsky/notification/putPreferences.defs.js.map +1 -0
  134. package/dist/lexicons/chat/bsky/notification/putPreferences.js +6 -0
  135. package/dist/lexicons/chat/bsky/notification/putPreferences.js.map +1 -0
  136. package/dist/lexicons/chat/bsky/notification.d.ts +4 -0
  137. package/dist/lexicons/chat/bsky/notification.d.ts.map +1 -0
  138. package/dist/lexicons/chat/bsky/notification.js +7 -0
  139. package/dist/lexicons/chat/bsky/notification.js.map +1 -0
  140. package/dist/lexicons/chat/bsky.d.ts +1 -0
  141. package/dist/lexicons/chat/bsky.d.ts.map +1 -1
  142. package/dist/lexicons/chat/bsky.js +1 -0
  143. package/dist/lexicons/chat/bsky.js.map +1 -1
  144. package/dist/lexicons/tools/ozone/report/defs.defs.d.ts +4 -0
  145. package/dist/lexicons/tools/ozone/report/defs.defs.d.ts.map +1 -1
  146. package/dist/lexicons/tools/ozone/report/defs.defs.js +2 -0
  147. package/dist/lexicons/tools/ozone/report/defs.defs.js.map +1 -1
  148. package/dist/lexicons/tools/ozone/report/queryActivities.d.ts +3 -0
  149. package/dist/lexicons/tools/ozone/report/queryActivities.d.ts.map +1 -0
  150. package/dist/lexicons/tools/ozone/report/queryActivities.defs.d.ts +42 -0
  151. package/dist/lexicons/tools/ozone/report/queryActivities.defs.d.ts.map +1 -0
  152. package/dist/lexicons/tools/ozone/report/queryActivities.defs.js +31 -0
  153. package/dist/lexicons/tools/ozone/report/queryActivities.defs.js.map +1 -0
  154. package/dist/lexicons/tools/ozone/report/queryActivities.js +6 -0
  155. package/dist/lexicons/tools/ozone/report/queryActivities.js.map +1 -0
  156. package/dist/lexicons/tools/ozone/report.d.ts +1 -0
  157. package/dist/lexicons/tools/ozone/report.d.ts.map +1 -1
  158. package/dist/lexicons/tools/ozone/report.js +1 -0
  159. package/dist/lexicons/tools/ozone/report.js.map +1 -1
  160. package/dist/mailer/index.d.ts +2 -0
  161. package/dist/mailer/index.d.ts.map +1 -1
  162. package/dist/mailer/index.js +2 -0
  163. package/dist/mailer/index.js.map +1 -1
  164. package/dist/pipethrough.d.ts +3 -0
  165. package/dist/pipethrough.d.ts.map +1 -1
  166. package/dist/pipethrough.js +32 -0
  167. package/dist/pipethrough.js.map +1 -1
  168. package/dist/scripts/sequencer-recovery/recovery-db.js.map +1 -1
  169. package/dist/sequencer/events.d.ts.map +1 -1
  170. package/dist/sequencer/events.js +6 -13
  171. package/dist/sequencer/events.js.map +1 -1
  172. package/dist/sequencer/sequencer.d.ts +1 -1
  173. package/dist/sequencer/sequencer.d.ts.map +1 -1
  174. package/dist/sequencer/sequencer.js.map +1 -1
  175. package/package.json +14 -14
  176. package/src/account-manager/account-manager.ts +135 -36
  177. package/src/account-manager/db/migrations/005-oauth-account-management.ts +6 -5
  178. package/src/account-manager/db/schema/authorization-request.ts +2 -1
  179. package/src/account-manager/db/schema/authorized-client.ts +6 -2
  180. package/src/account-manager/db/schema/token.ts +2 -2
  181. package/src/account-manager/helpers/account-device.ts +5 -11
  182. package/src/account-manager/helpers/account.ts +39 -16
  183. package/src/account-manager/helpers/auth.ts +2 -2
  184. package/src/account-manager/helpers/authorization-request.ts +12 -8
  185. package/src/account-manager/helpers/authorized-client.ts +12 -6
  186. package/src/account-manager/helpers/device.ts +4 -4
  187. package/src/account-manager/helpers/lexicon.ts +8 -3
  188. package/src/account-manager/helpers/password.ts +6 -0
  189. package/src/account-manager/helpers/token.ts +17 -11
  190. package/src/account-manager/oauth-store.ts +129 -61
  191. package/src/actor-store/blob/reader.ts +8 -5
  192. package/src/actor-store/record/reader.ts +3 -3
  193. package/src/actor-store/repo/sql-repo-reader.ts +1 -1
  194. package/src/api/com/atproto/admin/updateSubjectStatus.ts +16 -4
  195. package/src/api/com/atproto/server/activateAccount.ts +27 -49
  196. package/src/api/com/atproto/server/deactivateAccount.ts +3 -7
  197. package/src/api/com/atproto/server/deleteAccount.ts +60 -43
  198. package/src/api/com/atproto/server/util.ts +24 -7
  199. package/src/background.ts +49 -18
  200. package/src/config/config.ts +7 -2
  201. package/src/context.ts +6 -40
  202. package/src/db/migrator.ts +2 -1
  203. package/src/db/pagination.ts +7 -7
  204. package/src/db/util.ts +5 -2
  205. package/src/index.ts +31 -13
  206. package/src/mailer/index.ts +4 -2
  207. package/src/pipethrough.ts +38 -1
  208. package/src/scripts/sequencer-recovery/recovery-db.ts +1 -1
  209. package/src/sequencer/events.ts +8 -13
  210. package/src/sequencer/sequencer.ts +1 -3
  211. package/tests/_util.ts +8 -25
  212. package/tests/account-deactivation.test.ts +1 -1
  213. package/tests/account-deletion.test.ts +1 -1
  214. package/tests/account-manager.test.ts +79 -0
  215. package/tests/account-migration.test.ts +2 -2
  216. package/tests/account-status.test.ts +206 -0
  217. package/tests/account.test.ts +1 -1
  218. package/tests/app-passwords.test.ts +1 -1
  219. package/tests/auth.test.ts +1 -1
  220. package/tests/blob-deletes.test.ts +1 -1
  221. package/tests/create-post.test.ts +1 -1
  222. package/tests/crud.test.ts +1 -1
  223. package/tests/db.test.ts +4 -4
  224. package/tests/email-confirmation.test.ts +1 -1
  225. package/tests/file-uploads.test.ts +2 -2
  226. package/tests/get-service-auth.test.ts +1 -1
  227. package/tests/handles.test.ts +1 -1
  228. package/tests/invite-codes.test.ts +1 -1
  229. package/tests/invites-admin.test.ts +1 -1
  230. package/tests/moderation.test.ts +1 -1
  231. package/tests/moderator-auth.test.ts +1 -1
  232. package/tests/oauth.test.ts +91 -0
  233. package/tests/plc-operations.test.ts +1 -1
  234. package/tests/preferences.test.ts +1 -1
  235. package/tests/proxied/admin.test.ts +1 -1
  236. package/tests/proxied/feedgen.test.ts +1 -1
  237. package/tests/proxied/notif.test.ts +6 -13
  238. package/tests/proxied/procedures.test.ts +1 -1
  239. package/tests/proxied/proxy-catchall.test.ts +9 -8
  240. package/tests/proxied/proxy-header.test.ts +12 -8
  241. package/tests/proxied/proxy-oauth-aud.test.ts +17 -10
  242. package/tests/proxied/read-after-write.test.ts +4 -5
  243. package/tests/proxied/views.test.ts +1 -1
  244. package/tests/races.test.ts +1 -1
  245. package/tests/rate-limits.test.ts +1 -1
  246. package/tests/recovery.test.ts +1 -1
  247. package/tests/seeds/basic.ts +2 -2
  248. package/tests/seeds/users.ts +4 -1
  249. package/tests/sequencer.test.ts +1 -1
  250. package/tests/server.test.ts +9 -12
  251. package/tests/sync/invertible-ops.test.ts +1 -1
  252. package/tests/sync/list.test.ts +1 -1
  253. package/tests/sync/subscribe-repos.test.ts +1 -1
  254. package/tests/sync/sync.test.ts +1 -1
  255. package/tests/takedown-appeal.test.ts +1 -1
  256. package/tsconfig.build.tsbuildinfo +1 -1
@@ -162,9 +162,7 @@ export class Sequencer extends (EventEmitter as new () => SequencerEmitter) {
162
162
  await wait(waitTime)
163
163
  }
164
164
 
165
- protected async sequenceEvts(
166
- events: readonly RepoSeqInsert[],
167
- ): Promise<number[]> {
165
+ async sequenceEvts(events: readonly RepoSeqInsert[]): Promise<number[]> {
168
166
  if (!events.length) return []
169
167
  const rows = await this.db.executeWithRetry(
170
168
  this.db.db.insertInto('repo_seq').values(events).returning('seq'),
package/tests/_util.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { Server } from 'node:http'
1
+ import { RequestListener, createServer } from 'node:http'
2
2
  import { AddressInfo } from 'node:net'
3
- import { type Express } from 'express'
3
+ // eslint-disable-next-line import/default
4
+ import httpTerminator from 'http-terminator'
4
5
  import { ToolsOzoneModerationDefs, lexToJson } from '@atproto/api'
5
6
  import { isCidString } from '@atproto/lex'
6
7
  import { isCid, isPlainObject } from '@atproto/lex-data'
@@ -146,19 +147,11 @@ export const paginateAll = async <T extends { cursor?: string }>(
146
147
  return results
147
148
  }
148
149
 
149
- export async function startServer(app: Express) {
150
- return new Promise<{
151
- origin: string
152
- server: Server
153
- stop: () => Promise<void>
154
- }>((resolve, reject) => {
150
+ export async function startServer(listener: RequestListener) {
151
+ return new Promise<AsyncDisposable & { port: number }>((resolve, reject) => {
155
152
  const onListen = () => {
156
153
  const port = (server.address() as AddressInfo).port
157
- resolve({
158
- server,
159
- origin: `http://localhost:${port}`,
160
- stop: () => stopServer(server),
161
- })
154
+ resolve({ port, [Symbol.asyncDispose]: () => terminator.terminate() })
162
155
  cleanup()
163
156
  }
164
157
  const onError = (err: Error) => {
@@ -170,22 +163,12 @@ export async function startServer(app: Express) {
170
163
  server.removeListener('error', onError)
171
164
  }
172
165
 
173
- const server = app
166
+ const server = createServer(listener)
174
167
  .listen(0)
175
168
  .once('listening', onListen)
176
169
  .once('error', onError)
177
- })
178
- }
179
170
 
180
- export async function stopServer(server: Server) {
181
- return new Promise<void>((resolve, reject) => {
182
- server.close((err) => {
183
- if (err) {
184
- reject(err)
185
- } else {
186
- resolve()
187
- }
188
- })
171
+ const terminator = httpTerminator.createHttpTerminator({ server })
189
172
  })
190
173
  }
191
174
 
@@ -39,7 +39,7 @@ describe('account deactivation', () => {
39
39
  })
40
40
 
41
41
  afterAll(async () => {
42
- await network.close()
42
+ await network?.close()
43
43
  })
44
44
 
45
45
  it('deactivates account', async () => {
@@ -57,7 +57,7 @@ describe('account deletion', () => {
57
57
 
58
58
  afterAll(async () => {
59
59
  mailer.transporter.sendMail = _origSendMail
60
- await network.close()
60
+ await network?.close()
61
61
  })
62
62
 
63
63
  const getMailFrom = async (promise): Promise<Mail.Options> => {
@@ -134,6 +134,7 @@ describe('account manager', () => {
134
134
  const [params] = sendResetPasswordMock.mock.lastCall!
135
135
  expect(params).toEqual({
136
136
  handle: 'bob.test',
137
+ locale: 'fr',
137
138
  token: expect.any(String),
138
139
  })
139
140
 
@@ -380,4 +381,82 @@ describe('account manager', () => {
380
381
  await page.clickOnText('Retour')
381
382
  await page.ensureTextVisibility('bob-renamed.test', 'span')
382
383
  })
384
+
385
+ it('allows deactivating & reactivating the account', async () => {
386
+ await using page = await PageHelper.from(browser, { languages })
387
+
388
+ await page.goto(new URL('/account', network.pds.url))
389
+
390
+ await page.assertTitle('Mon compte Atmosphère')
391
+
392
+ await page.clickOnText('Compte utilisateur', 'a')
393
+
394
+ await page.clickOnText('Désactiver le compte')
395
+
396
+ await page.ensureTextVisibility(
397
+ 'limite de temps pour la désactivation du compte',
398
+ )
399
+
400
+ await page.clickOnText('Oui, désactiver')
401
+
402
+ await page.waitForNetworkIdle()
403
+
404
+ // The row should now offer re-activation
405
+ await page.ensureTextVisibility('Réactiver le compte', 'span')
406
+
407
+ await page.clickOnText('Réactiver le compte')
408
+
409
+ // @NOTE The dialog's submit label ("Réactiver") is a substring of the
410
+ // trigger row ("Réactiver le compte"), which comes first in DOM order, so
411
+ // we target the dialog's submit button instead of using its text.
412
+ await page.clickOn('[role="dialog"] button[type="submit"]')
413
+
414
+ await page.waitForNetworkIdle()
415
+
416
+ await page.ensureTextVisibility('Désactiver le compte', 'span')
417
+ })
418
+
419
+ it('allows deleting the account', async () => {
420
+ await using page = await PageHelper.from(browser, { languages })
421
+
422
+ await page.goto(new URL('/account', network.pds.url))
423
+
424
+ await page.assertTitle('Mon compte Atmosphère')
425
+
426
+ await page.clickOnText('Compte utilisateur', 'a')
427
+
428
+ await page.clickOnText('Supprimer le compte')
429
+
430
+ using sendAccountDeleteMock = jest
431
+ .spyOn(network.pds.ctx.mailer, 'sendAccountDelete')
432
+ .mockImplementation(async () => {
433
+ // noop
434
+ })
435
+
436
+ await page.clickOnText("Envoyer l'email")
437
+
438
+ await page.waitForNetworkIdle()
439
+
440
+ expect(sendAccountDeleteMock).toHaveBeenCalledTimes(1)
441
+
442
+ const [params] = sendAccountDeleteMock.mock.lastCall!
443
+ expect(params).toEqual({
444
+ locale: 'fr',
445
+ token: expect.any(String),
446
+ })
447
+
448
+ await page.typeInInput('code', params.token)
449
+ await page.typeInInput('password', 'bob-new-pass')
450
+
451
+ await page.clickOnText('Supprimer mon compte')
452
+
453
+ // A final confirmation step is displayed
454
+ await page.ensureTextVisibility('Êtes-vous vraiment, vraiment sûr ?', 'h2')
455
+
456
+ await page.clickOnText('Oui, supprimer mon compte')
457
+
458
+ // Once the account is deleted, the session is gone and the user is
459
+ // brought back to the sign-in page.
460
+ await page.assertTitle('Se connecter')
461
+ })
383
462
  })
@@ -71,8 +71,8 @@ describe('account migration', () => {
71
71
  })
72
72
 
73
73
  afterAll(async () => {
74
- await newPds.close()
75
- await network.close()
74
+ await newPds?.close()
75
+ await network?.close()
76
76
  })
77
77
 
78
78
  it('migrates an account', async () => {
@@ -0,0 +1,206 @@
1
+ import { jest } from '@jest/globals'
2
+ import { TestNetworkNoAppView } from '@atproto/dev-env'
3
+ import { Client } from '@atproto/lex'
4
+ import { com } from '../src/lexicons/index.js'
5
+
6
+ describe('account-status', () => {
7
+ let network: TestNetworkNoAppView
8
+ let client: Client
9
+
10
+ beforeAll(async () => {
11
+ network = await TestNetworkNoAppView.create({
12
+ dbPostgresSchema: 'account_status',
13
+ })
14
+ client = network.pds.getClient()
15
+ })
16
+
17
+ afterEach(async () => {
18
+ await network.processAll()
19
+ })
20
+
21
+ afterAll(async () => {
22
+ await network?.close()
23
+ })
24
+
25
+ it('takedown + activation triggers an error', async () => {
26
+ const { did } = await client.call(com.atproto.server.createAccount, {
27
+ handle: 'iris.test',
28
+ email: 'iris@test.com',
29
+ password: 'password',
30
+ })
31
+
32
+ await expect(
33
+ client.call(
34
+ com.atproto.admin.updateSubjectStatus,
35
+ {
36
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
37
+ takedown: { applied: true },
38
+ deactivated: { applied: false },
39
+ },
40
+ { headers: { authorization: network.pds.adminAuth() } },
41
+ ),
42
+ ).rejects.toMatchObject({
43
+ error: 'InvalidRequest',
44
+ message: 'Cannot activate and takedown an account at the same time',
45
+ })
46
+ })
47
+
48
+ it('activating a taken down account causes an error', async () => {
49
+ const { did } = await client.call(com.atproto.server.createAccount, {
50
+ handle: 'iris2.test',
51
+ email: 'iris2@test.com',
52
+ password: 'password',
53
+ })
54
+
55
+ const mock = jest.spyOn(network.pds.ctx.sequencer, 'sequenceEvts')
56
+
57
+ // First deactivate
58
+ await client.call(
59
+ com.atproto.admin.updateSubjectStatus,
60
+ {
61
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
62
+ deactivated: { applied: true },
63
+ },
64
+ { headers: { authorization: network.pds.adminAuth() } },
65
+ )
66
+
67
+ expect(mock).toHaveBeenCalledTimes(1)
68
+
69
+ // Then takedown
70
+ await client.call(
71
+ com.atproto.admin.updateSubjectStatus,
72
+ {
73
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
74
+ takedown: { applied: true },
75
+ },
76
+ { headers: { authorization: network.pds.adminAuth() } },
77
+ )
78
+
79
+ expect(mock).toHaveBeenCalledTimes(2)
80
+
81
+ // Then remove the takedown
82
+ await client.call(
83
+ com.atproto.admin.updateSubjectStatus,
84
+ {
85
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
86
+ takedown: { applied: false },
87
+ },
88
+ { headers: { authorization: network.pds.adminAuth() } },
89
+ )
90
+
91
+ expect(mock).toHaveBeenCalledTimes(3)
92
+
93
+ // Then try to activate again (should work now)
94
+ await client.call(
95
+ com.atproto.admin.updateSubjectStatus,
96
+ {
97
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
98
+ deactivated: { applied: false },
99
+ },
100
+ { headers: { authorization: network.pds.adminAuth() } },
101
+ )
102
+
103
+ expect(mock).toHaveBeenCalledTimes(4)
104
+
105
+ // Attempt login to ensure account is active
106
+ await client.call(com.atproto.server.createSession, {
107
+ identifier: 'iris2.test',
108
+ password: 'password',
109
+ })
110
+
111
+ // @TODO use "using" (requires updating jest)
112
+ mock.mockRestore()
113
+ })
114
+
115
+ it('sequences an account status event when calling updateSubjectStatus without changing the status', async () => {
116
+ const { did } = await client.call(com.atproto.server.createAccount, {
117
+ handle: 'iris3.test',
118
+ email: 'iris3@test.com',
119
+ password: 'password',
120
+ })
121
+
122
+ const mock = jest.spyOn(network.pds.ctx.sequencer, 'sequenceEvts')
123
+
124
+ // Update the account status without changing the status (should still sequence an event)
125
+ await client.call(
126
+ com.atproto.admin.updateSubjectStatus,
127
+ {
128
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
129
+ },
130
+ { headers: { authorization: network.pds.adminAuth() } },
131
+ )
132
+
133
+ expect(mock).toHaveBeenCalledTimes(1)
134
+
135
+ // @TODO use "using" (requires updating jest)
136
+ mock.mockRestore()
137
+ })
138
+
139
+ it('allows to takedown, then deactivate, an account', async () => {
140
+ const { did } = await client.call(com.atproto.server.createAccount, {
141
+ handle: 'iris4.test',
142
+ email: 'iris4@test.com',
143
+ password: 'password',
144
+ })
145
+
146
+ await client.call(
147
+ com.atproto.admin.updateSubjectStatus,
148
+ {
149
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
150
+ takedown: { applied: true },
151
+ },
152
+ { headers: { authorization: network.pds.adminAuth() } },
153
+ )
154
+
155
+ await client.call(
156
+ com.atproto.admin.updateSubjectStatus,
157
+ {
158
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
159
+ deactivated: { applied: true },
160
+ },
161
+ { headers: { authorization: network.pds.adminAuth() } },
162
+ )
163
+
164
+ const status = await client.call(
165
+ com.atproto.admin.getSubjectStatus,
166
+ { did },
167
+ { headers: { authorization: network.pds.adminAuth() } },
168
+ )
169
+
170
+ expect(status).toEqual({
171
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
172
+ takedown: { applied: true, ref: expect.any(String) },
173
+ deactivated: { applied: true },
174
+ })
175
+ })
176
+
177
+ it('throws when trying to activate a takedown account', async () => {
178
+ const { did } = await client.call(com.atproto.server.createAccount, {
179
+ handle: 'iris5.test',
180
+ email: 'iris5@test.com',
181
+ password: 'password',
182
+ })
183
+
184
+ await client.call(
185
+ com.atproto.admin.updateSubjectStatus,
186
+ {
187
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
188
+ takedown: { applied: true },
189
+ },
190
+ { headers: { authorization: network.pds.adminAuth() } },
191
+ )
192
+
193
+ await expect(
194
+ client.call(
195
+ com.atproto.admin.updateSubjectStatus,
196
+ {
197
+ subject: com.atproto.admin.defs.repoRef.$build({ did }),
198
+ deactivated: { applied: false },
199
+ },
200
+ { headers: { authorization: network.pds.adminAuth() } },
201
+ ),
202
+ ).rejects.toMatchObject({
203
+ error: 'AccountNotFound',
204
+ })
205
+ })
206
+ })
@@ -60,7 +60,7 @@ describe('account', () => {
60
60
 
61
61
  afterAll(async () => {
62
62
  mailer.transporter.sendMail = _origSendMail
63
- await network.close()
63
+ await network?.close()
64
64
  })
65
65
 
66
66
  it('serves the accounts system config', async () => {
@@ -24,7 +24,7 @@ describe('app_passwords', () => {
24
24
  })
25
25
 
26
26
  afterAll(async () => {
27
- await network.close()
27
+ await network?.close()
28
28
  })
29
29
 
30
30
  let appPass: string
@@ -16,7 +16,7 @@ describe('auth', () => {
16
16
  })
17
17
 
18
18
  afterAll(async () => {
19
- await network.close()
19
+ await network?.close()
20
20
  })
21
21
 
22
22
  const createAccount = async (info) => {
@@ -35,7 +35,7 @@ describe('blob deletes', () => {
35
35
  })
36
36
 
37
37
  afterAll(async () => {
38
- await network.close()
38
+ await network?.close()
39
39
  })
40
40
 
41
41
  const getDbBlobsForDid = (did: string) => {
@@ -25,7 +25,7 @@ describe('pds posts record creation', () => {
25
25
  })
26
26
 
27
27
  afterAll(async () => {
28
- await network.close()
28
+ await network?.close()
29
29
  })
30
30
 
31
31
  it('allows for creating posts with tags', async () => {
@@ -47,7 +47,7 @@ describe('crud operations', () => {
47
47
  })
48
48
 
49
49
  afterAll(async () => {
50
- await network.close()
50
+ await network?.close()
51
51
  })
52
52
 
53
53
  it('registers users', async () => {
package/tests/db.test.ts CHANGED
@@ -14,15 +14,15 @@ describe('db', () => {
14
14
  })
15
15
 
16
16
  afterAll(async () => {
17
- await network.close()
17
+ await network?.close()
18
18
  })
19
19
 
20
20
  it('commits changes', async () => {
21
21
  const result = await db.transaction(async (dbTxn) => {
22
22
  return await dbTxn.db
23
23
  .insertInto('repo_root')
24
+ // @ts-expect-error invalid date is intentional for testing purposes
24
25
  .values({
25
- // @ts-expect-error invalid date is intentional for testing purposes
26
26
  did: 'x',
27
27
  cid: 'x',
28
28
  rev: 'x',
@@ -56,8 +56,8 @@ describe('db', () => {
56
56
  const promise = db.transaction(async (dbTxn) => {
57
57
  await dbTxn.db
58
58
  .insertInto('repo_root')
59
+ // @ts-expect-error invalid date is intentional for testing purposes
59
60
  .values({
60
- // @ts-expect-error invalid date is intentional for testing purposes
61
61
  did: 'y',
62
62
  cid: 'y',
63
63
  rev: 'y',
@@ -139,8 +139,8 @@ describe('db', () => {
139
139
  const name = `user${i}`
140
140
  const query = dbTxn.db
141
141
  .insertInto('repo_root')
142
+ // @ts-expect-error invalid date is intentional for testing purposes
142
143
  .values({
143
- // @ts-expect-error invalid date is intentional for testing purposes
144
144
  cid: name,
145
145
  did: name,
146
146
  rev: name,
@@ -42,7 +42,7 @@ describe('email confirmation', () => {
42
42
 
43
43
  afterAll(async () => {
44
44
  mailer.transporter.sendMail = _origSendMail
45
- await network.close()
45
+ await network?.close()
46
46
  })
47
47
 
48
48
  const getMailFrom = async (promise): Promise<SendMailOptions> => {
@@ -41,8 +41,8 @@ describe('file uploads', () => {
41
41
  })
42
42
 
43
43
  afterAll(async () => {
44
- aliceDb.close()
45
- await network.close()
44
+ aliceDb?.close()
45
+ await network?.close()
46
46
  })
47
47
 
48
48
  let smallBlob: TypedBlobRef
@@ -23,7 +23,7 @@ describe('com.atproto.server.getServiceAuth', () => {
23
23
  })
24
24
 
25
25
  afterAll(async () => {
26
- await network.close()
26
+ await network?.close()
27
27
  })
28
28
 
29
29
  it('issues a token whose aud matches a bare-DID input', async () => {
@@ -60,7 +60,7 @@ describe('handles', () => {
60
60
  })
61
61
 
62
62
  afterAll(async () => {
63
- await network.close()
63
+ await network?.close()
64
64
  })
65
65
 
66
66
  const getHandleFromDb = async (
@@ -26,7 +26,7 @@ describe('account', () => {
26
26
  })
27
27
 
28
28
  afterAll(async () => {
29
- await network.close()
29
+ await network?.close()
30
30
  })
31
31
 
32
32
  it('describes the fact that invites are required', async () => {
@@ -20,7 +20,7 @@ describe('pds admin invite views', () => {
20
20
  })
21
21
 
22
22
  afterAll(async () => {
23
- await network.close()
23
+ await network?.close()
24
24
  })
25
25
 
26
26
  let alice: string
@@ -47,7 +47,7 @@ describe('moderation', () => {
47
47
  })
48
48
 
49
49
  afterAll(async () => {
50
- await network.close()
50
+ await network?.close()
51
51
  })
52
52
 
53
53
  it('takes down accounts', async () => {
@@ -71,7 +71,7 @@ describe('moderator auth', () => {
71
71
  }, 20_000) // @NOTE seeding can take a while
72
72
 
73
73
  afterAll(async () => {
74
- await network.close()
74
+ await network?.close()
75
75
  })
76
76
 
77
77
  it('allows service auth requests from the configured appview did', async () => {
@@ -160,6 +160,7 @@ describe('oauth', () => {
160
160
  const [params] = sendTemplateMock.mock.lastCall!
161
161
  expect(params).toEqual({
162
162
  handle: 'alice.test',
163
+ locale: 'fr',
163
164
  token: expect.any(String),
164
165
  })
165
166
 
@@ -240,4 +241,94 @@ describe('oauth', () => {
240
241
 
241
242
  await page.waitForNetworkIdle()
242
243
  })
244
+
245
+ it('revokes OAuth sessions on deactivation & requires re-activation on sign-in', async () => {
246
+ await using page = await PageHelper.from(browser, { languages })
247
+
248
+ // Sign into the client (the device session is remembered, so the flow
249
+ // jumps straight to the consent screen).
250
+ await page.goto(appUrl)
251
+
252
+ await page.assertTitle('OAuth Client Example')
253
+
254
+ await page.navigationAction(async () => {
255
+ const input = await page.typeInInput('identifier', 'alice.test')
256
+ await input.press('Enter')
257
+ })
258
+
259
+ await page.assertTitle('Autoriser')
260
+
261
+ await page.navigationClick('Autoriser')
262
+
263
+ await page.assertTitle('OAuth Client Example')
264
+
265
+ await page.ensureTextVisibility('Token info', 'h2')
266
+
267
+ // While the client page is still open, deactivate the account through
268
+ // the account manager in another page.
269
+ {
270
+ await using accountPage = await PageHelper.from(browser, { languages })
271
+
272
+ await accountPage.goto(new URL('/account', network.pds.url))
273
+
274
+ await accountPage.assertTitle('Mon compte Atmosphère')
275
+
276
+ await accountPage.clickOnText('Compte utilisateur', 'a')
277
+
278
+ await accountPage.clickOnText('Désactiver le compte')
279
+
280
+ await accountPage.clickOnText('Oui, désactiver')
281
+
282
+ await accountPage.waitForNetworkIdle()
283
+
284
+ await accountPage.ensureTextVisibility('Réactiver le compte', 'span')
285
+
286
+ await network.processAll()
287
+ }
288
+
289
+ // Back in the client: deactivation revoked every OAuth session, so
290
+ // refreshing the credentials logs the user out.
291
+ await page.clickOnText('refresh').catch((_err) => {
292
+ // The OAuth app may have refreshed the session on it's own, causing the
293
+ // page to reset before the click is processed, which throws an error.
294
+ })
295
+
296
+ await page.waitForNetworkIdle()
297
+
298
+ await page.ensureTextVisibility('Login with the Atmosphere', 'h2')
299
+
300
+ // Signing back in with the deactivated account asks the user to
301
+ // re-activate it before the flow can proceed.
302
+ // @NOTE The PDS is used directly as issuer (rather than resolving the
303
+ // handle) because handle resolution does not work for deactivated
304
+ // accounts.
305
+
306
+ await page.navigationClick(`Login with ${new URL(network.pds.url).host}`)
307
+
308
+ await page.ensureTextVisibility('Se connecter en tant que...')
309
+
310
+ await page.clickOnText('alice.test', 'span')
311
+
312
+ await page.assertTitle('Heureux de vous revoir!')
313
+
314
+ await page.ensureTextVisibility('Vous avez précédemment désactivé')
315
+
316
+ await page.clickOnText('Oui, réactiver mon compte')
317
+
318
+ // Deactivation also cleared the authorized clients, so consent is
319
+ // required again.
320
+ await page.assertTitle('Autoriser')
321
+
322
+ await page.navigationClick('Autoriser')
323
+
324
+ await page.assertTitle('OAuth Client Example')
325
+
326
+ await page.ensureTextVisibility('Token info', 'h2')
327
+
328
+ await page.clickOnAriaLabel('User menu')
329
+
330
+ await page.clickOnText('Sign out')
331
+
332
+ await page.waitForNetworkIdle()
333
+ })
243
334
  })