@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
@@ -5,6 +5,8 @@
5
5
  * Used by the unified /api/v1/ endpoints.
6
6
  */
7
7
  import { NextRequest, NextResponse } from 'next/server';
8
+ import { type AuthenticateOptions, type DualAuthFailure } from './scope-policy';
9
+ export type { AuthenticateOptions, DualAuthFailure, DualAuthFailureCode } from './scope-policy';
8
10
  /**
9
11
  * System Admin Team - Members can bypass team context validation
10
12
  * This team is created in core/migrations/090_sample_data.sql
@@ -33,24 +35,56 @@ export interface DualAuthUser {
33
35
  }
34
36
  export interface DualAuthResult {
35
37
  success: boolean;
38
+ /**
39
+ * How the caller authenticated. On a failed result, `'api-key'` means a
40
+ * valid key WAS presented but rejected by scope enforcement (see `error`);
41
+ * `'none'` means no usable credential was found.
42
+ */
36
43
  type: 'api-key' | 'session' | 'none';
37
44
  user: DualAuthUser | null;
38
45
  scopes?: string[];
46
+ /** `api_key.id` of the key that authenticated the request (type 'api-key' only). Used for audit logging (#105). */
47
+ keyId?: string;
39
48
  rateLimitResponse?: Response;
40
49
  /** Set when a dev-only x-act-as-user override replaced the real caller. */
41
50
  actingAs?: {
42
51
  originalUserId: string;
43
52
  originalRole: string;
44
53
  };
54
+ /** Why the request was rejected. Only present when `success` is false. */
55
+ error?: DualAuthFailure;
45
56
  }
46
57
  /**
47
- * Try to authenticate request using either API Key or Session
58
+ * Try to authenticate request using either API Key or Session.
59
+ *
60
+ * API-key scope enforcement happens HERE and fails closed (#93): a route
61
+ * declares the scope it needs via `options.requiredScope`, or explicitly
62
+ * accepts any valid key via `options.allowAnyScope`. A key hitting a route
63
+ * that declared neither is rejected (403 `SCOPE_NOT_DECLARED`) and the route
64
+ * is named in the log, so a forgotten declaration is visible instead of a
65
+ * silently over-privileged credential. Session auth is unaffected.
66
+ *
67
+ * @example
68
+ * const authResult = await authenticateRequest(request, { requiredScope: 'users:read' })
69
+ * if (!authResult.success || !authResult.user) return createAuthFailureResponse(authResult)
48
70
  */
49
- export declare function authenticateRequest(request: NextRequest): Promise<DualAuthResult>;
71
+ export declare function authenticateRequest(request: NextRequest, options?: AuthenticateOptions): Promise<DualAuthResult>;
50
72
  /**
51
73
  * Check if user has required scope (for API Key auth)
74
+ *
75
+ * Since #93 the entry point already enforces the route's declared scope;
76
+ * this remains for routes that branch on a *second*, finer scope after
77
+ * authenticating (e.g. read-vs-write within one handler). An array is
78
+ * satisfied by any one of its scopes; an empty array by none.
79
+ *
80
+ * `admin:all` was previously accepted as an implicit full-access scope here,
81
+ * but it was never in `API_SCOPES`, never mintable via `validateScopesForUser`,
82
+ * and undocumented — a full-access string nothing legitimately produced.
83
+ * Removed rather than formalized: the wildcard `*` (mintable only by a real
84
+ * superadmin, see `validateScopesForUser`) already covers the "this key has
85
+ * full access" case in a documented, auditable way.
52
86
  */
53
- export declare function hasRequiredScope(authResult: DualAuthResult, requiredScope: string): boolean;
87
+ export declare function hasRequiredScope(authResult: DualAuthResult, requiredScope: string | string[]): boolean;
54
88
  /**
55
89
  * Resolve and validate team context from request.
56
90
  *
@@ -85,4 +119,12 @@ export declare function createAuthError(message?: string, status?: number): Next
85
119
  error: string;
86
120
  code: string;
87
121
  }>;
122
+ /**
123
+ * Turn a failed `authenticateRequest` result into the right HTTP response:
124
+ * 401 `AUTHENTICATION_FAILED` when nothing usable was presented, 403
125
+ * `INSUFFICIENT_SCOPE` / `SCOPE_NOT_DECLARED` when a valid API key was
126
+ * rejected by scope enforcement. Use it in the `!authResult.success` branch
127
+ * instead of a hand-written 401, so scope rejections are not mislabeled.
128
+ */
129
+ export declare function createAuthFailureResponse(authResult: DualAuthResult): NextResponse;
88
130
  //# sourceMappingURL=dual-auth.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAUvD;;;GAGG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AACnD,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;CAC5D;AAqBD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAkBvF;AAqKD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAY3F;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,CAsChC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAkBlB;AAkBD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAkC,EAAE,MAAM,GAAE,MAAY;;;;GAShG"}
