@nextsparkjs/core 0.1.0-beta.184 → 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 (420) 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 +103 -25
  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/hooks/useValidationSchemas.d.ts +7 -7
  77. package/dist/lib/api/api-error.d.ts +3 -1
  78. package/dist/lib/api/api-error.d.ts.map +1 -1
  79. package/dist/lib/api/api-error.js +4 -2
  80. package/dist/lib/api/auth/dual-auth.d.ts +45 -3
  81. package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
  82. package/dist/lib/api/auth/dual-auth.js +42 -7
  83. package/dist/lib/api/auth/permissions.d.ts +7 -2
  84. package/dist/lib/api/auth/permissions.d.ts.map +1 -1
  85. package/dist/lib/api/auth/permissions.js +2 -2
  86. package/dist/lib/api/auth/scope-policy.d.ts +71 -0
  87. package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
  88. package/dist/lib/api/auth/scope-policy.js +53 -0
  89. package/dist/lib/api/auth.d.ts +35 -3
  90. package/dist/lib/api/auth.d.ts.map +1 -1
  91. package/dist/lib/api/auth.js +54 -23
  92. package/dist/lib/api/entity/audit-log.d.ts +35 -0
  93. package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
  94. package/dist/lib/api/entity/audit-log.js +35 -0
  95. package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
  96. package/dist/lib/api/entity/generic-handler.js +333 -155
  97. package/dist/lib/api/helpers.d.ts +29 -4
  98. package/dist/lib/api/helpers.d.ts.map +1 -1
  99. package/dist/lib/api/helpers.js +30 -6
  100. package/dist/lib/api/keys.d.ts +16 -0
  101. package/dist/lib/api/keys.d.ts.map +1 -1
  102. package/dist/lib/api/keys.js +19 -1
  103. package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
  104. package/dist/lib/api/routes/patterns-usages.js +3 -11
  105. package/dist/lib/auth/auth-methods.d.ts +52 -0
  106. package/dist/lib/auth/auth-methods.d.ts.map +1 -0
  107. package/dist/lib/auth/auth-methods.js +63 -0
  108. package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
  109. package/dist/lib/auth/registration-helpers.d.ts +9 -0
  110. package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
  111. package/dist/lib/auth/registration-helpers.js +8 -2
  112. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
  113. package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
  114. package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
  115. package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
  116. package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
  117. package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
  118. package/dist/lib/auth/security-notifications/index.d.ts +20 -0
  119. package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
  120. package/dist/lib/auth/security-notifications/index.js +22 -0
  121. package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
  122. package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
  123. package/dist/lib/auth/security-notifications/registry.js +26 -0
  124. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
  125. package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
  126. package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
  127. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
  128. package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
  129. package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
  130. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
  131. package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
  132. package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
  133. package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
  134. package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
  135. package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
  136. package/dist/lib/auth/security-notifications/types.d.ts +74 -0
  137. package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
  138. package/dist/lib/auth/security-notifications/types.js +0 -0
  139. package/dist/lib/auth/session-config.d.ts +37 -0
  140. package/dist/lib/auth/session-config.d.ts.map +1 -0
  141. package/dist/lib/auth/session-config.js +59 -0
  142. package/dist/lib/auth/session-refresh.d.ts +46 -0
  143. package/dist/lib/auth/session-refresh.d.ts.map +1 -0
  144. package/dist/lib/auth/session-refresh.js +11 -0
  145. package/dist/lib/auth-client.d.ts +1489 -3445
  146. package/dist/lib/auth-client.d.ts.map +1 -1
  147. package/dist/lib/auth-client.js +4 -0
  148. package/dist/lib/auth.d.ts +1 -3057
  149. package/dist/lib/auth.d.ts.map +1 -1
  150. package/dist/lib/auth.js +21 -8
  151. package/dist/lib/billing/gateways/factory.d.ts +8 -3
  152. package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
  153. package/dist/lib/billing/gateways/factory.js +37 -23
  154. package/dist/lib/billing/schema.d.ts +1 -1
  155. package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
  156. package/dist/lib/billing/stripe-webhook.js +2 -1
  157. package/dist/lib/config/app.config.d.ts.map +1 -1
  158. package/dist/lib/config/app.config.js +71 -0
  159. package/dist/lib/config/config-sync.d.ts +1 -4
  160. package/dist/lib/config/config-sync.d.ts.map +1 -1
  161. package/dist/lib/config/config-sync.js +6 -2
  162. package/dist/lib/config/types.d.ts +130 -0
  163. package/dist/lib/config/types.d.ts.map +1 -1
  164. package/dist/lib/entities/entity-hooks.d.ts +9 -1
  165. package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
  166. package/dist/lib/entities/entity-hooks.js +77 -4
  167. package/dist/lib/entities/schema-generator.d.ts.map +1 -1
  168. package/dist/lib/entities/schema-generator.js +20 -4
  169. package/dist/lib/entities/serialization.d.ts +2 -2
  170. package/dist/lib/entities/serialization.d.ts.map +1 -1
  171. package/dist/lib/entities/serialization.js +3 -2
  172. package/dist/lib/entities/types.d.ts +8 -0
  173. package/dist/lib/entities/types.d.ts.map +1 -1
  174. package/dist/lib/locale.d.ts.map +1 -1
  175. package/dist/lib/locale.js +4 -1
  176. package/dist/lib/mcp/audit.d.ts +21 -0
  177. package/dist/lib/mcp/audit.d.ts.map +1 -0
  178. package/dist/lib/mcp/audit.js +48 -0
  179. package/dist/lib/mcp/engine.d.ts +28 -0
  180. package/dist/lib/mcp/engine.d.ts.map +1 -0
  181. package/dist/lib/mcp/engine.js +158 -0
  182. package/dist/lib/mcp/errors.d.ts +16 -0
  183. package/dist/lib/mcp/errors.d.ts.map +1 -0
  184. package/dist/lib/mcp/errors.js +66 -0
  185. package/dist/lib/mcp/executor.d.ts +15 -0
  186. package/dist/lib/mcp/executor.d.ts.map +1 -0
  187. package/dist/lib/mcp/executor.js +63 -0
  188. package/dist/lib/mcp/index.d.ts +9 -0
  189. package/dist/lib/mcp/index.d.ts.map +1 -0
  190. package/dist/lib/mcp/index.js +32 -0
  191. package/dist/lib/mcp/schema-builder.d.ts +45 -0
  192. package/dist/lib/mcp/schema-builder.d.ts.map +1 -0
  193. package/dist/lib/mcp/schema-builder.js +212 -0
  194. package/dist/lib/mcp/tool-generator.d.ts +31 -0
  195. package/dist/lib/mcp/tool-generator.d.ts.map +1 -0
  196. package/dist/lib/mcp/tool-generator.js +271 -0
  197. package/dist/lib/mcp/transport.d.ts +31 -0
  198. package/dist/lib/mcp/transport.d.ts.map +1 -0
  199. package/dist/lib/mcp/transport.js +58 -0
  200. package/dist/lib/mcp/types.d.ts +184 -0
  201. package/dist/lib/mcp/types.d.ts.map +1 -0
  202. package/dist/lib/mcp/types.js +11 -0
  203. package/dist/lib/plugins/hook-system.d.ts +13 -0
  204. package/dist/lib/plugins/hook-system.d.ts.map +1 -1
  205. package/dist/lib/plugins/hook-system.js +32 -2
  206. package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
  207. package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
  208. package/dist/lib/scheduled-actions/handlers/index.js +3 -1
  209. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
  210. package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
  211. package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
  212. package/dist/lib/selectors/core-selectors.d.ts +12 -0
  213. package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
  214. package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
  215. package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
  216. package/dist/lib/selectors/domains/auth.selectors.js +14 -1
  217. package/dist/lib/selectors/selectors.d.ts +24 -0
  218. package/dist/lib/selectors/selectors.d.ts.map +1 -1
  219. package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
  220. package/dist/lib/services/generic-entity.service.js +16 -32
  221. package/dist/lib/services/membership.service.d.ts +10 -2
  222. package/dist/lib/services/membership.service.d.ts.map +1 -1
  223. package/dist/lib/services/membership.service.js +37 -16
  224. package/dist/lib/services/subscription.service.d.ts.map +1 -1
  225. package/dist/lib/services/subscription.service.js +2 -1
  226. package/dist/lib/services/team-member.service.d.ts +15 -3
  227. package/dist/lib/services/team-member.service.d.ts.map +1 -1
  228. package/dist/lib/services/team-member.service.js +27 -6
  229. package/dist/lib/services/team.service.d.ts.map +1 -1
  230. package/dist/lib/teams/permissions.d.ts +3 -1
  231. package/dist/lib/teams/permissions.d.ts.map +1 -1
  232. package/dist/lib/teams/permissions.js +6 -0
  233. package/dist/lib/teams/schema.d.ts.map +1 -1
  234. package/dist/lib/teams/schema.js +3 -0
  235. package/dist/lib/theme/forced-theme.d.ts +34 -0
  236. package/dist/lib/theme/forced-theme.d.ts.map +1 -0
  237. package/dist/lib/theme/forced-theme.js +27 -0
  238. package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
  239. package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
  240. package/dist/lib/theme/get-default-theme-mode.js +5 -3
  241. package/dist/messages/de/auth.json +25 -2
  242. package/dist/messages/de/index.d.ts +81 -0
  243. package/dist/messages/de/index.d.ts.map +1 -1
  244. package/dist/messages/de/index.js +2 -0
  245. package/dist/messages/de/index.ts +2 -0
  246. package/dist/messages/en/auth.json +25 -2
  247. package/dist/messages/en/index.d.ts +81 -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/es/auth.json +25 -2
  252. package/dist/messages/es/index.d.ts +81 -0
  253. package/dist/messages/es/index.d.ts.map +1 -1
  254. package/dist/messages/es/index.js +2 -0
  255. package/dist/messages/es/index.ts +2 -0
  256. package/dist/messages/fr/auth.json +25 -2
  257. package/dist/messages/fr/index.d.ts +81 -0
  258. package/dist/messages/fr/index.d.ts.map +1 -1
  259. package/dist/messages/fr/index.js +2 -0
  260. package/dist/messages/fr/index.ts +2 -0
  261. package/dist/messages/it/auth.json +25 -2
  262. package/dist/messages/it/index.d.ts +81 -0
  263. package/dist/messages/it/index.d.ts.map +1 -1
  264. package/dist/messages/it/index.js +2 -0
  265. package/dist/messages/it/index.ts +2 -0
  266. package/dist/messages/pt/auth.json +25 -2
  267. package/dist/messages/pt/index.d.ts +81 -0
  268. package/dist/messages/pt/index.d.ts.map +1 -1
  269. package/dist/messages/pt/index.js +2 -0
  270. package/dist/messages/pt/index.ts +2 -0
  271. package/dist/migrations/005_api_audit_log.sql +2 -1
  272. package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  273. package/dist/migrations/026_login_events.sql +91 -0
  274. package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  275. package/dist/providers/DashboardProviders.d.ts +12 -3
  276. package/dist/providers/DashboardProviders.d.ts.map +1 -1
  277. package/dist/providers/DashboardProviders.js +6 -2
  278. package/dist/providers/theme-provider.d.ts +19 -2
  279. package/dist/providers/theme-provider.d.ts.map +1 -1
  280. package/dist/providers/theme-provider.js +6 -2
  281. package/dist/templates/app/(auth)/signup/page.tsx +8 -0
  282. package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
  283. package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
  284. package/dist/templates/app/api/health/route.ts +4 -2
  285. package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
  286. package/dist/templates/app/api/mcp/route.ts +176 -0
  287. package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  288. package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  289. package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  290. package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  291. package/dist/templates/app/api/v1/api-keys/route.ts +35 -45
  292. package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  293. package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
  294. package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  295. package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
  296. package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
  297. package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
  298. package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
  299. package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  300. package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  301. package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
  302. package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
  303. package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  304. package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  305. package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
  306. package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
  307. package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  308. package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  309. package/dist/templates/app/api/v1/media/route.ts +5 -9
  310. package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
  311. package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
  312. package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  313. package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  314. package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
  315. package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  316. package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  317. package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  318. package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
  319. package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  320. package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  321. package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  322. package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  323. package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  324. package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  325. package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  326. package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  327. package/dist/templates/app/api/v1/teams/route.ts +33 -27
  328. package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
  329. package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  330. package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  331. package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
  332. package/dist/templates/app/api/v1/users/route.ts +21 -25
  333. package/dist/templates/app/dashboard/layout.tsx +2 -6
  334. package/dist/templates/app/layout.ppr.tsx +9 -14
  335. package/dist/templates/app/layout.tsx +39 -19
  336. package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
  337. package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
  338. package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
  339. package/dist/templates/proxy.ts +46 -10
  340. package/dist/templates/tsconfig.json +4 -0
  341. package/dist/types/theme.d.ts +9 -0
  342. package/dist/types/theme.d.ts.map +1 -1
  343. package/migrations/005_api_audit_log.sql +2 -1
  344. package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
  345. package/migrations/026_login_events.sql +91 -0
  346. package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
  347. package/package.json +20 -12
  348. package/scripts/build/registry/__tests__/mcp-registry.test.mjs +64 -0
  349. package/scripts/build/registry/__tests__/scope-registry.test.mjs +106 -0
  350. package/scripts/build/registry/discovery/mcp-overrides.mjs +72 -0
  351. package/scripts/build/registry/discovery/mcp-overrides.test.mjs +86 -0
  352. package/scripts/build/registry/generators/mcp-registry.mjs +61 -0
  353. package/scripts/build/registry/generators/scope-registry.mjs +140 -88
  354. package/scripts/build/registry.mjs +8 -4
  355. package/templates/app/(auth)/signup/page.tsx +8 -0
  356. package/templates/app/(public)/[...slug]/page.tsx +9 -14
  357. package/templates/app/api/auth/[...all]/route.ts +23 -1
  358. package/templates/app/api/health/route.ts +4 -2
  359. package/templates/app/api/internal/user-metadata/route.ts +5 -3
  360. package/templates/app/api/mcp/route.ts +176 -0
  361. package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
  362. package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
  363. package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
  364. package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
  365. package/templates/app/api/v1/api-keys/route.ts +35 -45
  366. package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
  367. package/templates/app/api/v1/billing/cancel/route.ts +7 -6
  368. package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
  369. package/templates/app/api/v1/billing/check-action/route.ts +6 -4
  370. package/templates/app/api/v1/billing/checkout/route.ts +7 -5
  371. package/templates/app/api/v1/billing/plans/route.ts +8 -12
  372. package/templates/app/api/v1/billing/portal/route.ts +7 -5
  373. package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
  374. package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
  375. package/templates/app/api/v1/devtools/features/route.ts +5 -4
  376. package/templates/app/api/v1/devtools/flows/route.ts +5 -4
  377. package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
  378. package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
  379. package/templates/app/api/v1/devtools/testing/route.ts +5 -4
  380. package/templates/app/api/v1/media/[id]/route.ts +13 -25
  381. package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
  382. package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
  383. package/templates/app/api/v1/media/route.ts +5 -9
  384. package/templates/app/api/v1/media/upload/route.ts +9 -21
  385. package/templates/app/api/v1/media-tags/route.ts +9 -13
  386. package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
  387. package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
  388. package/templates/app/api/v1/post-categories/route.ts +5 -4
  389. package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
  390. package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
  391. package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
  392. package/templates/app/api/v1/team-invitations/route.ts +9 -11
  393. package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
  394. package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
  395. package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
  396. package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
  397. package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
  398. package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
  399. package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
  400. package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
  401. package/templates/app/api/v1/teams/route.ts +33 -27
  402. package/templates/app/api/v1/teams/switch/route.ts +11 -13
  403. package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
  404. package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
  405. package/templates/app/api/v1/users/[id]/route.ts +35 -68
  406. package/templates/app/api/v1/users/route.ts +21 -25
  407. package/templates/app/dashboard/layout.tsx +2 -6
  408. package/templates/app/layout.ppr.tsx +9 -14
  409. package/templates/app/layout.tsx +39 -19
  410. package/templates/contents/themes/starter/config/app.config.ts +19 -0
  411. package/templates/contents/themes/starter/config/theme.config.ts +6 -0
  412. package/templates/contents/themes/starter/styles/globals.css +6 -2
  413. package/templates/proxy.ts +46 -10
  414. package/templates/tsconfig.json +4 -0
  415. package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
  416. package/tests/jest/__mocks__/@nextsparkjs/registries/scope-registry.ts +56 -6
  417. package/tests/jest/__mocks__/next-server.js +24 -0
  418. package/dist/styles/classes.json +0 -1093
  419. package/dist/styles/ui.css +0 -2
  420. package/scripts/build/build-ui-css.mjs +0 -351
