@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.186

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 (386) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  3. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  4. package/dist/components/app/layouts/PublicFooter.js +2 -2
  5. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  7. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  8. package/dist/components/app/misc/ThemeToggle.js +4 -1
  9. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  10. package/dist/components/auth/DevKeyring.js +10 -2
  11. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  12. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  13. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  14. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  15. package/dist/components/auth/forms/LoginForm.js +300 -6
  16. package/dist/components/auth/index.d.ts +1 -0
  17. package/dist/components/auth/index.d.ts.map +1 -1
  18. package/dist/components/auth/index.js +2 -0
  19. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  20. package/dist/components/billing/InvoicesTable.js +4 -3
  21. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  22. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  23. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  24. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  27. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  29. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  30. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  31. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  32. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  33. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  34. package/dist/components/dashboard/layouts/index.js +2 -0
  35. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  36. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  37. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  38. package/dist/components/entities/EntityTable.js +2 -1
  39. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  40. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  41. package/dist/components/ui/combobox.d.ts +8 -1
  42. package/dist/components/ui/combobox.d.ts.map +1 -1
  43. package/dist/components/ui/combobox.js +37 -44
  44. package/dist/components/ui/country-select.d.ts +3 -1
  45. package/dist/components/ui/country-select.d.ts.map +1 -1
  46. package/dist/components/ui/country-select.js +3 -1
  47. package/dist/components/ui/currency-select.d.ts +3 -1
  48. package/dist/components/ui/currency-select.d.ts.map +1 -1
  49. package/dist/components/ui/currency-select.js +3 -1
  50. package/dist/components/ui/sheet.d.ts +5 -0
  51. package/dist/components/ui/sheet.d.ts.map +1 -1
  52. package/dist/components/ui/sheet.js +46 -26
  53. package/dist/components/ui/timezone-select.d.ts +3 -1
  54. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  55. package/dist/components/ui/timezone-select.js +3 -1
  56. package/dist/contexts/TeamContext.d.ts +6 -0
  57. package/dist/contexts/TeamContext.d.ts.map +1 -1
  58. package/dist/contexts/TeamContext.js +8 -1
  59. package/dist/emails/otp-verification.d.ts.map +1 -1
  60. package/dist/emails/reset-password.d.ts.map +1 -1
  61. package/dist/emails/team-invitation.d.ts.map +1 -1
  62. package/dist/emails/team-invitation.js +3 -2
  63. package/dist/emails/verify-email.d.ts.map +1 -1
  64. package/dist/hooks/index.d.ts +1 -0
  65. package/dist/hooks/index.d.ts.map +1 -1
  66. package/dist/hooks/index.js +1 -0
  67. package/dist/hooks/useAuth.d.ts +29 -0
  68. package/dist/hooks/useAuth.d.ts.map +1 -1
  69. package/dist/hooks/useAuth.js +24 -0
  70. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  71. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  72. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  73. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  74. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  75. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  76. package/dist/lib/api/api-error.d.ts +3 -1
  77. package/dist/lib/api/api-error.d.ts.map +1 -1
  78. package/dist/lib/api/api-error.js +4 -2
  79. package/dist/lib/api/auth/dual-auth.d.ts +38 -3
  80. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  81. package/dist/lib/api/auth/dual-auth.js +42 -7
  82. package/dist/lib/api/auth/permissions.d.ts +7 -2
  83. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  84. package/dist/lib/api/auth/permissions.js +2 -2
  85. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  86. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  87. package/dist/lib/api/auth/scope-policy.js +53 -0
  88. package/dist/lib/api/auth.d.ts +15 -0
  89. package/dist/lib/api/auth.d.ts.map +1 -1
  90. package/dist/lib/api/auth.js +17 -4
  91. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  92. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  93. package/dist/lib/api/entity/audit-log.js +35 -0
  94. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  95. package/dist/lib/api/entity/generic-handler.js +315 -134
  96. package/dist/lib/api/helpers.d.ts +29 -4
  97. package/dist/lib/api/helpers.d.ts.map +1 -1
  98. package/dist/lib/api/helpers.js +30 -6
  99. package/dist/lib/api/keys.d.ts +16 -0
  100. package/dist/lib/api/keys.d.ts.map +1 -1
  101. package/dist/lib/api/keys.js +19 -1
  102. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  103. package/dist/lib/api/routes/patterns-usages.js +3 -11
  104. package/dist/lib/auth/auth-methods.d.ts +52 -0
  105. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  106. package/dist/lib/auth/auth-methods.js +63 -0
  107. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  108. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  109. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  110. package/dist/lib/auth/registration-helpers.js +8 -2
  111. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  114. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  117. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  119. package/dist/lib/auth/security-notifications/index.js +22 -0
  120. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  122. package/dist/lib/auth/security-notifications/registry.js +26 -0
  123. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  126. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  129. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  132. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  135. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  137. package/dist/lib/auth/security-notifications/types.js +0 -0
  138. package/dist/lib/auth/session-config.d.ts +37 -0
  139. package/dist/lib/auth/session-config.d.ts.map +1 -0
  140. package/dist/lib/auth/session-config.js +59 -0
  141. package/dist/lib/auth/session-refresh.d.ts +46 -0
  142. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  143. package/dist/lib/auth/session-refresh.js +11 -0
  144. package/dist/lib/auth-client.d.ts +1639 -0
  145. package/dist/lib/auth-client.d.ts.map +1 -0
  146. package/dist/lib/auth-client.js +4 -0
  147. package/dist/lib/auth.d.ts.map +1 -1
  148. package/dist/lib/auth.js +21 -8
  149. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  150. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  151. package/dist/lib/billing/gateways/factory.js +37 -23
  152. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  153. package/dist/lib/billing/stripe-webhook.js +2 -1
  154. package/dist/lib/config/app.config.d.ts.map +1 -1
  155. package/dist/lib/config/app.config.js +71 -0
  156. package/dist/lib/config/config-sync.d.ts +1 -4
  157. package/dist/lib/config/config-sync.d.ts.map +1 -1
  158. package/dist/lib/config/config-sync.js +6 -2
  159. package/dist/lib/config/types.d.ts +130 -0
  160. package/dist/lib/config/types.d.ts.map +1 -1
  161. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  162. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  163. package/dist/lib/entities/entity-hooks.js +77 -4
  164. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  165. package/dist/lib/entities/schema-generator.js +20 -4
  166. package/dist/lib/entities/serialization.d.ts +2 -2
  167. package/dist/lib/entities/serialization.d.ts.map +1 -1
  168. package/dist/lib/entities/serialization.js +3 -2
  169. package/dist/lib/entities/types.d.ts +8 -0
  170. package/dist/lib/entities/types.d.ts.map +1 -1
  171. package/dist/lib/locale.d.ts.map +1 -1
  172. package/dist/lib/locale.js +4 -1
  173. package/dist/lib/mcp/audit.d.ts +8 -4
  174. package/dist/lib/mcp/audit.d.ts.map +1 -1
  175. package/dist/lib/plugins/hook-system.d.ts +13 -0
  176. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  177. package/dist/lib/plugins/hook-system.js +32 -2
  178. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  179. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  180. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  181. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  182. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  183. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  184. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  185. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  186. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  187. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  188. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  189. package/dist/lib/selectors/selectors.d.ts +24 -0
  190. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  191. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  192. package/dist/lib/services/generic-entity.service.js +16 -32
  193. package/dist/lib/services/membership.service.d.ts +10 -2
  194. package/dist/lib/services/membership.service.d.ts.map +1 -1
  195. package/dist/lib/services/membership.service.js +37 -16
  196. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  197. package/dist/lib/services/subscription.service.js +2 -1
  198. package/dist/lib/services/team-member.service.d.ts +15 -3
  199. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  200. package/dist/lib/services/team-member.service.js +27 -6
  201. package/dist/lib/services/team.service.d.ts.map +1 -1
  202. package/dist/lib/teams/permissions.d.ts +3 -1
  203. package/dist/lib/teams/permissions.d.ts.map +1 -1
  204. package/dist/lib/teams/permissions.js +6 -0
  205. package/dist/lib/teams/schema.d.ts.map +1 -1
  206. package/dist/lib/teams/schema.js +3 -0
  207. package/dist/lib/theme/forced-theme.d.ts +34 -0
  208. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  209. package/dist/lib/theme/forced-theme.js +27 -0
  210. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  211. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  212. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  213. package/dist/messages/de/auth.json +25 -2
  214. package/dist/messages/de/index.d.ts +81 -0
  215. package/dist/messages/de/index.d.ts.map +1 -1
  216. package/dist/messages/de/index.js +2 -0
  217. package/dist/messages/de/index.ts +2 -0
  218. package/dist/messages/en/auth.json +25 -2
  219. package/dist/messages/en/index.d.ts +81 -0
  220. package/dist/messages/en/index.d.ts.map +1 -1
  221. package/dist/messages/en/index.js +2 -0
  222. package/dist/messages/en/index.ts +2 -0
  223. package/dist/messages/es/auth.json +25 -2
  224. package/dist/messages/es/index.d.ts +81 -0
  225. package/dist/messages/es/index.d.ts.map +1 -1
  226. package/dist/messages/es/index.js +2 -0
  227. package/dist/messages/es/index.ts +2 -0
  228. package/dist/messages/fr/auth.json +25 -2
  229. package/dist/messages/fr/index.d.ts +81 -0
  230. package/dist/messages/fr/index.d.ts.map +1 -1
  231. package/dist/messages/fr/index.js +2 -0
  232. package/dist/messages/fr/index.ts +2 -0
  233. package/dist/messages/it/auth.json +25 -2
  234. package/dist/messages/it/index.d.ts +81 -0
  235. package/dist/messages/it/index.d.ts.map +1 -1
  236. package/dist/messages/it/index.js +2 -0
  237. package/dist/messages/it/index.ts +2 -0
  238. package/dist/messages/pt/auth.json +25 -2
  239. package/dist/messages/pt/index.d.ts +81 -0
  240. package/dist/messages/pt/index.d.ts.map +1 -1
  241. package/dist/messages/pt/index.js +2 -0
  242. package/dist/messages/pt/index.ts +2 -0
  243. package/dist/migrations/005_api_audit_log.sql +2 -1
  244. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  245. package/dist/migrations/026_login_events.sql +91 -0
  246. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  247. package/dist/providers/DashboardProviders.d.ts +12 -3
  248. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  249. package/dist/providers/DashboardProviders.js +6 -2
  250. package/dist/providers/theme-provider.d.ts +19 -2
  251. package/dist/providers/theme-provider.d.ts.map +1 -1
  252. package/dist/providers/theme-provider.js +6 -2
  253. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  254. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  255. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  256. package/dist/templates/app/api/health/route.ts +4 -2
  257. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  258. package/dist/templates/app/api/mcp/route.ts +4 -1
  259. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  260. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  261. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  262. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  263. package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
  264. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  265. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  266. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  267. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  268. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  269. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  270. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  271. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  272. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  273. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  274. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  275. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  276. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  277. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  278. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  279. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  280. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  281. package/dist/templates/app/api/v1/media/route.ts +5 -9
  282. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  283. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  284. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  285. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  286. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  287. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  288. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  289. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  290. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  291. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  292. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  293. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  294. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  295. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  296. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  297. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  298. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  299. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  300. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  301. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  302. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  303. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  304. package/dist/templates/app/api/v1/users/route.ts +21 -25
  305. package/dist/templates/app/dashboard/layout.tsx +2 -6
  306. package/dist/templates/app/layout.ppr.tsx +9 -14
  307. package/dist/templates/app/layout.tsx +37 -20
  308. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  309. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  310. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  311. package/dist/templates/proxy.ts +46 -10
  312. package/dist/templates/tsconfig.json +4 -0
  313. package/dist/types/theme.d.ts +9 -0
  314. package/dist/types/theme.d.ts.map +1 -1
  315. package/migrations/005_api_audit_log.sql +2 -1
  316. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  317. package/migrations/026_login_events.sql +91 -0
  318. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  319. package/package.json +14 -11
  320. package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
  321. package/scripts/build/registry/generators/scope-registry.mjs +9 -2
  322. package/templates/app/(auth)/signup/page.tsx +8 -0
  323. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  324. package/templates/app/api/auth/[...all]/route.ts +23 -1
  325. package/templates/app/api/health/route.ts +4 -2
  326. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  327. package/templates/app/api/mcp/route.ts +4 -1
  328. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  329. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  330. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  331. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  332. package/templates/app/api/v1/api-keys/route.ts +21 -43
  333. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  334. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  335. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  336. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  337. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  338. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  339. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  340. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  341. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  342. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  343. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  344. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  345. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  346. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  347. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  348. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  349. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  350. package/templates/app/api/v1/media/route.ts +5 -9
  351. package/templates/app/api/v1/media/upload/route.ts +9 -21
  352. package/templates/app/api/v1/media-tags/route.ts +9 -13
  353. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  354. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  355. package/templates/app/api/v1/post-categories/route.ts +5 -4
  356. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  357. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  358. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  359. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  360. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  361. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  362. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  363. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  364. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  365. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  366. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  367. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  368. package/templates/app/api/v1/teams/route.ts +33 -27
  369. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  370. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  371. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  372. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  373. package/templates/app/api/v1/users/route.ts +21 -25
  374. package/templates/app/dashboard/layout.tsx +2 -6
  375. package/templates/app/layout.ppr.tsx +9 -14
  376. package/templates/app/layout.tsx +37 -20
  377. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  378. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  379. package/templates/contents/themes/starter/styles/globals.css +6 -2
  380. package/templates/proxy.ts +46 -10
  381. package/templates/tsconfig.json +4 -0
  382. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  383. package/tests/jest/__mocks__/next-server.js +24 -0
  384. package/dist/styles/classes.json +0 -1093
  385. package/dist/styles/ui.css +0 -2
  386. package/scripts/build/build-ui-css.mjs +0 -351
