@podosoft/podokit 1.4.2 → 1.6.0

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 (245) hide show
  1. package/README.md +2 -1
  2. package/dist/app-proxy-timeouts.d.ts +27 -0
  3. package/dist/app-proxy-timeouts.js +180 -0
  4. package/dist/assemble.d.ts +5 -2
  5. package/dist/assemble.js +14 -4
  6. package/dist/deploy-checks.d.ts +44 -0
  7. package/dist/deploy-checks.js +267 -0
  8. package/dist/deploy-compose-profile.d.ts +6 -2
  9. package/dist/deploy-compose-profile.js +15 -6
  10. package/dist/deploy-compose-render.d.ts +12 -4
  11. package/dist/deploy-compose-render.js +47 -17
  12. package/dist/deploy-compose.d.ts +2 -5
  13. package/dist/deploy-compose.js +7 -2
  14. package/dist/deploy-profile.d.ts +6 -2
  15. package/dist/deploy-profile.js +11 -7
  16. package/dist/deploy-render.d.ts +4 -1
  17. package/dist/deploy-render.js +28 -20
  18. package/dist/deploy-schema.d.ts +61 -0
  19. package/dist/deploy-schema.js +157 -1
  20. package/dist/deploy.d.ts +2 -5
  21. package/dist/deploy.js +5 -2
  22. package/dist/dev.d.ts +21 -0
  23. package/dist/dev.js +78 -3
  24. package/dist/eject.js +2 -0
  25. package/dist/index.js +85 -5
  26. package/dist/lib.d.ts +2 -0
  27. package/dist/lib.js +5 -2
  28. package/dist/lockfile.d.ts +24 -0
  29. package/dist/lockfile.js +77 -5
  30. package/dist/provider.d.ts +1 -0
  31. package/dist/provider.js +12 -4
  32. package/dist/remove.js +1 -1
  33. package/dist/template-files.d.ts +42 -0
  34. package/dist/template-files.js +102 -0
  35. package/dist/templates/fullstack/AGENTS.md +44 -7
  36. package/dist/templates/fullstack/README.md +8 -7
  37. package/dist/templates/fullstack/apps/api/package.json +4 -4
  38. package/dist/templates/fullstack/apps/api/scripts/build.extensions.mjs +6 -0
  39. package/dist/templates/fullstack/apps/api/scripts/build.mjs +65 -16
  40. package/dist/templates/fullstack/apps/api/scripts/run-migrations.mjs +7 -3
  41. package/dist/templates/fullstack/apps/api/scripts/verify-api-contract.ts +5 -1
  42. package/dist/templates/fullstack/apps/api/src/app.extensions.ts +7 -4
  43. package/dist/templates/fullstack/apps/api/src/app.spec.ts +22 -0
  44. package/dist/templates/fullstack/apps/api/src/app.ts +47 -28
  45. package/dist/templates/fullstack/apps/api/src/build.spec.ts +118 -0
  46. package/dist/templates/fullstack/apps/api/src/config/env.extensions.ts +20 -0
  47. package/dist/templates/fullstack/apps/api/src/config/env.validation.spec.ts +76 -0
  48. package/dist/templates/fullstack/apps/api/src/config/env.validation.ts +46 -3
  49. package/dist/templates/fullstack/apps/api/src/config/provider-selection.spec.ts +53 -0
  50. package/dist/templates/fullstack/apps/api/src/config/provider-selection.ts +79 -0
  51. package/dist/templates/fullstack/apps/api/src/config/providers.ts +8 -2
  52. package/dist/templates/fullstack/apps/api/src/config/server-options.spec.ts +17 -6
  53. package/dist/templates/fullstack/apps/api/src/config/server-options.ts +5 -8
  54. package/dist/templates/fullstack/apps/api/src/core/api-contract.extensions.ts +6 -0
  55. package/dist/templates/fullstack/apps/api/src/core/api-contract.spec.ts +29 -0
  56. package/dist/templates/fullstack/apps/api/src/core/api-contract.ts +39 -2
  57. package/dist/templates/fullstack/apps/api/src/core/client-address.spec.ts +82 -0
  58. package/dist/templates/fullstack/apps/api/src/core/client-address.ts +68 -0
  59. package/dist/templates/fullstack/apps/api/src/core/services.ts +43 -0
  60. package/dist/templates/fullstack/apps/api/src/database/data-source.spec.ts +27 -0
  61. package/dist/templates/fullstack/apps/api/src/database/data-source.ts +7 -10
  62. package/dist/templates/fullstack/apps/api/src/database/database.spec.ts +12 -0
  63. package/dist/templates/fullstack/apps/api/src/database/database.ts +2 -3
  64. package/dist/templates/fullstack/apps/api/src/database/entities.ts +12 -0
  65. package/dist/templates/fullstack/apps/api/src/database/migrate.ts +12 -2
  66. package/dist/templates/fullstack/apps/api/src/database/migration-files.spec.ts +116 -0
  67. package/dist/templates/fullstack/apps/api/src/database/migration-files.ts +134 -0
  68. package/dist/templates/fullstack/apps/api/src/database/migration-readiness.spec.ts +36 -0
  69. package/dist/templates/fullstack/apps/api/src/database/migration-readiness.ts +25 -0
  70. package/dist/templates/fullstack/apps/api/src/database/run-migrations.spec.ts +12 -1
  71. package/dist/templates/fullstack/apps/api/src/database/sqlite-migrator.ts +2 -36
  72. package/dist/templates/fullstack/apps/api/src/main.ts +2 -2
  73. package/dist/templates/fullstack/apps/api/tsconfig.json +2 -0
  74. package/dist/templates/fullstack/apps/web/Dockerfile +4 -0
  75. package/dist/templates/fullstack/apps/web/package.json +1 -1
  76. package/dist/templates/fullstack/apps/web/src/app.css +3 -0
  77. package/dist/templates/fullstack/apps/web/src/app.extensions.css +11 -0
  78. package/dist/templates/fullstack/apps/web/src/lib/components/site-runtime.svelte +3 -1
  79. package/dist/templates/fullstack/apps/web/src/lib/server/api.test.ts +19 -0
  80. package/dist/templates/fullstack/apps/web/src/lib/server/api.ts +4 -1
  81. package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.test.ts +370 -36
  82. package/dist/templates/fullstack/apps/web/src/lib/server/backend-proxy.ts +250 -41
  83. package/dist/templates/fullstack/apps/web/src/lib/server/body-limits.ts +5 -2
  84. package/dist/templates/fullstack/apps/web/src/lib/server/proxy-timeouts.ts +23 -0
  85. package/dist/templates/fullstack/apps/web/tsconfig.json +1 -1
  86. package/dist/templates/fullstack/apps/web/vite.config.test.ts +33 -0
  87. package/dist/templates/fullstack/apps/web/vite.config.ts +55 -13
  88. package/dist/templates/fullstack/apps/web/vite.extensions.ts +23 -0
  89. package/dist/templates/fullstack/compose.dev.yaml +7 -4
  90. package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/SKILL.md +2 -2
  91. package/dist/templates/fullstack/dot-agents/skills/podokit-deploy/references/profile.md +12 -3
  92. package/dist/templates/fullstack/dot-claude/skills/podokit-update/SKILL.md +5 -2
  93. package/dist/templates/fullstack/dot-env.docker.example +7 -2
  94. package/dist/templates/fullstack/dot-env.example +21 -1
  95. package/dist/templates/fullstack/dot-podokit/dev.json +1 -1
  96. package/dist/templates/fullstack/infra/k3s/configmap.yaml +4 -0
  97. package/dist/templates/fullstack/tests/README.md +40 -1
  98. package/dist/templates/fullstack/tests/helpers/app-paths.ts +18 -0
  99. package/dist/templates/fullstack/tests/helpers/paths.ts +33 -0
  100. package/dist/templates/fullstack/tests/package.json +2 -2
  101. package/dist/templates/fullstack/tests/playwright.config.ts +7 -3
  102. package/dist/templates/fullstack/tests/playwright.extensions.ts +88 -1
  103. package/dist/templates/fullstack/tests/ui/smoke.ui.spec.ts +5 -4
  104. package/dist/templates/modules/admin-dashboard/files/apps/api/src/account-sessions/account-sessions.routes.spec.ts +152 -0
  105. package/dist/templates/modules/admin-dashboard/files/apps/api/src/account-sessions/account-sessions.routes.ts +112 -0
  106. package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.revoker.ts +24 -0
  107. package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.spec.ts +18 -0
  108. package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin-sessions/admin-sessions.service.ts +36 -13
  109. package/dist/templates/modules/admin-dashboard/files/apps/api/src/admin.module.ts +13 -1
  110. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.test.ts +37 -1
  111. package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +15 -6
  112. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/account-sessions.test.ts +33 -0
  113. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/account-sessions.ts +34 -0
  114. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.test.ts +31 -0
  115. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flag-settings.ts +31 -0
  116. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-flags.ts +12 -0
  117. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/app-nav.ts +11 -0
  118. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.test.ts +39 -0
  119. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/nav.ts +35 -0
  120. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/admin/registry.svelte.ts +15 -0
  121. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/account-page.svelte +73 -39
  122. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/admin-sidebar.svelte +2 -23
  123. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +7 -2
  124. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.test.ts +30 -0
  125. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/impersonation-banner.svelte +3 -2
  126. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/settings/app-flags-settings.svelte +65 -0
  127. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +32 -23
  128. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.test.ts +27 -0
  129. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-footer.svelte +4 -1
  130. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/site-runtime.svelte +3 -1
  131. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +3 -1
  132. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/en.json +6 -0
  133. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/catalogs/admin-dashboard/ko.json +6 -0
  134. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/app-hooks.ts +11 -0
  135. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/non-indexable-paths.ts +8 -0
  136. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.test.ts +31 -0
  137. package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/search-indexing.ts +5 -1
  138. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(admin)/admin/settings/+page.svelte +1 -1
  139. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.server.ts +2 -1
  140. package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/account/+page.svelte +1 -5
  141. package/dist/templates/modules/admin-dashboard/files/dot-claude/skills/podokit-data-table/SKILL.md +2 -0
  142. package/dist/templates/modules/admin-dashboard/files/tests/api/account-sessions.api.spec.ts +55 -0
  143. package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +1 -1
  144. package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +159 -170
  145. package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +72 -81
  146. package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +1 -6
  147. package/dist/templates/modules/admin-dashboard/files/tests/api/profile-image.api.spec.ts +1 -1
  148. package/dist/templates/modules/admin-dashboard/files/tests/api/site-settings.api.spec.ts +43 -53
  149. package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +29 -1
  150. package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +24 -6
  151. package/dist/templates/modules/admin-dashboard/files/tests/helpers/app-accounts.ts +24 -0
  152. package/dist/templates/modules/admin-dashboard/files/tests/helpers/devices.ts +55 -0
  153. package/dist/templates/modules/admin-dashboard/files/tests/helpers/disposable-users.ts +5 -3
  154. package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +3 -9
  155. package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +2 -4
  156. package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +11 -5
  157. package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +3 -10
  158. package/dist/templates/modules/admin-dashboard/files/tests/ui/account-flows.ui.spec.ts +4 -5
  159. package/dist/templates/modules/admin-dashboard/files/tests/ui/account-sessions.ui.spec.ts +46 -0
  160. package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +62 -59
  161. package/dist/templates/modules/admin-dashboard/files/tests/ui/app-flags.ui.spec.ts +63 -0
  162. package/dist/templates/modules/admin-dashboard/files/tests/ui/appearance.ui.spec.ts +12 -16
  163. package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +3 -2
  164. package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +32 -35
  165. package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +4 -2
  166. package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +1 -2
  167. package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +1 -2
  168. package/dist/templates/modules/admin-dashboard/files/tests/ui/general-effects.ui.spec.ts +82 -91
  169. package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +6 -2
  170. package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +16 -20
  171. package/dist/templates/modules/admin-dashboard/files/tests/ui/impersonation.ui.spec.ts +6 -3
  172. package/dist/templates/modules/admin-dashboard/files/tests/ui/login-methods.ui.spec.ts +2 -3
  173. package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +2 -4
  174. package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +5 -2
  175. package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +7 -2
  176. package/dist/templates/modules/admin-dashboard/files/tests/ui/same-origin-requests.ui.spec.ts +89 -0
  177. package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +5 -4
  178. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings-dialogs.ui.spec.ts +67 -73
  179. package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +84 -93
  180. package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +33 -7
  181. package/dist/templates/modules/admin-dashboard/files/tests/ui/theme-mode.ui.spec.ts +29 -0
  182. package/dist/templates/modules/admin-dashboard/files/tests/ui/two-factor.ui.spec.ts +51 -54
  183. package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +10 -9
  184. package/dist/templates/modules/admin-dashboard/module.manifest.json +7 -2
  185. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +2 -1
  186. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +5 -1
  187. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.spec.ts +33 -0
  188. package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +20 -1
  189. package/dist/templates/modules/audit-log/module.manifest.json +15 -3
  190. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-app-name.spec.ts +21 -0
  191. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-config-store.ts +13 -1
  192. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-environment.ts +9 -2
  193. package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +19 -3
  194. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.module.ts +6 -1
  195. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.runtime.spec.ts +10 -0
  196. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.spec.ts +54 -6
  197. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.service.ts +26 -4
  198. package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +15 -8
  199. package/dist/templates/modules/auth/files/apps/api/src/auth/revoke-other-sessions.spec.ts +56 -0
  200. package/dist/templates/modules/auth/files/apps/api/src/auth/revoke-other-sessions.ts +45 -0
  201. package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.spec.ts +86 -0
  202. package/dist/templates/modules/auth/files/apps/api/src/auth/schema-recheck.ts +44 -0
  203. package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +2 -1
  204. package/dist/templates/modules/auth/files/apps/api/src/migrate.ts +3 -1
  205. package/dist/templates/modules/auth/files/apps/api/src/settings/app-flags.ts +9 -0
  206. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.spec.ts +43 -0
  207. package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +30 -5
  208. package/dist/templates/modules/auth/module.manifest.json +8 -2
  209. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.spec.ts +57 -1
  210. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/bullmq-job.queue.ts +93 -9
  211. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.spec.ts +40 -0
  212. package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +52 -5
  213. package/dist/templates/modules/bullmq/files/apps/api/src/main-worker.ts +9 -7
  214. package/dist/templates/modules/bullmq/files/apps/api/src/worker.extensions.ts +13 -0
  215. package/dist/templates/modules/bullmq/module.manifest.json +5 -1
  216. package/dist/templates/modules/job-progress/files/apps/api/src/progress/progress.worker.spec.ts +2 -0
  217. package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.spec.ts +87 -1
  218. package/dist/templates/modules/jobs-local/files/apps/api/src/jobs-local/local-job.queue.ts +93 -3
  219. package/dist/templates/modules/jobs-local/module.manifest.json +3 -0
  220. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.spec.ts +16 -0
  221. package/dist/templates/modules/logging/files/apps/api/src/logging/logging.module.ts +15 -10
  222. package/dist/templates/modules/mailer/module.manifest.json +1 -1
  223. package/dist/templates/modules/object-storage-local/files/tests/api/storage-local.api.spec.ts +1 -1
  224. package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -4
  225. package/dist/templates/modules/object-storage-s3/files/tests/api/storage.api.spec.ts +1 -1
  226. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.spec.ts +2 -59
  227. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.config.ts +0 -65
  228. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.spec.ts +82 -21
  229. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.identity.ts +54 -15
  230. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.spec.ts +43 -5
  231. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.module.ts +10 -2
  232. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.spec.ts +61 -3
  233. package/dist/templates/modules/rate-limit/files/apps/api/src/rate-limit/rate-limit.routes.ts +48 -15
  234. package/dist/templates/modules/rate-limit/files/tests/api/rate-limit.api.spec.ts +63 -27
  235. package/dist/templates/modules/rate-limit/module.manifest.json +6 -9
  236. package/dist/templates/modules/sse/files/tests/api/events.api.spec.ts +1 -1
  237. package/dist/templates/todo/AGENTS.md +46 -6
  238. package/dist/templates/todo/README.md +6 -4
  239. package/dist/text-diff.d.ts +12 -0
  240. package/dist/text-diff.js +111 -0
  241. package/dist/update.d.ts +61 -3
  242. package/dist/update.js +152 -27
  243. package/package.json +2 -2
  244. package/dist/templates/modules/admin-dashboard/files/tests/api/exclusive.api.spec.ts +0 -75
  245. package/dist/templates/modules/admin-dashboard/files/tests/helpers/exclusive.ts +0 -80
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import AccountPage from "#lib/components/account-page.svelte";
3
- import ImpersonationBanner from "#lib/components/impersonation-banner.svelte";
4
3
  import LanguageSwitch from "#lib/components/language-switch.svelte";
