@etus/bhono-app 0.1.1

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 (688) hide show
  1. package/dist/cli.d.ts +13 -0
  2. package/dist/cli.js +46 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/cli.test.d.ts +1 -0
  5. package/dist/cli.test.js +26 -0
  6. package/dist/cli.test.js.map +1 -0
  7. package/dist/generator.d.ts +14 -0
  8. package/dist/generator.js +142 -0
  9. package/dist/generator.js.map +1 -0
  10. package/dist/generator.test.d.ts +1 -0
  11. package/dist/generator.test.js +127 -0
  12. package/dist/generator.test.js.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.js +97 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +25 -0
  17. package/dist/prompts.js +83 -0
  18. package/dist/prompts.js.map +1 -0
  19. package/dist/prompts.test.d.ts +1 -0
  20. package/dist/prompts.test.js +24 -0
  21. package/dist/prompts.test.js.map +1 -0
  22. package/dist/providers/cloudflare.d.ts +37 -0
  23. package/dist/providers/cloudflare.js +61 -0
  24. package/dist/providers/cloudflare.js.map +1 -0
  25. package/dist/providers/cloudflare.test.d.ts +1 -0
  26. package/dist/providers/cloudflare.test.js +29 -0
  27. package/dist/providers/cloudflare.test.js.map +1 -0
  28. package/dist/providers/github.d.ts +16 -0
  29. package/dist/providers/github.js +57 -0
  30. package/dist/providers/github.js.map +1 -0
  31. package/dist/providers/github.test.d.ts +1 -0
  32. package/dist/providers/github.test.js +16 -0
  33. package/dist/providers/github.test.js.map +1 -0
  34. package/dist/templates.d.ts +8 -0
  35. package/dist/templates.js +88 -0
  36. package/dist/templates.js.map +1 -0
  37. package/dist/templates.test.d.ts +1 -0
  38. package/dist/templates.test.js +26 -0
  39. package/dist/templates.test.js.map +1 -0
  40. package/package.json +36 -0
  41. package/templates/base/.claude/agents/architect-review.md +160 -0
  42. package/templates/base/.claude/agents/backend-architect.md +308 -0
  43. package/templates/base/.claude/agents/code-reviewer.md +170 -0
  44. package/templates/base/.claude/agents/performance-engineer.md +166 -0
  45. package/templates/base/.claude/agents/test-automator.md +219 -0
  46. package/templates/base/.claude/commands/check-skill-rules.md +53 -0
  47. package/templates/base/.claude/commands/claude-md.md +250 -0
  48. package/templates/base/.claude/commands/code-prompt.md +212 -0
  49. package/templates/base/.claude/commands/explain-code.md +194 -0
  50. package/templates/base/.claude/commands/init-projec.md +89 -0
  51. package/templates/base/.claude/commands/linear/README.md +297 -0
  52. package/templates/base/.claude/commands/linear/create-issue.md +190 -0
  53. package/templates/base/.claude/commands/linear/implement-issue.md +248 -0
  54. package/templates/base/.claude/commands/linear/process-triage.md +399 -0
  55. package/templates/base/.claude/commands/linear/setup.md +180 -0
  56. package/templates/base/.claude/commands/prime.md +9 -0
  57. package/templates/base/.claude/commands/review-gap.md +10 -0
  58. package/templates/base/.claude/commands/setup-aa.md +311 -0
  59. package/templates/base/.claude/commands/ship.md +262 -0
  60. package/templates/base/.claude/commands/tools.md +3 -0
  61. package/templates/base/.claude/docs/claude-progress.txt +107 -0
  62. package/templates/base/.claude/hooks/package-lock.json +556 -0
  63. package/templates/base/.claude/hooks/package.json +16 -0
  64. package/templates/base/.claude/hooks/skill-activation-prompt.sh +7 -0
  65. package/templates/base/.claude/hooks/skill-activation-prompt.ts +142 -0
  66. package/templates/base/.claude/hooks/tsconfig.json +19 -0
  67. package/templates/base/.claude/scripts/check-updates.sh +85 -0
  68. package/templates/base/.claude/scripts/install_pkgs.sh +66 -0
  69. package/templates/base/.claude/scripts/setup-project.sh +177 -0
  70. package/templates/base/.claude/scripts/validate-skill-rules.sh +94 -0
  71. package/templates/base/.claude/settings.json +113 -0
  72. package/templates/base/.claude/settings.local.json +11 -0
  73. package/templates/base/.claude/skills/architecture-analyzer/SKILL.md +531 -0
  74. package/templates/base/.claude/skills/architecture-analyzer/assets/report-template.md +215 -0
  75. package/templates/base/.claude/skills/architecture-analyzer/references/c4-templates.md +234 -0
  76. package/templates/base/.claude/skills/architecture-analyzer/references/confidence-levels.md +203 -0
  77. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_structure.py +266 -0
  78. package/templates/base/.claude/skills/architecture-analyzer/scripts/analyze_tech_debt.py +776 -0
  79. package/templates/base/.claude/skills/architecture-analyzer/scripts/extract_apis.py +338 -0
  80. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_c4.py +283 -0
  81. package/templates/base/.claude/skills/architecture-analyzer/scripts/generate_erd.py +935 -0
  82. package/templates/base/.claude/skills/architecture-analyzer/scripts/map_dependencies.py +555 -0
  83. package/templates/base/.claude/skills/dev-browser/SKILL.md +318 -0
  84. package/templates/base/.claude/skills/dev-browser/bun.lock +443 -0
  85. package/templates/base/.claude/skills/dev-browser/package-lock.json +2927 -0
  86. package/templates/base/.claude/skills/dev-browser/package.json +27 -0
  87. package/templates/base/.claude/skills/dev-browser/scripts/start-server.ts +117 -0
  88. package/templates/base/.claude/skills/dev-browser/server.sh +24 -0
  89. package/templates/base/.claude/skills/dev-browser/src/client.ts +403 -0
  90. package/templates/base/.claude/skills/dev-browser/src/index.ts +281 -0
  91. package/templates/base/.claude/skills/dev-browser/src/snapshot/__tests__/snapshot.test.ts +223 -0
  92. package/templates/base/.claude/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  93. package/templates/base/.claude/skills/dev-browser/src/snapshot/index.ts +14 -0
  94. package/templates/base/.claude/skills/dev-browser/src/snapshot/inject.ts +13 -0
  95. package/templates/base/.claude/skills/dev-browser/src/types.ts +27 -0
  96. package/templates/base/.claude/skills/dev-browser/tsconfig.json +36 -0
  97. package/templates/base/.claude/skills/dev-browser/vitest.config.ts +12 -0
  98. package/templates/base/.claude/skills/linear/SKILL.md +440 -0
  99. package/templates/base/.claude/skills/linear/examples.md +262 -0
  100. package/templates/base/.claude/skills/linear/lib/client.ts +51 -0
  101. package/templates/base/.claude/skills/linear/lib/config.ts +106 -0
  102. package/templates/base/.claude/skills/linear/lib/output.ts +34 -0
  103. package/templates/base/.claude/skills/linear/package-lock.json +698 -0
  104. package/templates/base/.claude/skills/linear/package.json +27 -0
  105. package/templates/base/.claude/skills/linear/reference.md +263 -0
  106. package/templates/base/.claude/skills/linear/scripts/comments/create.ts +47 -0
  107. package/templates/base/.claude/skills/linear/scripts/comments/list.ts +47 -0
  108. package/templates/base/.claude/skills/linear/scripts/issues/archive.ts +30 -0
  109. package/templates/base/.claude/skills/linear/scripts/issues/create.ts +279 -0
  110. package/templates/base/.claude/skills/linear/scripts/issues/get.ts +68 -0
  111. package/templates/base/.claude/skills/linear/scripts/issues/list.ts +67 -0
  112. package/templates/base/.claude/skills/linear/scripts/issues/update.ts +281 -0
  113. package/templates/base/.claude/skills/linear/scripts/labels/add-to-issue.ts +63 -0
  114. package/templates/base/.claude/skills/linear/scripts/labels/create.ts +45 -0
  115. package/templates/base/.claude/skills/linear/scripts/labels/list.ts +30 -0
  116. package/templates/base/.claude/skills/linear/scripts/list-teams.ts +52 -0
  117. package/templates/base/.claude/skills/linear/scripts/setup/setup-credentials.ts +96 -0
  118. package/templates/base/.claude/skills/linear/scripts/status/list.ts +31 -0
  119. package/templates/base/.claude/skills/linear/scripts/status/set-by-name.ts +78 -0
  120. package/templates/base/.claude/skills/linear/scripts/status/update.ts +44 -0
  121. package/templates/base/.claude/skills/linear/scripts/users/list.ts +59 -0
  122. package/templates/base/.claude/skills/linear/scripts/users/me.ts +20 -0
  123. package/templates/base/.claude/skills/linear/templates/README.md +203 -0
  124. package/templates/base/.claude/skills/linear/templates/api-reference.md +258 -0
  125. package/templates/base/.claude/skills/linear/templates/bug-report.md +99 -0
  126. package/templates/base/.claude/skills/linear/templates/feature-request.md +118 -0
  127. package/templates/base/.claude/skills/linear/templates/security-issue.md +162 -0
  128. package/templates/base/.claude/skills/linear/templates/sprint-task.md +175 -0
  129. package/templates/base/.claude/skills/linear/templates/tech-debt.md +137 -0
  130. package/templates/base/.claude/skills/linear/tsconfig.json +17 -0
  131. package/templates/base/.claude/skills/linear/workflows/issue-lifecycle.md +317 -0
  132. package/templates/base/.claude/skills/playwright-e2e-testing/SKILL.md +113 -0
  133. package/templates/base/.claude/skills/playwright-e2e-testing/assets/global-setup.template.js +97 -0
  134. package/templates/base/.claude/skills/playwright-e2e-testing/assets/playwright.config.template.js +171 -0
  135. package/templates/base/.claude/skills/playwright-e2e-testing/assets/test-template.spec.js +163 -0
  136. package/templates/base/.claude/skills/playwright-e2e-testing/examples/README.md +26 -0
  137. package/templates/base/.claude/skills/playwright-e2e-testing/examples/ads.email-deeplink.spec.ts +12 -0
  138. package/templates/base/.claude/skills/playwright-e2e-testing/examples/mobile.realism.spec.ts +16 -0
  139. package/templates/base/.claude/skills/playwright-e2e-testing/examples/smoke.home.spec.ts +6 -0
  140. package/templates/base/.claude/skills/playwright-e2e-testing/references/architecture.md +578 -0
  141. package/templates/base/.claude/skills/playwright-e2e-testing/references/best-practices.md +260 -0
  142. package/templates/base/.claude/skills/playwright-e2e-testing/references/ci-reporting.md +86 -0
  143. package/templates/base/.claude/skills/playwright-e2e-testing/references/debugging.md +629 -0
  144. package/templates/base/.claude/skills/playwright-e2e-testing/references/mobile-realism.md +50 -0
  145. package/templates/base/.claude/skills/playwright-e2e-testing/references/optimization.md +488 -0
  146. package/templates/base/.claude/skills/playwright-e2e-testing/references/patterns.md +513 -0
  147. package/templates/base/.claude/skills/playwright-e2e-testing/references/resources.md +44 -0
  148. package/templates/base/.claude/skills/playwright-e2e-testing/references/visual-a11y.md +66 -0
  149. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/auth-setup.js +202 -0
  150. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/performance-analyzer.js +240 -0
  151. package/templates/base/.claude/skills/playwright-e2e-testing/scripts/trace-url.js +132 -0
  152. package/templates/base/.claude/skills/playwright-e2e-testing/templates/ci/github-actions.playwright.yml +78 -0
  153. package/templates/base/.claude/skills/playwright-e2e-testing/templates/fixtures.ts +44 -0
  154. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global-setup.template.js +97 -0
  155. package/templates/base/.claude/skills/playwright-e2e-testing/templates/global.setup.ts +35 -0
  156. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/ad-gpt-observer.ts +80 -0
  157. package/templates/base/.claude/skills/playwright-e2e-testing/templates/helpers/chromium-mobile-profile.ts +93 -0
  158. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.template.js +171 -0
  159. package/templates/base/.claude/skills/playwright-e2e-testing/templates/playwright.config.ts +126 -0
  160. package/templates/base/.claude/skills/playwright-e2e-testing/templates/test-template.spec.js +163 -0
  161. package/templates/base/.claude/skills/playwright-e2e-testing/templates/tests/email-deeplink.ads.spec.ts +44 -0
  162. package/templates/base/.claude/skills/skill-rules.json +184 -0
  163. package/templates/base/.claude/skills/wrangler/SKILL.md +209 -0
  164. package/templates/base/.claude/skills/wrangler/resources/api.md +494 -0
  165. package/templates/base/.claude/skills/wrangler/resources/bundling.md +83 -0
  166. package/templates/base/.claude/skills/wrangler/resources/commands/cert.md +64 -0
  167. package/templates/base/.claude/skills/wrangler/resources/commands/check.md +66 -0
  168. package/templates/base/.claude/skills/wrangler/resources/commands/containers.md +157 -0
  169. package/templates/base/.claude/skills/wrangler/resources/commands/d1.md +843 -0
  170. package/templates/base/.claude/skills/wrangler/resources/commands/delete.md +27 -0
  171. package/templates/base/.claude/skills/wrangler/resources/commands/deploy.md +139 -0
  172. package/templates/base/.claude/skills/wrangler/resources/commands/deployments.md +56 -0
  173. package/templates/base/.claude/skills/wrangler/resources/commands/dev.md +157 -0
  174. package/templates/base/.claude/skills/wrangler/resources/commands/dispatch-namespace.md +69 -0
  175. package/templates/base/.claude/skills/wrangler/resources/commands/docs.md +61 -0
  176. package/templates/base/.claude/skills/wrangler/resources/commands/how-to-run.md +62 -0
  177. package/templates/base/.claude/skills/wrangler/resources/commands/hyperdrive.md +425 -0
  178. package/templates/base/.claude/skills/wrangler/resources/commands/init.md +31 -0
  179. package/templates/base/.claude/skills/wrangler/resources/commands/kv-bulk.md +265 -0
  180. package/templates/base/.claude/skills/wrangler/resources/commands/kv-key.md +353 -0
  181. package/templates/base/.claude/skills/wrangler/resources/commands/kv-namespace.md +265 -0
  182. package/templates/base/.claude/skills/wrangler/resources/commands/login.md +23 -0
  183. package/templates/base/.claude/skills/wrangler/resources/commands/logout.md +19 -0
  184. package/templates/base/.claude/skills/wrangler/resources/commands/mtls-certificate.md +69 -0
  185. package/templates/base/.claude/skills/wrangler/resources/commands/pages.md +175 -0
  186. package/templates/base/.claude/skills/wrangler/resources/commands/pipelines.md +76 -0
  187. package/templates/base/.claude/skills/wrangler/resources/commands/queues.md +132 -0
  188. package/templates/base/.claude/skills/wrangler/resources/commands/r2-bucket.md +342 -0
  189. package/templates/base/.claude/skills/wrangler/resources/commands/r2-object.md +267 -0
  190. package/templates/base/.claude/skills/wrangler/resources/commands/r2-sql.md +65 -0
  191. package/templates/base/.claude/skills/wrangler/resources/commands/rollback.md +40 -0
  192. package/templates/base/.claude/skills/wrangler/resources/commands/secret.md +308 -0
  193. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-secret.md +100 -0
  194. package/templates/base/.claude/skills/wrangler/resources/commands/secrets-store-store.md +60 -0
  195. package/templates/base/.claude/skills/wrangler/resources/commands/setup.md +67 -0
  196. package/templates/base/.claude/skills/wrangler/resources/commands/tail.md +37 -0
  197. package/templates/base/.claude/skills/wrangler/resources/commands/telemetry.md +64 -0
  198. package/templates/base/.claude/skills/wrangler/resources/commands/triggers.md +39 -0
  199. package/templates/base/.claude/skills/wrangler/resources/commands/types.md +73 -0
  200. package/templates/base/.claude/skills/wrangler/resources/commands/vectorize.md +941 -0
  201. package/templates/base/.claude/skills/wrangler/resources/commands/versions.md +95 -0
  202. package/templates/base/.claude/skills/wrangler/resources/commands/whoami.md +49 -0
  203. package/templates/base/.claude/skills/wrangler/resources/commands/workflows.md +117 -0
  204. package/templates/base/.claude/skills/wrangler/resources/commands.md +138 -0
  205. package/templates/base/.claude/skills/wrangler/resources/configuration.md +2176 -0
  206. package/templates/base/.claude/skills/wrangler/resources/custom-builds.md +55 -0
  207. package/templates/base/.claude/skills/wrangler/resources/deprecations.md +279 -0
  208. package/templates/base/.claude/skills/wrangler/resources/enviroments.md +416 -0
  209. package/templates/base/.claude/skills/wrangler/resources/extract_sections.py +119 -0
  210. package/templates/base/.claude/skills/wrangler/resources/process_content.py +94 -0
  211. package/templates/base/.claude/skills/wrangler/resources/system-enviroments-variables.md +120 -0
  212. package/templates/base/.dev.vars.example +15 -0
  213. package/templates/base/.env.example +29 -0
  214. package/templates/base/.github/workflows/test.yml +127 -0
  215. package/templates/base/.nycrc.json +16 -0
  216. package/templates/base/CLAUDE.md +218 -0
  217. package/templates/base/README.md +670 -0
  218. package/templates/base/auth-setup-error.png +0 -0
  219. package/templates/base/config/drizzle.config.ts +10 -0
  220. package/templates/base/config/eslint.config.js +364 -0
  221. package/templates/base/config/wrangler.json +76 -0
  222. package/templates/base/docs/app_spec.txt +879 -0
  223. package/templates/base/docs/app_spec_template.md +681 -0
  224. package/templates/base/docs/architecture/README.md +8 -0
  225. package/templates/base/docs/architecture/data-requirements.md +109 -0
  226. package/templates/base/docs/architecture/erd.md +91 -0
  227. package/templates/base/docs/features/feature_list.json +3128 -0
  228. package/templates/base/docs/hono-boilerplate-plan.md +1774 -0
  229. package/templates/base/docs/test-coverage-gap-analysis.md +242 -0
  230. package/templates/base/docs/testing.md +188 -0
  231. package/templates/base/index.html +16 -0
  232. package/templates/base/package.json +115 -0
  233. package/templates/base/playwright.config.ts +158 -0
  234. package/templates/base/pnpm-lock.yaml +8175 -0
  235. package/templates/base/scripts/capture-prod-session.ts +250 -0
  236. package/templates/base/scripts/generate-openapi.ts +23 -0
  237. package/templates/base/scripts/init.sh +121 -0
  238. package/templates/base/src/client/__tests__/button.test.tsx +30 -0
  239. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-dashboard-stats-cards-1.png +0 -0
  240. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-quick-action-cards-1.png +0 -0
  241. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-recent-activity-section-1.png +0 -0
  242. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-first-name-in-welcome-message-1.png +0 -0
  243. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-display-user-information-in-sidebar-1.png +0 -0
  244. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-render-dashboard-when-authenticated-1.png +0 -0
  245. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-authenticated-should-show-navigation-sidebar-1.png +0 -0
  246. package/templates/base/src/client/__tests__/routes/__screenshots__/dashboard.test.tsx/Dashboard-Page-when-unauthenticated-should-redirect-to-login-when-not-authenticated-1.png +0 -0
  247. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-display-Google-OAuth-login-button-1.png +0 -0
  248. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-have-a-link-back-to-home-page-1.png +0 -0
  249. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-content-without-waiting-for-authentication-1.png +0 -0
  250. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-render-login-page-at--login-route-1.png +0 -0
  251. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-show-Terms-of-Service-and-Privacy-Policy-links-1.png +0 -0
  252. package/templates/base/src/client/__tests__/routes/__screenshots__/login.test.tsx/Login-Page-should-trigger-OAuth-flow-when-clicking-login-button-1.png +0 -0
  253. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-display-404-text-on-not-found-page-1.png +0 -0
  254. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-have-navigation-options-on-404-page-1.png +0 -0
  255. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-404-handling-should-render-404-page-for-unknown-routes-1.png +0 -0
  256. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-account-page-1.png +0 -0
  257. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-integrations-page-1.png +0 -0
  258. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-settings-page-1.png +0 -0
  259. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-authenticated-navigation-should-navigate-from-dashboard-to-team-page-1.png +0 -0
  260. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-display-navigation-links-on-home-page-1.png +0 -0
  261. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-home-page-navigation-should-have-correct-link-destinations-on-home-page-1.png +0 -0
  262. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-home-page-without-authentication-1.png +0 -0
  263. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-allow-access-to-login-page-without-authentication-1.png +0 -0
  264. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-dashboard-to-login-1.png +0 -0
  265. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-settings-to-login-1.png +0 -0
  266. package/templates/base/src/client/__tests__/routes/__screenshots__/navigation.test.tsx/Navigation-unauthenticated-navigation-should-redirect-unauthenticated-users-from-team-to-login-1.png +0 -0
  267. package/templates/base/src/client/__tests__/routes/authenticated-layout.test.tsx +252 -0
  268. package/templates/base/src/client/__tests__/routes/dashboard.test.tsx +136 -0
  269. package/templates/base/src/client/__tests__/routes/error-components.test.tsx +186 -0
  270. package/templates/base/src/client/__tests__/routes/login.test.tsx +112 -0
  271. package/templates/base/src/client/__tests__/routes/navigation.test.tsx +272 -0
  272. package/templates/base/src/client/__tests__/routes/root-layout.test.tsx +65 -0
  273. package/templates/base/src/client/__tests__/setup-browser.ts +15 -0
  274. package/templates/base/src/client/__tests__/setup.ts +32 -0
  275. package/templates/base/src/client/__tests__/test-utils.tsx +135 -0
  276. package/templates/base/src/client/api/.gitkeep +0 -0
  277. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-can-be-collapsed-by-default-1.png +0 -0
  278. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-expands-when-collapsed-and-expand-button-is-clicked-1.png +0 -0
  279. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-logout-button-click-1.png +0 -0
  280. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-handles-navigation-clicks-1.png +0 -0
  281. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-hides-navigation-labels-when-collapsed-1.png +0 -0
  282. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-highlights-active-route-1.png +0 -0
  283. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-renders-sidebar-navigation-items-1.png +0 -0
  284. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-keyboard-shortcut-hint-when-expanded-1.png +0 -0
  285. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-info-when-authenticated-1.png +0 -0
  286. package/templates/base/src/client/components/__tests__/__screenshots__/sidebar.test.tsx/Sidebar-shows-user-initials-in-avatar-fallback-1.png +0 -0
  287. package/templates/base/src/client/components/__tests__/error-boundary.test.tsx +97 -0
  288. package/templates/base/src/client/components/__tests__/sidebar.test.tsx +281 -0
  289. package/templates/base/src/client/components/error-boundary.tsx +68 -0
  290. package/templates/base/src/client/components/icons.tsx +106 -0
  291. package/templates/base/src/client/components/layout/.gitkeep +0 -0
  292. package/templates/base/src/client/components/sidebar.tsx +267 -0
  293. package/templates/base/src/client/components/ui/.gitkeep +0 -0
  294. package/templates/base/src/client/components/ui/__tests__/avatar.test.tsx +308 -0
  295. package/templates/base/src/client/components/ui/__tests__/card.test.tsx +214 -0
  296. package/templates/base/src/client/components/ui/__tests__/dialog.test.tsx +297 -0
  297. package/templates/base/src/client/components/ui/__tests__/error-fallback.test.tsx +145 -0
  298. package/templates/base/src/client/components/ui/__tests__/input.test.tsx +98 -0
  299. package/templates/base/src/client/components/ui/__tests__/loading-skeleton.test.tsx +139 -0
  300. package/templates/base/src/client/components/ui/__tests__/skeleton.test.tsx +44 -0
  301. package/templates/base/src/client/components/ui/__tests__/sonner.test.tsx +28 -0
  302. package/templates/base/src/client/components/ui/__tests__/tabs.test.tsx +233 -0
  303. package/templates/base/src/client/components/ui/avatar.tsx +101 -0
  304. package/templates/base/src/client/components/ui/badge.tsx +46 -0
  305. package/templates/base/src/client/components/ui/button.tsx +72 -0
  306. package/templates/base/src/client/components/ui/card.tsx +86 -0
  307. package/templates/base/src/client/components/ui/dialog.tsx +140 -0
  308. package/templates/base/src/client/components/ui/error-fallback.tsx +179 -0
  309. package/templates/base/src/client/components/ui/form.tsx +172 -0
  310. package/templates/base/src/client/components/ui/input.tsx +24 -0
  311. package/templates/base/src/client/components/ui/label.tsx +22 -0
  312. package/templates/base/src/client/components/ui/loading-skeleton.tsx +154 -0
  313. package/templates/base/src/client/components/ui/separator.tsx +33 -0
  314. package/templates/base/src/client/components/ui/skeleton.tsx +16 -0
  315. package/templates/base/src/client/components/ui/sonner.tsx +29 -0
  316. package/templates/base/src/client/components/ui/tabs.tsx +121 -0
  317. package/templates/base/src/client/hooks/.gitkeep +0 -0
  318. package/templates/base/src/client/hooks/__tests__/use-auth.test.tsx +306 -0
  319. package/templates/base/src/client/hooks/__tests__/use-theme.test.tsx +172 -0
  320. package/templates/base/src/client/hooks/use-auth.ts +53 -0
  321. package/templates/base/src/client/hooks/use-theme.tsx +78 -0
  322. package/templates/base/src/client/index.css +881 -0
  323. package/templates/base/src/client/lib/query-client.ts +11 -0
  324. package/templates/base/src/client/lib/utils.ts +7 -0
  325. package/templates/base/src/client/main.tsx +26 -0
  326. package/templates/base/src/client/routeTree.gen.ts +258 -0
  327. package/templates/base/src/client/router.ts +15 -0
  328. package/templates/base/src/client/routes/$.tsx +77 -0
  329. package/templates/base/src/client/routes/.gitkeep +0 -0
  330. package/templates/base/src/client/routes/__root.tsx +34 -0
  331. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-accept-invitation-button-1.png +0 -0
  332. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-decline-button-linking-to-homepage-1.png +0 -0
  333. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-display-invitation-details--email--workspace--role--1.png +0 -0
  334. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-have-a-logo-link-to-homepage-1.png +0 -0
  335. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-render-invitation-page-with-inviter-name-and-workspace-1.png +0 -0
  336. package/templates/base/src/client/routes/__tests__/__screenshots__/invite.test.tsx/Invite-Token-Page-pending-invitation-state-should-show-terms-of-service-and-privacy-policy-links-1.png +0 -0
  337. package/templates/base/src/client/routes/__tests__/invite.test.tsx +138 -0
  338. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-Active-Sessions-section-with-session-cards-1.png +0 -0
  339. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-render-page-with-correct-title--Account--1.png +0 -0
  340. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-API-Access-section-1.png +0 -0
  341. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Connected-Accounts-section-with-Google-connected-1.png +0 -0
  342. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Danger-Zone-section-with-delete-button-1.png +0 -0
  343. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/account.test.tsx/Account-Page-should-show-Security-section-with-Two-Factor-Authentication-1.png +0 -0
  344. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-API-documentation-section-should-display-API-documentation-link-section-1.png +0 -0
  345. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-Create-Webhook-Dialog-should-have-Add-Webhook-trigger-button-1.png +0 -0
  346. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-category-filters-should-display-all-category-buttons-1.png +0 -0
  347. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-all-integration-cards-with-names-1.png +0 -0
  348. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-display-category-badges-on-cards-1.png +0 -0
  349. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Configure-button-for-connected-integrations-1.png +0 -0
  350. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connect-button-for-not-connected-integrations-1.png +0 -0
  351. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-integration-cards-should-show-Connected-badge-for-connected-integrations-1.png +0 -0
  352. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Available-Integrations-section-1.png +0 -0
  353. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-Webhooks-section-1.png +0 -0
  354. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-connected-count-1.png +0 -0
  355. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-page-description-1.png +0 -0
  356. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-display-search-input-1.png +0 -0
  357. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-page-rendering-should-render-with-correct-title--Integrations--1.png +0 -0
  358. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-filter-integrations-based-on-search-query-1.png +0 -0
  359. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-search-by-description-1.png +0 -0
  360. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-search-functionality-should-show-no-results-message-when-search-has-no-matches-1.png +0 -0
  361. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-existing-webhook-1.png +0 -0
  362. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-events-1.png +0 -0
  363. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-last-delivery-info-1.png +0 -0
  364. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-display-webhook-success-status-1.png +0 -0
  365. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/integrations.test.tsx/Integrations-Page-webhooks-section-should-have-Add-Webhook-button-1.png +0 -0
  366. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-connected-accounts-section-with-Google-provider-1.png +0 -0
  367. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Account-tab-should-display-sessions-and-danger-zone-sections-1.png +0 -0
  368. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-all-notification-toggle-options-1.png +0 -0
  369. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-email-notifications-section-1.png +0 -0
  370. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-display-toggle-switches-for-notification-options-1.png +0 -0
  371. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Notifications-tab-should-have-toggles-checked-by-default-1.png +0 -0
  372. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display--Save-Changes--button-1.png +0 -0
  373. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-personal-information-form-1.png +0 -0
  374. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-profile-picture-section-1.png +0 -0
  375. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-email-in-disabled-email-input-1.png +0 -0
  376. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-initials-in-avatar-1.png +0 -0
  377. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-Profile-tab-should-display-user-name-in-the-name-input-1.png +0 -0
  378. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-all-three-tabs-1.png +0 -0
  379. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-display-page-description-1.png +0 -0
  380. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-page-rendering-should-render-with-correct-title--Settings--1.png +0 -0
  381. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-show-Profile-tab-as-default-active-tab-1.png +0 -0
  382. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Account-tab-when-clicked-1.png +0 -0
  383. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/settings.test.tsx/Settings-Page-tab-navigation-should-switch-to-Notifications-tab-when-clicked-1.png +0 -0
  384. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Active-Members-section-with-member-count-1.png +0 -0
  385. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-display-Pending-Invitations-section-with-invitation-details-1.png +0 -0
  386. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-have-invite-member-button-that-can-be-clicked-1.png +0 -0
  387. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-page-with-correct-title-and-description-1.png +0 -0
  388. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-render-search-input-that-filters-team-members-1.png +0 -0
  389. package/templates/base/src/client/routes/_authenticated/__tests__/__screenshots__/team.test.tsx/Team-Page-should-show-current-user-with---you---indicator-and-role-badge-1.png +0 -0
  390. package/templates/base/src/client/routes/_authenticated/__tests__/account.test.tsx +324 -0
  391. package/templates/base/src/client/routes/_authenticated/__tests__/integrations.test.tsx +520 -0
  392. package/templates/base/src/client/routes/_authenticated/__tests__/settings.test.tsx +414 -0
  393. package/templates/base/src/client/routes/_authenticated/__tests__/team.test.tsx +374 -0
  394. package/templates/base/src/client/routes/_authenticated/account.tsx +416 -0
  395. package/templates/base/src/client/routes/_authenticated/dashboard.tsx +151 -0
  396. package/templates/base/src/client/routes/_authenticated/integrations.tsx +553 -0
  397. package/templates/base/src/client/routes/_authenticated/settings.tsx +310 -0
  398. package/templates/base/src/client/routes/_authenticated/team.tsx +395 -0
  399. package/templates/base/src/client/routes/_authenticated.tsx +69 -0
  400. package/templates/base/src/client/routes/index.tsx +155 -0
  401. package/templates/base/src/client/routes/invite.$token.tsx +191 -0
  402. package/templates/base/src/client/routes/login.tsx +92 -0
  403. package/templates/base/src/server/__tests__/fixtures.ts +461 -0
  404. package/templates/base/src/server/__tests__/mocks/__tests__/db.test.ts +239 -0
  405. package/templates/base/src/server/__tests__/mocks/__tests__/kv.test.ts +293 -0
  406. package/templates/base/src/server/__tests__/mocks/__tests__/r2.test.ts +363 -0
  407. package/templates/base/src/server/__tests__/mocks/db.ts +186 -0
  408. package/templates/base/src/server/__tests__/mocks/index.ts +33 -0
  409. package/templates/base/src/server/__tests__/mocks/kv.ts +286 -0
  410. package/templates/base/src/server/__tests__/mocks/r2.ts +397 -0
  411. package/templates/base/src/server/__tests__/setup.ts +281 -0
  412. package/templates/base/src/server/auth/__tests__/guards.test.ts +162 -0
  413. package/templates/base/src/server/auth/guards.ts +92 -0
  414. package/templates/base/src/server/auth/permissions.test.ts +45 -0
  415. package/templates/base/src/server/auth/permissions.ts +139 -0
  416. package/templates/base/src/server/auth/roles.test.ts +169 -0
  417. package/templates/base/src/server/auth/roles.ts +141 -0
  418. package/templates/base/src/server/db/client.ts +12 -0
  419. package/templates/base/src/server/db/schema/accounts.ts +20 -0
  420. package/templates/base/src/server/db/schema/audit-logs.ts +26 -0
  421. package/templates/base/src/server/db/schema/index.ts +7 -0
  422. package/templates/base/src/server/db/schema/invitations.ts +30 -0
  423. package/templates/base/src/server/db/schema/refresh-tokens.ts +22 -0
  424. package/templates/base/src/server/db/schema/user-accounts.ts +25 -0
  425. package/templates/base/src/server/db/schema/users.ts +33 -0
  426. package/templates/base/src/server/db/seed.ts +267 -0
  427. package/templates/base/src/server/env.test.ts +84 -0
  428. package/templates/base/src/server/env.ts +78 -0
  429. package/templates/base/src/server/index.ts +82 -0
  430. package/templates/base/src/server/lib/audit.ts +73 -0
  431. package/templates/base/src/server/lib/audited-db.test.ts +107 -0
  432. package/templates/base/src/server/lib/audited-db.ts +154 -0
  433. package/templates/base/src/server/lib/email.test.ts +116 -0
  434. package/templates/base/src/server/lib/email.ts +82 -0
  435. package/templates/base/src/server/lib/errors.test.ts +49 -0
  436. package/templates/base/src/server/lib/errors.ts +64 -0
  437. package/templates/base/src/server/lib/oauth.test.ts +238 -0
  438. package/templates/base/src/server/lib/oauth.ts +113 -0
  439. package/templates/base/src/server/lib/pagination.test.ts +52 -0
  440. package/templates/base/src/server/lib/pagination.ts +32 -0
  441. package/templates/base/src/server/lib/password.test.ts +151 -0
  442. package/templates/base/src/server/lib/password.ts +151 -0
  443. package/templates/base/src/server/lib/providers.test.ts +105 -0
  444. package/templates/base/src/server/lib/providers.ts +62 -0
  445. package/templates/base/src/server/lib/r2-storage.test.ts +202 -0
  446. package/templates/base/src/server/lib/r2-storage.ts +107 -0
  447. package/templates/base/src/server/lib/schema-helpers.ts +16 -0
  448. package/templates/base/src/server/lib/session.test.ts +758 -0
  449. package/templates/base/src/server/lib/session.ts +267 -0
  450. package/templates/base/src/server/lib/tokens.test.ts +208 -0
  451. package/templates/base/src/server/lib/tokens.ts +65 -0
  452. package/templates/base/src/server/lib/transaction.test.ts +45 -0
  453. package/templates/base/src/server/lib/transaction.ts +24 -0
  454. package/templates/base/src/server/middleware/account.test.ts +201 -0
  455. package/templates/base/src/server/middleware/account.ts +66 -0
  456. package/templates/base/src/server/middleware/auth.test.ts +345 -0
  457. package/templates/base/src/server/middleware/auth.ts +146 -0
  458. package/templates/base/src/server/middleware/cors.test.ts +88 -0
  459. package/templates/base/src/server/middleware/cors.ts +26 -0
  460. package/templates/base/src/server/middleware/error-handler.test.ts +69 -0
  461. package/templates/base/src/server/middleware/error-handler.ts +43 -0
  462. package/templates/base/src/server/middleware/index.ts +8 -0
  463. package/templates/base/src/server/middleware/rate-limit.test.ts +472 -0
  464. package/templates/base/src/server/middleware/rate-limit.ts +294 -0
  465. package/templates/base/src/server/middleware/request-context.test.ts +175 -0
  466. package/templates/base/src/server/middleware/request-context.ts +28 -0
  467. package/templates/base/src/server/middleware/request-logger.test.ts +92 -0
  468. package/templates/base/src/server/middleware/request-logger.ts +50 -0
  469. package/templates/base/src/server/routes/accounts/__tests__/handlers.test.ts +460 -0
  470. package/templates/base/src/server/routes/accounts/handlers.ts +179 -0
  471. package/templates/base/src/server/routes/accounts/index.ts +55 -0
  472. package/templates/base/src/server/routes/accounts/routes.ts +205 -0
  473. package/templates/base/src/server/routes/accounts/schemas.ts +31 -0
  474. package/templates/base/src/server/routes/api.ts +37 -0
  475. package/templates/base/src/server/routes/audits/__tests__/handlers.test.ts +349 -0
  476. package/templates/base/src/server/routes/audits/handlers.ts +37 -0
  477. package/templates/base/src/server/routes/audits/index.ts +14 -0
  478. package/templates/base/src/server/routes/audits/routes.ts +29 -0
  479. package/templates/base/src/server/routes/audits/schemas.ts +43 -0
  480. package/templates/base/src/server/routes/auth/__tests__/handlers.test.ts +381 -0
  481. package/templates/base/src/server/routes/auth/handlers.ts +222 -0
  482. package/templates/base/src/server/routes/auth/index.ts +37 -0
  483. package/templates/base/src/server/routes/auth/routes.ts +136 -0
  484. package/templates/base/src/server/routes/auth/schemas.ts +48 -0
  485. package/templates/base/src/server/routes/auth/test-login.ts +156 -0
  486. package/templates/base/src/server/routes/health/__tests__/handlers.test.ts +237 -0
  487. package/templates/base/src/server/routes/health/handlers.ts +83 -0
  488. package/templates/base/src/server/routes/health/index.ts +13 -0
  489. package/templates/base/src/server/routes/health/routes.ts +90 -0
  490. package/templates/base/src/server/routes/index.ts +53 -0
  491. package/templates/base/src/server/routes/invitations/__tests__/handlers.test.ts +473 -0
  492. package/templates/base/src/server/routes/invitations/handlers.ts +71 -0
  493. package/templates/base/src/server/routes/invitations/index.ts +25 -0
  494. package/templates/base/src/server/routes/invitations/routes.ts +69 -0
  495. package/templates/base/src/server/routes/invitations/schemas.ts +39 -0
  496. package/templates/base/src/server/routes/openapi.ts +14 -0
  497. package/templates/base/src/server/routes/schemas.ts +53 -0
  498. package/templates/base/src/server/routes/storage/__tests__/handlers.test.ts +408 -0
  499. package/templates/base/src/server/routes/storage/handlers.ts +100 -0
  500. package/templates/base/src/server/routes/storage/index.ts +42 -0
  501. package/templates/base/src/server/routes/storage/routes.ts +91 -0
  502. package/templates/base/src/server/routes/storage/schemas.ts +56 -0
  503. package/templates/base/src/server/routes/users/__tests__/handlers.test.ts +526 -0
  504. package/templates/base/src/server/routes/users/handlers.ts +228 -0
  505. package/templates/base/src/server/routes/users/index.ts +67 -0
  506. package/templates/base/src/server/routes/users/routes.ts +265 -0
  507. package/templates/base/src/server/routes/users/schemas.ts +67 -0
  508. package/templates/base/src/server/services/__tests__/accounts.test.ts +764 -0
  509. package/templates/base/src/server/services/__tests__/audits.test.ts +235 -0
  510. package/templates/base/src/server/services/__tests__/auth.test.ts +765 -0
  511. package/templates/base/src/server/services/__tests__/invitations.test.ts +704 -0
  512. package/templates/base/src/server/services/__tests__/users.test.ts +755 -0
  513. package/templates/base/src/server/services/accounts.ts +269 -0
  514. package/templates/base/src/server/services/audits.ts +82 -0
  515. package/templates/base/src/server/services/auth.ts +225 -0
  516. package/templates/base/src/server/services/index.ts +6 -0
  517. package/templates/base/src/server/services/invitations.ts +306 -0
  518. package/templates/base/src/server/services/users.ts +350 -0
  519. package/templates/base/src/server/types/auth.ts +36 -0
  520. package/templates/base/src/server/types/index.ts +117 -0
  521. package/templates/base/src/shared/schemas/.gitkeep +0 -0
  522. package/templates/base/src/shared/schemas/__tests__/schemas.test.ts +547 -0
  523. package/templates/base/src/shared/schemas/account.ts +15 -0
  524. package/templates/base/src/shared/schemas/index.ts +6 -0
  525. package/templates/base/src/shared/schemas/invitation.ts +9 -0
  526. package/templates/base/src/shared/schemas/profile.ts +10 -0
  527. package/templates/base/src/shared/schemas/user.ts +16 -0
  528. package/templates/base/src/shared/schemas/webhook.ts +12 -0
  529. package/templates/base/src/shared/types/.gitkeep +0 -0
  530. package/templates/base/src/shared/types/account.ts +12 -0
  531. package/templates/base/src/shared/types/api.ts +1399 -0
  532. package/templates/base/src/shared/types/auth.ts +24 -0
  533. package/templates/base/src/shared/types/index.ts +5 -0
  534. package/templates/base/src/shared/types/user.ts +31 -0
  535. package/templates/base/src/test/vitest-zod-matcher.ts +37 -0
  536. package/templates/base/src/test/vitest.d.ts +19 -0
  537. package/templates/base/tests/e2e/README.md +141 -0
  538. package/templates/base/tests/e2e/a11y/accessibility.spec.ts +925 -0
  539. package/templates/base/tests/e2e/a11y/keyboard-navigation.spec.ts +610 -0
  540. package/templates/base/tests/e2e/api/accounts.spec.ts +148 -0
  541. package/templates/base/tests/e2e/api/audit-logs.spec.ts +130 -0
  542. package/templates/base/tests/e2e/api/authenticated-api.spec.ts +311 -0
  543. package/templates/base/tests/e2e/api/storage.spec.ts +109 -0
  544. package/templates/base/tests/e2e/auth-flows.unauth.spec.ts +117 -0
  545. package/templates/base/tests/e2e/auth-logout.unauth.spec.ts +103 -0
  546. package/templates/base/tests/e2e/auth.setup.ts +115 -0
  547. package/templates/base/tests/e2e/auth.spec.ts +146 -0
  548. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts +152 -0
  549. package/templates/base/tests/e2e/compatibility/cross-browser.spec.ts-snapshots/login-chromium-chromium-darwin.png +0 -0
  550. package/templates/base/tests/e2e/crud/account.spec.ts +356 -0
  551. package/templates/base/tests/e2e/crud/integrations.spec.ts +419 -0
  552. package/templates/base/tests/e2e/crud/team.spec.ts +287 -0
  553. package/templates/base/tests/e2e/crud/users.spec.ts +239 -0
  554. package/templates/base/tests/e2e/errors/error-boundary.spec.ts +428 -0
  555. package/templates/base/tests/e2e/errors/error-handling.spec.ts +47 -0
  556. package/templates/base/tests/e2e/errors/error-handling.unauth.spec.ts +205 -0
  557. package/templates/base/tests/e2e/fixtures.ts +266 -0
  558. package/templates/base/tests/e2e/forms/validation.spec.ts +569 -0
  559. package/templates/base/tests/e2e/invitations/invite-flow.unauth.spec.ts +204 -0
  560. package/templates/base/tests/e2e/journeys/account-lifecycle.spec.ts +314 -0
  561. package/templates/base/tests/e2e/journeys/audit-investigation.spec.ts +299 -0
  562. package/templates/base/tests/e2e/journeys/auth-onboarding.spec.ts +232 -0
  563. package/templates/base/tests/e2e/journeys/critical-flows.spec.ts +281 -0
  564. package/templates/base/tests/e2e/journeys/error-recovery.spec.ts +354 -0
  565. package/templates/base/tests/e2e/journeys/file-management.spec.ts +307 -0
  566. package/templates/base/tests/e2e/journeys/integrations.spec.ts +372 -0
  567. package/templates/base/tests/e2e/journeys/multi-account.spec.ts +317 -0
  568. package/templates/base/tests/e2e/journeys/rbac-enforcement.spec.ts +389 -0
  569. package/templates/base/tests/e2e/journeys/settings-profile.spec.ts +400 -0
  570. package/templates/base/tests/e2e/journeys/team-collaboration.spec.ts +410 -0
  571. package/templates/base/tests/e2e/mobile/responsive.spec.ts +178 -0
  572. package/templates/base/tests/e2e/navigation/routing.spec.ts +371 -0
  573. package/templates/base/tests/e2e/navigation/sidebar.spec.ts +425 -0
  574. package/templates/base/tests/e2e/pages/ui-features.spec.ts +393 -0
  575. package/templates/base/tests/e2e/performance/baselines.spec.ts +162 -0
  576. package/templates/base/tests/e2e/performance/benchmarks.spec.ts +371 -0
  577. package/templates/base/tests/e2e/smoke.unauth.spec.ts +196 -0
  578. package/templates/base/tests/e2e/visual/components.spec.ts +650 -0
  579. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/confirmation-dialog-visual-darwin.png +0 -0
  580. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-background-visual-darwin.png +0 -0
  581. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-card-visual-darwin.png +0 -0
  582. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dark-mode-sidebar-visual-darwin.png +0 -0
  583. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dashboard-card-single-visual-darwin.png +0 -0
  584. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-content-visual-darwin.png +0 -0
  585. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/dialog-with-backdrop-visual-darwin.png +0 -0
  586. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/empty-search-results-visual-darwin.png +0 -0
  587. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-default-visual-darwin.png +0 -0
  588. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/input-focus-ring-visual-darwin.png +0 -0
  589. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-focus-visual-darwin.png +0 -0
  590. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-hover-visual-darwin.png +0 -0
  591. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/primary-button-visual-darwin.png +0 -0
  592. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-active-nav-item-visual-darwin.png +0 -0
  593. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-collapsed-visual-darwin.png +0 -0
  594. package/templates/base/tests/e2e/visual/components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  595. package/templates/base/tests/e2e/visual/core-components.spec.ts +192 -0
  596. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  597. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/create-webhook-dialog-visual-darwin.png +0 -0
  598. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/dashboard-page-visual-darwin.png +0 -0
  599. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/delete-account-dialog-visual-darwin.png +0 -0
  600. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/integrations-page-visual-darwin.png +0 -0
  601. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/invite-member-dialog-visual-darwin.png +0 -0
  602. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  603. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-account-tab-visual-darwin.png +0 -0
  604. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/settings-profile-tab-visual-darwin.png +0 -0
  605. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/sidebar-navigation-visual-darwin.png +0 -0
  606. package/templates/base/tests/e2e/visual/core-components.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  607. package/templates/base/tests/e2e/visual/screenshots.spec.ts +230 -0
  608. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-chromium-darwin.png +0 -0
  609. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/404-page-visual-darwin.png +0 -0
  610. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/account-page-visual-darwin.png +0 -0
  611. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-chromium-darwin.png +0 -0
  612. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/login-page-visual-darwin.png +0 -0
  613. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/settings-page-visual-darwin.png +0 -0
  614. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-invite-dialog-visual-darwin.png +0 -0
  615. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/team-page-visual-darwin.png +0 -0
  616. package/templates/base/tests/e2e/visual/screenshots.spec.ts-snapshots/webhook-create-dialog-visual-darwin.png +0 -0
  617. package/templates/base/tests/e2e/visual/theme-colors.spec.ts +293 -0
  618. package/templates/base/tests/e2e/visual/ui-components.spec.ts +502 -0
  619. package/templates/base/tests/integration/accounts/crud.test.ts +1402 -0
  620. package/templates/base/tests/integration/audits/list.test.ts +1133 -0
  621. package/templates/base/tests/integration/auth/auth-service.test.ts +415 -0
  622. package/templates/base/tests/integration/auth/invitation-token.test.ts +529 -0
  623. package/templates/base/tests/integration/auth/logout.test.ts +524 -0
  624. package/templates/base/tests/integration/auth/oauth.test.ts +768 -0
  625. package/templates/base/tests/integration/auth/refresh-token.test.ts +364 -0
  626. package/templates/base/tests/integration/auth/session-expiry.test.ts +569 -0
  627. package/templates/base/tests/integration/auth/session.test.ts +520 -0
  628. package/templates/base/tests/integration/auth/super-admin.test.ts +451 -0
  629. package/templates/base/tests/integration/authorization/analytics-role.test.ts +1026 -0
  630. package/templates/base/tests/integration/authorization/billing-role.test.ts +776 -0
  631. package/templates/base/tests/integration/authorization/guards-roles.test.ts +539 -0
  632. package/templates/base/tests/integration/authorization/multi-tenancy.test.ts +1112 -0
  633. package/templates/base/tests/integration/authorization/role-hierarchy.test.ts +931 -0
  634. package/templates/base/tests/integration/authorization/roles.test.ts +1347 -0
  635. package/templates/base/tests/integration/config/production-behavior.test.ts +536 -0
  636. package/templates/base/tests/integration/db/constraints.test.ts +695 -0
  637. package/templates/base/tests/integration/fixtures/accounts.ts +136 -0
  638. package/templates/base/tests/integration/fixtures/index.ts +232 -0
  639. package/templates/base/tests/integration/fixtures/invitations.ts +195 -0
  640. package/templates/base/tests/integration/fixtures/users.ts +144 -0
  641. package/templates/base/tests/integration/health/health.test.ts +351 -0
  642. package/templates/base/tests/integration/invitations/crud.test.ts +1457 -0
  643. package/templates/base/tests/integration/invitations/email.test.ts +506 -0
  644. package/templates/base/tests/integration/lib/email.test.ts +174 -0
  645. package/templates/base/tests/integration/lib/oauth.test.ts +368 -0
  646. package/templates/base/tests/integration/lib/password.test.ts +192 -0
  647. package/templates/base/tests/integration/lib/schema-helpers.test.ts +129 -0
  648. package/templates/base/tests/integration/lib/tokens.test.ts +304 -0
  649. package/templates/base/tests/integration/middleware/auth.test.ts +499 -0
  650. package/templates/base/tests/integration/middleware/cors.test.ts +334 -0
  651. package/templates/base/tests/integration/middleware/request-context.test.ts +156 -0
  652. package/templates/base/tests/integration/middleware/request-logger.test.ts +313 -0
  653. package/templates/base/tests/integration/performance/response-times.test.ts +509 -0
  654. package/templates/base/tests/integration/security/cookie-security.test.ts +567 -0
  655. package/templates/base/tests/integration/security/csrf-protection.test.ts +542 -0
  656. package/templates/base/tests/integration/security/jwt-validation.test.ts +209 -0
  657. package/templates/base/tests/integration/security/log-sanitization.test.ts +658 -0
  658. package/templates/base/tests/integration/security/rate-limiting.test.ts +1251 -0
  659. package/templates/base/tests/integration/security/sql-injection.test.ts +663 -0
  660. package/templates/base/tests/integration/security/token-hashing.test.ts +371 -0
  661. package/templates/base/tests/integration/security/xss-prevention.test.ts +541 -0
  662. package/templates/base/tests/integration/setup.ts +834 -0
  663. package/templates/base/tests/integration/smoke.test.ts +288 -0
  664. package/templates/base/tests/integration/storage/upload.test.ts +1162 -0
  665. package/templates/base/tests/integration/storage/validation.test.ts +746 -0
  666. package/templates/base/tests/integration/users/crud.test.ts +1297 -0
  667. package/templates/base/tests/integration/users/list.test.ts +698 -0
  668. package/templates/base/tests/integration/vitest.config.ts +80 -0
  669. package/templates/base/tsconfig.app.json +18 -0
  670. package/templates/base/tsconfig.json +39 -0
  671. package/templates/base/tsconfig.node.json +16 -0
  672. package/templates/base/tsconfig.server.json +26 -0
  673. package/templates/base/tsconfig.tsbuildinfo +1 -0
  674. package/templates/base/vite.config.ts +46 -0
  675. package/templates/base/vitest.config.browser.ts +47 -0
  676. package/templates/base/vitest.config.frontend.ts +47 -0
  677. package/templates/base/vitest.config.ts +82 -0
  678. package/templates/base/vitest.workspace.ts +22 -0
  679. package/templates/modules/audit-logs/.gitkeep +0 -0
  680. package/templates/modules/billing/.gitkeep +0 -0
  681. package/templates/modules/invitations/.gitkeep +0 -0
  682. package/templates/modules/storage/.gitkeep +0 -0
  683. package/templates/modules/webhooks/.gitkeep +0 -0
  684. package/templates/providers/auth-email/.gitkeep +0 -0
  685. package/templates/providers/auth-github/.gitkeep +0 -0
  686. package/templates/providers/auth-google/.gitkeep +0 -0
  687. package/templates/providers/email-resend/.gitkeep +0 -0
  688. package/templates/providers/email-sendgrid/.gitkeep +0 -0