@@ -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')
@@ -6,7 +6,7 @@ import {
6
6
  handleCorsPreflightRequest,
7
7
  addCorsHeaders,
8
8
  } from '@nextsparkjs/core/lib/api/helpers'
9
- import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
9
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
10
10
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
11
11
  import { TeamService } from '@nextsparkjs/core/lib/services'
12
12
  import { z } from 'zod'
@@ -16,21 +16,19 @@ const switchTeamSchema = z.object({
16
16
  })
17
17
 
18
18
  // Handle CORS preflight
19
- export async function OPTIONS() {
20
- return handleCorsPreflightRequest()
19
+ export async function OPTIONS(request: NextRequest) {
20
+ return handleCorsPreflightRequest(request)
21
21
  }
22
22
 
23
23
  // POST /api/v1/teams/switch - Switch active team context
24
24
  export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
25
25
  try {
26
- // Authenticate using dual auth
27
- const authResult = await authenticateRequest(req)
26
+ // Authenticate using dual auth. Only teams:read: this endpoint changes
27
+ // which team the caller sees, it never writes team data (#93).
28
+ const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
28
29
 
29
30
  if (!authResult.success) {
30
- return NextResponse.json(
31
- { success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
32
- { status: 401 }
33
- )
31
+ return createAuthFailureResponse(authResult)
34
32
  }
35
33
 
36
34
  if (authResult.rateLimitResponse) {
@@ -61,7 +59,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
61
59
  maxAge: 60 * 60 * 24 * 365 // 1 year
62
60
  })
63
61
 
64
- return addCorsHeaders(response)
62
+ return addCorsHeaders(response, req)
65
63
  } catch (error) {
66
64
  if (error instanceof Error && error.message === 'User is not a member of this team') {
67
65
  const response = createApiError(
@@ -70,7 +68,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
70
68
  null,
71
69
  'NOT_TEAM_MEMBER'
72
70
  )
73
- return addCorsHeaders(response)
71
+ return addCorsHeaders(response, req)
74
72
  }
75
73
 
76
74
  throw error
@@ -79,11 +77,11 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
79
77
  if (error instanceof Error && error.name === 'ZodError') {
80
78
  const zodError = error as { issues?: unknown[] }
81
79
  const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
82
- return addCorsHeaders(response)
80
+ return addCorsHeaders(response, req)
83
81
  }
84
82
 
85
83
  console.error('Error switching team:', error)
86
84
  const response = createApiError('Internal server error', 500)
87
- return addCorsHeaders(response)
85
+ return addCorsHeaders(response, req)
88
86
  }
89
87
  }), 'write')