5
4
  import SiteFooter from "#lib/components/site-footer.svelte";
6
5
  import ThemeToggle from "#lib/components/theme-toggle.svelte";
@@ -13,15 +12,12 @@
13
12
  let {
14
13
  data,
15
14
  }: {
16
- data: { user: SessionUser; currentSessionId: string | null; capabilities: Capabilities; impersonating: boolean };
15
+ data: { user: SessionUser; currentSessionId: string | null; capabilities: Capabilities };
17
16
  } = $props();
18
17
  const i18n = getI18n();
19
18
  </script>
20
19
 
21
20
  <div class="flex min-h-svh flex-col">
22
- {#if data.impersonating}
23
- <ImpersonationBanner email={data.user.email} />
24
- {/if}
25
21
  <header class="flex h-14 items-center gap-2 border-b px-4">
26
22
  <Button href="/" variant="ghost">{i18n.t.nav.home}</Button>
27
23
  <div class="flex-1"></div>
@@ -38,6 +38,8 @@ put pagination outside the table.
38
38
  - Responsive columns: set `hideBelow` to `sm`, `md`, `lg`, or `xl`, then apply
39
39
  `cellClass(columnKey)` to the matching `Table.Cell`. Never repeat the
40
40
  responsive class by hand; the shared class keeps headers and cells aligned.
41
+ - Header controls such as a select-all checkbox: pass a `head` snippet on the
42
+ column; it replaces the label and sort button. Give the control an aria-label.
41
43
  - Give each table an `ariaLabel` and scope Playwright locators to that table.
42
44
  - No footer needed: `perPage={0}`.
43
45
  - **Search & filters**: put the shared `table-toolbar.svelte` (`TableToolbar`)
@@ -0,0 +1,55 @@
1
+ import { signInOnSeparateDevices } from "../helpers/devices";
2
+ import { expect, test } from "../helpers/disposable-users";
3
+
4
+ const base = process.env.E2E_BASE_URL ?? "http://localhost:5001";
5
+ const origin = { origin: base };
6
+
7
+ type Page = {
8
+ current: { id: string } | null;
9
+ items: Array<{ id: string }>;
10
+ nextCursor: string | null;
11
+ total: number;
12
+ };
13
+
14
+ test("account sessions page the user's own sessions and revoke one by id", async ({ playwright, disposableUsers }) => {
15
+ const email = `api-account-sessions-${Date.now()}@example.com`;
16
+ const password = "Podokit3e-Str0ng!pw";
17
+ await disposableUsers.create({ email, password, name: "Account sessions" });
18
+ expect(await signInOnSeparateDevices(email, password, 4)).toEqual([200, 200, 200, 200]);
19
+ const ctx = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
20
+ expect((await ctx.post("/api/auth/sign-in/email", { data: { email, password } })).ok()).toBeTruthy();
21
+ const current = (await (await ctx.get("/api/auth/get-session")).json()) as { session: { id: string } };
22
+
23
+ const first = await ctx.get("/api/account/sessions?limit=2");
24
+ expect(first.ok()).toBeTruthy();
25
+ const firstText = await first.text();
26
+ expect(firstText).not.toContain("token");
27
+ const page1 = JSON.parse(firstText) as Page;
28
+ expect(page1.current?.id).toBe(current.session.id);
29
+ expect(page1.total).toBe(5);
30
+ expect(page1.items).toHaveLength(2);
31
+ expect(page1.nextCursor).toBeTruthy();
32
+ const page2 = (await (await ctx.get(`/api/account/sessions?limit=2&cursor=${encodeURIComponent(page1.nextCursor ?? "")}`)).json()) as Page;
33
+ expect(page2.current?.id).toBe(current.session.id);
34
+ expect(page2.nextCursor).toBeNull();
35
+ const ids = [...page1.items, ...page2.items].map((item) => item.id);
36
+ expect(new Set(ids).size).toBe(4);
37
+ expect(ids).not.toContain(current.session.id);
38
+
39
+ // Another user's session cannot be revoked through this endpoint.
40
+ const otherEmail = `api-account-sessions-other-${Date.now()}@example.com`;
41
+ await disposableUsers.create({ email: otherEmail, password, name: "Other user" });
42
+ const otherUser = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
43
+ expect((await otherUser.post("/api/auth/sign-in/email", { data: { email: otherEmail, password } })).ok()).toBeTruthy();
44
+ const otherSession = (await (await otherUser.get("/api/auth/get-session")).json()) as { session: { id: string } };
45
+ const foreign = await ctx.delete(`/api/account/sessions/${encodeURIComponent(otherSession.session.id)}`);
46
+ expect(foreign.status()).toBe(404);
47
+ expect(await foreign.json()).toMatchObject({ error: { code: "ACCOUNT_SESSION_NOT_FOUND" } });
48
+ expect((await otherUser.get("/api/auth/get-session")).ok()).toBeTruthy();
49
+ await otherUser.dispose();
50
+
51
+ const revoked = await ctx.delete(`/api/account/sessions/${encodeURIComponent(ids[0] ?? "")}`);
52
+ expect(revoked.status()).toBe(204);
53
+ expect(((await (await ctx.get("/api/account/sessions?limit=2")).json()) as Page).total).toBe(4);
54
+ await ctx.dispose();
55
+ });
@@ -128,7 +128,7 @@ test("the audit log rejects bad paging parameters", async ({ playwright }) => {
128
128
 
129
129
  // Audit logging is a DB-backed on/off toggle (auth_config `server`, applied live).
130
130
  // Runs after the recording tests and restores the flag so nothing else is affected.
131
- test("audit logging honours the DB on/off toggle @smoke", async ({ playwright, disposableUsers }) => {
131
+ test("audit logging honours the DB on/off toggle @smoke", { tag: "@exclusive" }, async ({ playwright, disposableUsers }) => {
132
132
  const admin = await signedIn(playwright, ADMIN);
133
133
  test.skip((await admin.get("/api/audit-logs")).status() === 404, "audit-log module not installed");
134
134
  const auditLog = async () => (await (await admin.get("/api/account/capabilities")).json()).auditLog as boolean;
@@ -4,7 +4,6 @@ import {
4
4
  isUserCreationAllowed,
5
5
  } from "../../apps/api/src/auth/feature-gate";
6
6
  import { ADMIN } from "../helpers/accounts";
7
- import { exclusive } from "../helpers/exclusive";
8
7
 
9
8
  const base = process.env.E2E_BASE_URL ?? "http://localhost:5001";
10
9
  const origin = { origin: base };
@@ -17,69 +16,65 @@ async function adminCtx(playwright: import("@playwright/test").PlaywrightWorkerA
17
16
  return ctx;
18
17
  }
19
18
 
20
- test("admin can enable Google OAuth from the DB and it applies without a restart", async ({ playwright }) => {
19
+ test("admin can enable Google OAuth from the DB and it applies without a restart", { tag: "@exclusive" }, async ({ playwright }) => {
21
20
  test.slow();
22
- await exclusive("social-providers", async () => {
23
- const admin = await adminCtx(playwright);
24
- await admin.put("/api/account/auth-config", { data: { social: { google: { enabled: false } } } }); // clean start
25
- const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
26
- const social = () => anon.post("/api/auth/sign-in/social", { data: { provider: "google", callbackURL: `${base}/admin` } });
27
-
28
- // Before: provider not configured.
29
- await expect.poll(async () => (await social()).status(), { timeout: 8000 }).toBe(404);
30
-
31
- // Paste credentials via the admin API.
32
- const put = await admin.put("/api/account/auth-config", {
33
- data: { social: { google: { enabled: true, clientId: "dummy-google-client-id", clientSecret: "dummy-google-client-secret" } } },
34
- });
35
- expect(put.ok()).toBeTruthy();
36
- const view = await put.json();
37
- expect(view.social.google.enabled).toBe(true);
38
- expect(view.social.google.hasSecret).toBe(true);
39
- expect(view.social.google.clientId).toBe("dummy-google-client-id");
40
- expect("clientSecret" in view.social.google).toBe(false); // secret is never returned
41
-
42
- // Applied live (no restart): social sign-in now redirects to Google.
43
- await expect
44
- .poll(async () => {
45
- const r = await social();
46
- return r.ok() ? (((await r.json())?.url as string) ?? "") : "";
47
- }, { timeout: 8000 })
48
- .toContain("accounts.google.com");
49
- const caps = await (await anon.get("/api/account/capabilities")).json();
50
- expect(caps.providers).toContain("google");
51
-
52
- await admin.put("/api/account/auth-config", { data: { social: { google: { enabled: false } } } }); // restore
53
- await anon.dispose();
54
- await admin.dispose();
21
+ const admin = await adminCtx(playwright);
22
+ await admin.put("/api/account/auth-config", { data: { social: { google: { enabled: false } } } }); // clean start
23
+ const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
24
+ const social = () => anon.post("/api/auth/sign-in/social", { data: { provider: "google", callbackURL: `${base}/admin` } });
25
+
26
+ // Before: provider not configured.
27
+ await expect.poll(async () => (await social()).status(), { timeout: 8000 }).toBe(404);
28
+
29
+ // Paste credentials via the admin API.
30
+ const put = await admin.put("/api/account/auth-config", {
31
+ data: { social: { google: { enabled: true, clientId: "dummy-google-client-id", clientSecret: "dummy-google-client-secret" } } },
55
32
  });
33
+ expect(put.ok()).toBeTruthy();
34
+ const view = await put.json();
35
+ expect(view.social.google.enabled).toBe(true);
36
+ expect(view.social.google.hasSecret).toBe(true);
37
+ expect(view.social.google.clientId).toBe("dummy-google-client-id");
38
+ expect("clientSecret" in view.social.google).toBe(false); // secret is never returned
39
+
40
+ // Applied live (no restart): social sign-in now redirects to Google.
41
+ await expect
42
+ .poll(async () => {
43
+ const r = await social();
44
+ return r.ok() ? (((await r.json())?.url as string) ?? "") : "";
45
+ }, { timeout: 8000 })
46
+ .toContain("accounts.google.com");
47
+ const caps = await (await anon.get("/api/account/capabilities")).json();
48
+ expect(caps.providers).toContain("google");
49
+
50
+ await admin.put("/api/account/auth-config", { data: { social: { google: { enabled: false } } } }); // restore
51
+ await anon.dispose();
52
+ await admin.dispose();
56
53
  });
57
54
 
58
- test("admin can add and remove a social provider dynamically", async ({ playwright }) => {
55
+ test("admin can add and remove a social provider dynamically", { tag: "@exclusive" }, async ({ playwright }) => {
59
56
  test.slow();
60
- await exclusive("social-providers", async () => {
61
- const admin = await adminCtx(playwright);
62
- // The catalog offers providers beyond the two legacy env-backed ones.
63
- const catalog = (await (await admin.get("/api/account/auth-config")).json()).catalog as Array<{ id: string }>;
64
- expect(catalog.map((c) => c.id)).toEqual(expect.arrayContaining(["google", "github", "apple"]));
65
-
66
- const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
67
- const github = () => anon.post("/api/auth/sign-in/social", { data: { provider: "github", callbackURL: `${base}/admin` } });
68
-
69
- // Add GitHub (a provider with no env fallback) → applies live, no restart.
70
- await admin.put("/api/account/auth-config", { data: { social: { github: { enabled: true, clientId: "dummy-github-client-id", clientSecret: "dummy-github-client-secret" } } } });
71
- await expect
72
- .poll(async () => { const r = await github(); return r.ok() ? (((await r.json())?.url as string) ?? "") : ""; }, { timeout: 8000 })
73
- .toContain("github.com");
74
-
75
- // Remove it → the row is deleted and the provider stops working (again live).
76
- await admin.put("/api/account/auth-config", { data: { social: { github: { delete: true } } } });
77
- expect("github" in (await (await admin.get("/api/account/auth-config")).json()).social).toBe(false);
78
- await expect.poll(async () => (await github()).status(), { timeout: 8000 }).toBe(404);
57
+ const admin = await adminCtx(playwright);
58
+ // The catalog offers providers beyond the two legacy env-backed ones.
59
+ const catalog = (await (await admin.get("/api/account/auth-config")).json()).catalog as Array<{ id: string }>;
60
+ expect(catalog.map((c) => c.id)).toEqual(expect.arrayContaining(["google", "github", "apple"]));
79
61
 
80
- await anon.dispose();
81
- await admin.dispose();
82
- });
62
+ const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
63
+ const github = () => anon.post("/api/auth/sign-in/social", { data: { provider: "github", callbackURL: `${base}/admin` } });
64
+
65
+ // Add GitHub (a provider with no env fallback) → applies live, no restart.
66
+ await admin.put("/api/account/auth-config", { data: { social: { github: { enabled: true, clientId: "dummy-github-client-id", clientSecret: "dummy-github-client-secret" } } } });
67
+ await expect
68
+ .poll(async () => { const r = await github(); return r.ok() ? (((await r.json())?.url as string) ?? "") : ""; }, { timeout: 8000 })
69
+ .toContain("github.com");
70
+
71
+ // Remove it → the row is deleted and the provider stops working (again live).
72
+ await admin.put("/api/account/auth-config", { data: { social: { github: { delete: true } } } });
73
+ expect("github" in (await (await admin.get("/api/account/auth-config")).json()).social).toBe(false);
74
+ await expect.poll(async () => (await github()).status(), { timeout: 8000 }).toBe(404);
75
+
76
+ await anon.dispose();
77
+ await admin.dispose();
83
78
  });
84
79
 
85
80
  test("auth-config is admin-only and never leaks secrets @smoke", async ({ playwright }) => {
@@ -91,50 +86,47 @@ test("auth-config is admin-only and never leaks secrets @smoke", async ({ playwr
91
86
  await user.dispose();
92
87
  });
93
88
 
94
- test("automatic logout validates and applies a sliding session lifetime", async ({ playwright }) => {
95
- test.slow();
96
- await exclusive("session-idle-timeout", async () => {
97
- const admin = await adminCtx(playwright);
98
- const freshAdmin = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
99
-
100
- try {
101
- expect(
102
- (await admin.put("/api/account/auth-config", {
103
- data: { server: { sessionIdleTimeoutMinutes: 4 } },
104
- })).status(),
105
- ).toBe(400);
106
-
107
- const enabled = await admin.put("/api/account/auth-config", {
108
- data: { server: { sessionIdleTimeoutMinutes: 30 } },
109
- });
110
- expect(enabled.ok()).toBeTruthy();
111
- expect((await enabled.json()).server.sessionIdleTimeoutMinutes).toBe(30);
112
-
113
- const caps = await (await admin.get("/api/account/capabilities")).json();
114
- expect(caps.sessionIdleTimeoutMinutes).toBe(30);
115
-
116
- const signIn = await freshAdmin.post("/api/auth/sign-in/email", {
117
- data: { email: ADMIN.email, password: ADMIN.password },
118
- });
119
- expect(signIn.ok()).toBeTruthy();
120
- expect(signIn.headers()["set-cookie"]).toMatch(/Max-Age=1800(?:;|,)/i);
121
-
122
- const sessions = (await (await admin.get("/api/auth/list-sessions")).json()) as Array<{
123
- expiresAt: string;
124
- }>;
125
- const latestExpiry = Math.max(...sessions.map((session) => new Date(session.expiresAt).getTime()));
126
- expect(latestExpiry).toBeLessThanOrEqual(Date.now() + 30 * 60_000 + 10_000);
127
- } finally {
128
- await admin.put("/api/account/auth-config", {
129
- data: { server: { sessionIdleTimeoutMinutes: null } },
130
- });
131
- await freshAdmin.dispose();
132
- await admin.dispose();
133
- }
134
- });
89
+ test("automatic logout validates and applies a sliding session lifetime", { tag: "@exclusive" }, async ({ playwright }) => {
90
+ const admin = await adminCtx(playwright);
91
+ const freshAdmin = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
92
+
93
+ try {
94
+ expect(
95
+ (await admin.put("/api/account/auth-config", {
96
+ data: { server: { sessionIdleTimeoutMinutes: 4 } },
97
+ })).status(),
98
+ ).toBe(400);
99
+
100
+ const enabled = await admin.put("/api/account/auth-config", {
101
+ data: { server: { sessionIdleTimeoutMinutes: 30 } },
102
+ });
103
+ expect(enabled.ok()).toBeTruthy();
104
+ expect((await enabled.json()).server.sessionIdleTimeoutMinutes).toBe(30);
105
+
106
+ const caps = await (await admin.get("/api/account/capabilities")).json();
107
+ expect(caps.sessionIdleTimeoutMinutes).toBe(30);
108
+
109
+ const signIn = await freshAdmin.post("/api/auth/sign-in/email", {
110
+ data: { email: ADMIN.email, password: ADMIN.password },
111
+ });
112
+ expect(signIn.ok()).toBeTruthy();
113
+ expect(signIn.headers()["set-cookie"]).toMatch(/Max-Age=1800(?:;|,)/i);
114
+
115
+ const sessions = (await (await admin.get("/api/auth/list-sessions")).json()) as Array<{
116
+ expiresAt: string;
117
+ }>;
118
+ const latestExpiry = Math.max(...sessions.map((session) => new Date(session.expiresAt).getTime()));
119
+ expect(latestExpiry).toBeLessThanOrEqual(Date.now() + 30 * 60_000 + 10_000);
120
+ } finally {
121
+ await admin.put("/api/account/auth-config", {
122
+ data: { server: { sessionIdleTimeoutMinutes: null } },
123
+ });
124
+ await freshAdmin.dispose();
125
+ await admin.dispose();
126
+ }
135
127
  });
136
128
 
137
- test("new registrations require approval when the policy is enabled", async ({ playwright }) => {
129
+ test("new registrations require approval when the policy is enabled", { tag: "@exclusive" }, async ({ playwright }) => {
138
130
  const admin = await adminCtx(playwright);
139
131
  const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
140
132
  const password = "Podokit3e-Str0ng!pw";
@@ -213,79 +205,76 @@ test("new registrations require approval when the policy is enabled", async ({ p
213
205
  }
214
206
  });
215
207
 
216
- test("closed public sign-up blocks new OAuth users but preserves existing sign-in", async ({ playwright }) => {
217
- test.slow();
218
- await exclusive(["site-general", "social-providers"], async () => {
219
- const admin = await adminCtx(playwright);
220
- const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
221
- const password = "Podokit3e-Str0ng!pw";
222
- const adminCreatedEmail = `closed-admin-${Date.now()}@example.com`;
223
- const initialSite = (await (await anon.get("/api/site/settings")).json()) as {
224
- allowSignup?: string | null;
225
- };
226
- const initialAllowSignup = initialSite.allowSignup === "false" ? "false" : "true";
227
- let adminCreatedId = "";
228
-
229
- try {
230
- expect(
231
- (await admin.put("/api/account/auth-config", {
232
- data: {
233
- social: {
234
- google: {
235
- enabled: true,
236
- clientId: "dummy-google-client-id",
237
- clientSecret: "dummy-google-client-secret",
238
- },
208
+ test("closed public sign-up blocks new OAuth users but preserves existing sign-in", { tag: "@exclusive" }, async ({ playwright }) => {
209
+ const admin = await adminCtx(playwright);
210
+ const anon = await playwright.request.newContext({ baseURL: base, extraHTTPHeaders: origin });
211
+ const password = "Podokit3e-Str0ng!pw";
212
+ const adminCreatedEmail = `closed-admin-${Date.now()}@example.com`;
213
+ const initialSite = (await (await anon.get("/api/site/settings")).json()) as {
214
+ allowSignup?: string | null;
215
+ };
216
+ const initialAllowSignup = initialSite.allowSignup === "false" ? "false" : "true";
217
+ let adminCreatedId = "";
218
+
219
+ try {
220
+ expect(
221
+ (await admin.put("/api/account/auth-config", {
222
+ data: {
223
+ social: {
224
+ google: {
225
+ enabled: true,
226
+ clientId: "dummy-google-client-id",
227
+ clientSecret: "dummy-google-client-secret",
239
228
  },
240
229
  },
241
- })).ok(),
242
- ).toBeTruthy();
243
- expect((await admin.put("/api/site/settings", { data: { allowSignup: "false" } })).ok()).toBeTruthy();
230
+ },
231
+ })).ok(),
232
+ ).toBeTruthy();
233
+ expect((await admin.put("/api/site/settings", { data: { allowSignup: "false" } })).ok()).toBeTruthy();
244
234
 
245
- // The site policy uses a short cache shared by sign-up requests and user
246
- // creation hooks, so wait for the setting to become live.
247
- await new Promise((resolve) => setTimeout(resolve, 3_200));
235
+ // The site policy uses a short cache shared by sign-up requests and user
236
+ // creation hooks, so wait for the setting to become live.
237
+ await new Promise((resolve) => setTimeout(resolve, 3_200));
248
238
 
249
- const signup = await anon.post("/api/auth/sign-up/email", {
250
- data: {
251
- email: `closed-${Date.now()}@example.com`,
252
- password,
253
- name: "Closed Registration",
254
- },
255
- });
256
- expect(signup.status()).toBe(403);
257
- expect((await signup.json()).code).toBe(PUBLIC_SIGNUP_DISABLED);
258
-
259
- // OAuth initiation must stay available for existing social users. Only a
260
- // callback that attempts to create a new user is rejected by the DB hook.
261
- const social = await anon.post("/api/auth/sign-in/social", {
262
- data: { provider: "google", callbackURL: `${base}/` },
263
- });
264
- expect(social.ok()).toBeTruthy();
265
- expect(((await social.json()).url as string)).toContain("accounts.google.com");
266
- expect(isUserCreationAllowed(false, "/callback/google")).toBe(false);
267
- expect(isUserCreationAllowed(false, undefined)).toBe(false);
268
-
269
- // Deliberate administrator provisioning remains available while the public
270
- // site is closed.
271
- expect(isUserCreationAllowed(false, "/admin/create-user")).toBe(true);
272
- const adminCreated = await admin.post("/api/auth/admin/create-user", {
273
- data: { email: adminCreatedEmail, password, name: "Admin Created", role: "user" },
274
- });
275
- expect(adminCreated.ok()).toBeTruthy();
276
- adminCreatedId = ((await adminCreated.json()).user as { id: string }).id;
277
- } finally {
278
- if (adminCreatedId) {
279
- await admin.post("/api/auth/admin/remove-user", { data: { userId: adminCreatedId } });
280
- }
281
- await admin.put("/api/site/settings", { data: { allowSignup: initialAllowSignup } });
282
- await admin.put("/api/account/auth-config", {
283
- data: { social: { google: { enabled: false } } },
284
- });
285
- await new Promise((resolve) => setTimeout(resolve, 3_200));
286
- await anon.get("/api/auth/get-session");
287
- await anon.dispose();
288
- await admin.dispose();
239
+ const signup = await anon.post("/api/auth/sign-up/email", {
240
+ data: {
241
+ email: `closed-${Date.now()}@example.com`,
242
+ password,
243
+ name: "Closed Registration",
244
+ },
245
+ });
246
+ expect(signup.status()).toBe(403);
247
+ expect((await signup.json()).code).toBe(PUBLIC_SIGNUP_DISABLED);
248
+
249
+ // OAuth initiation must stay available for existing social users. Only a
250
+ // callback that attempts to create a new user is rejected by the DB hook.
251
+ const social = await anon.post("/api/auth/sign-in/social", {
252
+ data: { provider: "google", callbackURL: `${base}/` },
253
+ });
254
+ expect(social.ok()).toBeTruthy();
255
+ expect(((await social.json()).url as string)).toContain("accounts.google.com");
256
+ expect(isUserCreationAllowed(false, "/callback/google")).toBe(false);
257
+ expect(isUserCreationAllowed(false, undefined)).toBe(false);
258
+
259
+ // Deliberate administrator provisioning remains available while the public
260
+ // site is closed.
261
+ expect(isUserCreationAllowed(false, "/admin/create-user")).toBe(true);
262
+ const adminCreated = await admin.post("/api/auth/admin/create-user", {
263
+ data: { email: adminCreatedEmail, password, name: "Admin Created", role: "user" },
264
+ });
265
+ expect(adminCreated.ok()).toBeTruthy();
266
+ adminCreatedId = ((await adminCreated.json()).user as { id: string }).id;
267
+ } finally {
268
+ if (adminCreatedId) {
269
+ await admin.post("/api/auth/admin/remove-user", { data: { userId: adminCreatedId } });
289
270
  }
290
- });
271
+ await admin.put("/api/site/settings", { data: { allowSignup: initialAllowSignup } });
272
+ await admin.put("/api/account/auth-config", {
273
+ data: { social: { google: { enabled: false } } },
274
+ });
275
+ await new Promise((resolve) => setTimeout(resolve, 3_200));
276
+ await anon.get("/api/auth/get-session");
277
+ await anon.dispose();
278
+ await admin.dispose();
279
+ }
291
280
  });