@simple-auth-kit/cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (820) hide show
  1. package/README.md +88 -0
  2. package/bin/simple-auth-kit.mjs +18 -0
  3. package/lib/copy.ts +190 -0
  4. package/lib/rename-native.ts +173 -0
  5. package/package.json +31 -0
  6. package/registry/README.md +312 -0
  7. package/registry/admin-apps/README.md +71 -0
  8. package/registry/admin-apps/nextjs/shared/components.json +25 -0
  9. package/registry/admin-apps/nextjs/shared/next.config.ts +9 -0
  10. package/registry/admin-apps/nextjs/shared/postcss.config.mjs +7 -0
  11. package/registry/admin-apps/nextjs/shared/src/app/globals.css +159 -0
  12. package/registry/admin-apps/nextjs/shared/src/app/signup/page.tsx +72 -0
  13. package/registry/admin-apps/nextjs/shared/src/components/alert-modal.tsx +32 -0
  14. package/registry/admin-apps/nextjs/shared/src/components/breadcrumb.tsx +48 -0
  15. package/registry/admin-apps/nextjs/shared/src/components/form-error-alert.tsx +24 -0
  16. package/registry/admin-apps/nextjs/shared/src/components/loader/skeleton.tsx +39 -0
  17. package/registry/admin-apps/nextjs/shared/src/components/loader/table-skeleton-loader.tsx +57 -0
  18. package/registry/admin-apps/nextjs/shared/src/components/permission-group-select.tsx +101 -0
  19. package/registry/admin-apps/nextjs/shared/src/components/permission-group-view.tsx +53 -0
  20. package/registry/admin-apps/nextjs/shared/src/components/role-multi-select.tsx +71 -0
  21. package/registry/admin-apps/nextjs/shared/src/components/theme-toggle.tsx +26 -0
  22. package/registry/admin-apps/nextjs/shared/src/components/ui/alert.tsx +78 -0
  23. package/registry/admin-apps/nextjs/shared/src/components/ui/avatar.tsx +53 -0
  24. package/registry/admin-apps/nextjs/shared/src/components/ui/badge.tsx +51 -0
  25. package/registry/admin-apps/nextjs/shared/src/components/ui/breadcrumb.tsx +109 -0
  26. package/registry/admin-apps/nextjs/shared/src/components/ui/button.tsx +67 -0
  27. package/registry/admin-apps/nextjs/shared/src/components/ui/card.tsx +103 -0
  28. package/registry/admin-apps/nextjs/shared/src/components/ui/checkbox.tsx +32 -0
  29. package/registry/admin-apps/nextjs/shared/src/components/ui/collapsible.tsx +33 -0
  30. package/registry/admin-apps/nextjs/shared/src/components/ui/data-table.tsx +107 -0
  31. package/registry/admin-apps/nextjs/shared/src/components/ui/dialog.tsx +143 -0
  32. package/registry/admin-apps/nextjs/shared/src/components/ui/dropdown-menu.tsx +234 -0
  33. package/registry/admin-apps/nextjs/shared/src/components/ui/heading.tsx +16 -0
  34. package/registry/admin-apps/nextjs/shared/src/components/ui/input.tsx +19 -0
  35. package/registry/admin-apps/nextjs/shared/src/components/ui/label.tsx +24 -0
  36. package/registry/admin-apps/nextjs/shared/src/components/ui/modal.tsx +29 -0
  37. package/registry/admin-apps/nextjs/shared/src/components/ui/select.tsx +192 -0
  38. package/registry/admin-apps/nextjs/shared/src/components/ui/separator.tsx +28 -0
  39. package/registry/admin-apps/nextjs/shared/src/components/ui/sheet.tsx +135 -0
  40. package/registry/admin-apps/nextjs/shared/src/components/ui/sidebar.tsx +567 -0
  41. package/registry/admin-apps/nextjs/shared/src/components/ui/skeleton.tsx +13 -0
  42. package/registry/admin-apps/nextjs/shared/src/components/ui/sonner.tsx +34 -0
  43. package/registry/admin-apps/nextjs/shared/src/components/ui/table.tsx +116 -0
  44. package/registry/admin-apps/nextjs/shared/src/components/ui/tooltip.tsx +61 -0
  45. package/registry/admin-apps/nextjs/shared/src/hooks/use-live-audit-feed.ts +51 -0
  46. package/registry/admin-apps/nextjs/shared/src/hooks/use-mobile.ts +17 -0
  47. package/registry/admin-apps/nextjs/shared/src/lib/error.ts +23 -0
  48. package/registry/admin-apps/nextjs/shared/src/lib/query-client.tsx +10 -0
  49. package/registry/admin-apps/nextjs/shared/src/lib/token-storage.ts +36 -0
  50. package/registry/admin-apps/nextjs/shared/src/lib/utils.ts +6 -0
  51. package/registry/admin-apps/nextjs/shared/tsconfig.json +41 -0
  52. package/registry/admin-apps/nextjs/variants/base/.env.example +9 -0
  53. package/registry/admin-apps/nextjs/variants/base/Dockerfile +32 -0
  54. package/registry/admin-apps/nextjs/variants/base/package.json +58 -0
  55. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/account/change-password/page.tsx +163 -0
  56. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/account/page.tsx +569 -0
  57. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/audit-log/page.tsx +180 -0
  58. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/countries/[id]/edit/page.tsx +357 -0
  59. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/countries/[id]/page.tsx +191 -0
  60. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/countries/country-schema.ts +18 -0
  61. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/countries/new/page.tsx +305 -0
  62. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/countries/page.tsx +367 -0
  63. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/edit/page.tsx +386 -0
  64. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/[id]/page.tsx +213 -0
  65. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/customer-schema.ts +35 -0
  66. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/new/page.tsx +334 -0
  67. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/customers/page.tsx +400 -0
  68. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/dashboard/page.tsx +263 -0
  69. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/edit/page.tsx +275 -0
  70. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/[id]/page.tsx +183 -0
  71. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/language-schema.ts +20 -0
  72. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/new/page.tsx +226 -0
  73. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/languages/page.tsx +342 -0
  74. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/layout.tsx +50 -0
  75. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/edit/page.tsx +309 -0
  76. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/[id]/page.tsx +119 -0
  77. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/new/page.tsx +275 -0
  78. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/page.tsx +311 -0
  79. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/permissions/permission-schema.ts +44 -0
  80. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/[id]/edit/page.tsx +276 -0
  81. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/[id]/page.tsx +179 -0
  82. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/new/page.tsx +241 -0
  83. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/page.tsx +325 -0
  84. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/roles/role-schema.ts +20 -0
  85. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/[id]/edit/page.tsx +411 -0
  86. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/[id]/page.tsx +253 -0
  87. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/new/page.tsx +444 -0
  88. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/page.tsx +410 -0
  89. package/registry/admin-apps/nextjs/variants/base/src/app/(console)/users/user-schema.ts +37 -0
  90. package/registry/admin-apps/nextjs/variants/base/src/app/api/auth/[...nextauth]/route.ts +3 -0
  91. package/registry/admin-apps/nextjs/variants/base/src/app/icon.svg +5 -0
  92. package/registry/admin-apps/nextjs/variants/base/src/app/layout.tsx +31 -0
  93. package/registry/admin-apps/nextjs/variants/base/src/app/login/page.tsx +162 -0
  94. package/registry/admin-apps/nextjs/variants/base/src/app/page.tsx +22 -0
  95. package/registry/admin-apps/nextjs/variants/base/src/auth.ts +105 -0
  96. package/registry/admin-apps/nextjs/variants/base/src/components/app-sidebar.tsx +182 -0
  97. package/registry/admin-apps/nextjs/variants/base/src/components/nav-user.tsx +99 -0
  98. package/registry/admin-apps/nextjs/variants/base/src/components/permission-required.tsx +32 -0
  99. package/registry/admin-apps/nextjs/variants/base/src/components/photo-upload.tsx +203 -0
  100. package/registry/admin-apps/nextjs/variants/base/src/components/ui/accordion.tsx +66 -0
  101. package/registry/admin-apps/nextjs/variants/base/src/components/ui/alert-dialog.tsx +157 -0
  102. package/registry/admin-apps/nextjs/variants/base/src/components/ui/button-group.tsx +83 -0
  103. package/registry/admin-apps/nextjs/variants/base/src/components/ui/calendar.tsx +218 -0
  104. package/registry/admin-apps/nextjs/variants/base/src/components/ui/carousel.tsx +241 -0
  105. package/registry/admin-apps/nextjs/variants/base/src/components/ui/combobox.tsx +243 -0
  106. package/registry/admin-apps/nextjs/variants/base/src/components/ui/command.tsx +184 -0
  107. package/registry/admin-apps/nextjs/variants/base/src/components/ui/context-menu.tsx +252 -0
  108. package/registry/admin-apps/nextjs/variants/base/src/components/ui/date-picker.tsx +323 -0
  109. package/registry/admin-apps/nextjs/variants/base/src/components/ui/datetime-picker.tsx +896 -0
  110. package/registry/admin-apps/nextjs/variants/base/src/components/ui/drawer.tsx +135 -0
  111. package/registry/admin-apps/nextjs/variants/base/src/components/ui/empty.tsx +104 -0
  112. package/registry/admin-apps/nextjs/variants/base/src/components/ui/field.tsx +248 -0
  113. package/registry/admin-apps/nextjs/variants/base/src/components/ui/form.tsx +168 -0
  114. package/registry/admin-apps/nextjs/variants/base/src/components/ui/hover-card.tsx +44 -0
  115. package/registry/admin-apps/nextjs/variants/base/src/components/ui/input-group.tsx +170 -0
  116. package/registry/admin-apps/nextjs/variants/base/src/components/ui/input-otp.tsx +77 -0
  117. package/registry/admin-apps/nextjs/variants/base/src/components/ui/item.tsx +193 -0
  118. package/registry/admin-apps/nextjs/variants/base/src/components/ui/kbd.tsx +28 -0
  119. package/registry/admin-apps/nextjs/variants/base/src/components/ui/menubar.tsx +276 -0
  120. package/registry/admin-apps/nextjs/variants/base/src/components/ui/multi-select.tsx +381 -0
  121. package/registry/admin-apps/nextjs/variants/base/src/components/ui/multi-selector.tsx +641 -0
  122. package/registry/admin-apps/nextjs/variants/base/src/components/ui/native-select.tsx +48 -0
  123. package/registry/admin-apps/nextjs/variants/base/src/components/ui/navigation-menu.tsx +168 -0
  124. package/registry/admin-apps/nextjs/variants/base/src/components/ui/pagination.tsx +127 -0
  125. package/registry/admin-apps/nextjs/variants/base/src/components/ui/popover.tsx +48 -0
  126. package/registry/admin-apps/nextjs/variants/base/src/components/ui/progress.tsx +31 -0
  127. package/registry/admin-apps/nextjs/variants/base/src/components/ui/radio-group.tsx +45 -0
  128. package/registry/admin-apps/nextjs/variants/base/src/components/ui/resizable.tsx +55 -0
  129. package/registry/admin-apps/nextjs/variants/base/src/components/ui/scroll-area.tsx +58 -0
  130. package/registry/admin-apps/nextjs/variants/base/src/components/ui/spinner.tsx +27 -0
  131. package/registry/admin-apps/nextjs/variants/base/src/components/ui/switch.tsx +31 -0
  132. package/registry/admin-apps/nextjs/variants/base/src/components/ui/tabs.tsx +66 -0
  133. package/registry/admin-apps/nextjs/variants/base/src/components/ui/textarea.tsx +18 -0
  134. package/registry/admin-apps/nextjs/variants/base/src/components/ui/toggle-group.tsx +83 -0
  135. package/registry/admin-apps/nextjs/variants/base/src/components/ui/toggle.tsx +47 -0
  136. package/registry/admin-apps/nextjs/variants/base/src/components/user-menu.tsx +61 -0
  137. package/registry/admin-apps/nextjs/variants/base/src/lib/ability.ts +90 -0
  138. package/registry/admin-apps/nextjs/variants/base/src/lib/auth-client.ts +10 -0
  139. package/registry/admin-apps/nextjs/variants/base/src/lib/env.ts +10 -0
  140. package/registry/admin-apps/nextjs/variants/base/src/lib/server-token-storage.ts +33 -0
  141. package/registry/admin-apps/nextjs/variants/base/src/lib/stores/auth-store.ts +117 -0
  142. package/registry/admin-apps/nextjs/variants/base/src/lib/stores/store-context.tsx +50 -0
  143. package/registry/admin-apps/nextjs/variants/base/src/proxy.ts +81 -0
  144. package/registry/admin-apps/nextjs/variants/base/src/routes.ts +7 -0
  145. package/registry/admin-apps/nextjs/variants/workspaces/.env.example +7 -0
  146. package/registry/admin-apps/nextjs/variants/workspaces/Dockerfile +32 -0
  147. package/registry/admin-apps/nextjs/variants/workspaces/package.json +48 -0
  148. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/account/page.tsx +360 -0
  149. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/audit-log/page.tsx +185 -0
  150. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/countries/[id]/edit/page.tsx +296 -0
  151. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/countries/[id]/page.tsx +194 -0
  152. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/countries/country-schema.ts +27 -0
  153. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/countries/new/page.tsx +242 -0
  154. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/countries/page.tsx +373 -0
  155. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/customers/[id]/edit/page.tsx +306 -0
  156. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/customers/[id]/page.tsx +219 -0
  157. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/customers/customer-schema.ts +36 -0
  158. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/customers/new/page.tsx +251 -0
  159. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/customers/page.tsx +406 -0
  160. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/dashboard/page.tsx +146 -0
  161. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/languages/[id]/edit/page.tsx +240 -0
  162. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/languages/[id]/page.tsx +186 -0
  163. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/languages/language-schema.ts +26 -0
  164. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/languages/new/page.tsx +189 -0
  165. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/languages/page.tsx +348 -0
  166. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/layout.tsx +96 -0
  167. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/members/page.tsx +263 -0
  168. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/permissions/[id]/edit/page.tsx +264 -0
  169. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/permissions/[id]/page.tsx +126 -0
  170. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/permissions/new/page.tsx +238 -0
  171. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/permissions/page.tsx +294 -0
  172. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/permissions/permission-schema.ts +46 -0
  173. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/roles/[id]/edit/page.tsx +248 -0
  174. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/roles/[id]/page.tsx +186 -0
  175. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/roles/new/page.tsx +205 -0
  176. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/roles/page.tsx +485 -0
  177. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/roles/role-schema.ts +23 -0
  178. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/users/[id]/page.tsx +521 -0
  179. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/users/new/page.tsx +342 -0
  180. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/users/page.tsx +414 -0
  181. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/users/user-schema.ts +44 -0
  182. package/registry/admin-apps/nextjs/variants/workspaces/src/app/(console)/workspaces/new/page.tsx +66 -0
  183. package/registry/admin-apps/nextjs/variants/workspaces/src/app/layout.tsx +31 -0
  184. package/registry/admin-apps/nextjs/variants/workspaces/src/app/login/page.tsx +110 -0
  185. package/registry/admin-apps/nextjs/variants/workspaces/src/app/page.tsx +33 -0
  186. package/registry/admin-apps/nextjs/variants/workspaces/src/components/app-sidebar.tsx +180 -0
  187. package/registry/admin-apps/nextjs/variants/workspaces/src/components/change-password-dialog.tsx +119 -0
  188. package/registry/admin-apps/nextjs/variants/workspaces/src/components/nav-user.tsx +105 -0
  189. package/registry/admin-apps/nextjs/variants/workspaces/src/components/permission-required.tsx +32 -0
  190. package/registry/admin-apps/nextjs/variants/workspaces/src/components/photo-upload.tsx +226 -0
  191. package/registry/admin-apps/nextjs/variants/workspaces/src/components/ui/calendar.tsx +218 -0
  192. package/registry/admin-apps/nextjs/variants/workspaces/src/components/ui/datetime-picker.tsx +896 -0
  193. package/registry/admin-apps/nextjs/variants/workspaces/src/components/ui/popover.tsx +48 -0
  194. package/registry/admin-apps/nextjs/variants/workspaces/src/components/user-menu.tsx +67 -0
  195. package/registry/admin-apps/nextjs/variants/workspaces/src/components/workspace-switcher.tsx +57 -0
  196. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/ability.ts +101 -0
  197. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/active-workspace.ts +38 -0
  198. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/auth-client.ts +17 -0
  199. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/env.ts +4 -0
  200. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/stores/auth-store.ts +139 -0
  201. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/stores/store-context.tsx +66 -0
  202. package/registry/admin-apps/nextjs/variants/workspaces/src/lib/stores/workspace-store.ts +89 -0
  203. package/registry/admin-apps/react/shared/components.json +25 -0
  204. package/registry/admin-apps/react/shared/postcss.config.js +5 -0
  205. package/registry/admin-apps/react/shared/src/components/alert-modal.tsx +30 -0
  206. package/registry/admin-apps/react/shared/src/components/loader/skeleton.tsx +39 -0
  207. package/registry/admin-apps/react/shared/src/components/loader/table-skeleton-loader.tsx +57 -0
  208. package/registry/admin-apps/react/shared/src/components/nav-user.tsx +103 -0
  209. package/registry/admin-apps/react/shared/src/components/role-multi-select.tsx +69 -0
  210. package/registry/admin-apps/react/shared/src/components/theme-toggle.tsx +24 -0
  211. package/registry/admin-apps/react/shared/src/components/ui/accordion.tsx +66 -0
  212. package/registry/admin-apps/react/shared/src/components/ui/alert-dialog.tsx +157 -0
  213. package/registry/admin-apps/react/shared/src/components/ui/alert.tsx +78 -0
  214. package/registry/admin-apps/react/shared/src/components/ui/avatar.tsx +53 -0
  215. package/registry/admin-apps/react/shared/src/components/ui/badge.tsx +51 -0
  216. package/registry/admin-apps/react/shared/src/components/ui/breadcrumb.tsx +109 -0
  217. package/registry/admin-apps/react/shared/src/components/ui/button-group.tsx +83 -0
  218. package/registry/admin-apps/react/shared/src/components/ui/button.tsx +67 -0
  219. package/registry/admin-apps/react/shared/src/components/ui/calendar.tsx +218 -0
  220. package/registry/admin-apps/react/shared/src/components/ui/card.tsx +103 -0
  221. package/registry/admin-apps/react/shared/src/components/ui/carousel.tsx +241 -0
  222. package/registry/admin-apps/react/shared/src/components/ui/checkbox.tsx +32 -0
  223. package/registry/admin-apps/react/shared/src/components/ui/collapsible.tsx +33 -0
  224. package/registry/admin-apps/react/shared/src/components/ui/context-menu.tsx +252 -0
  225. package/registry/admin-apps/react/shared/src/components/ui/data-table.tsx +107 -0
  226. package/registry/admin-apps/react/shared/src/components/ui/datetime-picker.tsx +896 -0
  227. package/registry/admin-apps/react/shared/src/components/ui/dialog.tsx +143 -0
  228. package/registry/admin-apps/react/shared/src/components/ui/drawer.tsx +135 -0
  229. package/registry/admin-apps/react/shared/src/components/ui/dropdown-menu.tsx +234 -0
  230. package/registry/admin-apps/react/shared/src/components/ui/empty.tsx +104 -0
  231. package/registry/admin-apps/react/shared/src/components/ui/field.tsx +248 -0
  232. package/registry/admin-apps/react/shared/src/components/ui/form.tsx +168 -0
  233. package/registry/admin-apps/react/shared/src/components/ui/heading.tsx +16 -0
  234. package/registry/admin-apps/react/shared/src/components/ui/hover-card.tsx +44 -0
  235. package/registry/admin-apps/react/shared/src/components/ui/input-group.tsx +170 -0
  236. package/registry/admin-apps/react/shared/src/components/ui/input-otp.tsx +77 -0
  237. package/registry/admin-apps/react/shared/src/components/ui/input.tsx +19 -0
  238. package/registry/admin-apps/react/shared/src/components/ui/item.tsx +193 -0
  239. package/registry/admin-apps/react/shared/src/components/ui/kbd.tsx +28 -0
  240. package/registry/admin-apps/react/shared/src/components/ui/label.tsx +24 -0
  241. package/registry/admin-apps/react/shared/src/components/ui/menubar.tsx +276 -0
  242. package/registry/admin-apps/react/shared/src/components/ui/modal.tsx +29 -0
  243. package/registry/admin-apps/react/shared/src/components/ui/multi-select.tsx +381 -0
  244. package/registry/admin-apps/react/shared/src/components/ui/multi-selector.tsx +641 -0
  245. package/registry/admin-apps/react/shared/src/components/ui/native-select.tsx +48 -0
  246. package/registry/admin-apps/react/shared/src/components/ui/navigation-menu.tsx +168 -0
  247. package/registry/admin-apps/react/shared/src/components/ui/pagination.tsx +127 -0
  248. package/registry/admin-apps/react/shared/src/components/ui/popover.tsx +48 -0
  249. package/registry/admin-apps/react/shared/src/components/ui/progress.tsx +31 -0
  250. package/registry/admin-apps/react/shared/src/components/ui/radio-group.tsx +45 -0
  251. package/registry/admin-apps/react/shared/src/components/ui/resizable.tsx +55 -0
  252. package/registry/admin-apps/react/shared/src/components/ui/scroll-area.tsx +58 -0
  253. package/registry/admin-apps/react/shared/src/components/ui/select.tsx +192 -0
  254. package/registry/admin-apps/react/shared/src/components/ui/separator.tsx +28 -0
  255. package/registry/admin-apps/react/shared/src/components/ui/sheet.tsx +135 -0
  256. package/registry/admin-apps/react/shared/src/components/ui/sidebar.tsx +567 -0
  257. package/registry/admin-apps/react/shared/src/components/ui/skeleton.tsx +13 -0
  258. package/registry/admin-apps/react/shared/src/components/ui/spinner.tsx +27 -0
  259. package/registry/admin-apps/react/shared/src/components/ui/switch.tsx +31 -0
  260. package/registry/admin-apps/react/shared/src/components/ui/table.tsx +116 -0
  261. package/registry/admin-apps/react/shared/src/components/ui/tabs.tsx +66 -0
  262. package/registry/admin-apps/react/shared/src/components/ui/textarea.tsx +18 -0
  263. package/registry/admin-apps/react/shared/src/components/ui/toggle-group.tsx +83 -0
  264. package/registry/admin-apps/react/shared/src/components/ui/toggle.tsx +47 -0
  265. package/registry/admin-apps/react/shared/src/components/ui/tooltip.tsx +61 -0
  266. package/registry/admin-apps/react/shared/src/hooks/use-mobile.ts +17 -0
  267. package/registry/admin-apps/react/shared/src/lib/cn.ts +6 -0
  268. package/registry/admin-apps/react/shared/src/lib/error.ts +23 -0
  269. package/registry/admin-apps/react/shared/src/lib/query-client.tsx +8 -0
  270. package/registry/admin-apps/react/shared/src/lib/utils.ts +1 -0
  271. package/registry/admin-apps/react/shared/src/vite-env.d.ts +9 -0
  272. package/registry/admin-apps/react/shared/tsconfig.json +24 -0
  273. package/registry/admin-apps/react/variants/base/.env.example +3 -0
  274. package/registry/admin-apps/react/variants/base/Dockerfile +32 -0
  275. package/registry/admin-apps/react/variants/base/index.html +12 -0
  276. package/registry/admin-apps/react/variants/base/package.json +61 -0
  277. package/registry/admin-apps/react/variants/base/src/App.tsx +103 -0
  278. package/registry/admin-apps/react/variants/base/src/components/app-sidebar.tsx +181 -0
  279. package/registry/admin-apps/react/variants/base/src/components/breadcrumb.tsx +47 -0
  280. package/registry/admin-apps/react/variants/base/src/components/change-password-dialog.tsx +102 -0
  281. package/registry/admin-apps/react/variants/base/src/components/form-error-alert.tsx +23 -0
  282. package/registry/admin-apps/react/variants/base/src/components/layout/AppShell.tsx +36 -0
  283. package/registry/admin-apps/react/variants/base/src/components/layout/RequireAuth.tsx +66 -0
  284. package/registry/admin-apps/react/variants/base/src/components/permission-group-select.tsx +99 -0
  285. package/registry/admin-apps/react/variants/base/src/components/permission-group-view.tsx +53 -0
  286. package/registry/admin-apps/react/variants/base/src/components/photo-upload.tsx +221 -0
  287. package/registry/admin-apps/react/variants/base/src/components/ui/combobox.tsx +243 -0
  288. package/registry/admin-apps/react/variants/base/src/components/ui/command.tsx +177 -0
  289. package/registry/admin-apps/react/variants/base/src/components/ui/date-picker.tsx +322 -0
  290. package/registry/admin-apps/react/variants/base/src/components/ui/sonner.tsx +34 -0
  291. package/registry/admin-apps/react/variants/base/src/components/user-menu.tsx +65 -0
  292. package/registry/admin-apps/react/variants/base/src/hooks/use-live-audit-feed.ts +50 -0
  293. package/registry/admin-apps/react/variants/base/src/index.css +161 -0
  294. package/registry/admin-apps/react/variants/base/src/lib/ability.ts +72 -0
  295. package/registry/admin-apps/react/variants/base/src/lib/auth-client.ts +9 -0
  296. package/registry/admin-apps/react/variants/base/src/lib/theme.tsx +64 -0
  297. package/registry/admin-apps/react/variants/base/src/lib/token-storage.ts +34 -0
  298. package/registry/admin-apps/react/variants/base/src/main.tsx +24 -0
  299. package/registry/admin-apps/react/variants/base/src/pages/AccountPage.tsx +368 -0
  300. package/registry/admin-apps/react/variants/base/src/pages/AddCountryPage.tsx +227 -0
  301. package/registry/admin-apps/react/variants/base/src/pages/AddCustomerPage.tsx +220 -0
  302. package/registry/admin-apps/react/variants/base/src/pages/AddLanguagePage.tsx +167 -0
  303. package/registry/admin-apps/react/variants/base/src/pages/AddRolePage.tsx +187 -0
  304. package/registry/admin-apps/react/variants/base/src/pages/AddUserPage.tsx +328 -0
  305. package/registry/admin-apps/react/variants/base/src/pages/AuditLogPage.tsx +180 -0
  306. package/registry/admin-apps/react/variants/base/src/pages/CountriesPage.tsx +371 -0
  307. package/registry/admin-apps/react/variants/base/src/pages/CountryDetailPage.tsx +209 -0
  308. package/registry/admin-apps/react/variants/base/src/pages/CustomerDetailPage.tsx +231 -0
  309. package/registry/admin-apps/react/variants/base/src/pages/CustomersPage.tsx +404 -0
  310. package/registry/admin-apps/react/variants/base/src/pages/DashboardPage.tsx +190 -0
  311. package/registry/admin-apps/react/variants/base/src/pages/EditCountryPage.tsx +279 -0
  312. package/registry/admin-apps/react/variants/base/src/pages/EditCustomerPage.tsx +272 -0
  313. package/registry/admin-apps/react/variants/base/src/pages/EditLanguagePage.tsx +216 -0
  314. package/registry/admin-apps/react/variants/base/src/pages/EditRolePage.tsx +232 -0
  315. package/registry/admin-apps/react/variants/base/src/pages/EditUserPage.tsx +308 -0
  316. package/registry/admin-apps/react/variants/base/src/pages/LanguageDetailPage.tsx +201 -0
  317. package/registry/admin-apps/react/variants/base/src/pages/LanguagesPage.tsx +346 -0
  318. package/registry/admin-apps/react/variants/base/src/pages/LoginPage.tsx +149 -0
  319. package/registry/admin-apps/react/variants/base/src/pages/PermissionDetailPage.tsx +135 -0
  320. package/registry/admin-apps/react/variants/base/src/pages/PermissionsPage.tsx +331 -0
  321. package/registry/admin-apps/react/variants/base/src/pages/RoleDetailPage.tsx +167 -0
  322. package/registry/admin-apps/react/variants/base/src/pages/RolesPage.tsx +613 -0
  323. package/registry/admin-apps/react/variants/base/src/pages/UserDetailPage.tsx +268 -0
  324. package/registry/admin-apps/react/variants/base/src/pages/UsersPage.tsx +423 -0
  325. package/registry/admin-apps/react/variants/base/src/pages/country-schema.ts +29 -0
  326. package/registry/admin-apps/react/variants/base/src/pages/customer-schema.ts +37 -0
  327. package/registry/admin-apps/react/variants/base/src/pages/language-schema.ts +28 -0
  328. package/registry/admin-apps/react/variants/base/src/pages/user-schema.ts +36 -0
  329. package/registry/admin-apps/react/variants/base/src/stores/auth-store.ts +144 -0
  330. package/registry/admin-apps/react/variants/base/src/stores/store-context.tsx +15 -0
  331. package/registry/admin-apps/react/variants/base/vite.config.ts +15 -0
  332. package/registry/admin-apps/react/variants/workspaces/.env.example +5 -0
  333. package/registry/admin-apps/react/variants/workspaces/Dockerfile +32 -0
  334. package/registry/admin-apps/react/variants/workspaces/index.html +12 -0
  335. package/registry/admin-apps/react/variants/workspaces/package.json +61 -0
  336. package/registry/admin-apps/react/variants/workspaces/src/App.tsx +118 -0
  337. package/registry/admin-apps/react/variants/workspaces/src/components/app-sidebar.tsx +186 -0
  338. package/registry/admin-apps/react/variants/workspaces/src/components/breadcrumb.tsx +39 -0
  339. package/registry/admin-apps/react/variants/workspaces/src/components/change-password-dialog.tsx +111 -0
  340. package/registry/admin-apps/react/variants/workspaces/src/components/form-error-alert.tsx +23 -0
  341. package/registry/admin-apps/react/variants/workspaces/src/components/layout/AppShell.tsx +37 -0
  342. package/registry/admin-apps/react/variants/workspaces/src/components/layout/CreateWorkspaceDialog.tsx +66 -0
  343. package/registry/admin-apps/react/variants/workspaces/src/components/layout/RequireAuth.tsx +107 -0
  344. package/registry/admin-apps/react/variants/workspaces/src/components/layout/WorkspaceSwitcher.tsx +52 -0
  345. package/registry/admin-apps/react/variants/workspaces/src/components/permission-group-select.tsx +99 -0
  346. package/registry/admin-apps/react/variants/workspaces/src/components/permission-group-view.tsx +53 -0
  347. package/registry/admin-apps/react/variants/workspaces/src/components/photo-upload.tsx +223 -0
  348. package/registry/admin-apps/react/variants/workspaces/src/components/ui/combobox.tsx +243 -0
  349. package/registry/admin-apps/react/variants/workspaces/src/components/ui/command.tsx +184 -0
  350. package/registry/admin-apps/react/variants/workspaces/src/components/ui/date-picker.tsx +323 -0
  351. package/registry/admin-apps/react/variants/workspaces/src/components/ui/sonner.tsx +34 -0
  352. package/registry/admin-apps/react/variants/workspaces/src/components/user-menu.tsx +66 -0
  353. package/registry/admin-apps/react/variants/workspaces/src/hooks/use-live-audit-feed.ts +50 -0
  354. package/registry/admin-apps/react/variants/workspaces/src/index.css +162 -0
  355. package/registry/admin-apps/react/variants/workspaces/src/lib/ability.ts +78 -0
  356. package/registry/admin-apps/react/variants/workspaces/src/lib/auth-client.ts +27 -0
  357. package/registry/admin-apps/react/variants/workspaces/src/lib/permission-groups.ts +22 -0
  358. package/registry/admin-apps/react/variants/workspaces/src/lib/theme.tsx +64 -0
  359. package/registry/admin-apps/react/variants/workspaces/src/lib/token-storage.ts +37 -0
  360. package/registry/admin-apps/react/variants/workspaces/src/lib/user-fields.ts +13 -0
  361. package/registry/admin-apps/react/variants/workspaces/src/main.tsx +24 -0
  362. package/registry/admin-apps/react/variants/workspaces/src/pages/AccountPage.tsx +351 -0
  363. package/registry/admin-apps/react/variants/workspaces/src/pages/AddCountryPage.tsx +230 -0
  364. package/registry/admin-apps/react/variants/workspaces/src/pages/AddCustomerPage.tsx +224 -0
  365. package/registry/admin-apps/react/variants/workspaces/src/pages/AddLanguagePage.tsx +170 -0
  366. package/registry/admin-apps/react/variants/workspaces/src/pages/AddUserPage.tsx +297 -0
  367. package/registry/admin-apps/react/variants/workspaces/src/pages/AuditLogPage.tsx +195 -0
  368. package/registry/admin-apps/react/variants/workspaces/src/pages/CountriesPage.tsx +378 -0
  369. package/registry/admin-apps/react/variants/workspaces/src/pages/CountryDetailPage.tsx +215 -0
  370. package/registry/admin-apps/react/variants/workspaces/src/pages/CustomerDetailPage.tsx +237 -0
  371. package/registry/admin-apps/react/variants/workspaces/src/pages/CustomersPage.tsx +411 -0
  372. package/registry/admin-apps/react/variants/workspaces/src/pages/DashboardPage.tsx +191 -0
  373. package/registry/admin-apps/react/variants/workspaces/src/pages/EditCountryPage.tsx +285 -0
  374. package/registry/admin-apps/react/variants/workspaces/src/pages/EditCustomerPage.tsx +279 -0
  375. package/registry/admin-apps/react/variants/workspaces/src/pages/EditLanguagePage.tsx +222 -0
  376. package/registry/admin-apps/react/variants/workspaces/src/pages/LanguageDetailPage.tsx +207 -0
  377. package/registry/admin-apps/react/variants/workspaces/src/pages/LanguagesPage.tsx +353 -0
  378. package/registry/admin-apps/react/variants/workspaces/src/pages/LoginPage.tsx +149 -0
  379. package/registry/admin-apps/react/variants/workspaces/src/pages/MembersPage.tsx +349 -0
  380. package/registry/admin-apps/react/variants/workspaces/src/pages/PermissionDetailPage.tsx +123 -0
  381. package/registry/admin-apps/react/variants/workspaces/src/pages/PermissionsPage.tsx +317 -0
  382. package/registry/admin-apps/react/variants/workspaces/src/pages/RoleDetailPage.tsx +207 -0
  383. package/registry/admin-apps/react/variants/workspaces/src/pages/RolesPage.tsx +617 -0
  384. package/registry/admin-apps/react/variants/workspaces/src/pages/UserDetailPage.tsx +405 -0
  385. package/registry/admin-apps/react/variants/workspaces/src/pages/UsersPage.tsx +425 -0
  386. package/registry/admin-apps/react/variants/workspaces/src/pages/country-schema.ts +29 -0
  387. package/registry/admin-apps/react/variants/workspaces/src/pages/customer-schema.ts +34 -0
  388. package/registry/admin-apps/react/variants/workspaces/src/pages/language-schema.ts +27 -0
  389. package/registry/admin-apps/react/variants/workspaces/src/stores/auth-store.ts +169 -0
  390. package/registry/admin-apps/react/variants/workspaces/src/stores/store-context.tsx +43 -0
  391. package/registry/admin-apps/react/variants/workspaces/src/stores/workspace-store.ts +120 -0
  392. package/registry/admin-apps/react/variants/workspaces/vite.config.ts +15 -0
  393. package/registry/combos/express-drizzle/package.json +32 -0
  394. package/registry/combos/express-drizzle/scripts/materialize.mjs +104 -0
  395. package/registry/combos/express-drizzle/scripts/migrate.mjs +41 -0
  396. package/registry/combos/express-drizzle/scripts/prove-cycle.mjs +29 -0
  397. package/registry/combos/express-drizzle/scripts/seed.mjs +25 -0
  398. package/registry/combos/express-drizzle/shared/drizzle.config.ts +67 -0
  399. package/registry/combos/express-drizzle/shared/src/ability.ts +44 -0
  400. package/registry/combos/express-drizzle/shared/src/auth-core-error.middleware.ts +31 -0
  401. package/registry/combos/express-drizzle/shared/src/auth.config.ts +63 -0
  402. package/registry/combos/express-drizzle/shared/src/auth.middleware.ts +38 -0
  403. package/registry/combos/express-drizzle/shared/src/auth.router.ts +266 -0
  404. package/registry/combos/express-drizzle/shared/src/db.ts +12 -0
  405. package/registry/combos/express-drizzle/shared/src/http-error.ts +18 -0
  406. package/registry/combos/express-drizzle/shared/src/id.helper.ts +22 -0
  407. package/registry/combos/express-drizzle/shared/src/key-provider.ts +52 -0
  408. package/registry/combos/express-drizzle/shared/src/oauth.repository.ts +47 -0
  409. package/registry/combos/express-drizzle/shared/src/openapi-fragment.ts +49 -0
  410. package/registry/combos/express-drizzle/shared/src/openapi-spec.ts +493 -0
  411. package/registry/combos/express-drizzle/shared/src/pagination.ts +30 -0
  412. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +35 -0
  413. package/registry/combos/express-drizzle/shared/src/permission-cache.ts +191 -0
  414. package/registry/combos/express-drizzle/shared/src/rate-limit.store.ts +24 -0
  415. package/registry/combos/express-drizzle/shared/src/request-context.ts +35 -0
  416. package/registry/combos/express-drizzle/shared/src/response-envelope.middleware.ts +46 -0
  417. package/registry/combos/express-drizzle/shared/src/route-tiers.ts +148 -0
  418. package/registry/combos/express-drizzle/shared/src/session.repository.ts +130 -0
  419. package/registry/combos/express-drizzle/shared/src/two-factor.repository.ts +38 -0
  420. package/registry/combos/express-drizzle/test/bootstrap.ts +40 -0
  421. package/registry/combos/express-drizzle/test/harness.ts +187 -0
  422. package/registry/combos/express-drizzle/test/prove-cycle.ts +789 -0
  423. package/registry/combos/express-drizzle/tsconfig.json +20 -0
  424. package/registry/combos/express-drizzle/variants/base/.env.example +25 -0
  425. package/registry/combos/express-drizzle/variants/base/drizzle/0000_init.sql +219 -0
  426. package/registry/combos/express-drizzle/variants/base/drizzle/0001_add_is_active_field.sql +1 -0
  427. package/registry/combos/express-drizzle/variants/base/drizzle/meta/0000_snapshot.json +1861 -0
  428. package/registry/combos/express-drizzle/variants/base/drizzle/meta/0001_snapshot.json +1868 -0
  429. package/registry/combos/express-drizzle/variants/base/drizzle/meta/_journal.json +20 -0
  430. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +307 -0
  431. package/registry/combos/express-drizzle/variants/base/src/audit-log.repository.ts +93 -0
  432. package/registry/combos/express-drizzle/variants/base/src/auth.service.ts +528 -0
  433. package/registry/combos/express-drizzle/variants/base/src/authz.middleware.ts +58 -0
  434. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +78 -0
  435. package/registry/combos/express-drizzle/variants/base/src/openapi-admin.ts +565 -0
  436. package/registry/combos/express-drizzle/variants/base/src/rbac.defaults.ts +218 -0
  437. package/registry/combos/express-drizzle/variants/base/src/rbac.repository.ts +606 -0
  438. package/registry/combos/express-drizzle/variants/base/src/schema.ts +425 -0
  439. package/registry/combos/express-drizzle/variants/base/src/seed.ts +138 -0
  440. package/registry/combos/express-drizzle/variants/base/test/variant-hooks.ts +179 -0
  441. package/registry/combos/express-drizzle/variants/workspaces/.env.example +30 -0
  442. package/registry/combos/express-drizzle/variants/workspaces/drizzle/0000_init.sql +251 -0
  443. package/registry/combos/express-drizzle/variants/workspaces/drizzle/0001_add_is_active_field.sql +1 -0
  444. package/registry/combos/express-drizzle/variants/workspaces/drizzle/meta/0000_snapshot.json +2155 -0
  445. package/registry/combos/express-drizzle/variants/workspaces/drizzle/meta/0001_snapshot.json +2162 -0
  446. package/registry/combos/express-drizzle/variants/workspaces/drizzle/meta/_journal.json +20 -0
  447. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +323 -0
  448. package/registry/combos/express-drizzle/variants/workspaces/src/audit-log.repository.ts +105 -0
  449. package/registry/combos/express-drizzle/variants/workspaces/src/auth.service.ts +545 -0
  450. package/registry/combos/express-drizzle/variants/workspaces/src/authz.middleware.ts +94 -0
  451. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +87 -0
  452. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-admin.ts +596 -0
  453. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +183 -0
  454. package/registry/combos/express-drizzle/variants/workspaces/src/rbac.defaults.ts +248 -0
  455. package/registry/combos/express-drizzle/variants/workspaces/src/rbac.repository.ts +698 -0
  456. package/registry/combos/express-drizzle/variants/workspaces/src/schema.ts +487 -0
  457. package/registry/combos/express-drizzle/variants/workspaces/src/seed.ts +173 -0
  458. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.repository.ts +254 -0
  459. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +123 -0
  460. package/registry/combos/express-drizzle/variants/workspaces/test/variant-hooks.ts +370 -0
  461. package/registry/combos/express-prisma/package.json +32 -0
  462. package/registry/combos/express-prisma/scripts/materialize.mjs +84 -0
  463. package/registry/combos/express-prisma/scripts/migrate.mjs +32 -0
  464. package/registry/combos/express-prisma/scripts/prove-cycle.mjs +30 -0
  465. package/registry/combos/express-prisma/scripts/seed.mjs +24 -0
  466. package/registry/combos/express-prisma/shared/prisma.config.ts +38 -0
  467. package/registry/combos/express-prisma/shared/src/ability.ts +44 -0
  468. package/registry/combos/express-prisma/shared/src/auth-core-error.middleware.ts +31 -0
  469. package/registry/combos/express-prisma/shared/src/auth.config.ts +63 -0
  470. package/registry/combos/express-prisma/shared/src/auth.middleware.ts +44 -0
  471. package/registry/combos/express-prisma/shared/src/auth.router.ts +269 -0
  472. package/registry/combos/express-prisma/shared/src/http-error.ts +18 -0
  473. package/registry/combos/express-prisma/shared/src/id.helper.ts +22 -0
  474. package/registry/combos/express-prisma/shared/src/key-provider.ts +52 -0
  475. package/registry/combos/express-prisma/shared/src/oauth.repository.ts +33 -0
  476. package/registry/combos/express-prisma/shared/src/openapi-fragment.ts +49 -0
  477. package/registry/combos/express-prisma/shared/src/openapi-spec.ts +487 -0
  478. package/registry/combos/express-prisma/shared/src/pagination.ts +30 -0
  479. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +29 -0
  480. package/registry/combos/express-prisma/shared/src/permission-cache.ts +191 -0
  481. package/registry/combos/express-prisma/shared/src/rate-limit.store.ts +21 -0
  482. package/registry/combos/express-prisma/shared/src/request-context.ts +35 -0
  483. package/registry/combos/express-prisma/shared/src/response-envelope.middleware.ts +46 -0
  484. package/registry/combos/express-prisma/shared/src/route-tiers.ts +148 -0
  485. package/registry/combos/express-prisma/shared/src/session.repository.ts +140 -0
  486. package/registry/combos/express-prisma/shared/src/two-factor.repository.ts +30 -0
  487. package/registry/combos/express-prisma/test/bootstrap.ts +45 -0
  488. package/registry/combos/express-prisma/test/harness.ts +187 -0
  489. package/registry/combos/express-prisma/test/prove-cycle.ts +793 -0
  490. package/registry/combos/express-prisma/tsconfig.json +21 -0
  491. package/registry/combos/express-prisma/variants/base/.env.example +25 -0
  492. package/registry/combos/express-prisma/variants/base/prisma/migrations/20260810165456_init/migration.sql +370 -0
  493. package/registry/combos/express-prisma/variants/base/prisma/migrations/20260811151048_add_is_active_field/migration.sql +2 -0
  494. package/registry/combos/express-prisma/variants/base/prisma/migrations/migration_lock.toml +3 -0
  495. package/registry/combos/express-prisma/variants/base/prisma/schema/simple-auth-kit.prisma +332 -0
  496. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +314 -0
  497. package/registry/combos/express-prisma/variants/base/src/audit-log.repository.ts +91 -0
  498. package/registry/combos/express-prisma/variants/base/src/auth.service.ts +521 -0
  499. package/registry/combos/express-prisma/variants/base/src/authz.middleware.ts +58 -0
  500. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +93 -0
  501. package/registry/combos/express-prisma/variants/base/src/openapi-admin.ts +569 -0
  502. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +195 -0
  503. package/registry/combos/express-prisma/variants/base/src/rbac.repository.ts +484 -0
  504. package/registry/combos/express-prisma/variants/base/src/seed.ts +115 -0
  505. package/registry/combos/express-prisma/variants/base/test/variant-hooks.ts +159 -0
  506. package/registry/combos/express-prisma/variants/workspaces/.env.example +30 -0
  507. package/registry/combos/express-prisma/variants/workspaces/prisma/migrations/20260810165502_init/migration.sql +443 -0
  508. package/registry/combos/express-prisma/variants/workspaces/prisma/migrations/20260811151051_add_is_active_field/migration.sql +2 -0
  509. package/registry/combos/express-prisma/variants/workspaces/prisma/migrations/migration_lock.toml +3 -0
  510. package/registry/combos/express-prisma/variants/workspaces/prisma/schema/simple-auth-kit.prisma +394 -0
  511. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +330 -0
  512. package/registry/combos/express-prisma/variants/workspaces/src/audit-log.repository.ts +102 -0
  513. package/registry/combos/express-prisma/variants/workspaces/src/auth.service.ts +542 -0
  514. package/registry/combos/express-prisma/variants/workspaces/src/authz.middleware.ts +94 -0
  515. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +99 -0
  516. package/registry/combos/express-prisma/variants/workspaces/src/openapi-admin.ts +601 -0
  517. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +183 -0
  518. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +222 -0
  519. package/registry/combos/express-prisma/variants/workspaces/src/rbac.repository.ts +527 -0
  520. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +147 -0
  521. package/registry/combos/express-prisma/variants/workspaces/src/workspace.repository.ts +188 -0
  522. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +123 -0
  523. package/registry/combos/express-prisma/variants/workspaces/test/variant-hooks.ts +358 -0
  524. package/registry/combos/nestjs-drizzle/package.json +36 -0
  525. package/registry/combos/nestjs-drizzle/scripts/materialize.mjs +104 -0
  526. package/registry/combos/nestjs-drizzle/scripts/migrate.mjs +34 -0
  527. package/registry/combos/nestjs-drizzle/scripts/prove-cycle.mjs +29 -0
  528. package/registry/combos/nestjs-drizzle/scripts/seed.mjs +24 -0
  529. package/registry/combos/nestjs-drizzle/shared/drizzle.config.ts +67 -0
  530. package/registry/combos/nestjs-drizzle/shared/src/ability.guard.ts +46 -0
  531. package/registry/combos/nestjs-drizzle/shared/src/ability.ts +44 -0
  532. package/registry/combos/nestjs-drizzle/shared/src/auth-core-error.filter.ts +32 -0
  533. package/registry/combos/nestjs-drizzle/shared/src/auth.config.ts +65 -0
  534. package/registry/combos/nestjs-drizzle/shared/src/auth.controller.ts +299 -0
  535. package/registry/combos/nestjs-drizzle/shared/src/auth.guard.ts +34 -0
  536. package/registry/combos/nestjs-drizzle/shared/src/db.ts +12 -0
  537. package/registry/combos/nestjs-drizzle/shared/src/dto/auth.dto.ts +429 -0
  538. package/registry/combos/nestjs-drizzle/shared/src/id.helper.ts +22 -0
  539. package/registry/combos/nestjs-drizzle/shared/src/key-provider.ts +51 -0
  540. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +36 -0
  541. package/registry/combos/nestjs-drizzle/shared/src/pagination.ts +30 -0
  542. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +29 -0
  543. package/registry/combos/nestjs-drizzle/shared/src/permission-cache.ts +196 -0
  544. package/registry/combos/nestjs-drizzle/shared/src/rate-limit.store.ts +26 -0
  545. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +25 -0
  546. package/registry/combos/nestjs-drizzle/shared/src/response.interceptor.ts +48 -0
  547. package/registry/combos/nestjs-drizzle/shared/src/route-tiers.ts +144 -0
  548. package/registry/combos/nestjs-drizzle/shared/src/session.repository.ts +132 -0
  549. package/registry/combos/nestjs-drizzle/shared/src/two-factor.repository.ts +35 -0
  550. package/registry/combos/nestjs-drizzle/test/bootstrap.ts +49 -0
  551. package/registry/combos/nestjs-drizzle/test/harness.ts +187 -0
  552. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +795 -0
  553. package/registry/combos/nestjs-drizzle/tsconfig.json +20 -0
  554. package/registry/combos/nestjs-drizzle/variants/base/.env.example +25 -0
  555. package/registry/combos/nestjs-drizzle/variants/base/drizzle/0000_init.sql +219 -0
  556. package/registry/combos/nestjs-drizzle/variants/base/drizzle/0001_add_is_active_field.sql +1 -0
  557. package/registry/combos/nestjs-drizzle/variants/base/drizzle/0002_colossal_true_believers.sql +3 -0
  558. package/registry/combos/nestjs-drizzle/variants/base/drizzle/meta/0000_snapshot.json +1861 -0
  559. package/registry/combos/nestjs-drizzle/variants/base/drizzle/meta/0001_snapshot.json +1868 -0
  560. package/registry/combos/nestjs-drizzle/variants/base/drizzle/meta/0002_snapshot.json +1887 -0
  561. package/registry/combos/nestjs-drizzle/variants/base/drizzle/meta/_journal.json +27 -0
  562. package/registry/combos/nestjs-drizzle/variants/base/src/admin.controller.ts +361 -0
  563. package/registry/combos/nestjs-drizzle/variants/base/src/audit-log.repository.ts +90 -0
  564. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +83 -0
  565. package/registry/combos/nestjs-drizzle/variants/base/src/auth.service.ts +544 -0
  566. package/registry/combos/nestjs-drizzle/variants/base/src/authz.guard.ts +53 -0
  567. package/registry/combos/nestjs-drizzle/variants/base/src/dto/admin.dto.ts +130 -0
  568. package/registry/combos/nestjs-drizzle/variants/base/src/rbac.defaults.ts +218 -0
  569. package/registry/combos/nestjs-drizzle/variants/base/src/rbac.repository.ts +633 -0
  570. package/registry/combos/nestjs-drizzle/variants/base/src/schema.ts +427 -0
  571. package/registry/combos/nestjs-drizzle/variants/base/src/seed.ts +138 -0
  572. package/registry/combos/nestjs-drizzle/variants/base/test/variant-hooks.ts +179 -0
  573. package/registry/combos/nestjs-drizzle/variants/workspaces/.env.example +30 -0
  574. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/0000_init.sql +251 -0
  575. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/0001_add_is_active_field.sql +1 -0
  576. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/0002_calm_tyger_tiger.sql +3 -0
  577. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/meta/0000_snapshot.json +2155 -0
  578. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/meta/0001_snapshot.json +2162 -0
  579. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/meta/0002_snapshot.json +2181 -0
  580. package/registry/combos/nestjs-drizzle/variants/workspaces/drizzle/meta/_journal.json +27 -0
  581. package/registry/combos/nestjs-drizzle/variants/workspaces/src/admin.controller.ts +383 -0
  582. package/registry/combos/nestjs-drizzle/variants/workspaces/src/audit-log.repository.ts +102 -0
  583. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +87 -0
  584. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +553 -0
  585. package/registry/combos/nestjs-drizzle/variants/workspaces/src/authz.guard.ts +87 -0
  586. package/registry/combos/nestjs-drizzle/variants/workspaces/src/dto/admin.dto.ts +137 -0
  587. package/registry/combos/nestjs-drizzle/variants/workspaces/src/dto/workspace.dto.ts +60 -0
  588. package/registry/combos/nestjs-drizzle/variants/workspaces/src/rbac.defaults.ts +248 -0
  589. package/registry/combos/nestjs-drizzle/variants/workspaces/src/rbac.repository.ts +719 -0
  590. package/registry/combos/nestjs-drizzle/variants/workspaces/src/schema.ts +489 -0
  591. package/registry/combos/nestjs-drizzle/variants/workspaces/src/seed.ts +174 -0
  592. package/registry/combos/nestjs-drizzle/variants/workspaces/src/workspace.controller.ts +123 -0
  593. package/registry/combos/nestjs-drizzle/variants/workspaces/src/workspace.repository.ts +255 -0
  594. package/registry/combos/nestjs-drizzle/variants/workspaces/test/variant-hooks.ts +370 -0
  595. package/registry/combos/nestjs-prisma/package.json +41 -0
  596. package/registry/combos/nestjs-prisma/scripts/materialize.mjs +84 -0
  597. package/registry/combos/nestjs-prisma/scripts/migrate.mjs +32 -0
  598. package/registry/combos/nestjs-prisma/scripts/prove-cycle.mjs +30 -0
  599. package/registry/combos/nestjs-prisma/scripts/seed.mjs +24 -0
  600. package/registry/combos/nestjs-prisma/shared/prisma.config.ts +38 -0
  601. package/registry/combos/nestjs-prisma/shared/src/ability.guard.ts +29 -0
  602. package/registry/combos/nestjs-prisma/shared/src/ability.ts +23 -0
  603. package/registry/combos/nestjs-prisma/shared/src/auth-core-error.filter.ts +32 -0
  604. package/registry/combos/nestjs-prisma/shared/src/auth.config.ts +71 -0
  605. package/registry/combos/nestjs-prisma/shared/src/auth.controller.ts +303 -0
  606. package/registry/combos/nestjs-prisma/shared/src/auth.guard.ts +31 -0
  607. package/registry/combos/nestjs-prisma/shared/src/dto/auth.dto.ts +438 -0
  608. package/registry/combos/nestjs-prisma/shared/src/id.helper.ts +22 -0
  609. package/registry/combos/nestjs-prisma/shared/src/key-provider.ts +42 -0
  610. package/registry/combos/nestjs-prisma/shared/src/oauth.repository.ts +32 -0
  611. package/registry/combos/nestjs-prisma/shared/src/pagination.ts +30 -0
  612. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +29 -0
  613. package/registry/combos/nestjs-prisma/shared/src/permission-cache.ts +140 -0
  614. package/registry/combos/nestjs-prisma/shared/src/rate-limit.store.ts +26 -0
  615. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +19 -0
  616. package/registry/combos/nestjs-prisma/shared/src/response.interceptor.ts +48 -0
  617. package/registry/combos/nestjs-prisma/shared/src/route-tiers.ts +102 -0
  618. package/registry/combos/nestjs-prisma/shared/src/session.repository.ts +129 -0
  619. package/registry/combos/nestjs-prisma/shared/src/two-factor.repository.ts +29 -0
  620. package/registry/combos/nestjs-prisma/test/bootstrap.ts +54 -0
  621. package/registry/combos/nestjs-prisma/test/harness.ts +186 -0
  622. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +795 -0
  623. package/registry/combos/nestjs-prisma/tsconfig.json +21 -0
  624. package/registry/combos/nestjs-prisma/variants/base/.env.example +31 -0
  625. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810084937_init/migration.sql +269 -0
  626. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810122546_add_profile_audit_soft_delete/migration.sql +96 -0
  627. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810123620_add_display_name/migration.sql +3 -0
  628. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810131421_drop_signing_keys/migration.sql +3 -0
  629. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810161515_bigint_ids/migration.sql +305 -0
  630. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260810162120_drop_name_unique/migration.sql +6 -0
  631. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260811145755_add_is_active_field/migration.sql +2 -0
  632. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260811183529_add_countries_languages_customers/migration.sql +146 -0
  633. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/20260812121128_add_user_profile_details/migration.sql +4 -0
  634. package/registry/combos/nestjs-prisma/variants/base/prisma/migrations/migration_lock.toml +3 -0
  635. package/registry/combos/nestjs-prisma/variants/base/prisma/schema/simple-auth-kit.prisma +437 -0
  636. package/registry/combos/nestjs-prisma/variants/base/src/admin.controller.ts +684 -0
  637. package/registry/combos/nestjs-prisma/variants/base/src/audit-log.gateway.ts +51 -0
  638. package/registry/combos/nestjs-prisma/variants/base/src/audit-log.repository.ts +111 -0
  639. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +87 -0
  640. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +618 -0
  641. package/registry/combos/nestjs-prisma/variants/base/src/authz.guard.ts +34 -0
  642. package/registry/combos/nestjs-prisma/variants/base/src/country.repository.ts +149 -0
  643. package/registry/combos/nestjs-prisma/variants/base/src/customer.repository.ts +169 -0
  644. package/registry/combos/nestjs-prisma/variants/base/src/docs.ts +60 -0
  645. package/registry/combos/nestjs-prisma/variants/base/src/dto/admin.dto.ts +130 -0
  646. package/registry/combos/nestjs-prisma/variants/base/src/dto/country.dto.ts +120 -0
  647. package/registry/combos/nestjs-prisma/variants/base/src/dto/customer.dto.ts +147 -0
  648. package/registry/combos/nestjs-prisma/variants/base/src/dto/language.dto.ts +93 -0
  649. package/registry/combos/nestjs-prisma/variants/base/src/language.repository.ts +135 -0
  650. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +193 -0
  651. package/registry/combos/nestjs-prisma/variants/base/src/rbac.repository.ts +514 -0
  652. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +89 -0
  653. package/registry/combos/nestjs-prisma/variants/base/test/variant-hooks.ts +135 -0
  654. package/registry/combos/nestjs-prisma/variants/workspaces/.env.example +30 -0
  655. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810084938_init/migration.sql +308 -0
  656. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810123450_add_profile_audit_soft_delete/migration.sql +96 -0
  657. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810123632_add_display_name/migration.sql +3 -0
  658. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810124243_workspace_audit_soft_delete/migration.sql +25 -0
  659. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810131432_drop_signing_keys/migration.sql +3 -0
  660. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810161551_bigint_ids/migration.sql +393 -0
  661. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260810162131_drop_name_unique/migration.sql +3 -0
  662. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260811145805_add_is_active_field/migration.sql +2 -0
  663. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/20260813120000_add_countries_languages_customers/migration.sql +158 -0
  664. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/migrations/migration_lock.toml +3 -0
  665. package/registry/combos/nestjs-prisma/variants/workspaces/prisma/schema/simple-auth-kit.prisma +521 -0
  666. package/registry/combos/nestjs-prisma/variants/workspaces/src/admin.controller.ts +733 -0
  667. package/registry/combos/nestjs-prisma/variants/workspaces/src/audit-log.repository.ts +98 -0
  668. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +83 -0
  669. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +509 -0
  670. package/registry/combos/nestjs-prisma/variants/workspaces/src/authz.guard.ts +69 -0
  671. package/registry/combos/nestjs-prisma/variants/workspaces/src/country.repository.ts +158 -0
  672. package/registry/combos/nestjs-prisma/variants/workspaces/src/customer.repository.ts +179 -0
  673. package/registry/combos/nestjs-prisma/variants/workspaces/src/dto/admin.dto.ts +128 -0
  674. package/registry/combos/nestjs-prisma/variants/workspaces/src/dto/country.dto.ts +120 -0
  675. package/registry/combos/nestjs-prisma/variants/workspaces/src/dto/customer.dto.ts +147 -0
  676. package/registry/combos/nestjs-prisma/variants/workspaces/src/dto/language.dto.ts +93 -0
  677. package/registry/combos/nestjs-prisma/variants/workspaces/src/dto/workspace.dto.ts +60 -0
  678. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +143 -0
  679. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +211 -0
  680. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.repository.ts +505 -0
  681. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +122 -0
  682. package/registry/combos/nestjs-prisma/variants/workspaces/src/workspace.controller.ts +111 -0
  683. package/registry/combos/nestjs-prisma/variants/workspaces/src/workspace.repository.ts +177 -0
  684. package/registry/combos/nestjs-prisma/variants/workspaces/test/variant-hooks.ts +333 -0
  685. package/registry/core/crypto.ts +43 -0
  686. package/registry/core/oauth.ts +159 -0
  687. package/registry/core/package.json +20 -0
  688. package/registry/core/password-reset.ts +42 -0
  689. package/registry/core/rate-limit.ts +33 -0
  690. package/registry/core/rbac.ts +4 -0
  691. package/registry/core/session-policy.ts +96 -0
  692. package/registry/core/test/crypto.test.ts +25 -0
  693. package/registry/core/test/oauth.test.ts +213 -0
  694. package/registry/core/test/password-reset.test.ts +77 -0
  695. package/registry/core/test/rate-limit.test.ts +41 -0
  696. package/registry/core/test/rbac.test.ts +20 -0
  697. package/registry/core/test/session-policy.test.ts +209 -0
  698. package/registry/core/test/test-keys.ts +6 -0
  699. package/registry/core/test/token-service.test.ts +81 -0
  700. package/registry/core/test/two-factor.test.ts +72 -0
  701. package/registry/core/token-service.ts +143 -0
  702. package/registry/core/tsconfig.json +5 -0
  703. package/registry/core/two-factor.ts +108 -0
  704. package/registry/core/types.ts +127 -0
  705. package/registry/mobile-apps/README.md +65 -0
  706. package/registry/mobile-apps/bare-rn/shared/.bundle/config +2 -0
  707. package/registry/mobile-apps/bare-rn/shared/.eslintrc.js +4 -0
  708. package/registry/mobile-apps/bare-rn/shared/.prettierrc.js +5 -0
  709. package/registry/mobile-apps/bare-rn/shared/.watchmanconfig +1 -0
  710. package/registry/mobile-apps/bare-rn/shared/App.tsx +28 -0
  711. package/registry/mobile-apps/bare-rn/shared/Gemfile +17 -0
  712. package/registry/mobile-apps/bare-rn/shared/README.md +97 -0
  713. package/registry/mobile-apps/bare-rn/shared/__mocks__/reactNativeConfigMock.js +3 -0
  714. package/registry/mobile-apps/bare-rn/shared/__mocks__/styleMock.js +1 -0
  715. package/registry/mobile-apps/bare-rn/shared/__tests__/App.test.tsx +13 -0
  716. package/registry/mobile-apps/bare-rn/shared/android/app/build.gradle +121 -0
  717. package/registry/mobile-apps/bare-rn/shared/android/app/debug.keystore +0 -0
  718. package/registry/mobile-apps/bare-rn/shared/android/app/proguard-rules.pro +10 -0
  719. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/AndroidManifest.xml +27 -0
  720. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/java/com/mobilebarern/MainActivity.kt +22 -0
  721. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/java/com/mobilebarern/MainApplication.kt +27 -0
  722. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/drawable/rn_edit_text_material.xml +37 -0
  723. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
  724. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
  725. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
  726. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
  727. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
  728. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
  729. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
  730. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
  731. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
  732. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
  733. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/values/strings.xml +3 -0
  734. package/registry/mobile-apps/bare-rn/shared/android/app/src/main/res/values/styles.xml +9 -0
  735. package/registry/mobile-apps/bare-rn/shared/android/build.gradle +21 -0
  736. package/registry/mobile-apps/bare-rn/shared/android/gradle/wrapper/gradle-wrapper.jar +0 -0
  737. package/registry/mobile-apps/bare-rn/shared/android/gradle/wrapper/gradle-wrapper.properties +7 -0
  738. package/registry/mobile-apps/bare-rn/shared/android/gradle.properties +44 -0
  739. package/registry/mobile-apps/bare-rn/shared/android/gradlew +248 -0
  740. package/registry/mobile-apps/bare-rn/shared/android/gradlew.bat +98 -0
  741. package/registry/mobile-apps/bare-rn/shared/android/settings.gradle +6 -0
  742. package/registry/mobile-apps/bare-rn/shared/app.json +4 -0
  743. package/registry/mobile-apps/bare-rn/shared/babel.config.js +6 -0
  744. package/registry/mobile-apps/bare-rn/shared/global.css +2 -0
  745. package/registry/mobile-apps/bare-rn/shared/index.js +10 -0
  746. package/registry/mobile-apps/bare-rn/shared/ios/.xcode.env +11 -0
  747. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/AppDelegate.swift +48 -0
  748. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/Images.xcassets/AppIcon.appiconset/Contents.json +53 -0
  749. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/Images.xcassets/Contents.json +6 -0
  750. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/Info.plist +59 -0
  751. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/LaunchScreen.storyboard +47 -0
  752. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn/PrivacyInfo.xcprivacy +37 -0
  753. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn.xcodeproj/project.pbxproj +475 -0
  754. package/registry/mobile-apps/bare-rn/shared/ios/MobileBareRn.xcodeproj/xcshareddata/xcschemes/MobileBareRn.xcscheme +88 -0
  755. package/registry/mobile-apps/bare-rn/shared/ios/Podfile +34 -0
  756. package/registry/mobile-apps/bare-rn/shared/jest.config.js +17 -0
  757. package/registry/mobile-apps/bare-rn/shared/metro.config.js +55 -0
  758. package/registry/mobile-apps/bare-rn/shared/nativewind-env.d.ts +1 -0
  759. package/registry/mobile-apps/bare-rn/shared/src/api/tokenStorage.ts +29 -0
  760. package/registry/mobile-apps/bare-rn/shared/src/components/FormInput.tsx +21 -0
  761. package/registry/mobile-apps/bare-rn/shared/src/components/PrimaryButton.tsx +47 -0
  762. package/registry/mobile-apps/bare-rn/shared/src/screens/LoginScreen.tsx +58 -0
  763. package/registry/mobile-apps/bare-rn/shared/src/screens/SessionsScreen.tsx +77 -0
  764. package/registry/mobile-apps/bare-rn/shared/src/screens/SignupScreen.tsx +54 -0
  765. package/registry/mobile-apps/bare-rn/shared/src/screens/TwoFactorScreen.tsx +59 -0
  766. package/registry/mobile-apps/bare-rn/shared/tailwind.config.js +9 -0
  767. package/registry/mobile-apps/bare-rn/shared/tsconfig.json +17 -0
  768. package/registry/mobile-apps/bare-rn/variants/base/package.json +58 -0
  769. package/registry/mobile-apps/bare-rn/variants/base/src/api/authClient.ts +14 -0
  770. package/registry/mobile-apps/bare-rn/variants/base/src/navigation/RootNavigator.tsx +58 -0
  771. package/registry/mobile-apps/bare-rn/variants/base/src/navigation/types.ts +15 -0
  772. package/registry/mobile-apps/bare-rn/variants/base/src/screens/HomeScreen.tsx +64 -0
  773. package/registry/mobile-apps/bare-rn/variants/base/src/store/authStore.ts +143 -0
  774. package/registry/mobile-apps/bare-rn/variants/workspaces/package.json +58 -0
  775. package/registry/mobile-apps/bare-rn/variants/workspaces/src/api/authClient.ts +22 -0
  776. package/registry/mobile-apps/bare-rn/variants/workspaces/src/navigation/RootNavigator.tsx +72 -0
  777. package/registry/mobile-apps/bare-rn/variants/workspaces/src/navigation/types.ts +18 -0
  778. package/registry/mobile-apps/bare-rn/variants/workspaces/src/screens/HomeScreen.tsx +85 -0
  779. package/registry/mobile-apps/bare-rn/variants/workspaces/src/screens/WorkspaceSelectScreen.tsx +165 -0
  780. package/registry/mobile-apps/bare-rn/variants/workspaces/src/store/authStore.ts +177 -0
  781. package/registry/mobile-apps/bare-rn/variants/workspaces/src/store/workspaceStore.ts +90 -0
  782. package/registry/mobile-apps/bare-rn/variants/workspaces/src/workspace/activeWorkspace.ts +55 -0
  783. package/registry/mobile-apps/expo/shared/App.tsx +15 -0
  784. package/registry/mobile-apps/expo/shared/assets/android-icon-background.png +0 -0
  785. package/registry/mobile-apps/expo/shared/assets/android-icon-foreground.png +0 -0
  786. package/registry/mobile-apps/expo/shared/assets/android-icon-monochrome.png +0 -0
  787. package/registry/mobile-apps/expo/shared/assets/favicon.png +0 -0
  788. package/registry/mobile-apps/expo/shared/assets/icon.png +0 -0
  789. package/registry/mobile-apps/expo/shared/assets/splash-icon.png +0 -0
  790. package/registry/mobile-apps/expo/shared/babel.config.js +6 -0
  791. package/registry/mobile-apps/expo/shared/global.css +2 -0
  792. package/registry/mobile-apps/expo/shared/index.ts +8 -0
  793. package/registry/mobile-apps/expo/shared/metro.config.js +6 -0
  794. package/registry/mobile-apps/expo/shared/nativewind-env.d.ts +3 -0
  795. package/registry/mobile-apps/expo/shared/postcss.config.mjs +5 -0
  796. package/registry/mobile-apps/expo/shared/src/screens/LoginScreen.tsx +81 -0
  797. package/registry/mobile-apps/expo/shared/src/screens/SessionsScreen.tsx +77 -0
  798. package/registry/mobile-apps/expo/shared/src/screens/SignupScreen.tsx +77 -0
  799. package/registry/mobile-apps/expo/shared/src/screens/TwoFactorScreen.tsx +59 -0
  800. package/registry/mobile-apps/expo/shared/src/storage/asyncStorageTokenStorage.ts +32 -0
  801. package/registry/mobile-apps/expo/shared/tsconfig.json +6 -0
  802. package/registry/mobile-apps/expo/variants/base/app.json +25 -0
  803. package/registry/mobile-apps/expo/variants/base/package.json +35 -0
  804. package/registry/mobile-apps/expo/variants/base/src/api/authClient.ts +17 -0
  805. package/registry/mobile-apps/expo/variants/base/src/navigation/RootNavigator.tsx +58 -0
  806. package/registry/mobile-apps/expo/variants/base/src/navigation/types.ts +10 -0
  807. package/registry/mobile-apps/expo/variants/base/src/screens/HomeScreen.tsx +75 -0
  808. package/registry/mobile-apps/expo/variants/base/src/store/authStore.ts +146 -0
  809. package/registry/mobile-apps/expo/variants/workspaces/app.json +25 -0
  810. package/registry/mobile-apps/expo/variants/workspaces/package.json +35 -0
  811. package/registry/mobile-apps/expo/variants/workspaces/src/api/authClient.ts +21 -0
  812. package/registry/mobile-apps/expo/variants/workspaces/src/navigation/RootNavigator.tsx +82 -0
  813. package/registry/mobile-apps/expo/variants/workspaces/src/navigation/types.ts +20 -0
  814. package/registry/mobile-apps/expo/variants/workspaces/src/screens/HomeScreen.tsx +94 -0
  815. package/registry/mobile-apps/expo/variants/workspaces/src/screens/WorkspaceSelectScreen.tsx +175 -0
  816. package/registry/mobile-apps/expo/variants/workspaces/src/store/authStore.ts +179 -0
  817. package/registry/mobile-apps/expo/variants/workspaces/src/store/workspaceStore.ts +90 -0
  818. package/registry/mobile-apps/expo/variants/workspaces/src/workspace/activeWorkspace.ts +55 -0
  819. package/registry.json +263 -0
  820. package/simple-auth-kit.ts +715 -0