@@ -12,6 +12,7 @@ import {
12
12
  type ThemeRegistryEntry
13
13
  } from '@nextsparkjs/registries/theme-registry'
14
14
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
15
+ import { validateAndAuthenticateRequest } from '@nextsparkjs/core/lib/api/helpers'
15
16
 
16
17
  export const GET = withRateLimitTier(async (
17
18
  request: NextRequest,
@@ -111,6 +112,12 @@ async function handleThemeRequest(
111
112
  console.log(`[Theme API] No route found for ${requestPath}, method: ${method}`)
112
113
  }
113
114
 
115
+ // The endpoint catalog is only disclosed to authenticated callers.
116
+ // Anonymous requests still get theme info / 404, but never the route map.
117
+ const endpointCatalog = (await isAuthenticatedRequest(request))
118
+ ? { availableEndpoints: getThemeEndpoints(themeName) }
119
+ : {}
120
+
114
121
  // Default theme info endpoint
115
122
  if (!endpointPath && method === 'GET') {
116
123
  return NextResponse.json({
@@ -124,7 +131,7 @@ async function handleThemeRequest(
124
131
  entities: themeEntry.entities?.length || 0,
125
132
  routeFiles: themeEntry.routeFiles?.length || 0,
126
133
  plugins: themeEntry.plugins || [],
127
- availableEndpoints: getThemeEndpoints(themeName)
134
+ ...endpointCatalog
128
135
  }
129
136
  })
130
137
  }
@@ -136,7 +143,7 @@ async function handleThemeRequest(
136
143
  theme: themeName,
137
144
  path: endpointPath,
138
145
  method,
139
- availableEndpoints: getThemeEndpoints(themeName)
146
+ ...endpointCatalog
140
147
  },
141
148
  { status: 404 }
142
149
  )
@@ -154,6 +161,20 @@ async function handleThemeRequest(
154
161
  }
155
162
  }