1
+ {"version":3,"file":"dual-auth.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/dual-auth.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAMvD,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,eAAe,EAKrB,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EAAE,mBAAmB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAA;AAK/F;;;GAGG;AACH,eAAO,MAAM,oBAAoB,uBAAuB,CAAA;AAExD;;GAEG;AACH,eAAO,MAAM,mBAAmB,mBAAmB,CAAA;AACnD,eAAO,MAAM,kBAAkB,8BAA8B,CAAA;AAE7D;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,kBAAkB,CAAA;AAOjD,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAA;IAChB;;;;OAIG;IACH,IAAI,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAA;IACpC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,mHAAmH;IACnH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,iBAAiB,CAAC,EAAE,QAAQ,CAAA;IAC5B,2EAA2E;IAC3E,QAAQ,CAAC,EAAE;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3D,0EAA0E;IAC1E,KAAK,CAAC,EAAE,eAAe,CAAA;CACxB;AAqBD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,cAAc,CAAC,CAsCzB;AAuKD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAUtG;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,cAAc,GACzB,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,CAsChC;AAMD;;;;;;;;;GASG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,cAAc,EAC1B,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,OAAO,CAAC,CAkBlB;AA2BD;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAkC,EAAE,MAAM,GAAE,MAAY;;;;GAShG;AAED;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,UAAU,EAAE,cAAc,GAAG,YAAY,CAUlF"}
@@ -4,6 +4,12 @@ import { validateApiKey } from "../auth.js";
4
4
  import { queryOne } from "../../db.js";
5
5
  import { TeamMemberService } from "../../services/team-member.service.js";
6
6
  import { TeamService } from "../../services/team.service.js";
7
+ import {
8
+ authenticationFailed,
9
+ describeRoute,
10
+ resolveApiKeyScopeFailure,
11
+ scopesSatisfy
12
+ } from "./scope-policy.js";
7
13
  const SYSTEM_ADMIN_TEAM_ID = "team-nextspark-001";
8
14
  const ADMIN_BYPASS_HEADER = "x-admin-bypass";
9
15
  const ADMIN_BYPASS_VALUE = "confirm-cross-team-access";
@@ -24,9 +30,20 @@ async function getUserDefaultTeamId(userId) {
24
30
  return void 0;
25
31
  }
26
32
  }
