@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
@@ -0,0 +1,91 @@
1
+ -- Migration: 026_login_events.sql
2
+ -- Description: login_events — per-user device/login history for built-in
3
+ -- security email notifications (new-device login alerts).
4
+ -- Date: 2026-09-01
5
+ -- Issue: #75
6
+ --
7
+ -- Why 026 (and not 022 as the issue text proposed, nor 025):
8
+ -- The issue was written when the migration sequence ended earlier. By the
9
+ -- time this branch was authored the sequence already went through
10
+ -- 024_teams_soft_delete.sql (022 and 023 are taken by rls_runtime_roles and
11
+ -- the invitations index), so this migration was originally 025. It was
12
+ -- renumbered to 026 when merging to main, because 025 had in the meantime
13
+ -- been taken by 025_api_audit_log_nullable_api_key.sql (issue #105).
14
+ --
15
+ -- What this backs:
16
+ -- The security-notifications dispatcher records one row per successful sign-in
17
+ -- with a device fingerprint = sha256(normalizedUA | ip/24). A sign-in whose
18
+ -- (userId, deviceFingerprint) pair has never been seen is flagged isNew=true
19
+ -- and triggers a "new device" email via the auth:security-notification
20
+ -- scheduled action. Known fingerprints are still logged (isNew=false) but send
21
+ -- no email. The table is also the substrate for a future login-history UI
22
+ -- (out of scope here — see issue #75 "Out of scope").
23
+ --
24
+ -- Append-only: rows are never updated, so there is no "updatedAt"/set_updated_at
25
+ -- trigger (unlike mutable entity tables). city/country are nullable for a future
26
+ -- geolocation enhancement (also out of scope).
27
+
28
+ -- ============================================
29
+ -- TABLE
30
+ -- ============================================
31
+ DROP TABLE IF EXISTS public."login_events" CASCADE;
32
+
33
+ CREATE TABLE IF NOT EXISTS public."login_events" (
34
+ -- Primary Key (TEXT not UUID — Better Auth convention)
35
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
36
+
37
+ -- Relational Fields
38
+ "userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
39
+
40
+ -- Entity-specific fields
41
+ "ipAddress" TEXT,
42
+ "userAgent" TEXT,
43
+ "deviceFingerprint" TEXT NOT NULL,
44
+ city TEXT,
45
+ country TEXT,
46
+ "isNew" BOOLEAN NOT NULL DEFAULT false,
47
+
48
+ -- System fields (append-only: created only, never updated)
49
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now()
50
+ );
51
+
52
+ COMMENT ON TABLE public."login_events" IS 'Per-user login/device history backing built-in new-device security email notifications (issue #75).';
53
+ COMMENT ON COLUMN public."login_events"."userId" IS 'Owner of the login event.';
54
+ COMMENT ON COLUMN public."login_events"."ipAddress" IS 'Best-effort client IP captured at sign-in (may be a proxy address).';
55
+ COMMENT ON COLUMN public."login_events"."userAgent" IS 'Raw User-Agent header captured at sign-in.';
56
+ COMMENT ON COLUMN public."login_events"."deviceFingerprint" IS 'sha256(normalizedUA | ip/24). Stable across patch-version UA churn and within a /24 network so routine re-logins from the same device do not re-alert.';
57
+ COMMENT ON COLUMN public."login_events".city IS 'Reserved for future geolocation enrichment (nullable, unused today).';
58
+ COMMENT ON COLUMN public."login_events".country IS 'Reserved for future geolocation enrichment (nullable, unused today).';
59
+ COMMENT ON COLUMN public."login_events"."isNew" IS 'True when this (userId, deviceFingerprint) pair had not been seen before this row → a new-device email was queued.';
60
+
61
+ -- ============================================
62
+ -- INDEXES
63
+ -- ============================================
64
+ -- Most-recent-first history lookups per user (login-history UI, TTL checks).
65
+ CREATE INDEX IF NOT EXISTS idx_login_events_user_created
66
+ ON public."login_events" ("userId", "createdAt" DESC);
67
+
68
+ -- "Have we seen this fingerprint for this user?" — the new-device decision.
69
+ CREATE INDEX IF NOT EXISTS idx_login_events_user_fingerprint
70
+ ON public."login_events" ("userId", "deviceFingerprint");
71
+
72
+ -- ============================================
73
+ -- RLS
74
+ -- ============================================
75
+ -- A user may read ONLY their own login events; superadmin/developer bypass via
76
+ -- can_bypass_rls() (defined in 001). Writes are service-only: the dispatcher
77
+ -- inserts with no user GUC (service/bypass pool), matching how session/account
78
+ -- rows are written in 002. Mirrors the "self read + service write" pattern.
79
+ ALTER TABLE public."login_events" ENABLE ROW LEVEL SECURITY;
80
+
81
+ DROP POLICY IF EXISTS "Login events self read" ON public."login_events";
82
+ DROP POLICY IF EXISTS "Login events service write" ON public."login_events";
83
+
84
+ CREATE POLICY "Login events self read" ON public."login_events"
85
+ FOR SELECT TO authenticated
86
+ USING (public.can_bypass_rls() OR "userId" = public.get_auth_user_id());
87
+
88
+ CREATE POLICY "Login events service write" ON public."login_events"
89
+ FOR ALL TO authenticated
90
+ USING (public.can_bypass_rls())
91
+ WITH CHECK (public.can_bypass_rls());
@@ -0,0 +1,197 @@
1
+ -- Migration: 027_rls_bypass_developer_and_system_admin_team.sql
2
+ -- Description: Resolve the System Admin Team by a metadata flag instead of a
3
+ -- hardcoded team id, auto-enroll/unenroll developer/superadmin
4
+ -- users via a trigger, and fix 4 RLS policies that hardcoded
5
+ -- their own role check instead of calling can_bypass_rls().
6
+ -- Date: 2026-09-03
7
+ -- Issues: #108, #102
8
+ --
9
+ -- Why both issues in one migration: #102's fix (make a policy call
10
+ -- can_bypass_rls()) only makes 'developer' bypass actually reachable in a
11
+ -- real (non-seeded) deployment once #108's enrollment mechanism exists —
12
+ -- fixing one without the other leaves the combination still broken.
13
+ --
14
+ -- #108 — System Admin Team enrollment:
15
+ -- can_bypass_rls() grants 'developer' a conditional bypass: membership in
16
+ -- the System Admin Team. Nothing ever enrolled anyone into it outside of
17
+ -- 090_sample_data.sql's explicit INSERTs, and the team id was a literal
18
+ -- ('team-nextspark-001') duplicated in this function AND in
19
+ -- dual-auth.ts's SYSTEM_ADMIN_TEAM_ID constant. This migration:
20
+ -- 1. Resolves the System Admin Team by `teams.metadata.systemAdmin =
21
+ -- true` instead of a literal id — 090_sample_data.sql's team already
22
+ -- carries this flag, so seeded deployments keep working unchanged;
23
+ -- a real deployment flags its own team, no code change needed.
24
+ -- 2. Adds a trigger on users(role) that auto-enrolls a user into that
25
+ -- team (as 'owner' for superadmin, 'admin' for developer) when their
26
+ -- role becomes bypass-eligible, and removes the membership when it
27
+ -- stops being eligible. Framework-level (a Postgres trigger fires
28
+ -- regardless of which application code performed the UPDATE) rather
29
+ -- than an app-level hook, because no Server Action in core actually
30
+ -- assigns users.role today — the only code that does lives in
31
+ -- apps/dev, reimplementable by any consumer of this framework.
32
+ -- 3. Backfills existing developer/superadmin users into the team, for
33
+ -- deployments that already have such users predating this trigger.
34
+ -- Deliberately NOT included here (separate, larger concern — see #108):
35
+ -- POST /api/v1/users creating a "half" user (no account/team/subscription
36
+ -- row). That depends on patterns from the not-yet-approved #98 MCP work.
37
+ --
38
+ -- #102 (+ 3 more found while auditing the same pattern) — RLS policies that
39
+ -- hardcoded `role IN ('admin','superadmin')` or `role = 'superadmin'`
40
+ -- instead of calling can_bypass_rls() have drifted from it in both
41
+ -- directions. 'admin' is NOT a valid users.role value (see check_users_role
42
+ -- in 002_auth_tables.sql: only 'member' | 'superadmin' | 'developer'), so
43
+ -- that branch never matched any real row — removing it changes nothing for
44
+ -- existing behavior. All four also silently excluded 'developer', who
45
+ -- can_bypass_rls() already grants conditional bypass everywhere else.
46
+ -- Switching to can_bypass_rls() is a pure widening for 'developer' and an
47
+ -- exact match for 'superadmin' — no regression, only fixes a gap:
48
+ -- - users_metas_owner_or_admin_all (003_user_metas.sql)
49
+ -- - "API key owner or admin can do all" (004_api_key.sql)
50
+ -- - "API logs superadmin can select" (005_api_audit_log.sql)
51
+ -- - "Plans superadmin write" (012_billing_plans.sql)
52
+
53
+ -- ============================================
54
+ -- #108.1 — can_bypass_rls(): resolve System Admin Team by metadata flag
55
+ -- ============================================
56
+
57
+ CREATE OR REPLACE FUNCTION public.can_bypass_rls()
58
+ RETURNS BOOLEAN AS $$
59
+ DECLARE
60
+ current_user_id TEXT;
61
+ user_role TEXT;
62
+ admin_team_id TEXT;
63
+ is_system_admin_member BOOLEAN;
64
+ BEGIN
65
+ current_user_id := public.get_auth_user_id();
66
+
67
+ SELECT role INTO user_role
68
+ FROM public."users"
69
+ WHERE id = current_user_id;
70
+
71
+ IF user_role = 'superadmin' THEN
72
+ RETURN TRUE;
73
+ END IF;
74
+
75
+ IF user_role = 'developer' THEN
76
+ -- The System Admin Team is whichever team has metadata.systemAdmin =
77
+ -- true, not a hardcoded id — each deployment can flag its own team.
78
+ SELECT id INTO admin_team_id
79
+ FROM public."teams"
80
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
81
+ LIMIT 1;
82
+
83
+ IF admin_team_id IS NULL THEN
84
+ RETURN FALSE;
85
+ END IF;
86
+
87
+ SELECT EXISTS(
88
+ SELECT 1 FROM public."team_members"
89
+ WHERE "userId" = current_user_id
90
+ AND "teamId" = admin_team_id
91
+ ) INTO is_system_admin_member;
92
+
93
+ RETURN is_system_admin_member;
94
+ END IF;
95
+
96
+ RETURN FALSE;
97
+ END;
98
+ $$ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = public;
99
+
100
+ -- ============================================
101
+ -- #108.2 — auto-enroll/unenroll on role change
102
+ -- ============================================
103
+
104
+ CREATE OR REPLACE FUNCTION public.sync_system_admin_team_membership()
105
+ RETURNS TRIGGER
106
+ LANGUAGE plpgsql
107
+ SECURITY DEFINER
108
+ SET search_path = public
109
+ AS $$
110
+ DECLARE
111
+ admin_team_id TEXT;
112
+ new_team_role TEXT;
113
+ BEGIN
114
+ SELECT id INTO admin_team_id
115
+ FROM public."teams"
116
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
117
+ LIMIT 1;
118
+
119
+ -- No team flagged as the System Admin Team in this deployment yet —
120
+ -- nothing to enroll into. Not an error: same as no bypass being possible.
121
+ IF admin_team_id IS NULL THEN
122
+ RETURN NEW;
123
+ END IF;
124
+
125
+ IF NEW.role IN ('developer', 'superadmin') THEN
126
+ new_team_role := CASE WHEN NEW.role = 'superadmin' THEN 'owner' ELSE 'admin' END;
127
+
128
+ INSERT INTO public."team_members" ("teamId", "userId", role)
129
+ VALUES (admin_team_id, NEW.id, new_team_role)
130
+ ON CONFLICT ("teamId", "userId") DO UPDATE SET role = EXCLUDED.role;
131
+
132
+ ELSIF TG_OP = 'UPDATE' AND OLD.role IN ('developer', 'superadmin') THEN
133
+ -- Role changed away from a bypass-eligible tier: revoke membership.
134
+ DELETE FROM public."team_members"
135
+ WHERE "teamId" = admin_team_id AND "userId" = NEW.id;
136
+ END IF;
137
+
138
+ RETURN NEW;
139
+ END;
140
+ $$;
141
+
142
+ DROP TRIGGER IF EXISTS trg_sync_system_admin_team_membership ON public."users";
143
+ CREATE TRIGGER trg_sync_system_admin_team_membership
144
+ AFTER INSERT OR UPDATE OF role ON public."users"
145
+ FOR EACH ROW
146
+ EXECUTE FUNCTION public.sync_system_admin_team_membership();
147
+
148
+ -- ============================================
149
+ -- #108.3 — backfill existing developer/superadmin users
150
+ -- ============================================
151
+
152
+ DO $$
153
+ DECLARE
154
+ admin_team_id TEXT;
155
+ BEGIN
156
+ SELECT id INTO admin_team_id
157
+ FROM public."teams"
158
+ WHERE (metadata->>'systemAdmin')::boolean IS TRUE
159
+ LIMIT 1;
160
+
161
+ IF admin_team_id IS NOT NULL THEN
162
+ INSERT INTO public."team_members" ("teamId", "userId", role)
163
+ SELECT admin_team_id, u.id, CASE WHEN u.role = 'superadmin' THEN 'owner' ELSE 'admin' END
164
+ FROM public."users" u
165
+ WHERE u.role IN ('developer', 'superadmin')
166
+ ON CONFLICT ("teamId", "userId") DO UPDATE SET role = EXCLUDED.role;
167
+ END IF;
168
+ END $$;
169
+
170
+ -- ============================================
171
+ -- #102 + siblings — switch drifted policies to can_bypass_rls()
172
+ -- ============================================
173
+
174
+ DROP POLICY IF EXISTS "users_metas_owner_or_admin_all" ON "users_metas";
175
+ CREATE POLICY "users_metas_owner_or_admin_all" ON "users_metas"
176
+ FOR ALL
177
+ TO authenticated
178
+ USING ("userId" = public.get_auth_user_id() OR public.can_bypass_rls())
179
+ WITH CHECK ("userId" = public.get_auth_user_id() OR public.can_bypass_rls());
180
+
181
+ DROP POLICY IF EXISTS "API key owner or admin can do all" ON "api_key";
182
+ CREATE POLICY "API key owner or admin can do all" ON "api_key"
183
+ FOR ALL TO authenticated
184
+ USING ("userId" = public.get_auth_user_id() OR public.can_bypass_rls())
185
+ WITH CHECK ("userId" = public.get_auth_user_id() OR public.can_bypass_rls());
186
+
187
+ DROP POLICY IF EXISTS "API logs superadmin can select" ON "api_audit_log";
188
+ CREATE POLICY "API logs superadmin can select" ON "api_audit_log"
189
+ FOR SELECT TO authenticated
190
+ USING (public.can_bypass_rls());
191
+
192
+ DROP POLICY IF EXISTS "Plans superadmin write" ON public."plans";
193
+ CREATE POLICY "Plans superadmin write"
194
+ ON public."plans"
195
+ FOR ALL TO authenticated
196
+ USING (public.can_bypass_rls())
197
+ WITH CHECK (public.can_bypass_rls());
@@ -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
  }[]>,