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

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 (518) hide show
  1. package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
  2. package/dist/components/app/guards/DeveloperGuard.js +8 -4
  3. package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
  4. package/dist/components/app/guards/SuperAdminGuard.js +8 -4
  5. package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
  6. package/dist/components/app/layouts/PublicFooter.js +2 -2
  7. package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
  8. package/dist/components/app/layouts/PublicNavbar.js +2 -2
  9. package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
  10. package/dist/components/app/misc/ThemeToggle.js +4 -1
  11. package/dist/components/auth/DevKeyring.d.ts.map +1 -1
  12. package/dist/components/auth/DevKeyring.js +10 -2
  13. package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
  14. package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
  15. package/dist/components/auth/SessionCookieRefresher.js +11 -0
  16. package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
  17. package/dist/components/auth/forms/LoginForm.js +300 -6
  18. package/dist/components/auth/index.d.ts +1 -0
  19. package/dist/components/auth/index.d.ts.map +1 -1
  20. package/dist/components/auth/index.js +2 -0
  21. package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
  22. package/dist/components/billing/InvoicesTable.js +4 -3
  23. package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
  24. package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
  25. package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
  26. package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
  27. package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
  28. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
  29. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
  30. package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
  31. package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
  32. package/dist/components/dashboard/layouts/Sidebar.js +2 -3
  33. package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
  34. package/dist/components/dashboard/layouts/index.d.ts +1 -0
  35. package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
  36. package/dist/components/dashboard/layouts/index.js +2 -0
  37. package/dist/components/devtools/api-explorer/ApiExplorer.js +4 -4
  38. package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
  39. package/dist/components/entities/EntityFieldRenderer.js +3 -0
  40. package/dist/components/entities/EntityTable.d.ts.map +1 -1
  41. package/dist/components/entities/EntityTable.js +2 -1
  42. package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
  43. package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
  44. package/dist/components/patterns/pages/PatternEditPage.d.ts +5 -1
  45. package/dist/components/patterns/pages/PatternEditPage.d.ts.map +1 -1
  46. package/dist/components/patterns/pages/PatternEditPage.js +16 -5
  47. package/dist/components/patterns/pages/PatternsCreatePage.d.ts +2 -1
  48. package/dist/components/patterns/pages/PatternsCreatePage.d.ts.map +1 -1
  49. package/dist/components/patterns/pages/PatternsCreatePage.js +17 -5
  50. package/dist/components/ui/combobox.d.ts +8 -1
  51. package/dist/components/ui/combobox.d.ts.map +1 -1
  52. package/dist/components/ui/combobox.js +37 -44
  53. package/dist/components/ui/country-select.d.ts +3 -1
  54. package/dist/components/ui/country-select.d.ts.map +1 -1
  55. package/dist/components/ui/country-select.js +3 -1
  56. package/dist/components/ui/currency-select.d.ts +3 -1
  57. package/dist/components/ui/currency-select.d.ts.map +1 -1
  58. package/dist/components/ui/currency-select.js +3 -1
  59. package/dist/components/ui/sheet.d.ts +5 -0
  60. package/dist/components/ui/sheet.d.ts.map +1 -1
  61. package/dist/components/ui/sheet.js +46 -26
  62. package/dist/components/ui/timezone-select.d.ts +3 -1
  63. package/dist/components/ui/timezone-select.d.ts.map +1 -1
  64. package/dist/components/ui/timezone-select.js +3 -1
  65. package/dist/contexts/TeamContext.d.ts +6 -0
  66. package/dist/contexts/TeamContext.d.ts.map +1 -1
  67. package/dist/contexts/TeamContext.js +8 -1
  68. package/dist/emails/otp-verification.d.ts.map +1 -1
  69. package/dist/emails/reset-password.d.ts.map +1 -1
  70. package/dist/emails/team-invitation.d.ts.map +1 -1
  71. package/dist/emails/team-invitation.js +3 -2
  72. package/dist/emails/verify-email.d.ts.map +1 -1
  73. package/dist/hooks/index.d.ts +1 -0
  74. package/dist/hooks/index.d.ts.map +1 -1
  75. package/dist/hooks/index.js +1 -0
  76. package/dist/hooks/useAuth.d.ts +29 -0
  77. package/dist/hooks/useAuth.d.ts.map +1 -1
  78. package/dist/hooks/useAuth.js +24 -0
  79. package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
  80. package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
  81. package/dist/hooks/useEnsureUserMetadata.js +18 -5
  82. package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
  83. package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
  84. package/dist/hooks/useSessionCookieRefresh.js +49 -0
  85. package/dist/lib/api/api-error.d.ts +3 -1
  86. package/dist/lib/api/api-error.d.ts.map +1 -1
  87. package/dist/lib/api/api-error.js +4 -2
  88. package/dist/lib/api/auth/dual-auth.d.ts +38 -3
  89. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  90. package/dist/lib/api/auth/dual-auth.js +42 -7
  91. package/dist/lib/api/auth/permissions.d.ts +7 -2
  92. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  93. package/dist/lib/api/auth/permissions.js +2 -2
  94. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  95. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  96. package/dist/lib/api/auth/scope-policy.js +53 -0
  97. package/dist/lib/api/auth.d.ts +15 -0
  98. package/dist/lib/api/auth.d.ts.map +1 -1
  99. package/dist/lib/api/auth.js +17 -4
  100. package/dist/lib/api/entities.js +2 -1
  101. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  102. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  103. package/dist/lib/api/entity/audit-log.js +35 -0
  104. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  105. package/dist/lib/api/entity/generic-handler.js +333 -138
  106. package/dist/lib/api/entity-handler.js +2 -2
  107. package/dist/lib/api/helpers.d.ts +29 -4
  108. package/dist/lib/api/helpers.d.ts.map +1 -1
  109. package/dist/lib/api/helpers.js +30 -6
  110. package/dist/lib/api/keys.d.ts +16 -0
  111. package/dist/lib/api/keys.d.ts.map +1 -1
  112. package/dist/lib/api/keys.js +19 -1
  113. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  114. package/dist/lib/api/routes/patterns-usages.js +3 -11
  115. package/dist/lib/auth/auth-methods.d.ts +52 -0
  116. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  117. package/dist/lib/auth/auth-methods.js +63 -0
  118. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  119. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  120. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  121. package/dist/lib/auth/registration-helpers.js +8 -2
  122. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  123. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  124. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  125. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  126. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  127. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  128. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  129. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  130. package/dist/lib/auth/security-notifications/index.js +22 -0
  131. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  132. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  133. package/dist/lib/auth/security-notifications/registry.js +26 -0
  134. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  135. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  136. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  137. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  138. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  139. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  140. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  141. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  142. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  143. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  144. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  145. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  146. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  147. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  148. package/dist/lib/auth/session-config.d.ts +37 -0
  149. package/dist/lib/auth/session-config.d.ts.map +1 -0
  150. package/dist/lib/auth/session-config.js +59 -0
  151. package/dist/lib/auth/session-refresh.d.ts +46 -0
  152. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  153. package/dist/lib/auth/session-refresh.js +11 -0
  154. package/dist/lib/auth-client.d.ts +1639 -0
  155. package/dist/lib/auth-client.d.ts.map +1 -0
  156. package/dist/lib/auth-client.js +13 -1
  157. package/dist/lib/auth.d.ts.map +1 -1
  158. package/dist/lib/auth.js +45 -11
  159. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  160. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  161. package/dist/lib/billing/gateways/factory.js +37 -23
  162. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  163. package/dist/lib/billing/stripe-webhook.js +2 -1
  164. package/dist/lib/config/app.config.d.ts.map +1 -1
  165. package/dist/lib/config/app.config.js +71 -0
  166. package/dist/lib/config/config-sync.d.ts +1 -4
  167. package/dist/lib/config/config-sync.d.ts.map +1 -1
  168. package/dist/lib/config/config-sync.js +6 -2
  169. package/dist/lib/config/types.d.ts +130 -0
  170. package/dist/lib/config/types.d.ts.map +1 -1
  171. package/dist/lib/email/templates.d.ts +3 -3
  172. package/dist/lib/email/templates.d.ts.map +1 -1
  173. package/dist/lib/entities/api-generator.js +5 -5
  174. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  175. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  176. package/dist/lib/entities/entity-hooks.js +77 -4
  177. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  178. package/dist/lib/entities/schema-generator.js +20 -4
  179. package/dist/lib/entities/serialization.d.ts +2 -2
  180. package/dist/lib/entities/serialization.d.ts.map +1 -1
  181. package/dist/lib/entities/serialization.js +3 -2
  182. package/dist/lib/entities/types.d.ts +20 -0
  183. package/dist/lib/entities/types.d.ts.map +1 -1
  184. package/dist/lib/locale.d.ts.map +1 -1
  185. package/dist/lib/locale.js +4 -1
  186. package/dist/lib/mcp/audit.d.ts +8 -4
  187. package/dist/lib/mcp/audit.d.ts.map +1 -1
  188. package/dist/lib/plugins/hook-system.d.ts +13 -0
  189. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  190. package/dist/lib/plugins/hook-system.js +32 -2
  191. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  192. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  193. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  194. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  195. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  196. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  197. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  198. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  199. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  200. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  201. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  202. package/dist/lib/selectors/selectors.d.ts +24 -0
  203. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  204. package/dist/lib/services/api-docs.service.d.ts +5 -1
  205. package/dist/lib/services/api-docs.service.d.ts.map +1 -1
  206. package/dist/lib/services/api-presets.service.d.ts +6 -1
  207. package/dist/lib/services/api-presets.service.d.ts.map +1 -1
  208. package/dist/lib/services/feature.service.d.ts +58 -4
  209. package/dist/lib/services/feature.service.d.ts.map +1 -1
  210. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  211. package/dist/lib/services/generic-entity.service.js +16 -32
  212. package/dist/lib/services/membership.service.d.ts +10 -2
  213. package/dist/lib/services/membership.service.d.ts.map +1 -1
  214. package/dist/lib/services/membership.service.js +37 -16
  215. package/dist/lib/services/permission.service.d.ts +10 -4
  216. package/dist/lib/services/permission.service.d.ts.map +1 -1
  217. package/dist/lib/services/plugin.service.d.ts +1 -1
  218. package/dist/lib/services/plugin.service.d.ts.map +1 -1
  219. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  220. package/dist/lib/services/subscription.service.js +2 -1
  221. package/dist/lib/services/team-member.service.d.ts +15 -3
  222. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  223. package/dist/lib/services/team-member.service.js +27 -6
  224. package/dist/lib/services/team.service.d.ts.map +1 -1
  225. package/dist/lib/teams/permissions.d.ts +3 -1
  226. package/dist/lib/teams/permissions.d.ts.map +1 -1
  227. package/dist/lib/teams/permissions.js +6 -0
  228. package/dist/lib/teams/schema.d.ts.map +1 -1
  229. package/dist/lib/teams/schema.js +3 -0
  230. package/dist/lib/template-resolver.d.ts +3 -3
  231. package/dist/lib/theme/forced-theme.d.ts +34 -0
  232. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  233. package/dist/lib/theme/forced-theme.js +27 -0
  234. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  235. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  236. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  237. package/dist/lib/utils/cors.d.ts +13 -0
  238. package/dist/lib/utils/cors.d.ts.map +1 -1
  239. package/dist/lib/utils/cors.js +20 -0
  240. package/dist/messages/de/auth.json +25 -2
  241. package/dist/messages/de/index.d.ts +83 -0
  242. package/dist/messages/de/index.d.ts.map +1 -1
  243. package/dist/messages/de/index.js +2 -0
  244. package/dist/messages/de/index.ts +2 -0
  245. package/dist/messages/de/navigation.json +3 -1
  246. package/dist/messages/en/auth.json +25 -2
  247. package/dist/messages/en/index.d.ts +83 -0
  248. package/dist/messages/en/index.d.ts.map +1 -1
  249. package/dist/messages/en/index.js +2 -0
  250. package/dist/messages/en/index.ts +2 -0
  251. package/dist/messages/en/navigation.json +3 -1
  252. package/dist/messages/es/auth.json +25 -2
  253. package/dist/messages/es/index.d.ts +83 -0
  254. package/dist/messages/es/index.d.ts.map +1 -1
  255. package/dist/messages/es/index.js +2 -0
  256. package/dist/messages/es/index.ts +2 -0
  257. package/dist/messages/es/navigation.json +3 -1
  258. package/dist/messages/fr/auth.json +25 -2
  259. package/dist/messages/fr/index.d.ts +83 -0
  260. package/dist/messages/fr/index.d.ts.map +1 -1
  261. package/dist/messages/fr/index.js +2 -0
  262. package/dist/messages/fr/index.ts +2 -0
  263. package/dist/messages/fr/navigation.json +3 -1
  264. package/dist/messages/it/auth.json +25 -2
  265. package/dist/messages/it/index.d.ts +83 -0
  266. package/dist/messages/it/index.d.ts.map +1 -1
  267. package/dist/messages/it/index.js +2 -0
  268. package/dist/messages/it/index.ts +2 -0
  269. package/dist/messages/it/navigation.json +3 -1
  270. package/dist/messages/pt/auth.json +25 -2
  271. package/dist/messages/pt/index.d.ts +83 -0
  272. package/dist/messages/pt/index.d.ts.map +1 -1
  273. package/dist/messages/pt/index.js +2 -0
  274. package/dist/messages/pt/index.ts +2 -0
  275. package/dist/messages/pt/navigation.json +3 -1
  276. package/dist/migrations/005_api_audit_log.sql +2 -1
  277. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  278. package/dist/migrations/026_login_events.sql +91 -0
  279. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  280. package/dist/nextspark-registries.d.ts +580 -236
  281. package/dist/providers/DashboardProviders.d.ts +12 -3
  282. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  283. package/dist/providers/DashboardProviders.js +6 -2
  284. package/dist/providers/theme-provider.d.ts +19 -2
  285. package/dist/providers/theme-provider.d.ts.map +1 -1
  286. package/dist/providers/theme-provider.js +6 -2
  287. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  288. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  289. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  290. package/dist/templates/app/api/health/route.ts +4 -2
  291. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  292. package/dist/templates/app/api/mcp/route.ts +4 -1
  293. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  294. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  295. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  296. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  297. package/dist/templates/app/api/v1/api-keys/route.ts +21 -43
  298. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  299. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  300. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  301. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  302. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  303. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  304. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  305. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  306. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  307. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  308. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  309. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  310. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  311. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  312. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  313. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  314. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  315. package/dist/templates/app/api/v1/media/route.ts +5 -9
  316. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  317. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  318. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  319. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  320. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  321. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  322. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  323. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  324. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  325. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  326. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  327. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  328. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  329. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  330. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  331. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  332. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  333. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  334. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  335. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  336. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  337. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  338. package/dist/templates/app/api/v1/users/route.ts +21 -25
  339. package/dist/templates/app/dashboard/layout.tsx +2 -6
  340. package/dist/templates/app/layout.ppr.tsx +15 -15
  341. package/dist/templates/app/layout.tsx +46 -20
  342. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  343. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  344. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  345. package/dist/templates/proxy.ts +46 -10
  346. package/dist/templates/tsconfig.json +4 -0
  347. package/dist/types/theme.d.ts +9 -0
  348. package/dist/types/theme.d.ts.map +1 -1
  349. package/migrations/005_api_audit_log.sql +2 -1
  350. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  351. package/migrations/026_login_events.sql +91 -0
  352. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  353. package/package.json +14 -11
  354. package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
  355. package/scripts/build/registry/generators/scope-registry.mjs +9 -2
  356. package/templates/app/(auth)/signup/page.tsx +8 -0
  357. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  358. package/templates/app/api/auth/[...all]/route.ts +23 -1
  359. package/templates/app/api/health/route.ts +4 -2
  360. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  361. package/templates/app/api/mcp/route.ts +4 -1
  362. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  363. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  364. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  365. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  366. package/templates/app/api/v1/api-keys/route.ts +21 -43
  367. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  368. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  369. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  370. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  371. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  372. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  373. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  374. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  375. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  376. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  377. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  378. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  379. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  380. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  381. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  382. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  383. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  384. package/templates/app/api/v1/media/route.ts +5 -9
  385. package/templates/app/api/v1/media/upload/route.ts +9 -21
  386. package/templates/app/api/v1/media-tags/route.ts +9 -13
  387. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  388. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  389. package/templates/app/api/v1/post-categories/route.ts +5 -4
  390. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  391. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  392. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  393. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  394. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  395. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  396. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  397. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  398. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  399. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  400. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  401. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  402. package/templates/app/api/v1/teams/route.ts +33 -27
  403. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  404. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  405. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  406. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  407. package/templates/app/api/v1/users/route.ts +21 -25
  408. package/templates/app/dashboard/layout.tsx +2 -6
  409. package/templates/app/layout.ppr.tsx +15 -15
  410. package/templates/app/layout.tsx +46 -20
  411. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  412. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  413. package/templates/contents/themes/starter/styles/globals.css +6 -2
  414. package/templates/proxy.ts +46 -10
  415. package/templates/tsconfig.json +4 -0
  416. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  417. package/tests/jest/__mocks__/next-server.js +24 -0
  418. package/dist/components/teams/TeamSwitchModal.d.js +0 -0
  419. package/dist/components/ui/accordion.d.js +0 -0
  420. package/dist/components/ui/avatar.d.js +0 -0
  421. package/dist/components/ui/badge.d.js +0 -0
  422. package/dist/components/ui/button.d.js +0 -0
  423. package/dist/components/ui/card.d.js +0 -0
  424. package/dist/components/ui/dialog.d.js +0 -0
  425. package/dist/components/ui/dropdown-menu.d.js +0 -0
  426. package/dist/components/ui/input.d.js +0 -0
  427. package/dist/components/ui/progress.d.js +0 -0
  428. package/dist/components/ui/scroll-area.d.js +0 -0
  429. package/dist/components/ui/select.d.js +0 -0
  430. package/dist/components/ui/separator.d.js +0 -0
  431. package/dist/components/ui/table.d.js +0 -0
  432. package/dist/components/ui/textarea.d.js +0 -0
  433. package/dist/contexts/TeamContext.d.js +0 -0
  434. package/dist/hooks/useAuth.d.js +0 -0
  435. package/dist/hooks/useLastAuthMethod.d.js +0 -0
  436. package/dist/hooks/useOrigin.d.js +0 -0
  437. package/dist/lib/api/auth/dual-auth.d.js +0 -0
  438. package/dist/lib/api/auth.d.js +0 -0
  439. package/dist/lib/api/cache.d.js +0 -0
  440. package/dist/lib/api/keys.d.js +0 -0
  441. package/dist/lib/auth-client.d.js +0 -0
  442. package/dist/lib/auth-context.d.js +0 -0
  443. package/dist/lib/auth.d.js +0 -0
  444. package/dist/lib/billing/config-types.d.js +0 -0
  445. package/dist/lib/billing/enforcement.d.js +0 -0
  446. package/dist/lib/billing/helpers.d.js +0 -0
  447. package/dist/lib/billing/jobs.d.js +0 -0
  448. package/dist/lib/billing/types.d.js +0 -0
  449. package/dist/lib/blocks/loader.d.js +0 -0
  450. package/dist/lib/config/app.config.d.js +0 -0
  451. package/dist/lib/config/config-loader.d.js +0 -0
  452. package/dist/lib/config/config-sync.d.js +0 -0
  453. package/dist/lib/config/config-types.d.js +0 -0
  454. package/dist/lib/config/config-utils.d.js +0 -0
  455. package/dist/lib/config/dashboard-types.d.js +0 -0
  456. package/dist/lib/config/dashboard.config.d.js +0 -0
  457. package/dist/lib/config/icon-map.d.js +0 -0
  458. package/dist/lib/config/index.d.js +0 -7
  459. package/dist/lib/config/nextspark-types.d.js +0 -0
  460. package/dist/lib/config/roles-merge.d.js +0 -0
  461. package/dist/lib/config/types.d.js +0 -0
  462. package/dist/lib/db.d.js +0 -0
  463. package/dist/lib/email/factory.d.js +0 -0
  464. package/dist/lib/email/index.d.js +0 -3
  465. package/dist/lib/email/providers/console.d.js +0 -0
  466. package/dist/lib/email/providers/resend.d.js +0 -0
  467. package/dist/lib/email/templates.d.js +0 -0
  468. package/dist/lib/email/types.d.js +0 -0
  469. package/dist/lib/entities/types.d.js +0 -0
  470. package/dist/lib/permissions/system.d.js +0 -0
  471. package/dist/lib/permissions/types.d.js +0 -0
  472. package/dist/lib/plugins/hook-system.d.js +0 -0
  473. package/dist/lib/scheduled-actions/cleanup.d.js +0 -0
  474. package/dist/lib/scheduled-actions/index.d.js +0 -19
  475. package/dist/lib/scheduled-actions/initializer.d.js +0 -1
  476. package/dist/lib/scheduled-actions/processor.d.js +0 -0
  477. package/dist/lib/scheduled-actions/registry.d.js +0 -0
  478. package/dist/lib/scheduled-actions/scheduler.d.js +0 -0
  479. package/dist/lib/scheduled-actions/types.d.js +0 -0
  480. package/dist/lib/services/api-routes.service.d.js +0 -0
  481. package/dist/lib/services/entity-type.service.d.js +0 -0
  482. package/dist/lib/services/index.d.js +0 -40
  483. package/dist/lib/services/invoice.service.d.js +0 -0
  484. package/dist/lib/services/membership.service.d.js +0 -0
  485. package/dist/lib/services/meta.service.d.js +0 -0
  486. package/dist/lib/services/middleware.service.d.js +0 -0
  487. package/dist/lib/services/namespace.service.d.js +0 -0
  488. package/dist/lib/services/permission.service.d.js +0 -0
  489. package/dist/lib/services/plan.service.d.js +0 -0
  490. package/dist/lib/services/plugin.service.d.js +0 -0
  491. package/dist/lib/services/route-handler.service.d.js +0 -0
  492. package/dist/lib/services/scope.service.d.js +0 -0
  493. package/dist/lib/services/subscription.service.d.js +0 -0
  494. package/dist/lib/services/team-member.service.d.js +0 -0
  495. package/dist/lib/services/team.service.d.js +0 -0
  496. package/dist/lib/services/theme.service.d.js +0 -0
  497. package/dist/lib/services/usage.service.d.js +0 -0
  498. package/dist/lib/services/user-flags.service.d.js +0 -0
  499. package/dist/lib/services/user.service.d.js +0 -0
  500. package/dist/lib/teams/helpers.d.js +0 -0
  501. package/dist/lib/teams/permissions.d.js +0 -0
  502. package/dist/lib/teams/types.d.js +0 -0
  503. package/dist/lib/utils/config-merge.d.js +0 -0
  504. package/dist/lib/utils/file-logger.d.js +0 -0
  505. package/dist/lib/utils.d.js +0 -0
  506. package/dist/styles/classes.json +0 -1093
  507. package/dist/styles/ui.css +0 -2
  508. package/dist/types/auth.d.js +0 -0
  509. package/dist/types/blocks.d.js +0 -0
  510. package/dist/types/meta.types.d.js +0 -0
  511. package/dist/types/plugin.d.js +0 -0
  512. package/dist/types/testing.types.d.js +0 -0
  513. package/dist/types/theme.d.js +0 -0
  514. package/dist/types/user.types.d.js +0 -0
  515. package/scripts/build/build-ui-css.mjs +0 -351
  516. /package/dist/{components/app/layouts/PublicFooter.d.js → lib/auth/security-notifications/types.js} +0 -0
  517. /package/dist/{components/app/layouts/PublicNavbar.d.js → nextspark-registries.d.js} +0 -0
  518. /package/dist/{components/app/misc/ThemeToggle.d.js → types/use-intl-react-shim.d.js} +0 -0