@@ -1 +1 @@
1
- {"version":3,"file":"TeamContext.d.ts","sourceRoot":"","sources":["../../src/contexts/TeamContext.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAwE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGvG,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAM7D,UAAU,gBAAgB;IACxB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,EAAE,OAAO,CAAA;IACjC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAKD,eAAO,MAAM,eAAe,yBAA0B,CAAA;AAGtD,wBAAsB,cAAc,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAwBpE;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAuNjE;AAED,wBAAgB,cAAc,qBAM7B"}
1
+ {"version":3,"file":"TeamContext.d.ts","sourceRoot":"","sources":["../../src/contexts/TeamContext.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAwE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGvG,OAAO,EAAE,IAAI,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA;AAM7D,UAAU,gBAAgB;IACxB,WAAW,EAAE,IAAI,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,kBAAkB,EAAE,CAAA;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,wBAAwB,EAAE,OAAO,CAAA;IACjC,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAClC;AAKD,eAAO,MAAM,eAAe,yBAA0B,CAAA;AAGtD,wBAAsB,cAAc,IAAI,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAwBpE;AAED,wBAAgB,YAAY,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAqOjE;AAED,wBAAgB,cAAc,qBAM7B;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,gBAAgB,GAAG,SAAS,CAErE"}
@@ -82,7 +82,10 @@ function TeamProvider({ children }) {
82
82
  if (currentTeam && userTeams.some((t) => t.team.id === currentTeam.id)) return;
83
83
  const storedTeamId = typeof window !== "undefined" ? localStorage.getItem("activeTeamId") : null;
84
84
  const storedTeam = storedTeamId ? userTeams.find((t) => t.team.id === storedTeamId) : null;
85
- const activeTeam = storedTeam || userTeams[0];
85
+ const defaultTeam = storedTeam ? void 0 : [...userTeams].sort(
86
+ (a, b) => new Date(a.joinedAt).getTime() - new Date(b.joinedAt).getTime()
87
+ )[0];
88
+ const activeTeam = storedTeam || defaultTeam;
86
89
  if (activeTeam) {
87
90
  setCurrentTeam(activeTeam.team);
88
91
  if (typeof window !== "undefined") {
@@ -181,9 +184,13 @@ function useTeamContext() {
181
184
  }
182
185
  return context;
183
186
  }
187
+ function useOptionalTeamContext() {
188
+ return useContext(TeamContext);
189
+ }
184
190
  export {
185
191
  TEAMS_QUERY_KEY,
186
192
  TeamProvider,
187
193
  fetchUserTeams,
194
+ useOptionalTeamContext,
188
195
  useTeamContext
189
196
  };
@@ -1 +1 @@
1
- {"version":3,"file":"otp-verification.d.ts","sourceRoot":"","sources":["../../src/emails/otp-verification.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAKjF,wBAA8B,eAAe,CAC3C,IAAI,EAAE,wBAAwB,EAC9B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAkEvB"}
1
+ {"version":3,"file":"otp-verification.d.ts","sourceRoot":"","sources":["../../src/emails/otp-verification.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,wBAAwB,EAAE,MAAM,oBAAoB,CAAC;AAKjF,wBAA8B,eAAe,CAC3C,IAAI,EAAE,wBAAwB,EAC9B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAmEvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["../../src/emails/reset-password.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAI/E,wBAA8B,aAAa,CACzC,IAAI,EAAE,sBAAsB,EAC5B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA0FvB"}
1
+ {"version":3,"file":"reset-password.d.ts","sourceRoot":"","sources":["../../src/emails/reset-password.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAI/E,wBAA8B,aAAa,CACzC,IAAI,EAAE,sBAAsB,EAC5B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA2FvB"}
@@ -1 +1 @@
1
- {"version":3,"file":"team-invitation.d.ts","sourceRoot":"","sources":["../../src/emails/team-invitation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAIhF,wBAA8B,cAAc,CAC1C,IAAI,EAAE,uBAAuB,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAwFvB"}
1
+ {"version":3,"file":"team-invitation.d.ts","sourceRoot":"","sources":["../../src/emails/team-invitation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAKhF,wBAA8B,cAAc,CAC1C,IAAI,EAAE,uBAAuB,EAC7B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAyFvB"}
@@ -1,4 +1,5 @@
1
1
  import { getTranslations } from "next-intl/server";
2
+ import { escapeHtml } from "../lib/auth/security-notifications/templates/shared.js";
2
3
  const APP_NAME_FALLBACK = process.env.NEXT_PUBLIC_APP_NAME || "Your App";
3
4
  async function teamInvitation(data, locale) {
4
5
  const t = await getTranslations({ locale, namespace: "email.teamInvitation" });
@@ -32,7 +33,7 @@ async function teamInvitation(data, locale) {
32
33
  <h2 style="color: #333333; font-size: 24px; margin: 0 0 20px 0; font-weight: 600;">${t("title")}</h2>
33
34
 
34
35
  <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 20px 0;">
35
- ${t("bodyIntro", { inviterName: data.inviterName, teamName: data.teamName, role: data.role })}
36
+ ${t("bodyIntro", { inviterName: escapeHtml(data.inviterName), teamName: escapeHtml(data.teamName), role: data.role })}
36
37
  </p>
37
38
 
38
39
  <p style="color: #666666; font-size: 16px; line-height: 1.6; margin: 0 0 30px 0;">
@@ -74,7 +75,7 @@ async function teamInvitation(data, locale) {
74
75
  ${t("copyright", { year, appName })}
75
76
  </p>
76
77
  <p style="color: #999999; font-size: 12px; margin: 0;">
77
- ${t("footerSentTo", { inviteeEmail: data.inviteeEmail })}<br>
78
+ ${t("footerSentTo", { inviteeEmail: escapeHtml(data.inviteeEmail) })}<br>
78
79
  ${t("footerUnexpected")}
79
80
  </p>
80
81
  </td>
@@ -1 +1 @@
1
- {"version":3,"file":"verify-email.d.ts","sourceRoot":"","sources":["../../src/emails/verify-email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAI9E,wBAA8B,WAAW,CACvC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CA+EvB"}
1
+ {"version":3,"file":"verify-email.d.ts","sourceRoot":"","sources":["../../src/emails/verify-email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAI9E,wBAA8B,WAAW,CACvC,IAAI,EAAE,qBAAqB,EAC3B,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,YAAY,CAAC,CAgFvB"}
@@ -17,6 +17,7 @@ export * from './useFeature';
17
17
  export * from './useInvoice';
18
18
  export * from './useInvoices';
19
19
  export * from './useLastAuthMethod';
20
+ export * from './useSessionCookieRefresh';
20
21
  export * from './useLocale';
21
22
  export * from './useMedia';
22
23
  export * from './useMediaUpload';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,WAAW,CAAA;AACzB,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/hooks/index.ts"],"names":[],"mappings":"AAGA,cAAc,4BAA4B,CAAA;AAC1C,cAAc,WAAW,CAAA;AACzB,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,+BAA+B,CAAA;AAC7C,cAAc,eAAe,CAAA;AAC7B,cAAc,sBAAsB,CAAA;AACpC,cAAc,yBAAyB,CAAA;AACvC,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,2BAA2B,CAAA;AACzC,cAAc,aAAa,CAAA;AAC3B,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,oBAAoB,CAAA;AAClC,cAAc,aAAa,CAAA;AAC3B,cAAc,oBAAoB,CAAA;AAClC,cAAc,0BAA0B,CAAA;AACxC,cAAc,YAAY,CAAA;AAC1B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,aAAa,CAAA;AAC3B,cAAc,mBAAmB,CAAA;AACjC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA;AACzB,cAAc,sBAAsB,CAAA;AACpC,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,kBAAkB,CAAA;AAChC,cAAc,YAAY,CAAA;AAC1B,cAAc,yBAAyB,CAAA;AACvC,cAAc,qBAAqB,CAAA;AACnC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,mBAAmB,CAAA;AACjC,cAAc,wBAAwB,CAAA"}
@@ -17,6 +17,7 @@ export * from "./useFeature.js";
17
17
  export * from "./useInvoice.js";
18
18
  export * from "./useInvoices.js";
19
19
  export * from "./useLastAuthMethod.js";
20
+ export * from "./useSessionCookieRefresh.js";
20
21
  export * from "./useLocale.js";
21
22
  export * from "./useMedia.js";
22
23
  export * from "./useMediaUpload.js";
@@ -102,6 +102,35 @@ export declare function useAuth(): {
102
102
  })>;
103
103
  signOut: () => Promise<void>;
104
104
  googleSignIn: (redirectTo?: string) => Promise<void>;
105
+ sendOtp: (email: string) => Promise<{
106
+ success: boolean;
107
+ }>;
108
+ signInWithOtp: ({ email, otp, redirectTo }: {
109
+ email: string;
110
+ otp: string;
111
+ redirectTo?: string;
112
+ }) => Promise<Omit<{
113
+ token: string;
114
+ user: {
115
+ id: string;
116
+ createdAt: Date;
117
+ updatedAt: Date;
118
+ email: string;
119
+ emailVerified: boolean;
120
+ name: string;
121
+ image?: string | null | undefined;
122
+ };
123
+ }, "user"> & {
124
+ user: import("better-auth").StripEmptyObjects<{
125
+ id: string;
126
+ createdAt: Date;
127
+ updatedAt: Date;
128
+ emailVerified: boolean;
129
+ name: string;
130
+ email?: string;
131
+ image?: string;
132
+ } & {} & {}>;
133
+ }>;
105
134
  resetPassword: (email: string) => Promise<{
106
135
  success: boolean;
107
136
  data: any;
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/hooks/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAS9C,wBAAgB,OAAO;UAuMS,WAAW,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;8CAjMa;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;+DAmBvC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAkC1H,MAAM;2BAYX,MAAM;;;;;;;;;;;;kCA6BC,MAAM,UAAU,MAAM;;;;;;;;;;;sCAwClB,MAAM,eAAe,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCA8B5B,MAAM;;;;;;;;;;;;;;;EA6C3D"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/hooks/useAuth.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAS9C,wBAAgB,OAAO;UA4OS,WAAW,GAAG,IAAI;;;;;;;;;;;;;;;;;;;;;;;8CAtOa;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;+DAmBvC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gCAuE1H,MAAM;qBAhCjB,MAAM;;;gDAgBiB;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE;;;;;;;;;iBA8Kyf,CAAC;;;;;;;;;;;;;2BAlJ9jB,MAAM;;;;;;;;;;;;kCA6BC,MAAM,UAAU,MAAM;;;;;;;;;;;sCAwClB,MAAM,eAAe,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;qCA8B5B,MAAM;;;;;;;;;;;;;;;EA+C3D"}
@@ -29,6 +29,7 @@ function useAuth() {
29
29
  email,
30
30
  password,
31
31
  name: `${firstName || ""} ${lastName || ""}`.trim() || email.split("@")[0],
32
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
32
33
  firstName,
33
34
  lastName
34
35
  },
@@ -48,6 +49,27 @@ function useAuth() {
48
49
  await authClient.signOut();
49
50
  router.push("/login");
50
51
  };
52
+ const handleSendOtp = async (email) => {
53
+ const { data, error } = await authClient.emailOtp.sendVerificationOtp({
54
+ email,
55
+ type: "sign-in"
56
+ });
57
+ if (error) {
58
+ throw new Error(error.message || "Failed to send the sign-in code");
59
+ }
60
+ return data;
61
+ };
62
+ const handleOtpSignIn = async ({ email, otp, redirectTo }) => {
63
+ const { data, error } = await authClient.signIn.emailOtp({ email, otp });
64
+ if (error) {
65
+ throw new Error(error.message || "Invalid or expired code");
66
+ }
67
+ if (data) {
68
+ saveAuthMethod("email");
69
+ router.push(redirectTo || "/dashboard");
70
+ }
71
+ return data;
72
+ };
51
73
  const handleGoogleSignIn = async (redirectTo) => {
52
74
  const callbackURL = redirectTo ? `${redirectTo}${redirectTo.includes("?") ? "&" : "?"}auth_method=google` : "/dashboard?auth_method=google";
53
75
  await authClient.signIn.social({
@@ -174,6 +196,8 @@ function useAuth() {
174
196
  signUp: handleSignUp,
175
197
  signOut: handleSignOut,
176
198
  googleSignIn: handleGoogleSignIn,
199
+ sendOtp: handleSendOtp,
200
+ signInWithOtp: handleOtpSignIn,
177
201
  resetPassword: handleResetPassword,
178
202
  updatePassword: handleUpdatePassword,
179
203
  changePassword: handleChangePassword,
@@ -1,6 +1,28 @@
1
+ interface UserMetadata {
2
+ uiPreferences?: {
3
+ theme?: string;
4
+ sidebarCollapsed?: boolean;
5
+ };
6
+ securityPreferences?: {
7
+ loginAlertsEnabled?: boolean;
8
+ };
9
+ notificationsPreferences?: Record<string, boolean>;
10
+ }
11
+ /**
12
+ * Returns the default blocks (uiPreferences, securityPreferences,
13
+ * notificationsPreferences) that are still missing from the user's meta.
14
+ *
15
+ * Each block is checked independently: other flows (onboarding progress, tours,
16
+ * active team pointer, ...) write their own keys into `meta` early, so an
17
+ * "is meta empty?" guard would almost never fire for real users (see #119).
18
+ * Only the missing blocks are returned so existing preferences are never
19
+ * overwritten.
20
+ */
21
+ export declare function getMissingMetadataDefaults(meta: UserMetadata | null | undefined): Partial<UserMetadata>;
1
22
  /**
2
23
  * Hook to ensure user has default metadata
3
24
  * Uses TanStack Query for proper caching and deduplication
4
25
  */
5
26
  export declare function useEnsureUserMetadata(): void;
27
+ export {};
6
28
  //# sourceMappingURL=useEnsureUserMetadata.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useEnsureUserMetadata.d.ts","sourceRoot":"","sources":["../../src/hooks/useEnsureUserMetadata.ts"],"names":[],"mappings":"AAsDA;;;GAGG;AACH,wBAAgB,qBAAqB,SA+DpC"}
1
+ {"version":3,"file":"useEnsureUserMetadata.d.ts","sourceRoot":"","sources":["../../src/hooks/useEnsureUserMetadata.ts"],"names":[],"mappings":"AAOA,UAAU,YAAY;IACpB,aAAa,CAAC,EAAE;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,gBAAgB,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;IACD,mBAAmB,CAAC,EAAE;QACpB,kBAAkB,CAAC,EAAE,OAAO,CAAA;KAC7B,CAAA;IACD,wBAAwB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnD;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CACxC,IAAI,EAAE,YAAY,GAAG,IAAI,GAAG,SAAS,GACpC,OAAO,CAAC,YAAY,CAAC,CASvB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,SAgEpC"}
@@ -33,6 +33,17 @@ const DEFAULT_METADATA = {
33
33
  featureAnnouncementsPush: false
34
34
  }
35
35
  };
36
+ const METADATA_BLOCKS = Object.keys(DEFAULT_METADATA);
37
+ function getMissingMetadataDefaults(meta) {
38
+ const missing = {};
39
+ for (const block of METADATA_BLOCKS) {
40
+ const current = meta == null ? void 0 : meta[block];
41
+ if (current === null || current === void 0) {
42
+ missing[block] = DEFAULT_METADATA[block];
43
+ }
44
+ }
45
+ return missing;
46
+ }
36
47
  function useEnsureUserMetadata() {
37
48
  var _a, _b;
38
49
  const session = authClient.useSession();
@@ -57,7 +68,7 @@ function useEnsureUserMetadata() {
57
68
  // 30 minutes cache
58
69
  });
59
70
  const createMetadataMutation = useMutation({
60
- mutationFn: async () => {
71
+ mutationFn: async (metadata) => {
61
72
  const response = await fetch("/api/internal/user-metadata", {
62
73
  method: "POST",
63
74
  headers: {
@@ -65,7 +76,7 @@ function useEnsureUserMetadata() {
65
76
  },
66
77
  body: JSON.stringify({
67
78
  userId,
68
- metadata: DEFAULT_METADATA
79
+ metadata
69
80
  })
70
81
  });
71
82
  if (!response.ok) {
@@ -82,14 +93,16 @@ function useEnsureUserMetadata() {
82
93
  });
83
94
  useEffect(() => {
84
95
  if (!isFetched || !userData || hasCreatedMetadata.current) return;
85
- const needsMetadata = !userData.meta || Object.keys(userData.meta).length === 0;
96
+ const missingDefaults = getMissingMetadataDefaults(userData.meta);
97
+ const needsMetadata = Object.keys(missingDefaults).length > 0;
86
98
  if (needsMetadata && !createMetadataMutation.isPending) {
87
99
  hasCreatedMetadata.current = true;
88
- console.log("Creating default metadata for user:", userId);
89
- createMetadataMutation.mutate();
100
+ console.log("Creating default metadata for user:", userId, Object.keys(missingDefaults));
101
+ createMetadataMutation.mutate(missingDefaults);
90
102
  }
91
103
  }, [isFetched, userData, userId, createMetadataMutation]);
92
104
  }
93
105
  export {
106
+ getMissingMetadataDefaults,
94
107
  useEnsureUserMetadata
95
108
  };
@@ -0,0 +1,35 @@
1
+ export interface UseSessionCookieRefreshOptions {
2
+ /**
3
+ * Minimum time between two refresh calls, in milliseconds. Every trigger
4
+ * inside this window is ignored. Default: 5 minutes (matches the default
5
+ * session cookie cache, so at most one extra DB read per 5 minutes).
6
+ */
7
+ minIntervalMs?: number;
8
+ /**
9
+ * Also refresh once right after mount — i.e. every time the app is opened
10
+ * (PWA cold start, full reload). Default: true.
11
+ */
12
+ refreshOnMount?: boolean;
13
+ /** Set to false to turn the hook off (e.g. on public/auth pages). Default: true. */
14
+ enabled?: boolean;
15
+ }
16
+ export declare const DEFAULT_SESSION_REFRESH_INTERVAL_MS: number;
17
+ /**
18
+ * Keep the session cookie alive for installed PWAs / long-lived tabs.
19
+ *
20
+ * Server Component renders read the session with `disableRefresh`, so they
21
+ * never renew the cookie (Next.js cannot write cookies during render). This
22
+ * hook triggers the real renewal through Better Auth's `get-session` Route
23
+ * Handler — where `Set-Cookie` works — whenever the app is (re)opened:
24
+ *
25
+ * - on mount (app open / reload), unless `refreshOnMount: false`
26
+ * - when the document becomes visible again (`visibilitychange`, `focus`,
27
+ * `pageshow` for bfcache restores)
28
+ * - when connectivity comes back (`online`)
29
+ *
30
+ * Calls are throttled by `minIntervalMs`; a failed call (offline, server
31
+ * down) does not count towards the throttle, so the next trigger retries.
32
+ * Anonymous visitors just get a cheap `null` session response.
33
+ */
34
+ export declare function useSessionCookieRefresh(options?: UseSessionCookieRefreshOptions): void;
35
+ //# sourceMappingURL=useSessionCookieRefresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSessionCookieRefresh.d.ts","sourceRoot":"","sources":["../../src/hooks/useSessionCookieRefresh.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,oFAAoF;IACpF,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,eAAO,MAAM,mCAAmC,QAAgB,CAAA;AAEhE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,GAAE,8BAAmC,QAmDnF"}
@@ -0,0 +1,49 @@
1
+ "use client";
2
+ import { useEffect, useRef } from "react";
3
+ import { refreshSessionCookie } from "../lib/auth-client.js";
4
+ const DEFAULT_SESSION_REFRESH_INTERVAL_MS = 5 * 60 * 1e3;
5
+ function useSessionCookieRefresh(options = {}) {
6
+ const {
7
+ minIntervalMs = DEFAULT_SESSION_REFRESH_INTERVAL_MS,
8
+ refreshOnMount = true,
9
+ enabled = true
10
+ } = options;
11
+ const lastRefreshAtRef = useRef(0);
12
+ const inFlightRef = useRef(false);
13
+ useEffect(() => {
14
+ if (!enabled || typeof window === "undefined") return;
15
+ const refresh = async () => {
16
+ if (inFlightRef.current) return;
17
+ const now = Date.now();
18
+ if (now - lastRefreshAtRef.current < minIntervalMs) return;
19
+ inFlightRef.current = true;
20
+ lastRefreshAtRef.current = now;
21
+ try {
22
+ await refreshSessionCookie();
23
+ } catch {
24
+ lastRefreshAtRef.current = 0;
25
+ } finally {
26
+ inFlightRef.current = false;
27
+ }
28
+ };
29
+ const onVisibilityChange = () => {
30
+ if (document.visibilityState === "visible") void refresh();
31
+ };
32
+ const onWake = () => void refresh();
33
+ document.addEventListener("visibilitychange", onVisibilityChange);
34
+ window.addEventListener("focus", onWake);
35
+ window.addEventListener("pageshow", onWake);
36
+ window.addEventListener("online", onWake);
37
+ if (refreshOnMount) void refresh();
38
+ return () => {
39
+ document.removeEventListener("visibilitychange", onVisibilityChange);
40
+ window.removeEventListener("focus", onWake);
41
+ window.removeEventListener("pageshow", onWake);
42
+ window.removeEventListener("online", onWake);
43
+ };
44
+ }, [enabled, minIntervalMs, refreshOnMount]);
45
+ }
46
+ export {
47
+ DEFAULT_SESSION_REFRESH_INTERVAL_MS,
48
+ useSessionCookieRefresh
49
+ };
@@ -7,8 +7,10 @@
7
7
  export declare const API_ERROR_CODES: {
8
8
  /** Role-based permission check failed (checkPermission) */
9
9
  readonly PERMISSION_DENIED: "PERMISSION_DENIED";
10
- /** API key scope check failed (hasRequiredScope) */
10
+ /** API key lacks the scope the route declared (authenticateRequest / hasRequiredScope) */
11
11
  readonly INSUFFICIENT_SCOPE: "INSUFFICIENT_SCOPE";
12
+ /** Route declared no required API key scope, so API keys are rejected (fail closed, #93) */
13
+ readonly SCOPE_NOT_DECLARED: "SCOPE_NOT_DECLARED";
12
14
  };
13
15
  export type ApiErrorCode = typeof API_ERROR_CODES[keyof typeof API_ERROR_CODES];
14
16
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../../src/lib/api/api-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,eAAe;IAC1B,2DAA2D;;IAE3D,oDAAoD;;CAE5C,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,OAAO,eAAe,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;gBAEL,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;IAa5F;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;;;;OAKG;WACU,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAqC1F"}
1
+ {"version":3,"file":"api-error.d.ts","sourceRoot":"","sources":["../../../src/lib/api/api-error.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,eAAe;IAC1B,2DAA2D;;IAE3D,0FAA0F;;IAE1F,4FAA4F;;CAEpF,CAAA;AAEV,MAAM,MAAM,YAAY,GAAG,OAAO,eAAe,CAAC,MAAM,OAAO,eAAe,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,CAAA;gBAEL,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE;IAa5F;;OAEG;IACH,kBAAkB,IAAI,OAAO;IAI7B;;;;;OAKG;WACU,YAAY,CAAC,QAAQ,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;CAqC1F"}
@@ -1,8 +1,10 @@
1
1
  const API_ERROR_CODES = {
2
2
  /** Role-based permission check failed (checkPermission) */
3
3
  PERMISSION_DENIED: "PERMISSION_DENIED",
4
- /** API key scope check failed (hasRequiredScope) */
5
- INSUFFICIENT_SCOPE: "INSUFFICIENT_SCOPE"
4
+ /** API key lacks the scope the route declared (authenticateRequest / hasRequiredScope) */
5
+ INSUFFICIENT_SCOPE: "INSUFFICIENT_SCOPE",
6
+ /** Route declared no required API key scope, so API keys are rejected (fail closed, #93) */
7
+ SCOPE_NOT_DECLARED: "SCOPE_NOT_DECLARED"
6
8
  };
7
9
  class ApiError extends Error {
8
10
  code;
@@ -5,6 +5,8 @@
5
5
  * Used by the unified /api/v1/ endpoints.
6
6
  */
7
7
  import { NextRequest, NextResponse } from 'next/server';
8
+ import { type AuthenticateOptions, type DualAuthFailure } from './scope-policy';
9
+ export type { AuthenticateOptions, DualAuthFailure, DualAuthFailureCode } from './scope-policy';
8
10
  /**
9
11
  * System Admin Team - Members can bypass team context validation
10
12
  * This team is created in core/migrations/090_sample_data.sql
@@ -33,23 +35,48 @@ export interface DualAuthUser {
33
35
  }
34
36
  export interface DualAuthResult {
35
37
  success: boolean;
38
+ /**
39
+ * How the caller authenticated. On a failed result, `'api-key'` means a
40
+ * valid key WAS presented but rejected by scope enforcement (see `error`);
41
+ * `'none'` means no usable credential was found.
42
+ */
36
43
  type: 'api-key' | 'session' | 'none';
37
44
  user: DualAuthUser | null;
38
45
  scopes?: string[];
46
+ /** `api_key.id` of the key that authenticated the request (type 'api-key' only). Used for audit logging (#105). */
47
+ keyId?: string;
39
48
  rateLimitResponse?: Response;
40
49
  /** Set when a dev-only x-act-as-user override replaced the real caller. */
41
50
  actingAs?: {
42
51
  originalUserId: string;
43
52
  originalRole: string;
44
53
  };
54
+ /** Why the request was rejected. Only present when `success` is false. */
55
+ error?: DualAuthFailure;
45
56
  }
46
57
  /**
47
- * Try to authenticate request using either API Key or Session
58
+ * Try to authenticate request using either API Key or Session.
59
+ *
60
+ * API-key scope enforcement happens HERE and fails closed (#93): a route
61
+ * declares the scope it needs via `options.requiredScope`, or explicitly
62
+ * accepts any valid key via `options.allowAnyScope`. A key hitting a route
63
+ * that declared neither is rejected (403 `SCOPE_NOT_DECLARED`) and the route
64
+ * is named in the log, so a forgotten declaration is visible instead of a
65
+ * silently over-privileged credential. Session auth is unaffected.
66
+ *
67
+ * @example
68
+ * const authResult = await authenticateRequest(request, { requiredScope: 'users:read' })
69
+ * if (!authResult.success || !authResult.user) return createAuthFailureResponse(authResult)
48
70
  */
49
- export declare function authenticateRequest(request: NextRequest): Promise<DualAuthResult>;
71
+ export declare function authenticateRequest(request: NextRequest, options?: AuthenticateOptions): Promise<DualAuthResult>;
50
72
  /**
51
73
  * Check if user has required scope (for API Key auth)
52
74
  *
75
+ * Since #93 the entry point already enforces the route's declared scope;
76
+ * this remains for routes that branch on a *second*, finer scope after
77
+ * authenticating (e.g. read-vs-write within one handler). An array is
78
+ * satisfied by any one of its scopes; an empty array by none.
79
+ *
53
80
  * `admin:all` was previously accepted as an implicit full-access scope here,
54
81
  * but it was never in `API_SCOPES`, never mintable via `validateScopesForUser`,
55
82
  * and undocumented — a full-access string nothing legitimately produced.
@@ -57,7 +84,7 @@ export declare function authenticateRequest(request: NextRequest): Promise<DualA
57
84
  * superadmin, see `validateScopesForUser`) already covers the "this key has
58
85
  * full access" case in a documented, auditable way.
59
86
  */
60
- export declare function hasRequiredScope(authResult: DualAuthResult, requiredScope: string): boolean;
87
+ export declare function hasRequiredScope(authResult: DualAuthResult, requiredScope: string | string[]): boolean;
61
88
  /**
62
89
  * Resolve and validate team context from request.
63
90
  *
@@ -92,4 +119,12 @@ export declare function createAuthError(message?: string, status?: number): Next
92
119
  error: string;
93
120
  code: string;
94
121
  }>;
122
+ /**
123
+ * Turn a failed `authenticateRequest` result into the right HTTP response:
124
+ * 401 `AUTHENTICATION_FAILED` when nothing usable was presented, 403
125
+ * `INSUFFICIENT_SCOPE` / `SCOPE_NOT_DECLARED` when a valid API key was
126
+ * rejected by scope enforcement. Use it in the `!authResult.success` branch
127
+ * instead of a hand-written 401, so scope rejections are not mislabeled.
128
+ */
129
+ export declare function createAuthFailureResponse(authResult: DualAuthResult): NextResponse;
95
130
  //# sourceMappingURL=dual-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAUvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AACnD,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5D;AAqBD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBvF;AAqKD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAW3F;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,CAsChC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAkBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAkC,EAAE,MAAM,GAAE,MAAY;;;;GAShG"}
1
+ {"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAMvD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAKrB,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAK/F;;;GAGG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AACnD,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,mHAAmH;IACnH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,0EAA0E;IAC1E,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAqBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,cAAc,CAAC,CAsCzB;AAuKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAUtG;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,CAsChC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAkBlB;AA2BD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAkC,EAAE,MAAM,GAAE,MAAY;;;;GAShG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,cAAc,GAAG,YAAY,CAUlF"}
@@ -4,6 +4,12 @@ import { validateApiKey } from "../auth.js";
4
4
  import { queryOne } from "../../db.js";
5
5
  import { TeamMemberService } from "../../services/team-member.service.js";
6
6
  import { TeamService } from "../../services/team.service.js";
7
+ import {
8
+ authenticationFailed,
9
+ describeRoute,
10
+ resolveApiKeyScopeFailure,
11
+ scopesSatisfy
12
+ } from "./scope-policy.js";
7
13
  const SYSTEM_ADMIN_TEAM_ID = "team-nextspark-001";
8
14
  const ADMIN_BYPASS_HEADER = "x-admin-bypass";
9
15
  const ADMIN_BYPASS_VALUE = "confirm-cross-team-access";
@@ -24,9 +30,20 @@ async function getUserDefaultTeamId(userId) {
24
30
  return void 0;
25
31
  }
26
32
  }
27
- async function authenticateRequest(request) {
33
+ async function authenticateRequest(request, options = {}) {
28
34
  const apiKeyResult = await tryApiKeyAuth(request);
29
35
  if (apiKeyResult.success) {
36
+ const failure = resolveApiKeyScopeFailure(apiKeyResult.scopes, options, describeRoute(request));
37
+ if (failure) {
38
+ return {
39
+ success: false,
40
+ type: "api-key",
41
+ user: apiKeyResult.user,
42
+ scopes: apiKeyResult.scopes,
43
+ keyId: apiKeyResult.keyId,
44
+ error: failure
45
+ };
46
+ }
30
47
  return applyActAsOverride(request, apiKeyResult);
31
48
  }
32
49
  const sessionResult = await trySessionAuth(request);
@@ -36,7 +53,8 @@ async function authenticateRequest(request) {
36
53
  return {
37
54
  success: false,
38
55
  type: "none",
39
- user: null
56
+ user: null,
57
+ error: authenticationFailed()
40
58
  };
41
59
  }
42
60
  function isProductionRuntime() {
@@ -104,7 +122,8 @@ async function tryApiKeyAuth(request) {
104
122
  name: userInfo == null ? void 0 : userInfo.name,
105
123
  defaultTeamId
106
124
  },
107
- scopes: apiAuth.scopes || []
125
+ scopes: apiAuth.scopes || [],
126
+ keyId: apiAuth.keyId
108
127
  };
109
128
  } catch (error) {
110
129
  console.error("API Key auth failed:", error);
@@ -128,6 +147,7 @@ async function trySessionAuth(request) {
128
147
  user: {
129
148
  id: session.user.id,
130
149
  email: session.user.email,
150
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
131
151
  role: session.user.role || "user",
132
152
  name: session.user.name,
133
153
  defaultTeamId
@@ -144,8 +164,8 @@ function hasRequiredScope(authResult, requiredScope) {
144
164
  if (authResult.type === "session") {
145
165
  return true;
146
166
  }
147
- if (authResult.type === "api-key" && authResult.scopes) {
148
- return authResult.scopes.includes(requiredScope) || authResult.scopes.includes("*");
167
+ if (authResult.type === "api-key") {
168
+ return scopesSatisfy(authResult.scopes, requiredScope);
149
169
  }
150
170
  return false;
151
171
  }
@@ -191,8 +211,11 @@ async function canBypassTeamContext(authResult, request) {
191
211
  async function checkSystemAdminMembership(userId) {
192
212
  try {
193
213
  const member = await queryOne(
194
- 'SELECT id FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
195
- [SYSTEM_ADMIN_TEAM_ID, userId]
214
+ `SELECT tm.id FROM "team_members" tm
215
+ JOIN "teams" t ON t.id = tm."teamId"
216
+ WHERE tm."userId" = $1 AND (t.metadata->>'systemAdmin')::boolean IS TRUE
217
+ LIMIT 1`,
218
+ [userId]
196
219
  );
197
220
  return !!member;
198
221
  } catch (error) {
@@ -210,6 +233,17 @@ function createAuthError(message = "Authentication required", status = 401) {
210
233
  { status }
211
234
  );
212
235
  }
236
+ function createAuthFailureResponse(authResult) {
237
+ const failure = authResult.error ?? authenticationFailed();
238
+ return NextResponse.json(
239
+ {
240
+ success: false,
241
+ error: failure.message,
242
+ code: failure.code
243
+ },
244
+ { status: failure.status }
245
+ );
246
+ }
213
247
  export {
214
248
  ACT_AS_USER_HEADER,
215
249
  ADMIN_BYPASS_HEADER,
@@ -218,6 +252,7 @@ export {
218
252
  authenticateRequest,
219
253
  canBypassTeamContext,
220
254
  createAuthError,
255
+ createAuthFailureResponse,
221
256
  hasRequiredScope,
222
257
  resolveTeamContext
223
258
  };
@@ -17,9 +17,14 @@ export declare function isSuperAdmin(authResult: DualAuthResult): boolean;
17
17
  * Verifica si el usuario tiene permisos para operaciones administrativas
18
18
  * Combina validacion de superadmin + scopes de API key
19
19
  *
20
+ * Fail closed for API keys (#93): a key is only granted admin access when the
21
+ * caller names the scope the operation needs and the key holds it. Omitting
22
+ * `requiredScope` used to hand a narrowly-scoped key its superadmin owner's
23
+ * full permissions; now it denies the key instead. Sessions are unaffected.
24
+ *
20
25
  * @param authResult - Resultado de autenticacion
21
- * @param requiredScope - Scope requerido para API keys (opcional)
26
+ * @param requiredScope - Scope requerido para API keys (obligatorio para que una API key pase)
22
27
  * @returns true si tiene permisos de admin
23
28
  */
24
- export declare function hasAdminPermission(authResult: DualAuthResult, requiredScope?: string): boolean;
29
+ export declare function hasAdminPermission(authResult: DualAuthResult, requiredScope?: string | string[]): boolean;
25
30
  //# sourceMappingURL=permissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAYT"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,OAAO,CAYT"}