27
- async function authenticateRequest(request) {
33
+ async function authenticateRequest(request, options = {}) {
28
34
  const apiKeyResult = await tryApiKeyAuth(request);
29
35
  if (apiKeyResult.success) {
36
+ const failure = resolveApiKeyScopeFailure(apiKeyResult.scopes, options, describeRoute(request));
37
+ if (failure) {
38
+ return {
39
+ success: false,
40
+ type: "api-key",
41
+ user: apiKeyResult.user,
42
+ scopes: apiKeyResult.scopes,
43
+ keyId: apiKeyResult.keyId,
44
+ error: failure
45
+ };
46
+ }
30
47
  return applyActAsOverride(request, apiKeyResult);
31
48
  }
32
49
  const sessionResult = await trySessionAuth(request);
@@ -36,7 +53,8 @@ async function authenticateRequest(request) {
36
53
  return {
37
54
  success: false,
38
55
  type: "none",
39
- user: null
56
+ user: null,
57
+ error: authenticationFailed()
40
58
  };
41
59
  }
42
60
  function isProductionRuntime() {
@@ -104,7 +122,8 @@ async function tryApiKeyAuth(request) {
104
122
  name: userInfo == null ? void 0 : userInfo.name,
105
123
  defaultTeamId
106
124
  },
107
- scopes: apiAuth.scopes || []
125
+ scopes: apiAuth.scopes || [],
126
+ keyId: apiAuth.keyId
108
127
  };
109
128
  } catch (error) {
110
129
  console.error("API Key auth failed:", error);
@@ -128,6 +147,7 @@ async function trySessionAuth(request) {
128
147
  user: {
129
148
  id: session.user.id,
130
149
  email: session.user.email,
150
+ // @ts-expect-error — pre-existing type error, tracked in https://github.com/NextSpark-js/nextspark/issues/131
131
151
  role: session.user.role || "user",
132
152
  name: session.user.name,
133
153
  defaultTeamId
@@ -144,8 +164,8 @@ function hasRequiredScope(authResult, requiredScope) {
144
164
  if (authResult.type === "session") {
145
165
  return true;
146
166
  }
147
- if (authResult.type === "api-key" && authResult.scopes) {
148
- return authResult.scopes.includes(requiredScope) || authResult.scopes.includes("admin:all") || authResult.scopes.includes("*");
167
+ if (authResult.type === "api-key") {
168
+ return scopesSatisfy(authResult.scopes, requiredScope);
149
169
  }
150
170
  return false;
151
171
  }
@@ -191,8 +211,11 @@ async function canBypassTeamContext(authResult, request) {
191
211
  async function checkSystemAdminMembership(userId) {
192
212
  try {
193
213
  const member = await queryOne(
194
- 'SELECT id FROM "team_members" WHERE "teamId" = $1 AND "userId" = $2',
195
- [SYSTEM_ADMIN_TEAM_ID, userId]
214
+ `SELECT tm.id FROM "team_members" tm
215
+ JOIN "teams" t ON t.id = tm."teamId"
216
+ WHERE tm."userId" = $1 AND (t.metadata->>'systemAdmin')::boolean IS TRUE
217
+ LIMIT 1`,
218
+ [userId]
196
219
  );
197
220
  return !!member;
198
221
  } catch (error) {
@@ -210,6 +233,17 @@ function createAuthError(message = "Authentication required", status = 401) {
210
233
  { status }
211
234
  );
212
235
  }
236
+ function createAuthFailureResponse(authResult) {
237
+ const failure = authResult.error ?? authenticationFailed();
238
+ return NextResponse.json(
239
+ {
240
+ success: false,
241
+ error: failure.message,
242
+ code: failure.code
243
+ },
244
+ { status: failure.status }
245
+ );
246
+ }
213
247
  export {
214
248
  ACT_AS_USER_HEADER,
215
249
  ADMIN_BYPASS_HEADER,
@@ -218,6 +252,7 @@ export {
218
252
  authenticateRequest,
219
253
  canBypassTeamContext,
220
254
  createAuthError,
255
+ createAuthFailureResponse,
221
256
  hasRequiredScope,
222
257
  resolveTeamContext
223
258
  };
@@ -17,9 +17,14 @@ export declare function isSuperAdmin(authResult: DualAuthResult): boolean;
17
17
  * Verifica si el usuario tiene permisos para operaciones administrativas
18
18
  * Combina validacion de superadmin + scopes de API key
19
19
  *
20
+ * Fail closed for API keys (#93): a key is only granted admin access when the
21
+ * caller names the scope the operation needs and the key holds it. Omitting
22
+ * `requiredScope` used to hand a narrowly-scoped key its superadmin owner's
23
+ * full permissions; now it denies the key instead. Sessions are unaffected.
24
+ *
20
25
  * @param authResult - Resultado de autenticacion
21
- * @param requiredScope - Scope requerido para API keys (opcional)
26
+ * @param requiredScope - Scope requerido para API keys (obligatorio para que una API key pase)
22
27
  * @returns true si tiene permisos de admin
23
28
  */
24
- export declare function hasAdminPermission(authResult: DualAuthResult, requiredScope?: string): boolean;
29
+ export declare function hasAdminPermission(authResult: DualAuthResult, requiredScope?: string | string[]): boolean;
25
30
  //# sourceMappingURL=permissions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAYT"}
1
+ {"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/permissions.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,cAAc,GAAG,OAAO,CAOhE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,cAAc,EAC1B,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,OAAO,CAYT"}
@@ -9,8 +9,8 @@ function hasAdminPermission(authResult, requiredScope) {
9
9
  if (!isSuperAdmin(authResult)) {
10
10
  return false;
11
11
  }
12
- if (authResult.type === "api-key" && requiredScope) {
13
- return hasRequiredScope(authResult, requiredScope);
12
+ if (authResult.type === "api-key") {
13
+ return requiredScope !== void 0 && hasRequiredScope(authResult, requiredScope);
14
14
  }
15
15
  return true;
16
16
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * API-key scope policy (#93)
3
+ *
4
+ * Shared by both auth entry points — `authenticateRequest` (dual-auth.ts) and
5
+ * `validateAndAuthenticateRequest` (helpers.ts) — so the rule cannot drift
6
+ * between them. Pure: no I/O, no framework imports.
7
+ *
8
+ * The rule, applied only to API-key authentication (sessions carry no scopes
9
+ * and are never gated here):
10
+ *
11
+ * 1. The route declared `{ requiredScope }` → the key must hold one of the
12
+ * listed scopes (or the wildcard `*`). Otherwise `INSUFFICIENT_SCOPE`.
13
+ * 2. The route declared `{ allowAnyScope: true }` → any valid key passes.
14
+ * This is the explicit, self-documenting opt-out for routes that are
15
+ * genuinely scope-agnostic (a public route that only *enriches* its
16
+ * answer for authenticated callers, an MCP transport whose tools enforce
17
+ * scopes themselves, ...).
18
+ * 3. The route declared nothing → the key is rejected with
19
+ * `SCOPE_NOT_DECLARED` and the route is named in the server log.
20
+ *
21
+ * Rule 3 is the point of the change: forgetting a declaration now produces a
22
+ * visible 403 on an integration instead of a silently over-privileged
23
+ * credential. Before, `scopes` were populated and nobody was obliged to look.
24
+ */
25
+ export type DualAuthFailureCode = 'AUTHENTICATION_FAILED' | 'INSUFFICIENT_SCOPE' | 'SCOPE_NOT_DECLARED';
26
+ export interface DualAuthFailure {
27
+ code: DualAuthFailureCode;
28
+ /** 401 when no usable credential was presented, 403 when a valid key was rejected on scope. */
29
+ status: 401 | 403;
30
+ message: string;
31
+ }
32
+ export interface AuthenticateOptions {
33
+ /**
34
+ * Scope(s) an API key must hold to use this route. A string requires that
35
+ * scope; an array is satisfied by any one of its entries. Ignored for
36
+ * session auth. An empty array counts as "nothing declared".
37
+ */
38
+ requiredScope?: string | string[];
39
+ /**
40
+ * Accept any valid API key regardless of its scopes. Explicit opt-out of
41
+ * fail-closed enforcement — say why in a comment at the call site.
42
+ * A declared `requiredScope` still wins if both are set.
43
+ */
44
+ allowAnyScope?: boolean;
45
+ }
46
+ /** Failure attached to a result that carried no usable credential at all. */
47
+ export declare function authenticationFailed(): DualAuthFailure;
48
+ export declare function normalizeRequiredScopes(requiredScope: AuthenticateOptions['requiredScope']): string[];
49
+ /**
50
+ * True when `scopes` (an API key's stored scopes) satisfy `requiredScope`:
51
+ * holds one of the required scopes, or the wildcard `*`. An empty requirement
52
+ * is never satisfied — "no scope needed" must be said with `allowAnyScope`,
53
+ * not with an empty list.
54
+ */
55
+ export declare function scopesSatisfy(scopes: readonly string[] | undefined, requiredScope: string | string[]): boolean;
56
+ /** Minimal request shape needed to name a route in logs (works for mocks too). */
57
+ export interface RouteDescribable {
58
+ method?: string;
59
+ url?: string;
60
+ nextUrl?: {
61
+ pathname: string;
62
+ };
63
+ }
64
+ export declare function describeRoute(request: RouteDescribable): string;
65
+ /**
66
+ * Apply the policy to an already-authenticated API key.
67
+ *
68
+ * @returns null when the key may proceed, otherwise the failure to report.
69
+ */
70
+ export declare function resolveApiKeyScopeFailure(scopes: readonly string[] | undefined, options: AuthenticateOptions, route: string): DualAuthFailure | null;
71
+ //# sourceMappingURL=scope-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scope-policy.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/auth/scope-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,MAAM,mBAAmB,GAC3B,uBAAuB,GACvB,oBAAoB,GACpB,oBAAoB,CAAA;AAExB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,CAAA;IACzB,+FAA+F;IAC/F,MAAM,EAAE,GAAG,GAAG,GAAG,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;IACjC;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,IAAI,eAAe,CAEtD;AAED,wBAAgB,uBAAuB,CAAC,aAAa,EAAE,mBAAmB,CAAC,eAAe,CAAC,GAAG,MAAM,EAAE,CAKrG;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,aAAa,EAAE,MAAM,GAAG,MAAM,EAAE,GAC/B,OAAO,CAIT;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC/B;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAU/D;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACrC,OAAO,EAAE,mBAAmB,EAC5B,KAAK,EAAE,MAAM,GACZ,eAAe,GAAG,IAAI,CAuBxB"}
@@ -0,0 +1,53 @@
1
+ function authenticationFailed() {
2
+ return { code: "AUTHENTICATION_FAILED", status: 401, message: "Authentication required" };
3
+ }
4
+ function normalizeRequiredScopes(requiredScope) {
5
+ if (requiredScope === void 0) return [];
6
+ return (Array.isArray(requiredScope) ? requiredScope : [requiredScope]).filter(
7
+ (scope) => typeof scope === "string" && scope.length > 0
8
+ );
9
+ }
10
+ function scopesSatisfy(scopes, requiredScope) {
11
+ const required = normalizeRequiredScopes(requiredScope);
12
+ if (required.length === 0 || !scopes) return false;
13
+ return scopes.includes("*") || required.some((scope) => scopes.includes(scope));
14
+ }
15
+ function describeRoute(request) {
16
+ var _a;
17
+ let pathname = (_a = request.nextUrl) == null ? void 0 : _a.pathname;
18
+ if (!pathname && request.url) {
19
+ try {
20
+ pathname = new URL(request.url).pathname;
21
+ } catch {
22
+ pathname = request.url;
23
+ }
24
+ }
25
+ return `${request.method ?? "GET"} ${pathname ?? "<unknown route>"}`;
26
+ }
27
+ function resolveApiKeyScopeFailure(scopes, options, route) {
28
+ const required = normalizeRequiredScopes(options.requiredScope);
29
+ if (required.length > 0) {
30
+ if (scopesSatisfy(scopes, required)) return null;
31
+ return {
32
+ code: "INSUFFICIENT_SCOPE",
33
+ status: 403,
34
+ message: `API key lacks required scope '${required.join("' or '")}'`
35
+ };
36
+ }
37
+ if (options.allowAnyScope) return null;
38
+ console.error(
39
+ `[auth] ${route} did not declare a required API key scope; rejecting the API-key request (fail closed). Pass { requiredScope } \u2014 or { allowAnyScope: true } if any valid key is really acceptable \u2014 to the auth entry point.`
40
+ );
41
+ return {
42
+ code: "SCOPE_NOT_DECLARED",
43
+ status: 403,
44
+ message: "API key access is not enabled for this route: it declares no required scope"
45
+ };
46
+ }
47
+ export {
48
+ authenticationFailed,
49
+ describeRoute,
50
+ normalizeRequiredScopes,
51
+ resolveApiKeyScopeFailure,
52
+ scopesSatisfy
53
+ };
@@ -25,6 +25,21 @@ export interface ApiKeyValidationResult {
25
25
  * Valida una API Key desde el header Authorization o x-api-key
26
26
  */
27
27
  export declare function validateApiKey(request: NextRequest): Promise<ApiKeyAuth | null>;
28
+ /**
29
+ * Elimina del cache la entrada de una API key (indexada por keyHash).
30
+ *
31
+ * Debe llamarse cada vez que cambia el `status` de una key (revocación,
32
+ * desactivación, expiración): validateApiKey() consulta el cache ANTES que la
33
+ * base de datos, así que sin esta invalidación una key revocada sigue
34
+ * autenticando hasta que vence el TTL (5 min). Ver #92.
35
+ */
36
+ export declare function invalidateApiKeyCache(keyHash: string): void;
37
+ /**
38
+ * Variante por id para los call sites que no tienen el hash a mano.
39
+ * Hace una lectura extra; preferí invalidateApiKeyCache(keyHash) cuando el
40
+ * hash ya viene en la fila (por ejemplo vía RETURNING "keyHash").
41
+ */
42
+ export declare function invalidateApiKeyCacheById(keyId: string): Promise<void>;
28
43
  /**
29
44
  * Verifica si una autenticación tiene alguno de los scopes requeridos
30
45
  */
@@ -44,13 +59,30 @@ export declare function getApiKeyUser(auth: ApiKeyAuth): Promise<{
44
59
  role: string;
45
60
  }>;
46
61
  /**
47
- * Verifica si un usuario puede crear API keys
62
+ * Verifica si un usuario puede crear API keys.
63
+ *
64
+ * Superadmin is a GLOBAL role (`users.role`) and is always allowed. Any
65
+ * other user is allowed only if they hold a team role that itself grants
66
+ * `admin:api-keys`/`admin:users`-equivalent scopes for at least one team —
67
+ * i.e. the same source of truth `validateScopesForUser` uses, checked
68
+ * without a specific team in mind (any qualifying membership is enough).
48
69
  */
49
70
  export declare function canCreateApiKeys(userId: string): Promise<boolean>;
50
71
  /**
51
- * Valida que los scopes solicitados sean permitidos para el usuario
72
+ * Valida que los scopes solicitados sean permitidos para el usuario, en el
73
+ * contexto de un team específico.
74
+ *
75
+ * Superadmin is a GLOBAL role (`users.role`) — it never appears in
76
+ * `AVAILABLE_ROLES` (team roles), so it's special-cased here rather than
77
+ * inside the scope registry. Every other user is resolved through their
78
+ * TEAM role for `teamId` (not their global `users.role` — NextSpark's
79
+ * entity authorization model is team-scoped, see `app.config.ts`), and the
80
+ * allowed-scope set comes from the same registry-backed
81
+ * `ScopeService.getRoleScopes()` that session auth's implicit scopes
82
+ * (`generateScopesForRole` in `helpers.ts`) and `checkPermission()` both
83
+ * ultimately derive from — so minting and enforcement can't diverge again.
52
84
  */
53
- export declare function validateScopesForUser(userId: string, requestedScopes: string[]): Promise<{
85
+ export declare function validateScopesForUser(userId: string, teamId: string | null, requestedScopes: string[]): Promise<{
54
86
  valid: boolean;
55
87
  allowedScopes: string[];
56
88
  deniedScopes: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/api/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK1C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAoHrF;AAiDD;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAM/E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAMhF;AAiBD;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU;QAG1C,MAAM;WACH,MAAM;eACF,MAAM,GAAG,IAAI;cACd,MAAM,GAAG,IAAI;UACjB,MAAM;GAWjB;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBvE;AAED;;GAEG;AACH,wBAAsB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IAC9F,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC,CAuDD"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/lib/api/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO1C;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAEnF;AAED;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAoHrF;AA8CD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ5E;AAID;;GAEG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAM/E;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAMhF;AAiBD;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,UAAU;QAG1C,MAAM;WACH,MAAM;eACF,MAAM,GAAG,IAAI;cACd,MAAM,GAAG,IAAI;UACjB,MAAM;GAWjB;AAED;;;;;;;;GAQG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAwBvE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,eAAe,EAAE,MAAM,EAAE,GACxB,OAAO,CAAC;IACT,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB,CAAC,CAyED"}
@@ -1,6 +1,8 @@
1
1
  import { queryOne } from "../db.js";
2
2
  import { ApiKeyManager } from "./keys.js";
3
3
  import { apiKeyCache, getCacheKey } from "./cache.js";
4
+ import { TeamMemberService } from "../services/team-member.service.js";
5
+ import { ScopeService } from "../services/scope.service.js";
4
6
  function hasScope(auth, requiredScope) {
5
7
  return auth.scopes.includes(requiredScope);
6
8
  }
@@ -60,7 +62,7 @@ async function validateApiKey(request) {
60
62
  return null;
61
63
  }
62
64
  if (keyData.expiresAt && new Date(keyData.expiresAt) < /* @__PURE__ */ new Date()) {
63
- markAsExpired(keyData.id).catch(console.error);
65
+ markAsExpired(keyData.id, keyHash).catch(console.error);
64
66
  await constantTimeDelay(startTime);
65
67
  return null;
66
68
  }
@@ -99,18 +101,29 @@ async function resetFailedAttempts(keyId) {
99
101
  console.error("Failed to reset failed attempts:", error);
100
102
  }
101
103
  }
102
- async function markAsExpired(keyId) {
104
+ async function markAsExpired(keyId, keyHash) {
103
105
  try {
104
106
  await queryOne(
105
107
  'UPDATE "api_key" SET status = $1 WHERE id = $2',
106
108
  ["expired", keyId]
107
109
  );
108
- const cacheKey = getCacheKey("api_key", keyId);
109
- apiKeyCache.delete(cacheKey);
110
+ invalidateApiKeyCache(keyHash);
110
111
  } catch (error) {
111
112
  console.error("Failed to mark API key as expired:", error);
112
113
  }
113
114
  }
115
+ function invalidateApiKeyCache(keyHash) {
116
+ apiKeyCache.delete(getCacheKey("api_key", keyHash));
117
+ }
118
+ async function invalidateApiKeyCacheById(keyId) {
119
+ const row = await queryOne(
120
+ 'SELECT "keyHash" FROM "api_key" WHERE id = $1',
121
+ [keyId]
122
+ );
123
+ if (row == null ? void 0 : row.keyHash) {
124
+ invalidateApiKeyCache(row.keyHash);
125
+ }
126
+ }
114
127
  function hasAnyScope(auth, requiredScopes) {
115
128
  if (auth.scopes.includes("*")) {
116
129
  return true;
@@ -154,13 +167,20 @@ async function canCreateApiKeys(userId) {
154
167
  if (!user) {
155
168
  return false;
156
169
  }
157
- return ["admin", "superadmin"].includes(user.role);
170
+ if (user.role === "superadmin") {
171
+ return true;
172
+ }
173
+ const memberships = await TeamMemberService.listByUser(userId);
174
+ return memberships.some(({ role }) => {
175
+ const scopes = ScopeService.getRoleScopes(role);
176
+ return scopes.includes("*") || scopes.includes("admin:api-keys");
177
+ });
158
178
  } catch (error) {
159
179
  console.error("Error checking API key creation permissions:", error);
160
180
  return false;
161
181
  }
162
182
  }
163
- async function validateScopesForUser(userId, requestedScopes) {
183
+ async function validateScopesForUser(userId, teamId, requestedScopes) {
164
184
  try {
165
185
  const user = await queryOne(
166
186
  'SELECT role FROM "users" WHERE id = $1',
@@ -173,23 +193,32 @@ async function validateScopesForUser(userId, requestedScopes) {
173
193
  deniedScopes: requestedScopes
174
194
  };
175
195
  }
176
- const scopesByRole = {
177
- member: ["tasks:read", "tasks:write"],
178
- colaborator: ["tasks:read", "tasks:write", "tasks:delete", "users:read"],
179
- admin: [
180
- "users:read",
181
- "users:write",
182
- "users:delete",
183
- "tasks:read",
184
- "tasks:write",
185
- "tasks:delete",
186
- "admin:api-keys",
187
- "admin:users"
188
- ],
189
- superadmin: ["*"]
190
- // Acceso completo
191
- };
192
- const allowedScopes = scopesByRole[user.role] || [];
196
+ if (user.role === "superadmin") {
197
+ return {
198
+ valid: true,
199
+ allowedScopes: requestedScopes,
200
+ deniedScopes: []
201
+ };
202
+ }
203
+ if (!teamId) {
204
+ return {
205
+ valid: false,
206
+ allowedScopes: [],
207
+ deniedScopes: requestedScopes
208
+ };
209
+ }
210
+ const teamRole = await TeamMemberService.getRole(teamId, userId);
211
+ if (!teamRole) {
212
+ return {
213
+ valid: false,
214
+ allowedScopes: [],
215
+ deniedScopes: requestedScopes
216
+ };
217
+ }
218
+ const allowedScopes = [
219
+ ...ScopeService.getBaseScopes(),
220
+ ...ScopeService.getRoleScopes(teamRole)
221
+ ];
193
222
  if (allowedScopes.includes("*")) {
194
223
  return {
195
224
  valid: true,
@@ -219,6 +248,8 @@ export {
219
248
  hasAllScopes,
220
249
  hasAnyScope,
221
250
  hasScope,
251
+ invalidateApiKeyCache,
252
+ invalidateApiKeyCacheById,
222
253
  validateApiKey,
223
254
  validateScopesForUser
224
255
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Audit logging for the generic entity routes (#105).
3
+ *
4
+ * `logApiUsage` (lib/api/helpers.ts) is hard-typed to `ApiKeyAuth` and is
5
+ * only triggered by the legacy `withApiLogging` wrapper, whose header-based
6
+ * `getApiAuth` never sees the dual-auth flow the generic handlers use — so
7
+ * neither session nor API-key activity on `/api/v1/[entity]` was ever
8
+ * written to `api_audit_log`.
9
+ *
10
+ * This module accepts the real `DualAuthResult` the handlers already hold and
11
+ * writes one row per REQUEST WITH AN IDENTIFIABLE PRINCIPAL (`apiKeyId` is
12
+ * NULL for sessions; migration 025 dropped the NOT NULL) — a real session or
13
+ * a real API key, whether the request was ultimately allowed or denied for
14
+ * scope/permission reasons (#128). A request with no usable credential at all
15
+ * (anonymous, missing, or invalid) is still never logged: there is no "who"
16
+ * to attribute it to, and logging it would mostly capture bot/scanner noise
17
+ * rather than anything actionable. It is invoked fire-and-forget from
18
+ * `runWithAuditLog` in generic-handler.ts, so a logging failure can never turn
19
+ * a successful response into an error.
20
+ *
21
+ * The request body is deliberately NOT stored: entity payloads routinely
22
+ * carry personal data, and endpoint + method + status already identify what
23
+ * was touched (the record id is part of the path for read/update/delete).
24
+ */
25
+ import type { NextRequest } from 'next/server';
26
+ import type { DualAuthResult } from '../auth/dual-auth';
27
+ /**
28
+ * Mutable slot a handler fills in once it has authenticated the request, so
29
+ * the wrapper can attribute the response to a user / API key.
30
+ */
31
+ export interface AuditContext {
32
+ auth: DualAuthResult | null;
33
+ }
34
+ export declare function logGenericHandlerUsage(auth: DualAuthResult | null, request: NextRequest, statusCode: number, responseTime?: number): Promise<void>;
35
+ //# sourceMappingURL=audit-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/audit-log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAE9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,cAAc,GAAG,IAAI,CAAA;CAC5B;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,cAAc,GAAG,IAAI,EAC3B,OAAO,EAAE,WAAW,EACpB,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC,CAuCf"}
@@ -0,0 +1,35 @@
1
+ import { mutateWithRLS } from "../../db.js";
2
+ async function logGenericHandlerUsage(auth, request, statusCode, responseTime) {
3
+ var _a;
4
+ const userId = (_a = auth == null ? void 0 : auth.user) == null ? void 0 : _a.id;
5
+ if (!userId) return;
6
+ const apiKeyId = (auth == null ? void 0 : auth.type) === "api-key" ? auth.keyId ?? null : null;
7
+ try {
8
+ const endpoint = request.nextUrl.pathname;
9
+ const method = request.method;
10
+ const ipAddress = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "unknown";
11
+ const userAgent = request.headers.get("user-agent");
12
+ await mutateWithRLS(
13
+ `INSERT INTO "api_audit_log"
14
+ ("apiKeyId", "userId", endpoint, method, "statusCode", "ipAddress", "userAgent", "requestBody", "responseTime")
15
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
16
+ [
17
+ apiKeyId,
18
+ userId,
19
+ endpoint,
20
+ method,
21
+ statusCode,
22
+ ipAddress,
23
+ userAgent,
24
+ null,
25
+ typeof responseTime === "number" ? Math.round(responseTime) : null
26
+ ],
27
+ userId
28
+ );
29
+ } catch (error) {
30
+ console.error("[generic-handler:audit] failed to write api_audit_log entry:", error);
31
+ }
32
+ }
33
+ export {
34
+ logGenericHandlerUsage
35
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA8iBvD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAugBnF;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAmZrF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CA0MpJ;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAyXtJ;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAoJtJ;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtF"}
1
+ {"version":3,"file":"generic-handler.d.ts","sourceRoot":"","sources":["../../../../src/lib/api/entity/generic-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AA4wBvD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEnF;AA4kBD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAErF;AAyaD;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEpJ;AAgND;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtJ;AA+YD;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtJ;AAwKD;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAEtF"}