@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
@@ -8,7 +8,7 @@ import {
8
8
  handleCorsPreflightRequest,
9
9
  addCorsHeaders,
10
10
  } from '@nextsparkjs/core/lib/api/helpers'
11
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
11
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
12
12
  import { isSuperAdmin } from '@nextsparkjs/core/lib/api/auth/permissions'
13
13
  import { checkRateLimit, withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
14
14
  import { RATE_LIMITS } from '@nextsparkjs/core/lib/api/keys'
@@ -21,22 +21,20 @@ import { sendTeamInvitationEmail } from '@nextsparkjs/core/lib/email/send'
21
21
  import { I18N_CONFIG } from '@nextsparkjs/core/lib/config'
22
22
 
23
23
  // Handle CORS preflight
24
- export async function OPTIONS() {
25
- return handleCorsPreflightRequest()
24
+ export async function OPTIONS(request: NextRequest) {
25
+ return handleCorsPreflightRequest(request)
26
26
  }
27
27
 
28
28
  // GET /api/v1/teams/:teamId/members - List team members
29
29
  export const GET = withRateLimitTier(withApiLogging(
30
30
  async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
31
31
  try {
32
- // Authenticate using dual auth
33
- const authResult = await authenticateRequest(req)
32
+ // Authenticate using dual auth; the API-key scope is declared at the entry
33
+ // point, which fails closed for keys that lack it (#93).
34
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
34
35
 
35
36
  if (!authResult.success) {
36
- return NextResponse.json(
37
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
38
- { status: 401 }
39
- )
37
+ return createAuthFailureResponse(authResult)
40
38
  }
41
39
 
42
40
  if (authResult.rateLimitResponse) {
@@ -48,19 +46,23 @@ export const GET = withRateLimitTier(withApiLogging(
48
46
  // Validate that teamId is not empty
49
47
  if (!teamId || teamId.trim() === '') {
50
48
  const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
51
- return addCorsHeaders(response)
49
+ return addCorsHeaders(response, req)
52
50
  }
53
51
 
54
52
  // Superadmins can view members of any team
55
53
  const userIsSuperAdmin = isSuperAdmin(authResult)
56
54
 
57
- // Check if user is a member of the team (unless superadmin)
55
+ // Listing the roster is gated by the 'team.members.view' permission, not
56
+ // bare membership — membership only proves you belong to the team, not
57
+ // that your role is allowed to see who else does (a theme may grant
58
+ // 'team.members.view' to a subset of roles; see GHSA-rw2j-9mxg-rx98).
58
59
  if (!userIsSuperAdmin) {
59
- const isMember = await TeamMemberService.isMember(teamId, authResult.user!.id)
60
+ const membership = await MembershipService.get(authResult.user!.id, teamId)
61
+ const actionResult = membership.canPerformAction('team.members.view')
60
62
 
61
- if (!isMember) {
63
+ if (!actionResult.allowed) {
62
64
  const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
63
- return addCorsHeaders(response)
65
+ return addCorsHeaders(response, req)
64
66
  }
65
67
  }
66
68
 
@@ -141,11 +143,11 @@ export const GET = withRateLimitTier(withApiLogging(
141
143
  const paginationMeta = createPaginationMeta(page, limit, total)
142
144
 
143
145
  const response = createApiResponse(members, paginationMeta)
144
- return addCorsHeaders(response)
146
+ return addCorsHeaders(response, req)
145
147
  } catch (error) {
146
148
  console.error('Error fetching team members:', error)
147
149
  const response = createApiError('Internal server error', 500)
148
- return addCorsHeaders(response)
150
+ return addCorsHeaders(response, req)
149
151
  }
150
152
  }
151
153
  ), 'read')
@@ -154,14 +156,12 @@ export const GET = withRateLimitTier(withApiLogging(
154
156
  export const POST = withRateLimitTier(withApiLogging(
155
157
  async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
156
158
  try {
157
- // Authenticate using dual auth
158
- const authResult = await authenticateRequest(req)
159
+ // Authenticate using dual auth; the API-key scope is declared at the entry
160
+ // point, which fails closed for keys that lack it (#93).
161
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
159
162
 
160
163
  if (!authResult.success) {
161
- return NextResponse.json(
162
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
163
- { status: 401 }
164
- )
164
+ return createAuthFailureResponse(authResult)
165
165
  }
166
166
 
167
167
  if (authResult.rateLimitResponse) {
@@ -180,7 +180,7 @@ export const POST = withRateLimitTier(withApiLogging(
180
180
  { retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1000) },
181
181
  'RATE_LIMIT_EXCEEDED'
182
182
  )
183
- return addCorsHeaders(response)
183
+ return addCorsHeaders(response, req)
184
184
  }
185
185
 
186
186
  const { teamId } = await params
@@ -188,12 +188,12 @@ export const POST = withRateLimitTier(withApiLogging(
188
188
  // Validate that teamId is not empty
189
189
  if (!teamId || teamId.trim() === '') {
190
190
  const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
191
- return addCorsHeaders(response)
191
+ return addCorsHeaders(response, req)
192
192
  }
193
193
 
194
194
  // Check if user has permission to invite members using MembershipService
195
195
  const membership = await MembershipService.get(authResult.user!.id, teamId)
196
- const actionResult = membership.canPerformAction('members.invite')
196
+ const actionResult = membership.canPerformAction('team.members.invite')
197
197
 
198
198
  if (!actionResult.allowed) {
199
199
  const response = NextResponse.json(
@@ -205,7 +205,7 @@ export const POST = withRateLimitTier(withApiLogging(
205
205
  },
206
206
  { status: 403 }
207
207
  )
208
- return addCorsHeaders(response)
208
+ return addCorsHeaders(response, req)
209
209
  }
210
210
 
211
211
  // Get user's role for role hierarchy validation
@@ -223,7 +223,7 @@ export const POST = withRateLimitTier(withApiLogging(
223
223
  null,
224
224
  'ROLE_HIERARCHY_VIOLATION'
225
225
  )
226
- return addCorsHeaders(response)
226
+ return addCorsHeaders(response, req)
227
227
  }
228
228
 
229
229
  // Check if user already exists and is already a member
@@ -243,7 +243,7 @@ export const POST = withRateLimitTier(withApiLogging(
243
243
 
244
244
  if (existingMember) {
245
245
  const response = createApiError('User is already a member of this team', 409, null, 'ALREADY_MEMBER')
246
- return addCorsHeaders(response)
246
+ return addCorsHeaders(response, req)
247
247
  }
248
248
  }
249
249
 
@@ -262,7 +262,7 @@ export const POST = withRateLimitTier(withApiLogging(
262
262
  null,
263
263
  'INVITATION_EXISTS'
264
264
  )
265
- return addCorsHeaders(response)
265
+ return addCorsHeaders(response, req)
266
266
  }
267
267
 
268
268
  // Get team info for email
@@ -274,7 +274,7 @@ export const POST = withRateLimitTier(withApiLogging(
274
274
 
275
275
  if (!team) {
276
276
  const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
277
- return addCorsHeaders(response)
277
+ return addCorsHeaders(response, req)
278
278
  }
279
279
 
280
280
  // Create invitation
@@ -333,17 +333,17 @@ export const POST = withRateLimitTier(withApiLogging(
333
333
  }
334
334
 
335
335
  const response = createApiResponse(invitation, { created: true }, 201)
336
- return addCorsHeaders(response)
336
+ return addCorsHeaders(response, req)
337
337
  } catch (error) {
338
338
  if (error instanceof Error && error.name === 'ZodError') {
339
339
  const zodError = error as { issues?: unknown[] }
340
340
  const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
341
- return addCorsHeaders(response)
341
+ return addCorsHeaders(response, req)
342
342
  }
343
343
 
344
344
  console.error('Error creating invitation:', error)
345
345
  const response = createApiError('Internal server error', 500)
346
- return addCorsHeaders(response)
346
+ return addCorsHeaders(response, req)
347
347
  }
348
348
  }
349
349
  ), 'write')
@@ -7,29 +7,27 @@ import {
7
7
  handleCorsPreflightRequest,
8
8
  addCorsHeaders,
9
9
  } from '@nextsparkjs/core/lib/api/helpers'
10
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
10
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
11
11
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
12
12
  import { updateTeamSchema } from '@nextsparkjs/core/lib/teams/schema'
13
13
  import { TeamService, MembershipService } from '@nextsparkjs/core/lib/services'
14
14
  import type { Team, TeamRole } from '@nextsparkjs/core/lib/teams/types'
15
15
 
16
16
  // Handle CORS preflight
17
- export async function OPTIONS() {
18
- return handleCorsPreflightRequest()
17
+ export async function OPTIONS(request: NextRequest) {
18
+ return handleCorsPreflightRequest(request)
19
19
  }
20
20
 
21
21
  // GET /api/v1/teams/:teamId - Get team details
22
22
  export const GET = withRateLimitTier(withApiLogging(
23
23
  async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
24
24
  try {
25
- // Authenticate using dual auth
26
- const authResult = await authenticateRequest(req)
25
+ // Authenticate using dual auth; the API-key scope is declared at the entry
26
+ // point, which fails closed for keys that lack it (#93).
27
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
27
28
 
28
29
  if (!authResult.success) {
29
- return NextResponse.json(
30
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
31
- { status: 401 }
32
- )
30
+ return createAuthFailureResponse(authResult)
33
31
  }
34
32
 
35
33
  if (authResult.rateLimitResponse) {
@@ -41,7 +39,7 @@ export const GET = withRateLimitTier(withApiLogging(
41
39
  // Validate that teamId is not empty
42
40
  if (!teamId || teamId.trim() === '') {
43
41
  const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
44
- return addCorsHeaders(response)
42
+ return addCorsHeaders(response, req)
45
43
  }
46
44
 
47
45
  // Check if user is a member of the team
@@ -53,7 +51,7 @@ export const GET = withRateLimitTier(withApiLogging(
53
51
 
54
52
  if (!userRole) {
55
53
  const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
56
- return addCorsHeaders(response)
54
+ return addCorsHeaders(response, req)
57
55
  }
58
56
 
59
57
  // Fetch team with member count
@@ -72,7 +70,7 @@ export const GET = withRateLimitTier(withApiLogging(
72
70
 
73
71
  if (!team) {
74
72
  const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
75
- return addCorsHeaders(response)
73
+ return addCorsHeaders(response, req)
76
74
  }
77
75
 
78
76
  const responseData = {
@@ -81,11 +79,11 @@ export const GET = withRateLimitTier(withApiLogging(
81
79
  }
82
80
 
83
81
  const response = createApiResponse(responseData)
84
- return addCorsHeaders(response)
82
+ return addCorsHeaders(response, req)
85
83
  } catch (error) {
86
84
  console.error('Error fetching team:', error)
87
85
  const response = createApiError('Internal server error', 500)
88
- return addCorsHeaders(response)
86
+ return addCorsHeaders(response, req)
89
87
  }
90
88
  }
91
89
  ), 'read')
@@ -94,14 +92,12 @@ export const GET = withRateLimitTier(withApiLogging(
94
92
  export const PATCH = withRateLimitTier(withApiLogging(
95
93
  async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
96
94
  try {
97
- // Authenticate using dual auth
98
- const authResult = await authenticateRequest(req)
95
+ // Authenticate using dual auth; the API-key scope is declared at the entry
96
+ // point, which fails closed for keys that lack it (#93).
97
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
99
98
 
100
99
  if (!authResult.success) {
101
- return NextResponse.json(
102
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
103
- { status: 401 }
104
- )
100
+ return createAuthFailureResponse(authResult)
105
101
  }
106
102
 
107
103
  if (authResult.rateLimitResponse) {
@@ -113,12 +109,12 @@ export const PATCH = withRateLimitTier(withApiLogging(
113
109
  // Validate that teamId is not empty
114
110
  if (!teamId || teamId.trim() === '') {
115
111
  const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
116
- return addCorsHeaders(response)
112
+ return addCorsHeaders(response, req)
117
113
  }
118
114
 
119
115
  // Check if user has permission to edit team using MembershipService
120
116
  const membership = await MembershipService.get(authResult.user!.id, teamId)
121
- const actionResult = membership.canPerformAction('teams.update')
117
+ const actionResult = membership.canPerformAction('team.edit')
122
118
 
123
119
  if (!actionResult.allowed) {
124
120
  const response = NextResponse.json(
@@ -130,7 +126,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
130
126
  },
131
127
  { status: 403 }
132
128
  )
133
- return addCorsHeaders(response)
129
+ return addCorsHeaders(response, req)
134
130
  }
135
131
 
136
132
  const body = await req.json()
@@ -141,7 +137,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
141
137
  const slugAvailable = await TeamService.isSlugAvailable(validatedData.slug, teamId)
142
138
  if (!slugAvailable) {
143
139
  const response = createApiError('Team slug already exists', 409, null, 'SLUG_EXISTS')
144
- return addCorsHeaders(response)
140
+ return addCorsHeaders(response, req)
145
141
  }
146
142
  }
147
143
 
@@ -177,7 +173,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
177
173
 
178
174
  if (updates.length === 0) {
179
175
  const response = createApiError('No fields to update', 400, null, 'NO_FIELDS')
180
- return addCorsHeaders(response)
176
+ return addCorsHeaders(response, req)
181
177
  }
182
178
 
183
179
  updates.push(`"updatedAt" = CURRENT_TIMESTAMP`)
@@ -194,7 +190,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
194
190
 
195
191
  if (result.rows.length === 0) {
196
192
  const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
197
- return addCorsHeaders(response)
193
+ return addCorsHeaders(response, req)
198
194
  }
199
195
 
200
196
  // Fetch team with member count
@@ -216,17 +212,17 @@ export const PATCH = withRateLimitTier(withApiLogging(
216
212
  }
217
213
 
218
214
  const response = createApiResponse(responseData)
219
- return addCorsHeaders(response)
215
+ return addCorsHeaders(response, req)
220
216
  } catch (error) {
221
217
  if (error instanceof Error && error.name === 'ZodError') {
222
218
  const zodError = error as { issues?: unknown[] }
223
219
  const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
224
- return addCorsHeaders(response)
220
+ return addCorsHeaders(response, req)
225
221
  }
226
222
 
227
223
  console.error('Error updating team:', error)
228
224
  const response = createApiError('Internal server error', 500)
229
- return addCorsHeaders(response)
225
+ return addCorsHeaders(response, req)
230
226
  }
231
227
  }
232
228
  ), 'write')
@@ -235,14 +231,12 @@ export const PATCH = withRateLimitTier(withApiLogging(
235
231
  export const DELETE = withRateLimitTier(withApiLogging(
236
232
  async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
237
233
  try {
238
- // Authenticate using dual auth
239
- const authResult = await authenticateRequest(req)
234
+ // Authenticate using dual auth; the API-key scope is declared at the entry
235
+ // point, which fails closed for keys that lack it (#93).
236
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:delete' })
240
237
 
241
238
  if (!authResult.success) {
242
- return NextResponse.json(
243
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
244
- { status: 401 }
245
- )
239
+ return createAuthFailureResponse(authResult)
246
240
  }
247
241
 
248
242
  if (authResult.rateLimitResponse) {
@@ -254,7 +248,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
254
248
  // Validate that teamId is not empty
255
249
  if (!teamId || teamId.trim() === '') {
256
250
  const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
257
- return addCorsHeaders(response)
251
+ return addCorsHeaders(response, req)
258
252
  }
259
253
 
260
254
  // Use the TeamService.delete (handles owner check and personal team protection)
@@ -262,7 +256,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
262
256
  await TeamService.delete(teamId, authResult.user!.id)
263
257
 
264
258
  const response = createApiResponse({ deleted: true, id: teamId })
265
- return addCorsHeaders(response)
259
+ return addCorsHeaders(response, req)
266
260
  } catch (error) {
267
261
  if (error instanceof Error && error.message === 'Only team owner can delete the team') {
268
262
  const response = createApiError(
@@ -271,17 +265,17 @@ export const DELETE = withRateLimitTier(withApiLogging(
271
265
  null,
272
266
  'INSUFFICIENT_PERMISSIONS'
273
267
  )
274
- return addCorsHeaders(response)
268
+ return addCorsHeaders(response, req)
275
269
  }
276
270
 
277
271
  if (error instanceof Error && error.message === 'Team not found') {
278
272
  const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
279
- return addCorsHeaders(response)
273
+ return addCorsHeaders(response, req)
280
274
  }
281
275
 
282
276
  if (error instanceof Error && error.message === 'Personal teams cannot be deleted') {
283
277
  const response = createApiError('Personal teams cannot be deleted', 403, null, 'PERSONAL_TEAM_DELETE_FORBIDDEN')
284
- return addCorsHeaders(response)
278
+ return addCorsHeaders(response, req)
285
279
  }
286
280
 
287
281
  throw error
@@ -289,7 +283,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
289
283
  } catch (error) {
290
284
  console.error('Error deleting team:', error)
291
285
  const response = createApiError('Internal server error', 500)
292
- return addCorsHeaders(response)
286
+ return addCorsHeaders(response, req)
293
287
  }
294
288
  }
295
289
  ), 'write')
@@ -14,16 +14,18 @@ interface RouteParams {
14
14
  }
15
15
 
16
16
  export const GET = withRateLimitTier(async function GET(request: NextRequest, props: RouteParams) {
17
- // Authenticate request
18
- const { auth, rateLimitResponse } = await validateAndAuthenticateRequest(request)
17
+ // Authenticate request; the API-key scope is declared at the entry point,
18
+ // which fails closed for keys that lack it (#93).
19
+ const { auth, rateLimitResponse, errorResponse } = await validateAndAuthenticateRequest(request, { requiredScope: 'billing:read' })
19
20
  if (rateLimitResponse) return rateLimitResponse
21
+ if (!auth) return errorResponse ?? createApiError('Authentication required', 401, undefined, 'AUTHENTICATION_REQUIRED')
20
22
 
21
23
  const { teamId } = await props.params
22
24
 
23
25
  try {
24
26
  // Check if user has permission to view subscription using MembershipService
25
27
  const membership = await MembershipService.get(auth.userId, teamId)
26
- const actionResult = membership.canPerformAction('billing.view')
28
+ const actionResult = membership.canPerformAction('team.billing.view')
27
29
 
28
30
  if (!actionResult.allowed) {
29
31
  return NextResponse.json(
@@ -16,16 +16,18 @@ interface RouteParams {
16
16
  }
17
17
 
18
18
  export const GET = withRateLimitTier(async function GET(request: NextRequest, props: RouteParams) {
19
- // Authenticate request
20
- const { auth, rateLimitResponse } = await validateAndAuthenticateRequest(request)
19
+ // Authenticate request; the API-key scope is declared at the entry point,
20
+ // which fails closed for keys that lack it (#93).
21
+ const { auth, rateLimitResponse, errorResponse } = await validateAndAuthenticateRequest(request, { requiredScope: 'billing:read' })
21
22
  if (rateLimitResponse) return rateLimitResponse
23
+ if (!auth) return errorResponse ?? createApiError('Authentication required', 401, undefined, 'AUTHENTICATION_REQUIRED')
22
24
 
23
25
  const { teamId, limitSlug } = await props.params
24
26
 
25
27
  try {
26
28
  // Check if user has permission to view usage using MembershipService
27
29
  const membership = await MembershipService.get(auth.userId, teamId)
28
- const actionResult = membership.canPerformAction('billing.view')
30
+ const actionResult = membership.canPerformAction('team.billing.view')
29
31
 
30
32
  if (!actionResult.allowed) {
31
33
  return NextResponse.json(
@@ -48,16 +50,18 @@ export const GET = withRateLimitTier(async function GET(request: NextRequest, pr
48
50
  }, 'read')
49
51
 
50
52
  export const POST = withRateLimitTier(async function POST(request: NextRequest, props: RouteParams) {
51
- // Authenticate request
52
- const { auth, rateLimitResponse } = await validateAndAuthenticateRequest(request)
53
+ // Authenticate request; the API-key scope is declared at the entry point,
54
+ // which fails closed for keys that lack it (#93).
55
+ const { auth, rateLimitResponse, errorResponse } = await validateAndAuthenticateRequest(request, { requiredScope: 'billing:write' })
53
56
  if (rateLimitResponse) return rateLimitResponse
57
+ if (!auth) return errorResponse ?? createApiError('Authentication required', 401, undefined, 'AUTHENTICATION_REQUIRED')
54
58
 
55
59
  const { teamId, limitSlug } = await props.params
56
60
 
57
61
  try {
58
62
  // Check if user has permission to track usage using MembershipService
59
63
  const membership = await MembershipService.get(auth.userId, teamId)
60
- const actionResult = membership.canPerformAction('billing.manage')
64
+ const actionResult = membership.canPerformAction('team.billing.manage')
61
65
 
62
66
  if (!actionResult.allowed) {
63
67
  return NextResponse.json(
@@ -8,7 +8,7 @@ import {
8
8
  handleCorsPreflightRequest,
9
9
  addCorsHeaders,
10
10
  } from '@nextsparkjs/core/lib/api/helpers'
11
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
11
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
12
12
  import { isSuperAdmin } from '@nextsparkjs/core/lib/api/auth/permissions'
13
13
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
14
14
  import { createTeamSchema, teamListQuerySchema } from '@nextsparkjs/core/lib/teams/schema'
@@ -17,21 +17,19 @@ import type { Team } from '@nextsparkjs/core/lib/teams/types'
17
17
  import { APP_CONFIG_MERGED } from '@nextsparkjs/core/lib/config/config-sync'
18
18
 
19
19
  // Handle CORS preflight
20
- export async function OPTIONS() {
21
- return handleCorsPreflightRequest()
20
+ export async function OPTIONS(request: NextRequest) {
21
+ return handleCorsPreflightRequest(request)
22
22
  }
23
23
 
24
24
  // GET /api/v1/teams - List user's teams
25
25
  export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
26
26
  try {
27
- // Authenticate using dual auth
28
- const authResult = await authenticateRequest(req)
27
+ // Authenticate using dual auth; the API-key scope is declared at the entry
28
+ // point, which fails closed for keys that lack it (#93).
29
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
29
30
 
30
31
  if (!authResult.success) {
31
- return NextResponse.json(
32
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
33
- { status: 401 }
34
- )
32
+ return createAuthFailureResponse(authResult)
35
33
  }
36
34
 
37
35
  if (authResult.rateLimitResponse) {
@@ -62,7 +60,7 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
62
60
  'Insufficient permissions. Superadmin access required for scope=all.',
63
61
  403
64
62
  )
65
- return addCorsHeaders(response)
63
+ return addCorsHeaders(response, req)
66
64
  }
67
65
 
68
66
  // Build WHERE clause based on filters
@@ -93,7 +91,7 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
93
91
  // Add pagination params
94
92
  queryValues.push(limit, offset)
95
93
 
96
- let teams: (Team & { userRole: string | null; memberCount: string })[]
94
+ let teams: (Team & { userRole: string | null; joinedAt?: string; memberCount: string })[]
97
95
  let totalResult: { count: string }[]
98
96
 
99
97
  if (requestAllTeams) {
@@ -122,17 +120,27 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
122
120
  )
123
121
  } else {
124
122
  // Regular user query: filter by membership
125
- teams = await queryWithRLS<Team & { userRole: string; memberCount: string }>(
123
+ // tm."joinedAt" is unambiguous here (not an aggregation concern): the
124
+ // WHERE clause already pins tm."userId" = the caller, so tm is exactly
125
+ // this user's own membership row in each team — the same "joinedAt"
126
+ // dual-auth.ts's getUserDefaultTeamId() uses to pick a default team.
127
+ // Its absence here (never selected, even though the client sorted by
128
+ // it) made TeamContext's #115 fix a no-op in production: every
129
+ // `joinedAt` came back undefined, so `new Date(undefined).getTime()`
130
+ // compared NaN to NaN and the client silently kept the API's row
131
+ // order instead of the earliest-joined team.
132
+ teams = await queryWithRLS<Team & { userRole: string; joinedAt: string; memberCount: string }>(
126
133
  `SELECT
127
134
  t.*,
128
135
  tm.role as "userRole",
136
+ tm."joinedAt" as "joinedAt",
129
137
  COUNT(DISTINCT tm2.id) as "memberCount"
130
138
  FROM "teams" t
131
139
  INNER JOIN "team_members" tm ON t.id = tm."teamId"
132
140
  LEFT JOIN "team_members" tm2 ON t.id = tm2."teamId"
133
141
  ${whereClause}
134
142
  GROUP BY t.id, t.name, t.slug, t.description, t."ownerId", t."avatarUrl",
135
- t.settings, t.metadata, t."createdAt", t."updatedAt", tm.role
143
+ t.settings, t.metadata, t."createdAt", t."updatedAt", tm.role, tm."joinedAt"
136
144
  ORDER BY ${orderByClause}
137
145
  LIMIT $${paramCount++} OFFSET $${paramCount++}`,
138
146
  queryValues,
@@ -160,25 +168,23 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
160
168
  }))
161
169
 
162
170
  const response = createApiResponse(teamsWithMembers, paginationMeta)
163
- return addCorsHeaders(response)
171
+ return addCorsHeaders(response, req)
164
172
  } catch (error) {
165
173
  console.error('Error fetching teams:', error)
166
174
  const response = createApiError('Internal server error', 500)
167
- return addCorsHeaders(response)
175
+ return addCorsHeaders(response, req)
168
176
  }
169
177
  }), 'read')
170
178
 
171
179
  // POST /api/v1/teams - Create new team
172
180
  export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
173
181
  try {
174
- // Authenticate using dual auth
175
- const authResult = await authenticateRequest(req)
182
+ // Authenticate using dual auth; the API-key scope is declared at the entry
183
+ // point, which fails closed for keys that lack it (#93).
184
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
176
185
 
177
186
  if (!authResult.success) {
178
- return NextResponse.json(
179
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
180
- { status: 401 }
181
- )
187
+ return createAuthFailureResponse(authResult)
182
188
  }
183
189
 
184
190
  if (authResult.rateLimitResponse) {
@@ -199,7 +205,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
199
205
  null,
200
206
  'TEAM_CREATION_DISABLED'
201
207
  )
202
- return addCorsHeaders(response)
208
+ return addCorsHeaders(response, req)
203
209
  }
204
210
 
205
211
  // Validate allowCreateTeams option
@@ -220,7 +226,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
220
226
  null,
221
227
  'MAX_TEAMS_REACHED'
222
228
  )
223
- return addCorsHeaders(response)
229
+ return addCorsHeaders(response, req)
224
230
  }
225
231
  }
226
232
 
@@ -228,7 +234,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
228
234
  const slugAvailable = await TeamService.isSlugAvailable(validatedData.slug)
229
235
  if (!slugAvailable) {
230
236
  const response = createApiError('Team slug already exists', 409, null, 'SLUG_EXISTS')
231
- return addCorsHeaders(response)
237
+ return addCorsHeaders(response, req)
232
238
  }
233
239
 
234
240
  // Use transaction to ensure atomicity
@@ -280,7 +286,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
280
286
  }
281
287
 
282
288
  const response = createApiResponse(responseData, { created: true }, 201)
283
- return addCorsHeaders(response)
289
+ return addCorsHeaders(response, req)
284
290
  } catch (error) {
285
291
  await tx.rollback()
286
292
  throw error
@@ -289,11 +295,11 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
289
295
  if (error instanceof Error && error.name === 'ZodError') {
290
296
  const zodError = error as { issues?: unknown[] }
291
297
  const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
292
- return addCorsHeaders(response)
298
+ return addCorsHeaders(response, req)
293
299
  }
294
300
 
295
301
  console.error('Error creating team:', error)
296
302
  const response = createApiError('Internal server error', 500)
297
- return addCorsHeaders(response)
303
+ return addCorsHeaders(response, req)
298
304
  }
299
305
  }), 'write')