156
163
 
164
+ /**
165
+ * Check whether the request carries a valid session or API key.
166
+ * Used to gate the endpoint catalog (see #104 / #109): the list of registered
167
+ * theme routes must not be enumerable by anonymous callers.
168
+ */
169
+ async function isAuthenticatedRequest(request: NextRequest): Promise<boolean> {
170
+ try {
171
+ const { auth, rateLimitResponse } = await validateAndAuthenticateRequest(request)
172
+ return !rateLimitResponse && Boolean(auth?.userId)
173
+ } catch {
174
+ return false
175
+ }
176
+ }
177
+
157
178
  /**
158
179
  * Match route pattern with dynamic parameters
159
180
  * Example: 'orders/[orderId]/items' matches 'orders/123/items'
@@ -28,7 +28,7 @@ import {
28
28
  handleCorsPreflightRequest,
29
29
  addCorsHeaders,
30
30
  } from '@nextsparkjs/core/lib/api/helpers'
31
- import { authenticateRequest, hasRequiredScope } from '@nextsparkjs/core/lib/api/auth/dual-auth'
31
+ import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
32
32
  import { z } from 'zod'
33
33
  import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
34
34
 
@@ -41,8 +41,8 @@ const metadataValueSchema = z.object({
41
41
  })
42
42
 
43
43
  // Handle CORS preflight
44
- export async function OPTIONS() {
45
- return handleCorsPreflightRequest()
44
+ export async function OPTIONS(request: NextRequest) {
45
+ return handleCorsPreflightRequest(request)
46
46
  }
47
47
 
48
48
  /**
@@ -62,44 +62,29 @@ export const GET = withRateLimitTier(withApiLogging(async (
62
62
  { params }: { params: Promise<{ id: string; key: string }> }
63
63
  ): Promise<NextResponse> => {
64
64
  try {
65
- // Authenticate using dual auth
66
- const authResult = await authenticateRequest(req)
65
+ // Authenticate using dual auth; the API-key scope is declared at the entry
66
+ // point, which fails closed for keys that lack it (#93).
67
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:read' })
67
68
 
68
69
  if (!authResult.success || !authResult.user) {
69
- return NextResponse.json(
70
- createApiError('Authentication required', 401, null, 'AUTHENTICATION_FAILED'),
71
- { status: 401 }
72
- )
70
+ return createAuthFailureResponse(authResult)
73
71
  }
74
72
 
75
73
  if (authResult.rateLimitResponse) {
76
74
  return authResult.rateLimitResponse as NextResponse
77
75
  }
78
76
 
79
- // Check required permissions
80
- const hasPermission =
81
- authResult.type === 'session' ||
82
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:read'))
83
-
84
- if (!hasPermission) {
85
- const response = createApiError(
86
- 'Insufficient permissions. Admin access required for user metadata.',
87
- 403
88
- )
89
- return addCorsHeaders(response)
90
- }
91
-
92
77
  const { id, key } = await params
93
78
 
94
79
  // Validate parameters
95
80
  if (!id || id.trim() === '') {
96
81
  const response = createApiError('User ID is required', 400, null, 'MISSING_USER_ID')
97
- return addCorsHeaders(response)
82
+ return addCorsHeaders(response, req)
98
83
  }
99
84
 
100
85
  if (!key || key.trim() === '') {
101
86
  const response = createApiError('Metadata key is required', 400, null, 'MISSING_META_KEY')
102
- return addCorsHeaders(response)
87
+ return addCorsHeaders(response, req)
103
88
  }
104
89
 
105
90
  // Validate key length
@@ -110,7 +95,7 @@ export const GET = withRateLimitTier(withApiLogging(async (
110
95
  null,
111
96
  'INVALID_META_KEY'
112
97
  )
113
- return addCorsHeaders(response)
98
+ return addCorsHeaders(response, req)
114
99
  }
115
100
 
116
101
  // Get user metadata value using UserService
@@ -126,14 +111,14 @@ export const GET = withRateLimitTier(withApiLogging(async (
126
111
  key,
127
112
  value: metaValue ?? null,
128
113
  })
129
- return addCorsHeaders(response)
114
+ return addCorsHeaders(response, req)
130
115
  } catch (error) {
131
116
  const response = createApiError(
132
117
  'Failed to fetch user metadata',
133
118
  500,
134
119
  error instanceof Error ? error.message : undefined
135
120
  )
136
- return addCorsHeaders(response)
121
+ return addCorsHeaders(response, req)
137
122
  }
138
123
  }), 'read');
139
124
 
@@ -159,44 +144,29 @@ export const PUT = withRateLimitTier(withApiLogging(async (
159
144
  { params }: { params: Promise<{ id: string; key: string }> }
160
145
  ): Promise<NextResponse> => {
161
146
  try {
162
- // Authenticate using dual auth
163
- const authResult = await authenticateRequest(req)
147
+ // Authenticate using dual auth; the API-key scope is declared at the entry
148
+ // point, which fails closed for keys that lack it (#93).
149
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' })
164
150
 
165
151
  if (!authResult.success || !authResult.user) {
166
- return NextResponse.json(
167
- createApiError('Authentication required', 401, null, 'AUTHENTICATION_FAILED'),
168
- { status: 401 }
169
- )
152
+ return createAuthFailureResponse(authResult)
170
153
  }
171
154
 
172
155
  if (authResult.rateLimitResponse) {
173
156
  return authResult.rateLimitResponse as NextResponse
174
157
  }
175
158
 
176
- // Check required permissions
177
- const hasPermission =
178
- authResult.type === 'session' ||
179
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:write'))
180
-
181
- if (!hasPermission) {
182
- const response = createApiError(
183
- 'Insufficient permissions. Admin access required for user metadata.',
184
- 403
185
- )
186
- return addCorsHeaders(response)
187
- }
188
-
189
159
  const { id, key } = await params
190
160
 
191
161
  // Validate parameters
192
162
  if (!id || id.trim() === '') {
193
163
  const response = createApiError('User ID is required', 400, null, 'MISSING_USER_ID')
194
- return addCorsHeaders(response)
164
+ return addCorsHeaders(response, req)
195
165
  }
196
166
 
197
167
  if (!key || key.trim() === '') {
198
168
  const response = createApiError('Metadata key is required', 400, null, 'MISSING_META_KEY')
199
- return addCorsHeaders(response)
169
+ return addCorsHeaders(response, req)
200
170
  }
201
171
 
202
172
  // Validate key length
@@ -207,7 +177,7 @@ export const PUT = withRateLimitTier(withApiLogging(async (
207
177
  null,
208
178
  'INVALID_META_KEY'
209
179
  )
210
- return addCorsHeaders(response)
180
+ return addCorsHeaders(response, req)
211
181
  }
212
182
 
213
183
  // Parse and validate request body
@@ -223,7 +193,7 @@ export const PUT = withRateLimitTier(withApiLogging(async (
223
193
  null,
224
194
  'VALUE_TOO_LARGE'
225
195
  )
226
- return addCorsHeaders(response)
196
+ return addCorsHeaders(response, req)
227
197
  }
228
198
 
229
199
  // Update metadata using UserService
@@ -245,7 +215,7 @@ export const PUT = withRateLimitTier(withApiLogging(async (
245
215
  value: validatedData.value,
246
216
  updated: true,
247
217
  })
248
- return addCorsHeaders(response)
218
+ return addCorsHeaders(response, req)
249
219
  } catch (error) {
250
220
  // Handle validation errors
251
221
  if (error instanceof z.ZodError) {
@@ -255,7 +225,7 @@ export const PUT = withRateLimitTier(withApiLogging(async (
255
225
  error.issues,
256
226
  'VALIDATION_ERROR'
257
227
  )
258
- return addCorsHeaders(response)
228
+ return addCorsHeaders(response, req)
259
229
  }
260
230
 
261
231
  const response = createApiError(
@@ -263,7 +233,7 @@ export const PUT = withRateLimitTier(withApiLogging(async (
263
233
  500,
264
234
  error instanceof Error ? error.message : undefined
265
235
  )
266
- return addCorsHeaders(response)
236
+ return addCorsHeaders(response, req)
267
237
  }
268
238
  }), 'write');
269
239
 
@@ -287,44 +257,29 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
287
257
  { params }: { params: Promise<{ id: string; key: string }> }
288
258
  ): Promise<NextResponse> => {
289
259
  try {
290
- // Authenticate using dual auth
291
- const authResult = await authenticateRequest(req)
260
+ // Authenticate using dual auth; the API-key scope is declared at the entry
261
+ // point, which fails closed for keys that lack it (#93).
262
+ const authResult = await authenticateRequest(req, { requiredScope: 'users:write' })
292
263
 
293
264
  if (!authResult.success || !authResult.user) {
294
- return NextResponse.json(
295
- createApiError('Authentication required', 401, null, 'AUTHENTICATION_FAILED'),
296
- { status: 401 }
297
- )
265
+ return createAuthFailureResponse(authResult)
298
266
  }
299
267
 
300
268
  if (authResult.rateLimitResponse) {
301
269
  return authResult.rateLimitResponse as NextResponse
302
270
  }
303
271
 
304
- // Check required permissions
305
- const hasPermission =
306
- authResult.type === 'session' ||
307
- (authResult.type === 'api-key' && hasRequiredScope(authResult, 'users:write'))
308
-
309
- if (!hasPermission) {
310
- const response = createApiError(
311
- 'Insufficient permissions. Admin access required for user metadata.',
312
- 403
313
- )
314
- return addCorsHeaders(response)
315
- }
316
-
317
272
  const { id, key } = await params
318
273
 
319
274
  // Validate parameters
320
275
  if (!id || id.trim() === '') {
321
276
  const response = createApiError('User ID is required', 400, null, 'MISSING_USER_ID')
322
- return addCorsHeaders(response)
277
+ return addCorsHeaders(response, req)
323
278
  }
324
279
 
325
280
  if (!key || key.trim() === '') {
326
281
  const response = createApiError('Metadata key is required', 400, null, 'MISSING_META_KEY')
327
- return addCorsHeaders(response)
282
+ return addCorsHeaders(response, req)
328
283
  }
329
284
 
330
285
  // Validate key length
@@ -335,7 +290,7 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
335
290
  null,
336
291
  'INVALID_META_KEY'
337
292
  )
338
- return addCorsHeaders(response)
293
+ return addCorsHeaders(response, req)
339
294
  }
340
295
 
341
296
  // Delete metadata using UserService
@@ -346,13 +301,13 @@ export const DELETE = withRateLimitTier(withApiLogging(async (
346
301
  key,
347
302
  deleted: true,
348
303
  })
349
- return addCorsHeaders(response)
304
+ return addCorsHeaders(response, req)
350
305
  } catch (error) {
351
306
  const response = createApiError(
352
307
  'Failed to delete user metadata',
353
308
  500,
354
309
  error instanceof Error ? error.message : undefined
355
310
  )
356
- return addCorsHeaders(response)
311
+ return addCorsHeaders(response, req)
357
312
  }
358
313
  }), 'write');