@@ -1,8 +1,17 @@
1
1
  /**
2
- * DashboardProviders — Client providers needed only for authenticated routes.
2
+ * DashboardProviders — Client providers needed by authenticated routes.
3
3
  *
4
- * These were moved out of the root layout to avoid loading ~500KB of unused JS
5
- * (TanStack Query, team context, billing hooks) on public landing pages.
4
+ * Since #115 the generated root layout mounts QueryProvider, TeamProvider,
5
+ * SubscriptionProvider and Toaster itself (matching layout.ppr.tsx) so the
6
+ * activeTeamId cookie is synced from any route, not only from the dashboard.
7
+ * When a TeamProvider is already mounted above, this component renders its
8
+ * children as-is: nesting a second TeamProvider would duplicate the teams
9
+ * fetch, the cookie sync and the TeamSwitchModal, and a second Toaster would
10
+ * show every toast twice.
11
+ *
12
+ * The full provider stack is kept as a fallback for apps whose generated root
13
+ * layout predates that change (it originally lived here to keep ~500KB of
14
+ * client JS off public landing pages).
6
15
  *
7
16
  * Used by: dashboard/layout.tsx, superadmin/layout.tsx, devtools/layout.tsx
8
17
  */
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardProviders.d.ts","sourceRoot":"","sources":["../../src/providers/DashboardProviders.tsx"],"names":[],"mappings":"AAQA;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAW7E"}
1
+ {"version":3,"file":"DashboardProviders.d.ts","sourceRoot":"","sources":["../../src/providers/DashboardProviders.tsx"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAAE,2CAiB7E"}
@@ -1,11 +1,15 @@
1
1
  "use client";
2
- import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { Suspense } from "react";
4
4
  import { QueryProvider } from "./query-provider.js";
5
- import { TeamProvider } from "../contexts/TeamContext.js";
5
+ import { TeamProvider, useOptionalTeamContext } from "../contexts/TeamContext.js";
6
6
  import { SubscriptionProvider } from "../contexts/SubscriptionContext.js";
7
7
  import { Toaster } from "../components/ui/sonner.js";
8
8
  function DashboardProviders({ children }) {
9
+ const rootTeamContext = useOptionalTeamContext();
10
+ if (rootTeamContext) {
11
+ return /* @__PURE__ */ jsx(Fragment, { children });
12
+ }
9
13
  return /* @__PURE__ */ jsx(QueryProvider, { children: /* @__PURE__ */ jsx(TeamProvider, { children: /* @__PURE__ */ jsxs(SubscriptionProvider, { children: [
10
14
  children,
11
15
  /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(Toaster, { position: "bottom-left" }) })
@@ -1,3 +1,20 @@
1
- import { type ThemeProviderProps } from "next-themes";
2
- export declare function ThemeProvider({ children, ...props }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
1
+ import { type ThemeProviderProps as NextThemesProviderProps } from "next-themes";
2
+ import { type ForcedThemeRoutes } from "../lib/theme/forced-theme";
3
+ export interface ThemeProviderProps extends NextThemesProviderProps {
4
+ /**
5
+ * Routes whose theme is forced regardless of user/system preference
6
+ * (`theme.config.ts` → `forcedThemeRoutes`).
7
+ *
8
+ * Resolved here, on the client, against `usePathname()` instead of once in
9
+ * the root layout from request headers: the root layout does not re-render
10
+ * on client-side navigations, so a server-computed value would go stale as
11
+ * soon as the user navigated into or out of a forced route. On the initial
12
+ * request this component is still server-rendered, so next-themes' blocking
13
+ * script receives the forced value and there is no flash.
14
+ *
15
+ * Takes precedence over the plain `forcedTheme` prop.
16
+ */
17
+ forcedThemeRoutes?: ForcedThemeRoutes;
18
+ }
19
+ export declare function ThemeProvider({ children, forcedThemeRoutes, forcedTheme, ...props }: ThemeProviderProps): import("react/jsx-runtime").JSX.Element;
3
20
  //# sourceMappingURL=theme-provider.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../src/providers/theme-provider.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAErD,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,kBAAkB,2CAEvE"}
1
+ {"version":3,"file":"theme-provider.d.ts","sourceRoot":"","sources":["../../src/providers/theme-provider.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,IAAI,uBAAuB,EAAE,MAAM,aAAa,CAAA;AAChF,OAAO,EAAsB,KAAK,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAEtF,MAAM,WAAW,kBAAmB,SAAQ,uBAAuB;IACjE;;;;;;;;;;;;OAYG;IACH,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACtC;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,WAAW,EAAE,GAAG,KAAK,EAAE,EAAE,kBAAkB,2CASvG"}
@@ -1,8 +1,12 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
+ import { usePathname } from "next/navigation";
3
4
  import { ThemeProvider as NextThemesProvider } from "next-themes";
4
- function ThemeProvider({ children, ...props }) {
5
- return /* @__PURE__ */ jsx(NextThemesProvider, { ...props, children });
5
+ import { resolveForcedTheme } from "../lib/theme/forced-theme.js";
6
+ function ThemeProvider({ children, forcedThemeRoutes, forcedTheme, ...props }) {
7
+ const pathname = usePathname();
8
+ const routeForcedTheme = resolveForcedTheme(pathname, forcedThemeRoutes);
9
+ return /* @__PURE__ */ jsx(NextThemesProvider, { ...props, forcedTheme: routeForcedTheme ?? forcedTheme, children });
6
10
  }
7
11
  export {
8
12
  ThemeProvider
@@ -4,6 +4,7 @@ import { redirect } from 'next/navigation'
4
4
  import { SignupForm } from '@nextsparkjs/core/components/auth/forms/SignupForm'
5
5
  import { getTemplateOrDefault, getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
6
6
  import { AUTH_CONFIG } from '@nextsparkjs/core/lib/config'
7
+ import { resolveAuthMethods } from '@nextsparkjs/core/lib/auth/auth-methods'
7
8
  import { TeamService } from '@nextsparkjs/core/lib/services'
8
9
 
9
10
  // This page reads DB state (TeamService.hasGlobal) and redirect()s during render,
@@ -23,6 +24,13 @@ export const metadata: Metadata = getMetadataOrDefault(
23
24
  async function SignupPageContent() {
24
25
  const registrationMode = AUTH_CONFIG?.registration?.mode ?? 'open'
25
26
 
27
+ // Passwordless preset (no 'email-password' in auth.methods): the account is
28
+ // created by the first one-time-code sign-in, so there is no password signup
29
+ // form to show — send people to /login instead.
30
+ if (!resolveAuthMethods(AUTH_CONFIG).includes('email-password')) {
31
+ redirect('/login')
32
+ }
33
+
26
34
  // In invitation-only mode, allow the first user to register
27
35
  // (when no global team exists yet). Subsequent users need invitations.
28
36
  if (registrationMode === 'invitation-only') {
@@ -13,7 +13,6 @@
13
13
  * 2. Falls back to default PageRenderer if no template
14
14
  */
15
15
 
16
- import { Suspense } from 'react'
17
16
  import { notFound } from 'next/navigation'
18
17
  import { query } from '@nextsparkjs/core/lib/db'
19
18
  import { PageRenderer } from '@nextsparkjs/core/components/public/pageBuilder'
@@ -249,9 +248,16 @@ export async function generateMetadata({
249
248
  }
250
249
 
251
250
  /**
252
- * Main catch-all page content (async, wrapped in Suspense by parent)
251
+ * Main catch-all page component
252
+ *
253
+ * Not wrapped in Suspense: this route's entire content depends on whether the
254
+ * slug resolves to a published item, so notFound() must be able to run before
255
+ * any part of the response commits to a 200. A Suspense boundary here would
256
+ * let Next.js flush the (null) fallback with a 200 status ahead of the
257
+ * DB-backed lookup, so a later notFound() inside the boundary could no longer
258
+ * change the status code — see #129.
253
259
  */
254
- async function DynamicPublicPageContent({
260
+ export default async function DynamicPublicPage({
255
261
  params,
256
262
  searchParams,
257
263
  }: PageProps) {
@@ -378,14 +384,3 @@ async function DynamicPublicPageContent({
378
384
  // No match found
379
385
  notFound()
380
386
  }
381
-
382
- /**
383
- * Main catch-all page component — wraps async content in Suspense for PPR compatibility
384
- */
385
- export default function DynamicPublicPage(props: PageProps) {
386
- return (
387
- <Suspense fallback={null}>
388
- <DynamicPublicPageContent {...props} />
389
- </Suspense>
390
- )
391
- }
@@ -9,6 +9,7 @@ import { TeamService } from "@nextsparkjs/core/lib/services";
9
9
  import { wrapAuthHandlerWithCors, handleCorsPreflightRequest, addCorsHeaders } from "@nextsparkjs/core/lib/api/helpers";
10
10
  import { checkDistributedRateLimit } from "@nextsparkjs/core/lib/api/rate-limit";
11
11
  import { withSignupContext } from "@nextsparkjs/core/lib/auth-context";
12
+ import { dispatchSecurityNotificationsForRequest } from "@nextsparkjs/core/lib/auth/security-notifications";
12
13
 
13
14
  const handlers = toNextJsHandler(auth);
14
15
 
@@ -181,11 +182,32 @@ export async function POST(req: NextRequest) {
181
182
  ? (req.cookies.get('signup-intent')?.value || undefined)
182
183
  : undefined;
183
184
 
185
+ // Clone the request BEFORE the handler consumes its body. The
186
+ // security-notifications dispatcher (below) needs the original request's
187
+ // headers (UA/IP) and, for change-email, its body (newEmail) — but
188
+ // handlers.POST(req) reads req's body exactly once.
189
+ const securityReq = req.clone() as NextRequest;
190
+
184
191
  // Wrap with CORS headers for cross-origin requests (mobile apps, etc.)
185
- return wrapAuthHandlerWithCors(
192
+ const response = await wrapAuthHandlerWithCors(
186
193
  signupIntent
187
194
  ? () => withSignupContext({ signupIntent }, () => handlers.POST(req))
188
195
  : () => handlers.POST(req),
189
196
  req
190
197
  );
198
+
199
+ // Built-in security email notifications (issue #75): after a successful
200
+ // sign-in / change-password / change-email, queue the matching security
201
+ // email. Guarded by AUTH_CONFIG.securityNotifications.enabled and wrapped in
202
+ // try/catch — a notification failure must NEVER change the auth response
203
+ // status (the dispatcher is itself best-effort, this is defense in depth).
204
+ if (AUTH_CONFIG.securityNotifications?.enabled) {
205
+ try {
206
+ await dispatchSecurityNotificationsForRequest(securityReq, response);
207
+ } catch (error) {
208
+ console.error("[auth] security-notifications dispatch error:", error);
209
+ }
210
+ }
211
+
212
+ return response;
191
213
  }
@@ -24,8 +24,10 @@ export const GET = withRateLimitTier(async (request: NextRequest) => {
24
24
  timestamp: new Date().toISOString(),
25
25
  };
26
26
 
27
- // Check if caller is authenticated for detailed info
28
- const authResult = await authenticateRequest(request);
27
+ // Check if caller is authenticated for detailed info. Public endpoint: any
28
+ // valid API key (whatever its scopes) may see the detailed status, so the
29
+ // scope-agnostic opt-out is explicit here (#93).
30
+ const authResult = await authenticateRequest(request, { allowAnyScope: true });
29
31
  const isAuthenticated = authResult.success && authResult.user;
30
32
 
31
33
  if (!isAuthenticated) {
@@ -1,14 +1,16 @@
1
1
  import { NextRequest, NextResponse } from 'next/server'
2
2
  import { MetaService } from '@nextsparkjs/core/lib/services/meta.service'
3
3
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
4
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
4
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
5
5
 
6
6
  // Endpoint interno para crear metadata default después del signup
7
7
  export const POST = withRateLimitTier(async (req: NextRequest) => {
8
8
  try {
9
- const authResult = await authenticateRequest(req)
9
+ // The API-key scope is declared at the entry point, which fails closed
10
+ // for keys that lack it (#93).
11
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' })
10
12
  if (!authResult.success || !authResult.user) {
11
- return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
13
+ return createAuthFailureResponse(authResult)
12
14
  }
13
15
 
14
16
  const body = await req.json()
@@ -106,7 +106,10 @@ async function handleMcpPost(request: NextRequest): Promise<NextResponse> {
106
106
  return jsonRpcError(403, -32000, 'Origin not allowed')
107
107
  }
108
108
 
109
- const authResult = await authenticateRequest(request)
109
+ // Transport-level auth only: per-tool scopes (`<entity>:read/write/delete`)
110
+ // are enforced by the generic entity handlers the executor invokes for each
111
+ // tool call, so this entry point accepts any valid key explicitly (#93).
112
+ const authResult = await authenticateRequest(request, { allowAnyScope: true })
110
113
  if (!authResult.success || authResult.type !== 'api-key' || !authResult.user) {
111
114
  return jsonRpcError(401, -32001, API_KEY_HELP)
112
115
  }
@@ -230,6 +230,7 @@ export const GET = withRateLimitTier(async (request: NextRequest) => {
230
230
  const totalPages = Math.ceil(total / limit);
231
231
 
232
232
  // Format subscriptions
233
+ const billingGateway = await getBillingGateway()
233
234
  const subscriptions = subscriptionsResult.map(sub => ({
234
235
  id: sub.id,
235
236
  team: {
@@ -257,7 +258,7 @@ export const GET = withRateLimitTier(async (request: NextRequest) => {
257
258
  cancelAtPeriodEnd: sub.cancelAtPeriodEnd,
258
259
  externalSubscriptionId: sub.externalSubscriptionId,
259
260
  paymentProvider: sub.paymentProvider,
260
- providerDashboardUrl: getBillingGateway().getSubscriptionDashboardUrl(sub.externalSubscriptionId),
261
+ providerDashboardUrl: billingGateway.getSubscriptionDashboardUrl(sub.externalSubscriptionId),
261
262
  createdAt: sub.createdAt
262
263
  }));
263
264
 
@@ -204,6 +204,7 @@ export const GET = withRateLimitTier(async (
204
204
  const subscription = subscriptionResult[0] || null;
205
205
 
206
206
  // Prepare response data
207
+ const billingGateway = subscription ? await getBillingGateway() : null
207
208
  const responseData = {
208
209
  team: {
209
210
  id: team.id,
@@ -225,7 +226,7 @@ export const GET = withRateLimitTier(async (
225
226
  role: member.role,
226
227
  joinedAt: member.joinedAt
227
228
  })),
228
- subscription: subscription ? {
229
+ subscription: subscription && billingGateway ? {
229
230
  id: subscription.id,
230
231
  plan: {
231
232
  id: subscription.planId,
@@ -246,8 +247,8 @@ export const GET = withRateLimitTier(async (
246
247
  externalSubscriptionId: subscription.externalSubscriptionId,
247
248
  externalCustomerId: subscription.externalCustomerId,
248
249
  paymentProvider: subscription.paymentProvider,
249
- providerName: getBillingGateway().getProviderName(),
250
- providerDashboardUrl: getBillingGateway().getSubscriptionDashboardUrl(subscription.externalSubscriptionId),
250
+ providerName: billingGateway.getProviderName(),
251
+ providerDashboardUrl: billingGateway.getSubscriptionDashboardUrl(subscription.externalSubscriptionId),
251
252
  createdAt: subscription.createdAt
252
253
  } : null,
253
254
  billingHistory: billingEventsResult.map((event) => ({
@@ -129,11 +129,18 @@ export const GET = withRateLimitTier(async (request: NextRequest, { params }: Ro
129
129
  ORDER BY "metaKey"
130
130
  `;
131
131
 
132
- // Execute all queries in parallel
132
+ // Execute all queries in parallel.
133
+ //
134
+ // The team_members queries run on the service (RLS-bypass) pool: team_members
135
+ // has no superadmin/developer bypass policy, so with the acting admin's own
136
+ // id as RLS context the SELECT policy silently ANDs "teams the actor belongs
137
+ // to" on top of WHERE "userId" = $1 and under-reports multi-team users.
138
+ // Authorization is already enforced above (superadmin/developer role check)
139
+ // and the WHERE clause scopes the rows to the subject user.
133
140
  const [userResults, teamsResults, countsResults, metasResults] = await Promise.all([
134
141
  queryWithRLS(userQuery, [userId], session.user.id) as Promise<UserResult[]>,
135
- queryWithRLS(teamsQuery, [userId], session.user.id) as Promise<TeamMembershipResult[]>,
136
- queryWithRLS(countsQuery, [userId], session.user.id) as Promise<{
142
+ queryWithRLS(teamsQuery, [userId], session.user.id, { service: true }) as Promise<TeamMembershipResult[]>,
143
+ queryWithRLS(countsQuery, [userId], session.user.id, { service: true }) as Promise<{
137
144
  totalTeams: number;
138
145
  ownedTeams: number;
139
146
  }[]>,
@@ -20,12 +20,13 @@ import {
20
20
  handleCorsPreflightRequest,
21
21
  addCorsHeaders
22
22
  } from '@nextsparkjs/core/lib/api/helpers';
23
- import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth';
23
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth';
24
+ import { invalidateApiKeyCache } from '@nextsparkjs/core/lib/api/auth';
24
25
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
25
26
 
26
27
  // Handle CORS preflight
27
- export async function OPTIONS() {
28
- return handleCorsPreflightRequest();
28
+ export async function OPTIONS(request: NextRequest) {
29
+ return handleCorsPreflightRequest(request);
29
30
  }
30
31
 
31
32
  // GET /api/v1/api-keys/:id - Get specific API key details
@@ -34,34 +35,23 @@ export const GET = withRateLimitTier(withApiLogging(async (
34
35
  { params }: { params: Promise<{ id: string }> }
35
36
  ): Promise<NextResponse> => {
36
37
  try {
37
- // Authenticate using dual auth
38
- const authResult = await authenticateRequest(req);
39
-
38
+ // Authenticate using dual auth; the API-key scope is declared at the entry
39
+ // point, which fails closed for keys that lack it (#93).
40
+ const authResult = await authenticateRequest(req, { requiredScope: 'admin:api-keys' });
41
+
40
42
  if (!authResult.success) {
41
- return NextResponse.json(
42
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
43
- { status: 401 }
44
- );
43
+ return createAuthFailureResponse(authResult);
45
44
  }
46
45
 
47
46
  if (authResult.rateLimitResponse) {
48
47
  return authResult.rateLimitResponse as NextResponse;
49
48
  }
50
49
 
51
- // Check required permissions - session users have admin access, API key users need specific scope
52
- const hasPermission = authResult.type === 'session' ||
53
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
54
-
55
- if (!hasPermission) {
56
- const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
57
- return addCorsHeaders(response);
58
- }
59
-
60
50
  const { id } = await params;
61
51
 
62
52
  if (!id || id.trim() === '') {
63
53
  const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
64
- return addCorsHeaders(response);
54
+ return addCorsHeaders(response, req);
65
55
  }
66
56
 
67
57
  const apiKey = await queryOneWithRLS(
@@ -74,7 +64,7 @@ export const GET = withRateLimitTier(withApiLogging(async (
74
64
 
75
65
  if (!apiKey) {
76
66
  const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
77
- return addCorsHeaders(response);
67
+ return addCorsHeaders(response, req);
78
68
  }
79
69
 
80
70
  // Get detailed usage statistics
@@ -129,11 +119,11 @@ export const GET = withRateLimitTier(withApiLogging(async (
129
119
  success_rate: null
130
120
  }
131
121
  });
132
- return addCorsHeaders(response);
122
+ return addCorsHeaders(response, req);
133
123
  } catch (error) {
134
124
  console.error('Error fetching API key:', error);
135
125
  const response = createApiError('Internal server error', 500);
136
- return addCorsHeaders(response);
126
+ return addCorsHeaders(response, req);
137
127
  }
138
128
  }), 'strict');
139
129
 
@@ -143,34 +133,23 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
143
133
  { params }: { params: Promise<{ id: string }> }
144
134
  ): Promise<NextResponse> => {
145
135
  try {
146
- // Authenticate using dual auth
147
- const authResult = await authenticateRequest(req);
148
-
136
+ // Authenticate using dual auth; the API-key scope is declared at the entry
137
+ // point, which fails closed for keys that lack it (#93).
138
+ const authResult = await authenticateRequest(req, { requiredScope: 'admin:api-keys' });
139
+
149
140
  if (!authResult.success) {
150
- return NextResponse.json(
151
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
152
- { status: 401 }
153
- );
141
+ return createAuthFailureResponse(authResult);
154
142
  }
155
143
 
156
144
  if (authResult.rateLimitResponse) {
157
145
  return authResult.rateLimitResponse as NextResponse;
158
146
  }
159
147
 
160
- // Check required permissions - session users have admin access, API key users need specific scope
161
- const hasPermission = authResult.type === 'session' ||
162
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
163
-
164
- if (!hasPermission) {
165
- const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
166
- return addCorsHeaders(response);
167
- }
168
-
169
148
  const { id } = await params;
170
149
 
171
150
  if (!id || id.trim() === '') {
172
151
  const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
173
- return addCorsHeaders(response);
152
+ return addCorsHeaders(response, req);
174
153
  }
175
154
 
176
155
  const body = await req.json();
@@ -185,11 +164,11 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
185
164
  if (body[field] !== undefined) {
186
165
  if (field === 'name' && typeof body[field] !== 'string') {
187
166
  const response = createApiError('Name must be a string', 400, null, 'INVALID_NAME_TYPE');
188
- return addCorsHeaders(response);
167
+ return addCorsHeaders(response, req);
189
168
  }
190
169
  if (field === 'status' && !['active', 'inactive', 'expired'].includes(body[field])) {
191
170
  const response = createApiError('status must be active, inactive, or expired', 400, null, 'INVALID_STATUS_TYPE');
192
- return addCorsHeaders(response);
171
+ return addCorsHeaders(response, req);
193
172
  }
194
173
 
195
174
  updates.push(`"${field}" = $${paramCount++}`);
@@ -199,7 +178,7 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
199
178
 
200
179
  if (updates.length === 0) {
201
180
  const response = createApiError('No valid fields to update', 400, null, 'NO_FIELDS');
202
- return addCorsHeaders(response);
181
+ return addCorsHeaders(response, req);
203
182
  }
204
183
 
205
184
  updates.push(`"updatedAt" = CURRENT_TIMESTAMP`);
@@ -209,22 +188,28 @@ export const PATCH = withRateLimitTier(withApiLogging(async (
209
188
  UPDATE "api_key"
210
189
  SET ${updates.join(", ")}
211
190
  WHERE id = $${paramCount} AND "userId" = $${paramCount + 1}
212
- RETURNING id, "keyPrefix", name, scopes, status, "lastUsedAt", "expiresAt", "createdAt", "updatedAt"
191
+ RETURNING id, "keyPrefix", "keyHash", name, scopes, status, "lastUsedAt", "expiresAt", "createdAt", "updatedAt"
213
192
  `;
214
193
 
215
194
  const result = await mutateWithRLS(query, values, authResult.user!.id);
216
195
 
217
196
  if (result.rows.length === 0) {
218
197
  const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
219
- return addCorsHeaders(response);
198
+ return addCorsHeaders(response, req);
220
199
  }
221
200
 
222
- const response = createApiResponse(result.rows[0]);
223
- return addCorsHeaders(response);
201
+ // A status change must take effect immediately: drop the cached validation
202
+ // entry so the next request re-reads the row (see #92). keyHash is only
203
+ // used for the invalidation and never returned to the client.
204
+ const { keyHash, ...updatedApiKey } = result.rows[0] as { keyHash: string } & Record<string, unknown>;
205
+ invalidateApiKeyCache(keyHash);
206
+
207
+ const response = createApiResponse(updatedApiKey);
208
+ return addCorsHeaders(response, req);
224
209
  } catch (error) {
225
210
  console.error('Error updating API key:', error);
226
211
  const response = createApiError('Internal server error', 500);
227
- return addCorsHeaders(response);
212
+ return addCorsHeaders(response, req);
228
213
  }
229
214
  }), 'strict');
230
215
 
@@ -234,34 +219,23 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
234
219
  { params }: { params: Promise<{ id: string }> }
235
220
  ): Promise<NextResponse> => {
236
221
  try {
237
- // Authenticate using dual auth
238
- const authResult = await authenticateRequest(req);
239
-
222
+ // Authenticate using dual auth; the API-key scope is declared at the entry
223
+ // point, which fails closed for keys that lack it (#93).
224
+ const authResult = await authenticateRequest(req, { requiredScope: 'admin:api-keys' });
225
+
240
226
  if (!authResult.success) {
241
- return NextResponse.json(
242
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
243
- { status: 401 }
244
- );
227
+ return createAuthFailureResponse(authResult);
245
228
  }
246
229
 
247
230
  if (authResult.rateLimitResponse) {
248
231
  return authResult.rateLimitResponse as NextResponse;
249
232
  }
250
233
 
251
- // Check required permissions - session users have admin access, API key users need specific scope
252
- const hasPermission = authResult.type === 'session' ||
253
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'admin:api-keys'));
254
-
255
- if (!hasPermission) {
256
- const response = createApiError('Insufficient permissions. Admin access required for API key management.', 403);
257
- return addCorsHeaders(response);
258
- }
259
-
260
234
  const { id } = await params;
261
235
 
262
236
  if (!id || id.trim() === '') {
263
237
  const response = createApiError('API key ID is required', 400, null, 'MISSING_API_KEY_ID');
264
- return addCorsHeaders(response);
238
+ return addCorsHeaders(response, req);
265
239
  }
266
240
 
267
241
  // For API key auth, prevent deletion of the current API key being used
@@ -272,7 +246,7 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
272
246
  null,
273
247
  'SELF_REVOKE_FORBIDDEN'
274
248
  );
275
- return addCorsHeaders(response);
249
+ return addCorsHeaders(response, req);
276
250
  }
277
251
 
278
252
  // Soft delete - just mark as inactive
@@ -280,25 +254,30 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
280
254
  `UPDATE "api_key"
281
255
  SET status = 'inactive', "updatedAt" = CURRENT_TIMESTAMP
282
256
  WHERE id = $1 AND "userId" = $2
283
- RETURNING id, name`,
257
+ RETURNING id, name, "keyHash"`,
284
258
  [id, authResult.user!.id],
285
259
  authResult.user!.id
286
260
  );
287
261
 
288
262
  if (result.rows.length === 0) {
289
263
  const response = createApiError('API key not found', 404, null, 'API_KEY_NOT_FOUND');
290
- return addCorsHeaders(response);
264
+ return addCorsHeaders(response, req);
291
265
  }
292
266
 
267
+ const revokedRow = result.rows[0] as { name: string; keyHash: string };
268
+
269
+ // Revocation must be effective right away, not after the 5 min cache TTL (see #92)
270
+ invalidateApiKeyCache(revokedRow.keyHash);
271
+
293
272
  const response = createApiResponse({
294
273
  revoked: true,
295
274
  id,
296
- name: (result.rows[0] as { name: string }).name
275
+ name: revokedRow.name
297
276
  });
298
- return addCorsHeaders(response);
277
+ return addCorsHeaders(response, req);
299
278
  } catch (error) {
300
279
  console.error('Error revoking API key:', error);
301
280
  const response = createApiError('Internal server error', 500);
302
- return addCorsHeaders(response);
281
+ return addCorsHeaders(response, req);
303
282
  }
304
283
  }), 'strict');