@@ -0,0 +1,1112 @@
1
+ /**
2
+ * Multi-Tenancy Isolation Integration Tests
3
+ *
4
+ * Comprehensive tests for tenant data isolation across all API endpoints.
5
+ *
6
+ * Test categories:
7
+ * 1. User access isolation - Users can only access users in their accounts
8
+ * 2. Account access isolation - Users can only access accounts they belong to
9
+ * 3. Invitation isolation - Users can only manage invitations in their accounts
10
+ * 4. Audit log isolation - Users can only view audit logs in their accounts
11
+ * 5. Account switching - Users with multiple accounts get correct role enforcement
12
+ * 6. Super admin override - Super admins can access any account
13
+ */
14
+
15
+ import { describe, it, expect, beforeAll } from 'vitest'
16
+ import { Hono } from 'hono'
17
+ import { getEnv, getDb, getSqlite, type TestEnv } from '../setup'
18
+ import {
19
+ createUser,
20
+ createSuperAdmin,
21
+ createUserSession,
22
+ createAccount,
23
+ addUserToAccount,
24
+ createMultiTenantScenario,
25
+ createInvitation,
26
+ type Role,
27
+ } from '../fixtures'
28
+ import type { HonoEnv } from '../../../src/server/types'
29
+ import { api } from '../../../src/server/routes'
30
+ import { errorHandler } from '../../../src/server/middleware/error-handler'
31
+ import { sessionMiddleware } from '../../../src/server/lib/session'
32
+
33
+ // ============================================================================
34
+ // TEST SETUP
35
+ // ============================================================================
36
+
37
+ /**
38
+ * Creates a database wrapper that adds the `execute` method
39
+ * The better-sqlite3 drizzle doesn't have execute, but D1 does
40
+ */
41
+ function createTestDb() {
42
+ const db = getDb()
43
+ return new Proxy(db, {
44
+ get(target, prop) {
45
+ if (prop === 'execute') {
46
+ return target.run.bind(target)
47
+ }
48
+ return (target as any)[prop]
49
+ },
50
+ })
51
+ }
52
+
53
+ /**
54
+ * Helper to create a user with a specific role in an account
55
+ */
56
+ async function createUserWithRole(
57
+ accountId: string,
58
+ role: Role,
59
+ options?: { email?: string; name?: string }
60
+ ): Promise<{
61
+ user: Awaited<ReturnType<typeof createUser>>
62
+ sessionId: string
63
+ headers: Record<string, string>
64
+ }> {
65
+ const user = await createUser({
66
+ email: options?.email ?? `${role.toLowerCase()}-user-${crypto.randomUUID().slice(0, 8)}@example.com`,
67
+ name: options?.name ?? `${role} User`,
68
+ })
69
+
70
+ await addUserToAccount(user.id, accountId, role)
71
+
72
+ const { sessionId, headers } = await createUserSession(user.id, {
73
+ email: user.email,
74
+ name: user.name,
75
+ })
76
+
77
+ return { user, sessionId, headers }
78
+ }
79
+
80
+ /**
81
+ * Helper to create an audit log entry directly in the database
82
+ */
83
+ function createAuditLog(options: {
84
+ accountId: string
85
+ userId: string
86
+ entity: string
87
+ entityId: string
88
+ action: 'INSERT' | 'UPDATE' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'SIGNUP' | 'TOKEN_REFRESH' | 'LOGIN_FAILED'
89
+ changes?: Record<string, unknown> | null
90
+ timestamp?: string
91
+ }) {
92
+ const sqlite = getSqlite()
93
+ const id = crypto.randomUUID()
94
+ const transactionId = crypto.randomUUID()
95
+ const timestamp = options.timestamp ?? new Date().toISOString()
96
+
97
+ sqlite.prepare(`
98
+ INSERT INTO audit_logs (id, transaction_id, account_id, user_id, entity, entity_id, action, changes, ip_address, user_agent, timestamp)
99
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
100
+ `).run(
101
+ id,
102
+ transactionId,
103
+ options.accountId,
104
+ options.userId,
105
+ options.entity,
106
+ options.entityId,
107
+ options.action,
108
+ options.changes ? JSON.stringify(options.changes) : null,
109
+ '127.0.0.1',
110
+ 'IntegrationTest/1.0',
111
+ timestamp
112
+ )
113
+
114
+ return { id, transactionId, timestamp }
115
+ }
116
+
117
+ // ============================================================================
118
+ // TESTS
119
+ // ============================================================================
120
+
121
+ describe('Multi-Tenancy Isolation', () => {
122
+ let app: Hono<HonoEnv>
123
+ let env: TestEnv
124
+
125
+ beforeAll(() => {
126
+ env = getEnv()
127
+ app = new Hono<HonoEnv>()
128
+
129
+ // Set up error handler
130
+ app.onError(errorHandler)
131
+
132
+ // Set up middleware to inject test environment
133
+ app.use('*', async (c, next) => {
134
+ // Inject environment bindings
135
+ ;(c as any).env = env
136
+
137
+ // Set up database
138
+ const db = createTestDb()
139
+ c.set('db', db)
140
+
141
+ // Set up request context variables
142
+ c.set('transactionId', crypto.randomUUID())
143
+ c.set('ip', '127.0.0.1')
144
+ c.set('userAgent', 'IntegrationTest/1.0')
145
+
146
+ await next()
147
+ })
148
+
149
+ // Session middleware - reads session from KV and sets sessionData in context
150
+ app.use('*', sessionMiddleware())
151
+
152
+ // Mount API routes (includes sessionAuth and accountMiddleware)
153
+ app.route('/api', api)
154
+ })
155
+
156
+ // ============================================================================
157
+ // USER ACCESS ISOLATION TESTS
158
+ // ============================================================================
159
+
160
+ describe('User access isolation', () => {
161
+ it('should only view users in accessible accounts', async () => {
162
+ const scenario = await createMultiTenantScenario()
163
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
164
+
165
+ // Create users in the accessible account
166
+ const userInAccessibleAccount = await createUser({
167
+ email: 'accessible-account-user@example.com',
168
+ name: 'Accessible Account User',
169
+ })
170
+ await addUserToAccount(userInAccessibleAccount.id, accountWithAdminAccess.account.id, 'VIEWER')
171
+
172
+ // Create users in an account without access
173
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
174
+ const userInInaccessibleAccount = await createUser({
175
+ email: 'inaccessible-account-user@example.com',
176
+ name: 'Inaccessible Account User',
177
+ })
178
+ await addUserToAccount(userInInaccessibleAccount.id, accountWithoutAccess.id, 'VIEWER')
179
+
180
+ // Request users from the accessible account - should work
181
+ const res = await app.request('/api/users', {
182
+ method: 'GET',
183
+ headers: {
184
+ ...scenario.headers,
185
+ 'User-Agent': 'IntegrationTest/1.0',
186
+ 'account-id': accountWithAdminAccess.account.id,
187
+ },
188
+ })
189
+
190
+ expect(res.status).toBe(200)
191
+ const body = await res.json()
192
+ const userIds = body.data.map((u: any) => u.id)
193
+
194
+ // Should include user from accessible account
195
+ expect(userIds).toContain(userInAccessibleAccount.id)
196
+ // Should NOT include user from inaccessible account
197
+ expect(userIds).not.toContain(userInInaccessibleAccount.id)
198
+ })
199
+
200
+ it('should NOT be able to view a specific user from another account', async () => {
201
+ const scenario = await createMultiTenantScenario()
202
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
203
+
204
+ // Create a user in another account
205
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
206
+ const otherUser = await createUser({
207
+ email: 'other-account-specific@example.com',
208
+ name: 'Other Account User',
209
+ })
210
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'VIEWER')
211
+
212
+ // Try to get the other user from the accessible account context
213
+ const res = await app.request(`/api/users/${otherUser.id}`, {
214
+ method: 'GET',
215
+ headers: {
216
+ ...scenario.headers,
217
+ 'User-Agent': 'IntegrationTest/1.0',
218
+ 'account-id': accountWithAdminAccess.account.id,
219
+ },
220
+ })
221
+
222
+ // Should return 404 (not 403) for security - don't reveal user exists
223
+ expect(res.status).toBe(404)
224
+ })
225
+
226
+ it('should NOT be able to update users in other accounts', async () => {
227
+ const scenario = await createMultiTenantScenario()
228
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
229
+
230
+ // Create a user in another account
231
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
232
+ const otherUser = await createUser({
233
+ email: 'update-other-account@example.com',
234
+ name: 'Update Other Account User',
235
+ })
236
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'VIEWER')
237
+
238
+ // Try to update the other user
239
+ const res = await app.request(`/api/users/${otherUser.id}`, {
240
+ method: 'PATCH',
241
+ headers: {
242
+ ...scenario.headers,
243
+ 'User-Agent': 'IntegrationTest/1.0',
244
+ 'account-id': accountWithAdminAccess.account.id,
245
+ 'Content-Type': 'application/json',
246
+ },
247
+ body: JSON.stringify({ name: 'Should Not Update' }),
248
+ })
249
+
250
+ // Should return 404 (not 403) for security
251
+ expect(res.status).toBe(404)
252
+
253
+ // Verify user was not updated
254
+ const sqlite = getSqlite()
255
+ const row = sqlite.prepare('SELECT name FROM users WHERE id = ?').get(otherUser.id) as { name: string }
256
+ expect(row.name).toBe('Update Other Account User')
257
+ })
258
+
259
+ it('should NOT be able to delete users in other accounts', async () => {
260
+ const scenario = await createMultiTenantScenario()
261
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
262
+
263
+ // Create a user in another account
264
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
265
+ const otherUser = await createUser({
266
+ email: 'delete-other-account@example.com',
267
+ name: 'Delete Other Account User',
268
+ })
269
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'VIEWER')
270
+
271
+ // Try to delete the other user
272
+ const res = await app.request(`/api/users/${otherUser.id}`, {
273
+ method: 'DELETE',
274
+ headers: {
275
+ ...scenario.headers,
276
+ 'User-Agent': 'IntegrationTest/1.0',
277
+ 'account-id': accountWithAdminAccess.account.id,
278
+ },
279
+ })
280
+
281
+ // Should return 404 (not 403) for security
282
+ expect(res.status).toBe(404)
283
+
284
+ // Verify user was not deleted
285
+ const sqlite = getSqlite()
286
+ const row = sqlite.prepare('SELECT deleted_at FROM users WHERE id = ?').get(otherUser.id) as { deleted_at: string | null }
287
+ expect(row.deleted_at).toBeNull()
288
+ })
289
+ })
290
+
291
+ // ============================================================================
292
+ // ACCOUNT ACCESS ISOLATION TESTS
293
+ // ============================================================================
294
+
295
+ describe('Account access isolation', () => {
296
+ it('should only view accounts user has access to', async () => {
297
+ const scenario = await createMultiTenantScenario()
298
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
299
+
300
+ // Request accounts list
301
+ const res = await app.request('/api/accounts', {
302
+ method: 'GET',
303
+ headers: {
304
+ ...scenario.headers,
305
+ 'User-Agent': 'IntegrationTest/1.0',
306
+ 'account-id': accountWithAdminAccess.account.id,
307
+ },
308
+ })
309
+
310
+ expect(res.status).toBe(200)
311
+ const body = await res.json()
312
+ const accountIds = body.data.map((a: any) => a.id)
313
+
314
+ // Should include all accounts with access
315
+ for (const { account } of scenario.accounts.withAccess) {
316
+ expect(accountIds).toContain(account.id)
317
+ }
318
+
319
+ // Should NOT include accounts without access
320
+ for (const account of scenario.accounts.withoutAccess) {
321
+ expect(accountIds).not.toContain(account.id)
322
+ }
323
+ })
324
+
325
+ it('should return 404 (not 403) when viewing other accounts', async () => {
326
+ const scenario = await createMultiTenantScenario()
327
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
328
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
329
+
330
+ // Try to get an account without access
331
+ const res = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
332
+ method: 'GET',
333
+ headers: {
334
+ ...scenario.headers,
335
+ 'User-Agent': 'IntegrationTest/1.0',
336
+ 'account-id': accountWithAdminAccess.account.id,
337
+ },
338
+ })
339
+
340
+ // Should return 404 for security - don't reveal account exists
341
+ expect(res.status).toBe(404)
342
+ })
343
+
344
+ it('should NOT be able to update other accounts', async () => {
345
+ const scenario = await createMultiTenantScenario()
346
+ const accountWithManagerAccess = scenario.accounts.withAccess.find(a => a.role === 'MANAGER')!
347
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
348
+
349
+ // Try to update an account without access
350
+ const res = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
351
+ method: 'PATCH',
352
+ headers: {
353
+ ...scenario.headers,
354
+ 'User-Agent': 'IntegrationTest/1.0',
355
+ 'account-id': accountWithManagerAccess.account.id,
356
+ 'Content-Type': 'application/json',
357
+ },
358
+ body: JSON.stringify({ name: 'Should Not Update Account' }),
359
+ })
360
+
361
+ // Should return 404 for security
362
+ expect(res.status).toBe(404)
363
+
364
+ // Verify account was not updated
365
+ const sqlite = getSqlite()
366
+ const row = sqlite.prepare('SELECT name FROM accounts WHERE id = ?').get(accountWithoutAccess.id) as { name: string }
367
+ expect(row.name).toBe(accountWithoutAccess.name)
368
+ })
369
+
370
+ it('should NOT be able to delete other accounts (even with ADMIN role)', async () => {
371
+ const scenario = await createMultiTenantScenario()
372
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
373
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
374
+
375
+ // Try to delete an account without access
376
+ const res = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
377
+ method: 'DELETE',
378
+ headers: {
379
+ ...scenario.headers,
380
+ 'User-Agent': 'IntegrationTest/1.0',
381
+ 'account-id': accountWithAdminAccess.account.id,
382
+ },
383
+ })
384
+
385
+ // Should return 403 (requires super-admin) or 404
386
+ expect([403, 404]).toContain(res.status)
387
+
388
+ // Verify account was not deleted
389
+ const sqlite = getSqlite()
390
+ const row = sqlite.prepare('SELECT deleted_at FROM accounts WHERE id = ?').get(accountWithoutAccess.id) as { deleted_at: string | null }
391
+ expect(row.deleted_at).toBeNull()
392
+ })
393
+ })
394
+
395
+ // ============================================================================
396
+ // INVITATION ISOLATION TESTS
397
+ // ============================================================================
398
+
399
+ describe('Invitation isolation', () => {
400
+ it('should only view invitations in accessible accounts', async () => {
401
+ const scenario = await createMultiTenantScenario()
402
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
403
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
404
+
405
+ // Create invitation in accessible account
406
+ const invitationInAccessible = await createInvitation({
407
+ accountId: accountWithAdminAccess.account.id,
408
+ email: 'accessible-invitation@example.com',
409
+ role: 'VIEWER',
410
+ invitedById: scenario.user.id,
411
+ })
412
+
413
+ // Create user in inaccessible account to create invitation
414
+ const otherUser = await createUser({
415
+ email: 'other-inviter@example.com',
416
+ name: 'Other Inviter',
417
+ })
418
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'ADMIN')
419
+
420
+ // Create invitation in inaccessible account
421
+ const invitationInInaccessible = await createInvitation({
422
+ accountId: accountWithoutAccess.id,
423
+ email: 'inaccessible-invitation@example.com',
424
+ role: 'VIEWER',
425
+ invitedById: otherUser.id,
426
+ })
427
+
428
+ // Request invitations from the accessible account
429
+ const res = await app.request('/api/invitations', {
430
+ method: 'GET',
431
+ headers: {
432
+ ...scenario.headers,
433
+ 'User-Agent': 'IntegrationTest/1.0',
434
+ 'account-id': accountWithAdminAccess.account.id,
435
+ },
436
+ })
437
+
438
+ expect(res.status).toBe(200)
439
+ const body = await res.json()
440
+ const invitationIds = body.data.map((i: any) => i.id)
441
+
442
+ // Should include invitation from accessible account
443
+ expect(invitationIds).toContain(invitationInAccessible.id)
444
+ // Should NOT include invitation from inaccessible account
445
+ expect(invitationIds).not.toContain(invitationInInaccessible.id)
446
+ })
447
+
448
+ it('should NOT be able to create invitations for other accounts', async () => {
449
+ const scenario = await createMultiTenantScenario()
450
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
451
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
452
+
453
+ const testEmail = 'cross-tenant-invite@example.com'
454
+
455
+ // Create an invitation in the authorized account context
456
+ const res = await app.request('/api/invitations', {
457
+ method: 'POST',
458
+ headers: {
459
+ ...scenario.headers,
460
+ 'User-Agent': 'IntegrationTest/1.0',
461
+ 'account-id': accountWithAdminAccess.account.id,
462
+ 'Content-Type': 'application/json',
463
+ },
464
+ body: JSON.stringify({
465
+ email: testEmail,
466
+ role: 'VIEWER',
467
+ }),
468
+ })
469
+
470
+ expect(res.status).toBe(200)
471
+
472
+ // Verify the invitation was created in the correct account by checking database
473
+ const sqlite = getSqlite()
474
+ const invitation = sqlite.prepare(
475
+ 'SELECT account_id FROM invitations WHERE email = ?'
476
+ ).get(testEmail) as { account_id: string }
477
+
478
+ // The invitation should be in the account from the header, not any other
479
+ expect(invitation.account_id).toBe(accountWithAdminAccess.account.id)
480
+ expect(invitation.account_id).not.toBe(accountWithoutAccess.id)
481
+ })
482
+
483
+ it('should NOT be able to revoke invitations in other accounts', async () => {
484
+ const scenario = await createMultiTenantScenario()
485
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
486
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
487
+
488
+ // Create user in inaccessible account
489
+ const otherUser = await createUser({
490
+ email: 'other-revoke-inviter@example.com',
491
+ name: 'Other Revoke Inviter',
492
+ })
493
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'ADMIN')
494
+
495
+ // Create invitation in inaccessible account
496
+ const invitation = await createInvitation({
497
+ accountId: accountWithoutAccess.id,
498
+ email: 'revoke-cross-tenant@example.com',
499
+ role: 'VIEWER',
500
+ invitedById: otherUser.id,
501
+ })
502
+
503
+ // Try to revoke the invitation from accessible account context
504
+ const res = await app.request(`/api/invitations/${invitation.id}`, {
505
+ method: 'DELETE',
506
+ headers: {
507
+ ...scenario.headers,
508
+ 'User-Agent': 'IntegrationTest/1.0',
509
+ 'account-id': accountWithAdminAccess.account.id,
510
+ },
511
+ })
512
+
513
+ // Should return 404 for security
514
+ expect(res.status).toBe(404)
515
+
516
+ // Verify invitation still exists
517
+ const sqlite = getSqlite()
518
+ const row = sqlite.prepare('SELECT id FROM invitations WHERE id = ?').get(invitation.id)
519
+ expect(row).not.toBeNull()
520
+ })
521
+ })
522
+
523
+ // ============================================================================
524
+ // AUDIT LOG ISOLATION TESTS
525
+ // ============================================================================
526
+
527
+ describe('Audit log isolation', () => {
528
+ it('should only view audit logs for accessible accounts', async () => {
529
+ const scenario = await createMultiTenantScenario()
530
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
531
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
532
+
533
+ // Create audit log in accessible account
534
+ const auditInAccessible = createAuditLog({
535
+ accountId: accountWithAdminAccess.account.id,
536
+ userId: scenario.user.id,
537
+ entity: 'User',
538
+ entityId: crypto.randomUUID(),
539
+ action: 'INSERT',
540
+ changes: { name: 'Accessible Audit' },
541
+ })
542
+
543
+ // Create user in inaccessible account for audit log
544
+ const otherUser = await createUser({
545
+ email: 'other-audit-user@example.com',
546
+ name: 'Other Audit User',
547
+ })
548
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'ADMIN')
549
+
550
+ // Create audit log in inaccessible account
551
+ const auditInInaccessible = createAuditLog({
552
+ accountId: accountWithoutAccess.id,
553
+ userId: otherUser.id,
554
+ entity: 'User',
555
+ entityId: crypto.randomUUID(),
556
+ action: 'INSERT',
557
+ changes: { name: 'Inaccessible Audit' },
558
+ })
559
+
560
+ // Request audit logs from the accessible account
561
+ const res = await app.request('/api/audits', {
562
+ method: 'GET',
563
+ headers: {
564
+ ...scenario.headers,
565
+ 'User-Agent': 'IntegrationTest/1.0',
566
+ 'account-id': accountWithAdminAccess.account.id,
567
+ },
568
+ })
569
+
570
+ expect(res.status).toBe(200)
571
+ const body = await res.json()
572
+ const auditIds = body.data.map((a: any) => a.id)
573
+
574
+ // Should include audit from accessible account
575
+ expect(auditIds).toContain(auditInAccessible.id)
576
+ // Should NOT include audit from inaccessible account
577
+ expect(auditIds).not.toContain(auditInInaccessible.id)
578
+ })
579
+
580
+ it('should NOT be able to view audit logs from other accounts via filtering', async () => {
581
+ const scenario = await createMultiTenantScenario()
582
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
583
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
584
+
585
+ // Create user and audit log in inaccessible account
586
+ const otherUser = await createUser({
587
+ email: 'other-filter-audit@example.com',
588
+ name: 'Other Filter Audit User',
589
+ })
590
+ await addUserToAccount(otherUser.id, accountWithoutAccess.id, 'ADMIN')
591
+
592
+ const specificEntityId = crypto.randomUUID()
593
+ createAuditLog({
594
+ accountId: accountWithoutAccess.id,
595
+ userId: otherUser.id,
596
+ entity: 'User',
597
+ entityId: specificEntityId,
598
+ action: 'INSERT',
599
+ })
600
+
601
+ // Try to filter by the specific entityId from the inaccessible account
602
+ const res = await app.request(`/api/audits?entityId=${specificEntityId}`, {
603
+ method: 'GET',
604
+ headers: {
605
+ ...scenario.headers,
606
+ 'User-Agent': 'IntegrationTest/1.0',
607
+ 'account-id': accountWithAdminAccess.account.id,
608
+ },
609
+ })
610
+
611
+ expect(res.status).toBe(200)
612
+ const body = await res.json()
613
+
614
+ // Should return empty - the audit is in another account
615
+ expect(body.data.length).toBe(0)
616
+ })
617
+ })
618
+
619
+ // ============================================================================
620
+ // ACCOUNT SWITCHING TESTS
621
+ // ============================================================================
622
+
623
+ describe('Account switching', () => {
624
+ it('should enforce different roles in different accounts', async () => {
625
+ const scenario = await createMultiTenantScenario()
626
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
627
+ const accountWithViewerAccess = scenario.accounts.withAccess.find(a => a.role === 'VIEWER')!
628
+
629
+ // Create a target user in both accounts
630
+ const targetUser = await createUser({
631
+ email: 'target-for-switching@example.com',
632
+ name: 'Target For Switching',
633
+ })
634
+ await addUserToAccount(targetUser.id, accountWithAdminAccess.account.id, 'VIEWER')
635
+ await addUserToAccount(targetUser.id, accountWithViewerAccess.account.id, 'VIEWER')
636
+
637
+ // Try to update user in account where user has ADMIN role - should succeed
638
+ const adminRes = await app.request(`/api/users/${targetUser.id}`, {
639
+ method: 'PATCH',
640
+ headers: {
641
+ ...scenario.headers,
642
+ 'User-Agent': 'IntegrationTest/1.0',
643
+ 'account-id': accountWithAdminAccess.account.id,
644
+ 'Content-Type': 'application/json',
645
+ },
646
+ body: JSON.stringify({ name: 'Admin Updated' }),
647
+ })
648
+ expect(adminRes.status).toBe(200)
649
+
650
+ // Try to update user in account where user has VIEWER role - should fail
651
+ const viewerRes = await app.request(`/api/users/${targetUser.id}`, {
652
+ method: 'PATCH',
653
+ headers: {
654
+ ...scenario.headers,
655
+ 'User-Agent': 'IntegrationTest/1.0',
656
+ 'account-id': accountWithViewerAccess.account.id,
657
+ 'Content-Type': 'application/json',
658
+ },
659
+ body: JSON.stringify({ name: 'Viewer Updated' }),
660
+ })
661
+ expect(viewerRes.status).toBe(403)
662
+ })
663
+
664
+ it('should allow same user to view users in both accounts with correct filtering', async () => {
665
+ const scenario = await createMultiTenantScenario()
666
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
667
+ const accountWithViewerAccess = scenario.accounts.withAccess.find(a => a.role === 'VIEWER')!
668
+
669
+ // Create users unique to each account
670
+ const userInAccount1 = await createUser({
671
+ email: 'unique-to-admin-account@example.com',
672
+ name: 'Unique To Admin Account',
673
+ })
674
+ await addUserToAccount(userInAccount1.id, accountWithAdminAccess.account.id, 'VIEWER')
675
+
676
+ const userInAccount2 = await createUser({
677
+ email: 'unique-to-viewer-account@example.com',
678
+ name: 'Unique To Viewer Account',
679
+ })
680
+ await addUserToAccount(userInAccount2.id, accountWithViewerAccess.account.id, 'VIEWER')
681
+
682
+ // Get users from admin account
683
+ const res1 = await app.request('/api/users', {
684
+ method: 'GET',
685
+ headers: {
686
+ ...scenario.headers,
687
+ 'User-Agent': 'IntegrationTest/1.0',
688
+ 'account-id': accountWithAdminAccess.account.id,
689
+ },
690
+ })
691
+ expect(res1.status).toBe(200)
692
+ const body1 = await res1.json()
693
+ const userIds1 = body1.data.map((u: any) => u.id)
694
+ expect(userIds1).toContain(userInAccount1.id)
695
+ expect(userIds1).not.toContain(userInAccount2.id)
696
+
697
+ // Get users from viewer account
698
+ const res2 = await app.request('/api/users', {
699
+ method: 'GET',
700
+ headers: {
701
+ ...scenario.headers,
702
+ 'User-Agent': 'IntegrationTest/1.0',
703
+ 'account-id': accountWithViewerAccess.account.id,
704
+ },
705
+ })
706
+ expect(res2.status).toBe(200)
707
+ const body2 = await res2.json()
708
+ const userIds2 = body2.data.map((u: any) => u.id)
709
+ expect(userIds2).toContain(userInAccount2.id)
710
+ expect(userIds2).not.toContain(userInAccount1.id)
711
+ })
712
+
713
+ it('should correctly enforce invitation permissions across accounts', async () => {
714
+ const scenario = await createMultiTenantScenario()
715
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
716
+ const accountWithViewerAccess = scenario.accounts.withAccess.find(a => a.role === 'VIEWER')!
717
+
718
+ // Try to create invitation in admin account - should succeed
719
+ const adminInviteRes = await app.request('/api/invitations', {
720
+ method: 'POST',
721
+ headers: {
722
+ ...scenario.headers,
723
+ 'User-Agent': 'IntegrationTest/1.0',
724
+ 'account-id': accountWithAdminAccess.account.id,
725
+ 'Content-Type': 'application/json',
726
+ },
727
+ body: JSON.stringify({
728
+ email: 'admin-invite-test@example.com',
729
+ role: 'VIEWER',
730
+ }),
731
+ })
732
+ expect(adminInviteRes.status).toBe(200)
733
+
734
+ // Try to create invitation in viewer account - should fail (VIEWER cannot invite)
735
+ const viewerInviteRes = await app.request('/api/invitations', {
736
+ method: 'POST',
737
+ headers: {
738
+ ...scenario.headers,
739
+ 'User-Agent': 'IntegrationTest/1.0',
740
+ 'account-id': accountWithViewerAccess.account.id,
741
+ 'Content-Type': 'application/json',
742
+ },
743
+ body: JSON.stringify({
744
+ email: 'viewer-invite-test@example.com',
745
+ role: 'VIEWER',
746
+ }),
747
+ })
748
+ expect(viewerInviteRes.status).toBe(403)
749
+ })
750
+ })
751
+
752
+ // ============================================================================
753
+ // SUPER ADMIN OVERRIDE TESTS
754
+ // ============================================================================
755
+
756
+ describe('Super admin override', () => {
757
+ it('should allow super admin to access any account', async () => {
758
+ // Create a super admin
759
+ const superAdmin = await createSuperAdmin({
760
+ email: 'super-admin-override@example.com',
761
+ name: 'Super Admin Override',
762
+ })
763
+
764
+ // Create an account for the super admin context
765
+ const adminContext = await createAccount({ name: 'Super Admin Context' })
766
+ await addUserToAccount(superAdmin.id, adminContext.id, 'ADMIN')
767
+
768
+ // Create another account with users
769
+ const otherAccount = await createAccount({ name: 'Other Account For Super' })
770
+ const otherUser = await createUser({
771
+ email: 'other-for-super@example.com',
772
+ name: 'Other For Super',
773
+ })
774
+ await addUserToAccount(otherUser.id, otherAccount.id, 'VIEWER')
775
+
776
+ const { headers } = await createUserSession(superAdmin.id, {
777
+ email: superAdmin.email,
778
+ name: superAdmin.name,
779
+ isSuperAdmin: true,
780
+ })
781
+
782
+ // Super admin should be able to see the other account
783
+ const res = await app.request(`/api/accounts/${otherAccount.id}`, {
784
+ method: 'GET',
785
+ headers: {
786
+ ...headers,
787
+ 'User-Agent': 'IntegrationTest/1.0',
788
+ 'account-id': adminContext.id,
789
+ },
790
+ })
791
+
792
+ expect(res.status).toBe(200)
793
+ const body = await res.json()
794
+ expect(body.data.id).toBe(otherAccount.id)
795
+ })
796
+
797
+ it('should allow super admin to view all users across all accounts', async () => {
798
+ // Create a super admin
799
+ const superAdmin = await createSuperAdmin({
800
+ email: 'super-admin-all-users@example.com',
801
+ name: 'Super Admin All Users',
802
+ })
803
+
804
+ // Create an account for the super admin context
805
+ const adminContext = await createAccount({ name: 'Super Admin Users Context' })
806
+ await addUserToAccount(superAdmin.id, adminContext.id, 'ADMIN')
807
+
808
+ // Create multiple accounts with users
809
+ const account1 = await createAccount({ name: 'Account 1 For Super' })
810
+ const user1 = await createUser({
811
+ email: 'user1-for-super@example.com',
812
+ name: 'User 1 For Super',
813
+ })
814
+ await addUserToAccount(user1.id, account1.id, 'VIEWER')
815
+
816
+ const account2 = await createAccount({ name: 'Account 2 For Super' })
817
+ const user2 = await createUser({
818
+ email: 'user2-for-super@example.com',
819
+ name: 'User 2 For Super',
820
+ })
821
+ await addUserToAccount(user2.id, account2.id, 'VIEWER')
822
+
823
+ const { headers } = await createUserSession(superAdmin.id, {
824
+ email: superAdmin.email,
825
+ name: superAdmin.name,
826
+ isSuperAdmin: true,
827
+ })
828
+
829
+ // Super admin can see user in account1
830
+ const res1 = await app.request(`/api/users/${user1.id}`, {
831
+ method: 'GET',
832
+ headers: {
833
+ ...headers,
834
+ 'User-Agent': 'IntegrationTest/1.0',
835
+ 'account-id': adminContext.id,
836
+ },
837
+ })
838
+ expect(res1.status).toBe(200)
839
+
840
+ // Super admin can see user in account2
841
+ const res2 = await app.request(`/api/users/${user2.id}`, {
842
+ method: 'GET',
843
+ headers: {
844
+ ...headers,
845
+ 'User-Agent': 'IntegrationTest/1.0',
846
+ 'account-id': adminContext.id,
847
+ },
848
+ })
849
+ expect(res2.status).toBe(200)
850
+ })
851
+
852
+ it('should allow super admin to modify any account', async () => {
853
+ // Create a super admin
854
+ const superAdmin = await createSuperAdmin({
855
+ email: 'super-admin-modify@example.com',
856
+ name: 'Super Admin Modify',
857
+ })
858
+
859
+ // Create an account for the super admin context
860
+ const adminContext = await createAccount({ name: 'Super Admin Modify Context' })
861
+ await addUserToAccount(superAdmin.id, adminContext.id, 'ADMIN')
862
+
863
+ // Create another account to modify
864
+ const targetAccount = await createAccount({
865
+ name: 'Target Account For Super',
866
+ description: 'Original Description',
867
+ })
868
+
869
+ const { headers } = await createUserSession(superAdmin.id, {
870
+ email: superAdmin.email,
871
+ name: superAdmin.name,
872
+ isSuperAdmin: true,
873
+ })
874
+
875
+ // Super admin should be able to update the account
876
+ const res = await app.request(`/api/accounts/${targetAccount.id}`, {
877
+ method: 'PATCH',
878
+ headers: {
879
+ ...headers,
880
+ 'User-Agent': 'IntegrationTest/1.0',
881
+ 'account-id': adminContext.id,
882
+ 'Content-Type': 'application/json',
883
+ },
884
+ body: JSON.stringify({
885
+ name: 'Super Admin Updated Name',
886
+ description: 'Super Admin Updated Description',
887
+ }),
888
+ })
889
+
890
+ expect(res.status).toBe(200)
891
+ const body = await res.json()
892
+ expect(body.data.name).toBe('Super Admin Updated Name')
893
+ expect(body.data.description).toBe('Super Admin Updated Description')
894
+ })
895
+
896
+ it('should allow super admin to view audit logs across all accounts', async () => {
897
+ // Create a super admin
898
+ const superAdmin = await createSuperAdmin({
899
+ email: 'super-admin-audits@example.com',
900
+ name: 'Super Admin Audits',
901
+ })
902
+
903
+ // Create an account for the super admin context
904
+ const adminContext = await createAccount({ name: 'Super Admin Audits Context' })
905
+ await addUserToAccount(superAdmin.id, adminContext.id, 'ADMIN')
906
+
907
+ // Create audit logs in different accounts
908
+ const account1 = await createAccount({ name: 'Audit Account 1' })
909
+ const user1 = await createUser({
910
+ email: 'audit-user1@example.com',
911
+ name: 'Audit User 1',
912
+ })
913
+ await addUserToAccount(user1.id, account1.id, 'ADMIN')
914
+
915
+ const audit1 = createAuditLog({
916
+ accountId: account1.id,
917
+ userId: user1.id,
918
+ entity: 'User',
919
+ entityId: crypto.randomUUID(),
920
+ action: 'INSERT',
921
+ changes: { name: 'Audit 1' },
922
+ })
923
+
924
+ const account2 = await createAccount({ name: 'Audit Account 2' })
925
+ const user2 = await createUser({
926
+ email: 'audit-user2@example.com',
927
+ name: 'Audit User 2',
928
+ })
929
+ await addUserToAccount(user2.id, account2.id, 'ADMIN')
930
+
931
+ const audit2 = createAuditLog({
932
+ accountId: account2.id,
933
+ userId: user2.id,
934
+ entity: 'User',
935
+ entityId: crypto.randomUUID(),
936
+ action: 'INSERT',
937
+ changes: { name: 'Audit 2' },
938
+ })
939
+
940
+ const { headers } = await createUserSession(superAdmin.id, {
941
+ email: superAdmin.email,
942
+ name: superAdmin.name,
943
+ isSuperAdmin: true,
944
+ })
945
+
946
+ // Super admin should see all audit logs
947
+ const res = await app.request('/api/audits', {
948
+ method: 'GET',
949
+ headers: {
950
+ ...headers,
951
+ 'User-Agent': 'IntegrationTest/1.0',
952
+ 'account-id': adminContext.id,
953
+ },
954
+ })
955
+
956
+ expect(res.status).toBe(200)
957
+ const body = await res.json()
958
+ const auditIds = body.data.map((a: any) => a.id)
959
+
960
+ // Should include audits from both accounts
961
+ expect(auditIds).toContain(audit1.id)
962
+ expect(auditIds).toContain(audit2.id)
963
+ })
964
+
965
+ it('should allow super admin to delete any account', async () => {
966
+ // Create a super admin
967
+ const superAdmin = await createSuperAdmin({
968
+ email: 'super-admin-delete@example.com',
969
+ name: 'Super Admin Delete',
970
+ })
971
+
972
+ // Create an account for the super admin context
973
+ const adminContext = await createAccount({ name: 'Super Admin Delete Context' })
974
+ await addUserToAccount(superAdmin.id, adminContext.id, 'ADMIN')
975
+
976
+ // Create another account to delete
977
+ const targetAccount = await createAccount({ name: 'Account To Be Deleted By Super' })
978
+
979
+ const { headers } = await createUserSession(superAdmin.id, {
980
+ email: superAdmin.email,
981
+ name: superAdmin.name,
982
+ isSuperAdmin: true,
983
+ })
984
+
985
+ // Super admin should be able to delete the account
986
+ const res = await app.request(`/api/accounts/${targetAccount.id}`, {
987
+ method: 'DELETE',
988
+ headers: {
989
+ ...headers,
990
+ 'User-Agent': 'IntegrationTest/1.0',
991
+ 'account-id': adminContext.id,
992
+ },
993
+ })
994
+
995
+ expect(res.status).toBe(204)
996
+
997
+ // Verify the account was soft-deleted
998
+ const sqlite = getSqlite()
999
+ const row = sqlite.prepare('SELECT deleted_at FROM accounts WHERE id = ?').get(targetAccount.id) as { deleted_at: string | null }
1000
+ expect(row.deleted_at).not.toBeNull()
1001
+ })
1002
+ })
1003
+
1004
+ // ============================================================================
1005
+ // CROSS-TENANT DATA LEAKAGE PREVENTION
1006
+ // ============================================================================
1007
+
1008
+ describe('Cross-tenant data leakage prevention', () => {
1009
+ it('should not leak user existence across accounts via response timing', async () => {
1010
+ const scenario = await createMultiTenantScenario()
1011
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
1012
+
1013
+ // Create a user in another account
1014
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
1015
+ const existingUser = await createUser({
1016
+ email: 'timing-test-user@example.com',
1017
+ name: 'Timing Test User',
1018
+ })
1019
+ await addUserToAccount(existingUser.id, accountWithoutAccess.id, 'VIEWER')
1020
+
1021
+ // Non-existent user ID
1022
+ const nonExistentId = crypto.randomUUID()
1023
+
1024
+ // Both should return 404 (same response for security)
1025
+ const resExisting = await app.request(`/api/users/${existingUser.id}`, {
1026
+ method: 'GET',
1027
+ headers: {
1028
+ ...scenario.headers,
1029
+ 'User-Agent': 'IntegrationTest/1.0',
1030
+ 'account-id': accountWithAdminAccess.account.id,
1031
+ },
1032
+ })
1033
+
1034
+ const resNonExistent = await app.request(`/api/users/${nonExistentId}`, {
1035
+ method: 'GET',
1036
+ headers: {
1037
+ ...scenario.headers,
1038
+ 'User-Agent': 'IntegrationTest/1.0',
1039
+ 'account-id': accountWithAdminAccess.account.id,
1040
+ },
1041
+ })
1042
+
1043
+ // Both should return the same status code (404)
1044
+ expect(resExisting.status).toBe(404)
1045
+ expect(resNonExistent.status).toBe(404)
1046
+ })
1047
+
1048
+ it('should not leak account existence across tenants via response timing', async () => {
1049
+ const scenario = await createMultiTenantScenario()
1050
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
1051
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
1052
+
1053
+ // Non-existent account ID
1054
+ const nonExistentId = crypto.randomUUID()
1055
+
1056
+ // Both should return 404
1057
+ const resExisting = await app.request(`/api/accounts/${accountWithoutAccess.id}`, {
1058
+ method: 'GET',
1059
+ headers: {
1060
+ ...scenario.headers,
1061
+ 'User-Agent': 'IntegrationTest/1.0',
1062
+ 'account-id': accountWithAdminAccess.account.id,
1063
+ },
1064
+ })
1065
+
1066
+ const resNonExistent = await app.request(`/api/accounts/${nonExistentId}`, {
1067
+ method: 'GET',
1068
+ headers: {
1069
+ ...scenario.headers,
1070
+ 'User-Agent': 'IntegrationTest/1.0',
1071
+ 'account-id': accountWithAdminAccess.account.id,
1072
+ },
1073
+ })
1074
+
1075
+ // Both should return the same status code (404)
1076
+ expect(resExisting.status).toBe(404)
1077
+ expect(resNonExistent.status).toBe(404)
1078
+ })
1079
+
1080
+ it('should prevent enumeration attacks on user IDs', async () => {
1081
+ const scenario = await createMultiTenantScenario()
1082
+ const accountWithAdminAccess = scenario.accounts.withAccess.find(a => a.role === 'ADMIN')!
1083
+
1084
+ // Create multiple users in another account
1085
+ const accountWithoutAccess = scenario.accounts.withoutAccess[0]
1086
+ const users = await Promise.all(
1087
+ Array.from({ length: 3 }, async (_, i) => {
1088
+ const user = await createUser({
1089
+ email: `enum-test-${i}@example.com`,
1090
+ name: `Enum Test User ${i}`,
1091
+ })
1092
+ await addUserToAccount(user.id, accountWithoutAccess.id, 'VIEWER')
1093
+ return user
1094
+ })
1095
+ )
1096
+
1097
+ // Try to enumerate users - all should return 404
1098
+ for (const user of users) {
1099
+ const res = await app.request(`/api/users/${user.id}`, {
1100
+ method: 'GET',
1101
+ headers: {
1102
+ ...scenario.headers,
1103
+ 'User-Agent': 'IntegrationTest/1.0',
1104
+ 'account-id': accountWithAdminAccess.account.id,
1105
+ },
1106
+ })
1107
+
1108
+ expect(res.status).toBe(404)
1109
+ }
1110
+ })
1111
+ })
1112
+ })