@@ -0,0 +1,111 @@
1
+ import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Inject, Param, Post, Put, Req, UseGuards } from "@nestjs/common";
2
+ import { ApiBearerAuth, ApiBody, ApiHeader, ApiOperation, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger";
3
+ import type { Request } from "express";
4
+ import { AbilityGuard } from "./ability.guard.js";
5
+ import { AuthGuard } from "./auth.guard.js";
6
+ import { WORKSPACE_HEADER, WorkspaceGuard } from "./authz.guard.js";
7
+ import { Authenticated, CheckAbility } from "./route-tiers.js";
8
+ import { OkResponseDto } from "./dto/auth.dto.js";
9
+ import { AddMemberDto, CreateWorkspaceDto, MemberRolesResponseDto, MembershipSummaryDto, SetMemberRolesDto, WorkspaceSummaryDto } from "./dto/workspace.dto.js";
10
+ import { RbacRepository } from "./rbac.repository.js";
11
+ import { WorkspaceRepository } from "./workspace.repository.js";
12
+
13
+ function requireString(value: unknown, field: string): string {
14
+ if (typeof value !== "string" || value.length === 0) throw new BadRequestException(`${field} is required`);
15
+ return value;
16
+ }
17
+
18
+ function requireStringArray(value: unknown, field: string): string[] {
19
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) throw new BadRequestException(`${field} must be an array of strings`);
20
+ return value as string[];
21
+ }
22
+
23
+ const WORKSPACE_HEADER_DOC = {
24
+ name: WORKSPACE_HEADER,
25
+ required: true,
26
+ description: "The workspace this request acts in. The caller must be a member of it.",
27
+ };
28
+
29
+ // Every route below that acts *inside* a workspace names it with the `X-Workspace-Id` header
30
+ // rather than a path segment. `POST /workspaces` and `GET /workspaces` are tier 2
31
+ // (`@Authenticated()`, no permission slug) since they're outside any workspace — any
32
+ // authenticated user may create one or list their own. `GET /workspaces/members` is gated on
33
+ // membership itself, proven by `WorkspaceGuard`. The three that mutate membership are
34
+ // permission-gated — see `rbac.defaults.ts` for the catalog.
35
+ @ApiTags("workspaces")
36
+ @Controller("workspaces")
37
+ @ApiBearerAuth()
38
+ export class WorkspaceController {
39
+ constructor(
40
+ @Inject(WorkspaceRepository) private readonly workspaces: WorkspaceRepository,
41
+ @Inject(RbacRepository) private readonly rbac: RbacRepository,
42
+ ) {}
43
+
44
+ @Post()
45
+ @Authenticated()
46
+ @UseGuards(AuthGuard)
47
+ @ApiOperation({ summary: "Create a workspace", description: "The creator becomes its first member, with the admin role." })
48
+ @ApiBody({ type: CreateWorkspaceDto })
49
+ @ApiResponse({ status: 201, type: WorkspaceSummaryDto })
50
+ async create(@Body() body: Record<string, unknown>, @Req() req: Request) {
51
+ return this.workspaces.create(req.auth!.sub, requireString(body.name, "name"));
52
+ }
53
+
54
+ @Get()
55
+ @Authenticated()
56
+ @UseGuards(AuthGuard)
57
+ @ApiOperation({ summary: "List the workspaces the current user belongs to, with their roles in each" })
58
+ @ApiResponse({ status: 200, type: [WorkspaceSummaryDto] })
59
+ async listMine(@Req() req: Request) {
60
+ return this.workspaces.listForUser(req.auth!.sub);
61
+ }
62
+
63
+ @Get("members")
64
+ @Authenticated()
65
+ @UseGuards(AuthGuard, WorkspaceGuard)
66
+ @ApiHeader(WORKSPACE_HEADER_DOC)
67
+ @ApiOperation({ summary: "List the members of the named workspace" })
68
+ @ApiResponse({ status: 200, type: [MembershipSummaryDto] })
69
+ async listMembers(@Req() req: Request) {
70
+ return this.workspaces.listMembers(req.authz!.workspaceId);
71
+ }
72
+
73
+ @Post("members")
74
+ @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
75
+ @CheckAbility("members:manage")
76
+ @ApiHeader(WORKSPACE_HEADER_DOC)
77
+ @ApiOperation({ summary: "[admin] Add an existing user to the named workspace" })
78
+ @ApiBody({ type: AddMemberDto })
79
+ @ApiResponse({ status: 201, type: MembershipSummaryDto })
80
+ async addMember(@Body() body: Record<string, unknown>, @Req() req: Request) {
81
+ // No `roles` in the body means "whatever this workspace flags as the default role".
82
+ const roles = body.roles === undefined ? undefined : requireStringArray(body.roles, "roles");
83
+ return this.workspaces.addMember(req.authz!.workspaceId, requireString(body.email, "email"), roles);
84
+ }
85
+
86
+ @Put("members/:memberId/roles")
87
+ @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
88
+ @CheckAbility("roles:assign")
89
+ @ApiHeader(WORKSPACE_HEADER_DOC)
90
+ @ApiOperation({ summary: "[admin] Replace a member's roles within the named workspace" })
91
+ @ApiParam({ name: "memberId" })
92
+ @ApiBody({ type: SetMemberRolesDto })
93
+ @ApiResponse({ status: 200, type: MemberRolesResponseDto })
94
+ async setMemberRoles(@Param("memberId") memberId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
95
+ if (memberId === req.authz!.memberId) throw new ForbiddenException("cannot change your own roles");
96
+ return this.rbac.setMemberRoles(req.authz!.workspaceId, memberId, requireStringArray(body.roles, "roles"));
97
+ }
98
+
99
+ @Delete("members/:memberId")
100
+ @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
101
+ @CheckAbility("members:manage")
102
+ @ApiHeader(WORKSPACE_HEADER_DOC)
103
+ @ApiOperation({ summary: "[admin] Remove a member from the named workspace", description: "Their direct permission grants go with the membership." })
104
+ @ApiParam({ name: "memberId" })
105
+ @ApiResponse({ status: 200, type: OkResponseDto })
106
+ async removeMember(@Param("memberId") memberId: string, @Req() req: Request) {
107
+ if (memberId === req.authz!.memberId) throw new ForbiddenException("cannot remove yourself from a workspace you administer");
108
+ await this.workspaces.removeMember(req.authz!.workspaceId, memberId);
109
+ return { ok: true };
110
+ }
111
+ }
@@ -0,0 +1,177 @@
1
+ import { ConflictException, Inject, Injectable, NotFoundException } from "@nestjs/common";
2
+ import { PrismaClient } from "../generated/prisma/client.js";
3
+ import { provisionDefaultRoles, WORKSPACE_CREATOR_ROLES } from "./rbac.defaults.js";
4
+ import { RbacRepository } from "./rbac.repository.js";
5
+ import { toId } from "./id.helper.js";
6
+
7
+ export interface WorkspaceSummary {
8
+ id: string;
9
+ name: string;
10
+ createdAt: string;
11
+ /** The calling user's role slugs in this workspace. */
12
+ roles: string[];
13
+ }
14
+
15
+ export interface MembershipSummary {
16
+ memberId: string;
17
+ userId: string;
18
+ email: string;
19
+ roles: string[];
20
+ createdAt: string;
21
+ }
22
+
23
+ @Injectable()
24
+ export class WorkspaceRepository {
25
+ constructor(
26
+ @Inject(PrismaClient) private readonly prisma: PrismaClient,
27
+ @Inject(RbacRepository) private readonly rbac: RbacRepository,
28
+ ) {}
29
+
30
+ // Creates a workspace, its creator's admin membership, and that workspace's default roles all
31
+ // in one transaction, so a workspace is never left without an administrator. `Role` is unique
32
+ // per `[workspaceId, slug]`, so a new workspace genuinely has no `Role` rows without this step.
33
+ async create(userId: string, name: string): Promise<WorkspaceSummary> {
34
+ const userIdBig = toId(userId);
35
+ const workspace = await this.prisma.$transaction(async (tx) => {
36
+ const created = await tx.workspace.create({ data: { name, createdBy: userIdBig, updatedBy: userIdBig } });
37
+ await provisionDefaultRoles(tx, created.id);
38
+ const roles = await tx.role.findMany({ where: { workspaceId: created.id, slug: { in: WORKSPACE_CREATOR_ROLES } }, select: { id: true } });
39
+ await tx.workspaceMember.create({
40
+ data: { workspaceId: created.id, userId: userIdBig, roles: { create: roles.map((role) => ({ roleId: role.id })) } },
41
+ });
42
+ return created;
43
+ });
44
+ return {
45
+ id: workspace.id.toString(),
46
+ name: workspace.name,
47
+ createdAt: workspace.createdAt.toISOString(),
48
+ roles: [...WORKSPACE_CREATOR_ROLES].sort(),
49
+ };
50
+ }
51
+
52
+ async listForUser(userId: string): Promise<WorkspaceSummary[]> {
53
+ const memberships = await this.prisma.workspaceMember.findMany({
54
+ where: { userId: toId(userId), workspace: { isDeleted: false, isActive: true } },
55
+ include: { workspace: true, roles: { select: { role: { select: { slug: true } } } } },
56
+ orderBy: { createdAt: "asc" },
57
+ });
58
+ return memberships.map((m) => ({
59
+ id: m.workspace.id.toString(),
60
+ name: m.workspace.name,
61
+ createdAt: m.workspace.createdAt.toISOString(),
62
+ roles: m.roles.map((r) => r.role.slug).sort(),
63
+ }));
64
+ }
65
+
66
+ async listMembers(workspaceId: string): Promise<MembershipSummary[]> {
67
+ const rows = await this.prisma.workspaceMember.findMany({
68
+ where: { workspaceId: toId(workspaceId), user: { isDeleted: false } },
69
+ include: { user: true, roles: { select: { role: { select: { slug: true } } } } },
70
+ orderBy: { createdAt: "asc" },
71
+ });
72
+ return rows.map((row) => ({
73
+ memberId: row.id.toString(),
74
+ userId: row.userId.toString(),
75
+ email: row.user.email,
76
+ roles: row.roles.map((r) => r.role.slug).sort(),
77
+ createdAt: row.createdAt.toISOString(),
78
+ }));
79
+ }
80
+
81
+ private async resolveMemberRoles(workspaceIdBig: bigint, roleSlugs?: string[]) {
82
+ const roles = await this.prisma.role.findMany({
83
+ where: roleSlugs
84
+ ? { workspaceId: workspaceIdBig, slug: { in: roleSlugs }, isDeleted: false }
85
+ : { workspaceId: workspaceIdBig, isDefault: true, isActive: true, isDeleted: false },
86
+ select: { id: true, slug: true },
87
+ });
88
+ const unknown = (roleSlugs ?? []).filter((slug) => !roles.some((role) => role.slug === slug));
89
+ if (unknown.length) throw new NotFoundException(`role(s) not defined in this workspace: ${unknown.join(", ")}`);
90
+ return roles;
91
+ }
92
+
93
+ // Adds an existing user by email — there is no invite/email flow in this library. With no
94
+ // roles named, the new membership gets whichever of this workspace's roles are flagged `isDefault`.
95
+ async addMember(workspaceId: string, email: string, roleSlugs?: string[]): Promise<MembershipSummary> {
96
+ const workspaceIdBig = toId(workspaceId);
97
+ const user = await this.prisma.user.findUnique({ where: { email, isDeleted: false } });
98
+ if (!user) throw new NotFoundException("no user with that email");
99
+
100
+ const existing = await this.prisma.workspaceMember.findUnique({ where: { userId_workspaceId: { userId: user.id, workspaceId: workspaceIdBig } } });
101
+ if (existing) throw new ConflictException("already a member of this workspace");
102
+
103
+ const roles = await this.resolveMemberRoles(workspaceIdBig, roleSlugs);
104
+ const member = await this.prisma.workspaceMember.create({
105
+ data: { workspaceId: workspaceIdBig, userId: user.id, roles: { create: roles.map((role) => ({ roleId: role.id })) } },
106
+ });
107
+ return {
108
+ memberId: member.id.toString(),
109
+ userId: user.id.toString(),
110
+ email: user.email,
111
+ roles: roles.map((role) => role.slug).sort(),
112
+ createdAt: member.createdAt.toISOString(),
113
+ };
114
+ }
115
+
116
+ /**
117
+ * An administrator provisioning a brand-new account and adding it to this workspace in one
118
+ * step — as distinct from `addMember`, which only ever attaches an account that already
119
+ * exists. No invite/email flow in this library either way: the password is usable immediately.
120
+ */
121
+ async createMember(
122
+ workspaceId: string,
123
+ input: {
124
+ email: string;
125
+ passwordHash: string;
126
+ firstName?: string;
127
+ lastName?: string;
128
+ displayName?: string;
129
+ phone?: string;
130
+ username?: string;
131
+ photo?: string;
132
+ roles?: string[];
133
+ },
134
+ actorUserId: string | null,
135
+ ): Promise<MembershipSummary> {
136
+ const workspaceIdBig = toId(workspaceId);
137
+ const existing = await this.prisma.user.findUnique({ where: { email: input.email } });
138
+ if (existing) throw new ConflictException("email already registered");
139
+
140
+ const roles = await this.resolveMemberRoles(workspaceIdBig, input.roles);
141
+ const user = await this.prisma.user.create({
142
+ data: {
143
+ email: input.email,
144
+ passwordHash: input.passwordHash,
145
+ firstName: input.firstName,
146
+ lastName: input.lastName,
147
+ displayName: input.displayName,
148
+ phone: input.phone,
149
+ username: input.username,
150
+ photo: input.photo,
151
+ createdBy: actorUserId ? toId(actorUserId) : null,
152
+ },
153
+ });
154
+ const member = await this.prisma.workspaceMember.create({
155
+ data: { workspaceId: workspaceIdBig, userId: user.id, roles: { create: roles.map((role) => ({ roleId: role.id })) } },
156
+ });
157
+ return {
158
+ memberId: member.id.toString(),
159
+ userId: user.id.toString(),
160
+ email: user.email,
161
+ roles: roles.map((role) => role.slug).sort(),
162
+ createdAt: member.createdAt.toISOString(),
163
+ };
164
+ }
165
+
166
+ async removeMember(workspaceId: string, memberId: string): Promise<void> {
167
+ const workspaceIdBig = toId(workspaceId);
168
+ const memberIdBig = toId(memberId);
169
+ const member = await this.prisma.workspaceMember.findUnique({ where: { id: memberIdBig } });
170
+ if (!member || member.workspaceId !== workspaceIdBig) throw new NotFoundException("member not found in this workspace");
171
+
172
+ // Role assignments and direct grants belong to the membership, so `onDelete: Cascade` on
173
+ // both join tables takes them with it.
174
+ await this.prisma.workspaceMember.delete({ where: { id: memberIdBig } });
175
+ await this.rbac.invalidateMember(member.userId.toString(), workspaceId);
176
+ }
177
+ }
@@ -0,0 +1,333 @@
1
+ // Variant hooks for the workspace variant. See test/harness.ts for the contract. Admin
2
+ // authority here is per workspace — making someone an admin means giving them a workspace to be
3
+ // admin of. `proveVariantProperties` carries the core security property: a role held in one
4
+ // workspace grants nothing in another.
5
+ import "dotenv/config";
6
+ import { PrismaPg } from "@prisma/adapter-pg";
7
+ import { PrismaClient } from "../generated/prisma/client.js";
8
+ import { POLICY_VERSION_KEY } from "../src/permission-cache.js";
9
+ import { permissionCacheStore } from "./bootstrap.js";
10
+ import { renewingToken, type AdminSession, type Principal, type ProofContext, type VariantHooks } from "./harness.js";
11
+
12
+ const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env["DATABASE_URL"] }) });
13
+
14
+ interface Workspace {
15
+ id: string;
16
+ name: string;
17
+ roles: string[];
18
+ }
19
+
20
+ /** Signs up a fresh user, creates a workspace they administer, and returns both. */
21
+ async function newAdminWithWorkspace(ctx: ProofContext, label: string) {
22
+ const email = ctx.uniqueEmail(label);
23
+ const password = `${label}-pw-12345`;
24
+ const tokens = await ctx.signup(email, password);
25
+ const userId = (await ctx.call("GET", "/auth/me", { token: tokens.accessToken })).body.sub as string;
26
+ const created = await ctx.call("POST", "/workspaces", { token: tokens.accessToken, body: { name: `${label}-workspace` } });
27
+ return { email, password, userId, token: tokens.accessToken, workspace: created.body as Workspace };
28
+ }
29
+
30
+ export const hooks: VariantHooks = {
31
+ variant: "workspaces",
32
+
33
+ async makeAdmin(ctx: ProofContext, principal: Principal): Promise<AdminSession> {
34
+ const created = await ctx.call("POST", "/workspaces", { token: principal.tokens.accessToken, body: { name: "proof-workspace" } });
35
+ ctx.assert(created.status === 201 || created.status === 200, `creating a workspace succeeds (got ${created.status})`);
36
+ const workspace = created.body as Workspace;
37
+ ctx.assert(workspace.roles.includes("admin"), "the creator of a workspace is an admin member of it");
38
+ // No re-login needed: the access token is workspace-agnostic and carries no authorization,
39
+ // so the membership is in effect immediately. freshToken() re-logs in anyway because the
40
+ // 2-second access-token TTL in test/bootstrap.ts can expire a token mid-section.
41
+ return {
42
+ token: principal.tokens.accessToken,
43
+ workspaceId: workspace.id,
44
+ freshToken: renewingToken(async () => {
45
+ const res = await ctx.call("POST", "/auth/login", { body: { identifier: principal.email, password: principal.password } });
46
+ return (res.body as { accessToken: string }).accessToken;
47
+ }),
48
+ };
49
+ },
50
+
51
+ async admitUser(ctx: ProofContext, admin: AdminSession, user: { userId: string; email: string }): Promise<void> {
52
+ const added = await ctx.call("POST", "/workspaces/members", { token: await admin.freshToken(), workspaceId: admin.workspaceId, body: { email: user.email } });
53
+ ctx.assert(added.status === 201 || added.status === 200, `adding a user to the workspace succeeds (got ${added.status})`);
54
+ },
55
+
56
+ async proveVariantProperties(ctx: ProofContext): Promise<void> {
57
+ // Two independent workspaces, each with its own admin.
58
+ const alpha = await newAdminWithWorkspace(ctx, "alpha");
59
+ const beta = await newAdminWithWorkspace(ctx, "beta");
60
+
61
+ ctx.assert(alpha.workspace.id !== beta.workspace.id, "two workspaces are distinct");
62
+
63
+ // --- a non-member cannot even see a workspace exists ---
64
+ const outsider = await ctx.call("GET", "/auth/admin/users", { token: alpha.token, workspaceId: beta.workspace.id });
65
+ ctx.assert(outsider.status === 403, `an admin of one workspace is refused in a workspace they are not a member of (got ${outsider.status})`);
66
+
67
+ const bogus = await ctx.call("GET", "/auth/admin/users", { token: alpha.token, workspaceId: "00000000-0000-0000-0000-000000000000" });
68
+ ctx.assert(bogus.status === 403, `an unknown workspace id is refused the same way as someone else's (got ${bogus.status})`);
69
+
70
+ const noHeader = await ctx.call("GET", "/auth/admin/users", { token: alpha.token });
71
+ ctx.assert(noHeader.status === 403, `a workspace-scoped route refuses a request that names no workspace (got ${noHeader.status})`);
72
+
73
+ // --- alpha joins beta as a plain member: their admin role in alpha must not follow them ---
74
+ const added = await ctx.call("POST", "/workspaces/members", { token: beta.token, workspaceId: beta.workspace.id, body: { email: alpha.email } });
75
+ ctx.assert(added.status === 201 || added.status === 200, `an admin can add an existing user to their workspace (got ${added.status})`);
76
+ const alphaInBeta = added.body as { memberId: string; roles: string[] };
77
+ ctx.assert(alphaInBeta.roles.join() === "member", `a new member defaults to the member role (got ${alphaInBeta.roles.join()})`);
78
+
79
+ const meInAlpha = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
80
+ ctx.assert(meInAlpha.body.roles.includes("admin"), "the same token is admin in its own workspace");
81
+
82
+ const meInBeta = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: beta.workspace.id });
83
+ ctx.assert(!meInBeta.body.roles.includes("admin"), "THE core property: the admin role held in workspace A is absent in workspace B");
84
+ ctx.assert(meInBeta.body.roles.includes("member"), "…and only the roles granted by workspace B's membership apply there");
85
+
86
+ const adminAttemptInBeta = await ctx.call("GET", "/auth/admin/users", { token: alpha.token, workspaceId: beta.workspace.id });
87
+ ctx.assert(adminAttemptInBeta.status === 403, `being an admin elsewhere does not unlock workspace B's admin endpoints (got ${adminAttemptInBeta.status})`);
88
+
89
+ // --- roles are per workspace: the same role name can mean different things ---
90
+ const roleInBeta = await ctx.call("POST", "/auth/admin/roles", { token: beta.token, workspaceId: beta.workspace.id, body: { slug: "shared-name" } });
91
+ const roleInAlpha = await ctx.call("POST", "/auth/admin/roles", { token: alpha.token, workspaceId: alpha.workspace.id, body: { slug: "shared-name" } });
92
+ ctx.assert(roleInBeta.status === 201 && roleInAlpha.status === 201, "the same role name can exist independently in two workspaces");
93
+
94
+ await ctx.call("POST", `/auth/admin/roles/${(roleInBeta.body as { id: string }).id}/permissions`, {
95
+ token: beta.token,
96
+ workspaceId: beta.workspace.id,
97
+ body: { permission: "beta:only" },
98
+ });
99
+ await ctx.call("POST", `/auth/admin/users/${alpha.userId}/roles`, { token: beta.token, workspaceId: beta.workspace.id, body: { role: "shared-name" } });
100
+
101
+ const permsInBeta = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: beta.workspace.id });
102
+ ctx.assert(permsInBeta.body.permissions.includes("beta:only"), "a role assigned in workspace B grants its permissions in workspace B");
103
+ const permsInAlpha = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
104
+ ctx.assert(!permsInAlpha.body.permissions.includes("beta:only"), "…and the identically-named role in workspace A does not carry them");
105
+
106
+ // --- direct grants attach to the membership, not the user ---
107
+ const directGrant = await ctx.call("POST", `/auth/admin/users/${alpha.userId}/permissions`, {
108
+ token: beta.token,
109
+ workspaceId: beta.workspace.id,
110
+ body: { permission: "beta:direct" },
111
+ });
112
+ ctx.assert(directGrant.status === 201 || directGrant.status === 200, `a direct grant inside workspace B succeeds (got ${directGrant.status})`);
113
+ const afterGrantBeta = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: beta.workspace.id });
114
+ ctx.assert(afterGrantBeta.body.permissions.includes("beta:direct"), "the direct grant applies in workspace B");
115
+ const afterGrantAlpha = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
116
+ ctx.assert(!afterGrantAlpha.body.permissions.includes("beta:direct"), "the direct grant does not leak into workspace A");
117
+
118
+ // --- an admin cannot act on a user who is not in their workspace ---
119
+ const crossBlock = await ctx.call("POST", `/auth/admin/users/${beta.userId}/block`, { token: alpha.token, workspaceId: alpha.workspace.id });
120
+ ctx.assert(crossBlock.status === 404, `an admin cannot block a user who is not a member of their workspace (got ${crossBlock.status})`);
121
+
122
+ // --- audit log is scoped too ---
123
+ const betaAudit = await ctx.call("GET", "/auth/admin/audit-log", { token: beta.token, workspaceId: beta.workspace.id });
124
+ ctx.assert(betaAudit.status === 200, `an admin can read their workspace's audit log (got ${betaAudit.status})`);
125
+ ctx.assert(
126
+ (betaAudit.body.items as Array<{ workspaceId: string | null }>).every((e) => e.workspaceId === beta.workspace.id),
127
+ "the audit log never returns another workspace's entries",
128
+ );
129
+
130
+ // --- membership management ---
131
+ const myWorkspaces = await ctx.call("GET", "/workspaces", { token: alpha.token });
132
+ ctx.assert(myWorkspaces.status === 200, `a user can list the workspaces they belong to (got ${myWorkspaces.status})`);
133
+ const ids = (myWorkspaces.body as Workspace[]).map((w) => w.id);
134
+ ctx.assert(ids.includes(alpha.workspace.id) && ids.includes(beta.workspace.id), "the list covers every workspace the user is a member of");
135
+
136
+ const promote = await ctx.call("PUT", `/workspaces/members/${alphaInBeta.memberId}/roles`, {
137
+ token: beta.token,
138
+ workspaceId: beta.workspace.id,
139
+ body: { roles: ["member", "shared-name"] },
140
+ });
141
+ ctx.assert(promote.status === 200, `an admin can set a member's roles (got ${promote.status})`);
142
+
143
+ const memberList = await ctx.call("GET", "/workspaces/members", { token: alpha.token, workspaceId: beta.workspace.id });
144
+ ctx.assert(memberList.status === 200 && memberList.body.length === 2, `any member can list the workspace's members (got ${memberList.status})`);
145
+
146
+ const removed = await ctx.call("DELETE", `/workspaces/members/${alphaInBeta.memberId}`, { token: beta.token, workspaceId: beta.workspace.id });
147
+ ctx.assert(removed.status === 200, `an admin can remove a member (got ${removed.status})`);
148
+
149
+ const afterRemoval = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: beta.workspace.id });
150
+ ctx.assert(afterRemoval.status === 403, `a removed member loses access to the workspace immediately (got ${afterRemoval.status})`);
151
+
152
+ const stillInAlpha = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
153
+ ctx.assert(stillInAlpha.status === 200 && stillInAlpha.body.roles.includes("admin"), "…while their own workspace is untouched — the same login still works");
154
+
155
+ // --- revocation is immediate here: the context is resolved per request, not per token ---
156
+ const immediate = await ctx.call("POST", `/auth/admin/users/${beta.userId}/permissions`, {
157
+ token: beta.token,
158
+ workspaceId: beta.workspace.id,
159
+ body: { permission: "probe:immediate" },
160
+ });
161
+ ctx.assert(immediate.status === 201 || immediate.status === 200, `a direct grant to oneself succeeds (got ${immediate.status})`);
162
+ const seen = await ctx.call("GET", "/auth/me", { token: beta.token, workspaceId: beta.workspace.id });
163
+ ctx.assert(seen.body.permissions.includes("probe:immediate"), "the grant is visible on the very next request, with no re-login");
164
+ await ctx.call("POST", `/auth/admin/users/${beta.userId}/permissions/${encodeURIComponent("probe:immediate")}/revoke`, {
165
+ token: beta.token,
166
+ workspaceId: beta.workspace.id,
167
+ });
168
+ const gone = await ctx.call("GET", "/auth/me", { token: beta.token, workspaceId: beta.workspace.id });
169
+ ctx.assert(!gone.body.permissions.includes("probe:immediate"), "…and the revocation lands on the very next request too, on the same token");
170
+
171
+ await proveNewWorkspaceIsProvisioned(ctx);
172
+ await proveWorkspaceHeaderCannotBeSkipped(ctx, alpha);
173
+ await proveTheCacheIsScopedAndReal(ctx, alpha, beta);
174
+ await proveARawDatabaseEditChangesEnforcement(ctx, beta);
175
+ },
176
+ };
177
+
178
+ // The lockout test: without `WorkspaceRepository.create` provisioning default roles in the same
179
+ // transaction, a brand-new workspace has no `Role` rows, and its creator's ["admin", "member"]
180
+ // would be names with nothing behind them — locked out with no way back in. Walks the entire
181
+ // administrative surface of a brand-new workspace as the person who just created it.
182
+ async function proveNewWorkspaceIsProvisioned(ctx: ProofContext): Promise<void> {
183
+ const creator = await newAdminWithWorkspace(ctx, "fresh");
184
+ const ws = creator.workspace.id;
185
+ const as = { token: creator.token, workspaceId: ws };
186
+ ctx.assert(creator.workspace.roles.includes("admin"), "the creator of a brand-new workspace holds the admin role in it");
187
+
188
+ const guestEmail = ctx.uniqueEmail("fresh-guest");
189
+ const guestTokens = await ctx.signup(guestEmail, "fresh-guest-pw-1");
190
+ const guestId = (await ctx.call("GET", "/auth/me", { token: guestTokens.accessToken })).body.sub as string;
191
+
192
+ const ok = (res: { status: number }) => res.status === 200 || res.status === 201;
193
+
194
+ const added = await ctx.call("POST", "/workspaces/members", { ...as, body: { email: guestEmail } });
195
+ ctx.assert(ok(added), `members:manage — the creator can add a member (got ${added.status})`);
196
+ const memberId = (added.body as { memberId: string }).memberId;
197
+
198
+ ctx.assert(ok(await ctx.call("GET", "/auth/admin/users", as)), "users:read — the creator can list the members");
199
+ ctx.assert(ok(await ctx.call("GET", "/auth/admin/audit-log", as)), "audit-log:read — the creator can read the audit log");
200
+
201
+ const role = await ctx.call("POST", "/auth/admin/roles", { ...as, body: { slug: "fresh-role" } });
202
+ ctx.assert(ok(role), `roles:manage — the creator can define a role (got ${role.status})`);
203
+ const attach = await ctx.call("POST", `/auth/admin/roles/${(role.body as { id: string }).id}/permissions`, { ...as, body: { permission: "fresh:probe" } });
204
+ ctx.assert(ok(attach), `roles:manage — the creator can say what that role carries (got ${attach.status})`);
205
+
206
+ ctx.assert(ok(await ctx.call("POST", `/auth/admin/users/${guestId}/roles`, { ...as, body: { role: "fresh-role" } })), "roles:assign — assign a role");
207
+ ctx.assert(ok(await ctx.call("POST", `/auth/admin/users/${guestId}/roles/fresh-role/revoke`, as)), "roles:assign — revoke a role");
208
+
209
+ ctx.assert(
210
+ ok(await ctx.call("POST", `/auth/admin/users/${guestId}/permissions`, { ...as, body: { permission: "fresh:direct" } })),
211
+ "permissions:grant — grant directly",
212
+ );
213
+ ctx.assert(
214
+ ok(await ctx.call("POST", `/auth/admin/users/${guestId}/permissions/${encodeURIComponent("fresh:direct")}/revoke`, as)),
215
+ "permissions:grant — revoke a direct grant",
216
+ );
217
+
218
+ ctx.assert(ok(await ctx.call("POST", `/auth/admin/users/${guestId}/block`, as)), "users:block — block a member");
219
+ ctx.assert(ok(await ctx.call("POST", `/auth/admin/users/${guestId}/unblock`, as)), "users:block — unblock a member");
220
+
221
+ ctx.assert(ok(await ctx.call("PUT", `/workspaces/members/${memberId}/roles`, { ...as, body: { roles: ["member"] } })), "roles:assign — set a member's roles");
222
+ ctx.assert(ok(await ctx.call("DELETE", `/workspaces/members/${memberId}`, as)), "members:manage — remove a member");
223
+
224
+ // A second workspace's "admin" is a different row that happens to share a name.
225
+ const other = await newAdminWithWorkspace(ctx, "fresh-other");
226
+ const trespass = await ctx.call("GET", "/auth/admin/users", { token: creator.token, workspaceId: other.workspace.id });
227
+ ctx.assert(trespass.status === 403, `being provisioned as admin of one new workspace grants nothing in another (got ${trespass.status})`);
228
+ }
229
+
230
+ // `X-Workspace-Id` is the only thing that says which workspace a request acts in, so a missing
231
+ // or junk header must never reach a handler with no workspace resolved — and must deny the same
232
+ // way a workspace you aren't a member of does, so a caller can't tell the two apart.
233
+ async function proveWorkspaceHeaderCannotBeSkipped(ctx: ProofContext, member: { token: string; workspace: Workspace }): Promise<void> {
234
+ const cases: Array<[string, Record<string, string>]> = [
235
+ ["absent", {}],
236
+ ["empty", { "x-workspace-id": "" }],
237
+ ["whitespace", { "x-workspace-id": " " }],
238
+ ["not a uuid", { "x-workspace-id": "not-a-uuid" }],
239
+ ["a sql fragment", { "x-workspace-id": "' OR 1=1 --" }],
240
+ ["absurdly long", { "x-workspace-id": "x".repeat(4096) }],
241
+ ];
242
+
243
+ for (const [label, headers] of cases) {
244
+ const admin = await ctx.call("GET", "/auth/admin/users", { token: member.token, headers });
245
+ ctx.assert(admin.status === 403, `a permission-gated admin route denies a request whose workspace id is ${label} (got ${admin.status})`);
246
+
247
+ const members = await ctx.call("GET", "/workspaces/members", { token: member.token, headers });
248
+ ctx.assert(members.status === 403, `a member-gated workspace route denies a request whose workspace id is ${label} (got ${members.status})`);
249
+ }
250
+
251
+ // An id that exists but isn't yours and one that exists nowhere must answer identically, body
252
+ // included, or the 403 becomes a workspace oracle.
253
+ const stranger = await newAdminWithWorkspace(ctx, "stranger");
254
+ const notYours = await ctx.call("GET", "/auth/admin/users", { token: member.token, workspaceId: stranger.workspace.id });
255
+ const notReal = await ctx.call("GET", "/auth/admin/users", { token: member.token, workspaceId: "00000000-0000-0000-0000-000000000000" });
256
+ ctx.assert(
257
+ notYours.status === notReal.status && notYours.body?.message === notReal.body?.message,
258
+ `someone else's workspace and a nonexistent one answer identically (${notYours.status}/${notYours.body?.message} vs ${notReal.status}/${notReal.body?.message})`,
259
+ );
260
+
261
+ // /auth/me works either way, since clients call it before they know which workspaces exist —
262
+ // it must answer with an empty context, never one borrowed from a workspace the user belongs to.
263
+ const meNoHeader = await ctx.call("GET", "/auth/me", { token: member.token });
264
+ ctx.assert(meNoHeader.status === 200, `GET /auth/me answers without a workspace header (got ${meNoHeader.status})`);
265
+ ctx.assert(
266
+ Array.isArray(meNoHeader.body?.roles) && meNoHeader.body.roles.length === 0 && meNoHeader.body.permissions.length === 0,
267
+ `…with empty roles and permissions rather than some workspace's (got roles=${JSON.stringify(meNoHeader.body?.roles)})`,
268
+ );
269
+ ctx.assert(meNoHeader.body?.sub && meNoHeader.body?.sessionId, "…while still identifying the user, which is what makes it callable before any workspace is chosen");
270
+
271
+ const meEmptyHeader = await ctx.call("GET", "/auth/me", { token: member.token, headers: { "x-workspace-id": "" } });
272
+ ctx.assert(
273
+ meEmptyHeader.status === 200 && meEmptyHeader.body.roles.length === 0,
274
+ `an empty workspace header is treated as naming no workspace, not as naming one (got ${meEmptyHeader.status})`,
275
+ );
276
+
277
+ const meBogusHeader = await ctx.call("GET", "/auth/me", { token: member.token, workspaceId: "00000000-0000-0000-0000-000000000000" });
278
+ ctx.assert(meBogusHeader.status === 403, `but a workspace id that is named and not yours is refused outright, even here (got ${meBogusHeader.status})`);
279
+ }
280
+
281
+ // The permission cache is keyed on the membership, not the user, so invalidating one workspace
282
+ // must not flush another. Reads the very store the app resolves through, so "the second request
283
+ // didn't touch the database" is a measurement rather than a claim.
284
+ async function proveTheCacheIsScopedAndReal(
285
+ ctx: ProofContext,
286
+ alpha: { token: string; workspace: Workspace },
287
+ beta: { token: string; userId: string; workspace: Workspace },
288
+ ): Promise<void> {
289
+ const N = 8;
290
+ await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id }); // warm
291
+ const before = { ...permissionCacheStore.stats };
292
+ for (let i = 0; i < N; i += 1) await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
293
+ const after = permissionCacheStore.stats;
294
+ ctx.assert(
295
+ after.misses === before.misses && after.hits === before.hits + N,
296
+ `${N} identical workspace-scoped requests resolve the membership 0 further times — all ${N} were cache hits (misses +${after.misses - before.misses}, hits +${after.hits - before.hits})`,
297
+ );
298
+
299
+ // A grant inside beta must invalidate beta's entry and leave alpha's alone — the cache
300
+ // subject is the (user, workspace) pair.
301
+ await ctx.call("POST", `/auth/admin/users/${beta.userId}/permissions`, { token: beta.token, workspaceId: beta.workspace.id, body: { permission: "probe:cache" } });
302
+ const beforeAlpha = { ...permissionCacheStore.stats };
303
+ const alphaAfter = await ctx.call("GET", "/auth/me", { token: alpha.token, workspaceId: alpha.workspace.id });
304
+ ctx.assert(
305
+ permissionCacheStore.stats.misses === beforeAlpha.misses && alphaAfter.status === 200,
306
+ "a grant made in workspace B does not invalidate the caller's cached membership of workspace A",
307
+ );
308
+ const betaAfter = await ctx.call("GET", "/auth/me", { token: beta.token, workspaceId: beta.workspace.id });
309
+ ctx.assert((betaAfter.body?.permissions ?? []).includes("probe:cache"), "…while workspace B sees the grant on its very next request");
310
+ await ctx.call("POST", `/auth/admin/users/${beta.userId}/permissions/${encodeURIComponent("probe:cache")}/revoke`, {
311
+ token: beta.token,
312
+ workspaceId: beta.workspace.id,
313
+ });
314
+ const betaRevoked = await ctx.call("GET", "/auth/me", { token: beta.token, workspaceId: beta.workspace.id });
315
+ ctx.assert(!(betaRevoked.body?.permissions ?? []).includes("probe:cache"), "…and loses it again on the request after the revoke");
316
+ }
317
+
318
+ // Writes the row directly rather than through the admin API, ruling out any in-memory side
319
+ // channel — a `psql` session would look exactly like this, bump included (permission-cache.ts).
320
+ async function proveARawDatabaseEditChangesEnforcement(ctx: ProofContext, beta: { token: string; workspace: Workspace }): Promise<void> {
321
+ const opened = await ctx.call("GET", "/auth/admin/audit-log", { token: beta.token, workspaceId: beta.workspace.id });
322
+ ctx.assert(opened.status === 200, `the workspace admin can read the audit log to begin with (got ${opened.status})`);
323
+
324
+ await prisma.permission.update({ where: { slug: "audit-log:read" }, data: { isActive: false } });
325
+ await permissionCacheStore.bump(POLICY_VERSION_KEY);
326
+ const denied = await ctx.call("GET", "/auth/admin/audit-log", { token: beta.token, workspaceId: beta.workspace.id });
327
+ ctx.assert(denied.status === 403, `a permission deactivated by a raw database write stops opening its route (got ${denied.status})`);
328
+
329
+ await prisma.permission.update({ where: { slug: "audit-log:read" }, data: { isActive: true } });
330
+ await permissionCacheStore.bump(POLICY_VERSION_KEY);
331
+ const restored = await ctx.call("GET", "/auth/admin/audit-log", { token: beta.token, workspaceId: beta.workspace.id });
332
+ ctx.assert(restored.status === 200, `…and reactivating it in the database opens the route again (got ${restored.status})`);
333
+ }