@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.
- package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicFooter.js +2 -2
- package/dist/components/app/layouts/PublicNavbar.d.ts.map +1 -1
- package/dist/components/app/layouts/PublicNavbar.js +2 -2
- package/dist/components/app/misc/ThemeToggle.d.ts.map +1 -1
- package/dist/components/app/misc/ThemeToggle.js +4 -1
- package/dist/components/auth/DevKeyring.d.ts.map +1 -1
- package/dist/components/auth/DevKeyring.js +10 -2
- package/dist/components/auth/SessionCookieRefresher.d.ts +21 -0
- package/dist/components/auth/SessionCookieRefresher.d.ts.map +1 -0
- package/dist/components/auth/SessionCookieRefresher.js +11 -0
- package/dist/components/auth/forms/LoginForm.d.ts.map +1 -1
- package/dist/components/auth/forms/LoginForm.js +300 -6
- package/dist/components/auth/index.d.ts +1 -0
- package/dist/components/auth/index.d.ts.map +1 -1
- package/dist/components/auth/index.js +2 -0
- package/dist/components/billing/InvoicesTable.d.ts.map +1 -1
- package/dist/components/billing/InvoicesTable.js +4 -3
- package/dist/components/dashboard/block-editor/dynamic-form.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/AuthGuard.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthLayout.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/DashboardAuthLayout.js +2 -2
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts +17 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.d.ts.map +1 -0
- package/dist/components/dashboard/layouts/DashboardAuthSkeleton.js +52 -0
- package/dist/components/dashboard/layouts/Sidebar.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/Sidebar.js +2 -3
- package/dist/components/dashboard/layouts/TopNavbar.js +2 -2
- package/dist/components/dashboard/layouts/index.d.ts +1 -0
- package/dist/components/dashboard/layouts/index.d.ts.map +1 -1
- package/dist/components/dashboard/layouts/index.js +2 -0
- package/dist/components/entities/EntityFieldRenderer.d.ts.map +1 -1
- package/dist/components/entities/EntityFieldRenderer.js +3 -0
- package/dist/components/entities/EntityTable.d.ts.map +1 -1
- package/dist/components/entities/EntityTable.js +2 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.d.ts.map +1 -1
- package/dist/components/entities/wrappers/EntityFormWrapper.js +7 -2
- package/dist/components/ui/combobox.d.ts +8 -1
- package/dist/components/ui/combobox.d.ts.map +1 -1
- package/dist/components/ui/combobox.js +37 -44
- package/dist/components/ui/country-select.d.ts +3 -1
- package/dist/components/ui/country-select.d.ts.map +1 -1
- package/dist/components/ui/country-select.js +3 -1
- package/dist/components/ui/currency-select.d.ts +3 -1
- package/dist/components/ui/currency-select.d.ts.map +1 -1
- package/dist/components/ui/currency-select.js +3 -1
- package/dist/components/ui/sheet.d.ts +5 -0
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +46 -26
- package/dist/components/ui/timezone-select.d.ts +3 -1
- package/dist/components/ui/timezone-select.d.ts.map +1 -1
- package/dist/components/ui/timezone-select.js +3 -1
- package/dist/contexts/TeamContext.d.ts +6 -0
- package/dist/contexts/TeamContext.d.ts.map +1 -1
- package/dist/contexts/TeamContext.js +8 -1
- package/dist/emails/otp-verification.d.ts.map +1 -1
- package/dist/emails/reset-password.d.ts.map +1 -1
- package/dist/emails/team-invitation.d.ts.map +1 -1
- package/dist/emails/team-invitation.js +3 -2
- package/dist/emails/verify-email.d.ts.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/useAuth.d.ts +103 -25
- package/dist/hooks/useAuth.d.ts.map +1 -1
- package/dist/hooks/useAuth.js +24 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts +22 -0
- package/dist/hooks/useEnsureUserMetadata.d.ts.map +1 -1
- package/dist/hooks/useEnsureUserMetadata.js +18 -5
- package/dist/hooks/useSessionCookieRefresh.d.ts +35 -0
- package/dist/hooks/useSessionCookieRefresh.d.ts.map +1 -0
- package/dist/hooks/useSessionCookieRefresh.js +49 -0
- package/dist/hooks/useValidationSchemas.d.ts +7 -7
- package/dist/lib/api/api-error.d.ts +3 -1
- package/dist/lib/api/api-error.d.ts.map +1 -1
- package/dist/lib/api/api-error.js +4 -2
- package/dist/lib/api/auth/dual-auth.d.ts +45 -3
- package/dist/lib/api/auth/dual-auth.d.ts.map +1 -1
- package/dist/lib/api/auth/dual-auth.js +42 -7
- package/dist/lib/api/auth/permissions.d.ts +7 -2
- package/dist/lib/api/auth/permissions.d.ts.map +1 -1
- package/dist/lib/api/auth/permissions.js +2 -2
- package/dist/lib/api/auth/scope-policy.d.ts +71 -0
- package/dist/lib/api/auth/scope-policy.d.ts.map +1 -0
- package/dist/lib/api/auth/scope-policy.js +53 -0
- package/dist/lib/api/auth.d.ts +35 -3
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +54 -23
- package/dist/lib/api/entity/audit-log.d.ts +35 -0
- package/dist/lib/api/entity/audit-log.d.ts.map +1 -0
- package/dist/lib/api/entity/audit-log.js +35 -0
- package/dist/lib/api/entity/generic-handler.d.ts.map +1 -1
- package/dist/lib/api/entity/generic-handler.js +333 -155
- package/dist/lib/api/helpers.d.ts +29 -4
- package/dist/lib/api/helpers.d.ts.map +1 -1
- package/dist/lib/api/helpers.js +30 -6
- package/dist/lib/api/keys.d.ts +16 -0
- package/dist/lib/api/keys.d.ts.map +1 -1
- package/dist/lib/api/keys.js +19 -1
- package/dist/lib/api/routes/patterns-usages.d.ts.map +1 -1
- package/dist/lib/api/routes/patterns-usages.js +3 -11
- package/dist/lib/auth/auth-methods.d.ts +52 -0
- package/dist/lib/auth/auth-methods.d.ts.map +1 -0
- package/dist/lib/auth/auth-methods.js +63 -0
- package/dist/lib/auth/registration-guard-plugin.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.d.ts +9 -0
- package/dist/lib/auth/registration-helpers.d.ts.map +1 -1
- package/dist/lib/auth/registration-helpers.js +8 -2
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts +52 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/device-fingerprint.js +42 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts +37 -0
- package/dist/lib/auth/security-notifications/dispatcher.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/dispatcher.js +157 -0
- package/dist/lib/auth/security-notifications/index.d.ts +20 -0
- package/dist/lib/auth/security-notifications/index.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/index.js +22 -0
- package/dist/lib/auth/security-notifications/registry.d.ts +36 -0
- package/dist/lib/auth/security-notifications/registry.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/registry.js +26 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts +12 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/email-changed.js +43 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/new-device-login.js +46 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts +7 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/password-changed.js +35 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts +36 -0
- package/dist/lib/auth/security-notifications/templates/shared.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/templates/shared.js +68 -0
- package/dist/lib/auth/security-notifications/types.d.ts +74 -0
- package/dist/lib/auth/security-notifications/types.d.ts.map +1 -0
- package/dist/lib/auth/security-notifications/types.js +0 -0
- package/dist/lib/auth/session-config.d.ts +37 -0
- package/dist/lib/auth/session-config.d.ts.map +1 -0
- package/dist/lib/auth/session-config.js +59 -0
- package/dist/lib/auth/session-refresh.d.ts +46 -0
- package/dist/lib/auth/session-refresh.d.ts.map +1 -0
- package/dist/lib/auth/session-refresh.js +11 -0
- package/dist/lib/auth-client.d.ts +1489 -3445
- package/dist/lib/auth-client.d.ts.map +1 -1
- package/dist/lib/auth-client.js +4 -0
- package/dist/lib/auth.d.ts +1 -3057
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +21 -8
- package/dist/lib/billing/gateways/factory.d.ts +8 -3
- package/dist/lib/billing/gateways/factory.d.ts.map +1 -1
- package/dist/lib/billing/gateways/factory.js +37 -23
- package/dist/lib/billing/schema.d.ts +1 -1
- package/dist/lib/billing/stripe-webhook.d.ts.map +1 -1
- package/dist/lib/billing/stripe-webhook.js +2 -1
- package/dist/lib/config/app.config.d.ts.map +1 -1
- package/dist/lib/config/app.config.js +71 -0
- package/dist/lib/config/config-sync.d.ts +1 -4
- package/dist/lib/config/config-sync.d.ts.map +1 -1
- package/dist/lib/config/config-sync.js +6 -2
- package/dist/lib/config/types.d.ts +130 -0
- package/dist/lib/config/types.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.d.ts +9 -1
- package/dist/lib/entities/entity-hooks.d.ts.map +1 -1
- package/dist/lib/entities/entity-hooks.js +77 -4
- package/dist/lib/entities/schema-generator.d.ts.map +1 -1
- package/dist/lib/entities/schema-generator.js +20 -4
- package/dist/lib/entities/serialization.d.ts +2 -2
- package/dist/lib/entities/serialization.d.ts.map +1 -1
- package/dist/lib/entities/serialization.js +3 -2
- package/dist/lib/entities/types.d.ts +8 -0
- package/dist/lib/entities/types.d.ts.map +1 -1
- package/dist/lib/locale.d.ts.map +1 -1
- package/dist/lib/locale.js +4 -1
- package/dist/lib/mcp/audit.d.ts +21 -0
- package/dist/lib/mcp/audit.d.ts.map +1 -0
- package/dist/lib/mcp/audit.js +48 -0
- package/dist/lib/mcp/engine.d.ts +28 -0
- package/dist/lib/mcp/engine.d.ts.map +1 -0
- package/dist/lib/mcp/engine.js +158 -0
- package/dist/lib/mcp/errors.d.ts +16 -0
- package/dist/lib/mcp/errors.d.ts.map +1 -0
- package/dist/lib/mcp/errors.js +66 -0
- package/dist/lib/mcp/executor.d.ts +15 -0
- package/dist/lib/mcp/executor.d.ts.map +1 -0
- package/dist/lib/mcp/executor.js +63 -0
- package/dist/lib/mcp/index.d.ts +9 -0
- package/dist/lib/mcp/index.d.ts.map +1 -0
- package/dist/lib/mcp/index.js +32 -0
- package/dist/lib/mcp/schema-builder.d.ts +45 -0
- package/dist/lib/mcp/schema-builder.d.ts.map +1 -0
- package/dist/lib/mcp/schema-builder.js +212 -0
- package/dist/lib/mcp/tool-generator.d.ts +31 -0
- package/dist/lib/mcp/tool-generator.d.ts.map +1 -0
- package/dist/lib/mcp/tool-generator.js +271 -0
- package/dist/lib/mcp/transport.d.ts +31 -0
- package/dist/lib/mcp/transport.d.ts.map +1 -0
- package/dist/lib/mcp/transport.js +58 -0
- package/dist/lib/mcp/types.d.ts +184 -0
- package/dist/lib/mcp/types.d.ts.map +1 -0
- package/dist/lib/mcp/types.js +11 -0
- package/dist/lib/plugins/hook-system.d.ts +13 -0
- package/dist/lib/plugins/hook-system.d.ts.map +1 -1
- package/dist/lib/plugins/hook-system.js +32 -2
- package/dist/lib/scheduled-actions/handlers/index.d.ts +1 -0
- package/dist/lib/scheduled-actions/handlers/index.d.ts.map +1 -1
- package/dist/lib/scheduled-actions/handlers/index.js +3 -1
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts +26 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.d.ts.map +1 -0
- package/dist/lib/scheduled-actions/handlers/security-notification.js +54 -0
- package/dist/lib/selectors/core-selectors.d.ts +12 -0
- package/dist/lib/selectors/core-selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.d.ts +12 -0
- package/dist/lib/selectors/domains/auth.selectors.d.ts.map +1 -1
- package/dist/lib/selectors/domains/auth.selectors.js +14 -1
- package/dist/lib/selectors/selectors.d.ts +24 -0
- package/dist/lib/selectors/selectors.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.d.ts.map +1 -1
- package/dist/lib/services/generic-entity.service.js +16 -32
- package/dist/lib/services/membership.service.d.ts +10 -2
- package/dist/lib/services/membership.service.d.ts.map +1 -1
- package/dist/lib/services/membership.service.js +37 -16
- package/dist/lib/services/subscription.service.d.ts.map +1 -1
- package/dist/lib/services/subscription.service.js +2 -1
- package/dist/lib/services/team-member.service.d.ts +15 -3
- package/dist/lib/services/team-member.service.d.ts.map +1 -1
- package/dist/lib/services/team-member.service.js +27 -6
- package/dist/lib/services/team.service.d.ts.map +1 -1
- package/dist/lib/teams/permissions.d.ts +3 -1
- package/dist/lib/teams/permissions.d.ts.map +1 -1
- package/dist/lib/teams/permissions.js +6 -0
- package/dist/lib/teams/schema.d.ts.map +1 -1
- package/dist/lib/teams/schema.js +3 -0
- package/dist/lib/theme/forced-theme.d.ts +34 -0
- package/dist/lib/theme/forced-theme.d.ts.map +1 -0
- package/dist/lib/theme/forced-theme.js +27 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts +3 -0
- package/dist/lib/theme/get-default-theme-mode.d.ts.map +1 -1
- package/dist/lib/theme/get-default-theme-mode.js +5 -3
- package/dist/messages/de/auth.json +25 -2
- package/dist/messages/de/index.d.ts +81 -0
- package/dist/messages/de/index.d.ts.map +1 -1
- package/dist/messages/de/index.js +2 -0
- package/dist/messages/de/index.ts +2 -0
- package/dist/messages/en/auth.json +25 -2
- package/dist/messages/en/index.d.ts +81 -0
- package/dist/messages/en/index.d.ts.map +1 -1
- package/dist/messages/en/index.js +2 -0
- package/dist/messages/en/index.ts +2 -0
- package/dist/messages/es/auth.json +25 -2
- package/dist/messages/es/index.d.ts +81 -0
- package/dist/messages/es/index.d.ts.map +1 -1
- package/dist/messages/es/index.js +2 -0
- package/dist/messages/es/index.ts +2 -0
- package/dist/messages/fr/auth.json +25 -2
- package/dist/messages/fr/index.d.ts +81 -0
- package/dist/messages/fr/index.d.ts.map +1 -1
- package/dist/messages/fr/index.js +2 -0
- package/dist/messages/fr/index.ts +2 -0
- package/dist/messages/it/auth.json +25 -2
- package/dist/messages/it/index.d.ts +81 -0
- package/dist/messages/it/index.d.ts.map +1 -1
- package/dist/messages/it/index.js +2 -0
- package/dist/messages/it/index.ts +2 -0
- package/dist/messages/pt/auth.json +25 -2
- package/dist/messages/pt/index.d.ts +81 -0
- package/dist/messages/pt/index.d.ts.map +1 -1
- package/dist/messages/pt/index.js +2 -0
- package/dist/messages/pt/index.ts +2 -0
- package/dist/migrations/005_api_audit_log.sql +2 -1
- package/dist/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/dist/migrations/026_login_events.sql +91 -0
- package/dist/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/dist/providers/DashboardProviders.d.ts +12 -3
- package/dist/providers/DashboardProviders.d.ts.map +1 -1
- package/dist/providers/DashboardProviders.js +6 -2
- package/dist/providers/theme-provider.d.ts +19 -2
- package/dist/providers/theme-provider.d.ts.map +1 -1
- package/dist/providers/theme-provider.js +6 -2
- package/dist/templates/app/(auth)/signup/page.tsx +8 -0
- package/dist/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/dist/templates/app/api/auth/[...all]/route.ts +23 -1
- package/dist/templates/app/api/health/route.ts +4 -2
- package/dist/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/dist/templates/app/api/mcp/route.ts +176 -0
- package/dist/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/dist/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/dist/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/dist/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/dist/templates/app/api/v1/api-keys/route.ts +35 -45
- package/dist/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/dist/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/dist/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/dist/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/dist/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/dist/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/dist/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/dist/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/dist/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/dist/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/dist/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/dist/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/dist/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/dist/templates/app/api/v1/media/route.ts +5 -9
- package/dist/templates/app/api/v1/media/upload/route.ts +9 -21
- package/dist/templates/app/api/v1/media-tags/route.ts +9 -13
- package/dist/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/dist/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/dist/templates/app/api/v1/post-categories/route.ts +5 -4
- package/dist/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/dist/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/dist/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/dist/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/dist/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/dist/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/dist/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/dist/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/dist/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/dist/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/dist/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/dist/templates/app/api/v1/teams/route.ts +33 -27
- package/dist/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/dist/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/dist/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/dist/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/dist/templates/app/api/v1/users/route.ts +21 -25
- package/dist/templates/app/dashboard/layout.tsx +2 -6
- package/dist/templates/app/layout.ppr.tsx +9 -14
- package/dist/templates/app/layout.tsx +39 -19
- package/dist/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/dist/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/dist/templates/contents/themes/starter/styles/globals.css +6 -2
- package/dist/templates/proxy.ts +46 -10
- package/dist/templates/tsconfig.json +4 -0
- package/dist/types/theme.d.ts +9 -0
- package/dist/types/theme.d.ts.map +1 -1
- package/migrations/005_api_audit_log.sql +2 -1
- package/migrations/025_api_audit_log_nullable_api_key.sql +12 -0
- package/migrations/026_login_events.sql +91 -0
- package/migrations/027_rls_bypass_developer_and_system_admin_team.sql +197 -0
- package/package.json +20 -12
- package/scripts/build/registry/__tests__/mcp-registry.test.mjs +64 -0
- package/scripts/build/registry/__tests__/scope-registry.test.mjs +106 -0
- package/scripts/build/registry/discovery/mcp-overrides.mjs +72 -0
- package/scripts/build/registry/discovery/mcp-overrides.test.mjs +86 -0
- package/scripts/build/registry/generators/mcp-registry.mjs +61 -0
- package/scripts/build/registry/generators/scope-registry.mjs +140 -88
- package/scripts/build/registry.mjs +8 -4
- package/templates/app/(auth)/signup/page.tsx +8 -0
- package/templates/app/(public)/[...slug]/page.tsx +9 -14
- package/templates/app/api/auth/[...all]/route.ts +23 -1
- package/templates/app/api/health/route.ts +4 -2
- package/templates/app/api/internal/user-metadata/route.ts +5 -3
- package/templates/app/api/mcp/route.ts +176 -0
- package/templates/app/api/superadmin/subscriptions/route.ts +2 -1
- package/templates/app/api/superadmin/teams/[teamId]/route.ts +4 -3
- package/templates/app/api/superadmin/users/[userId]/route.ts +10 -3
- package/templates/app/api/v1/api-keys/[id]/route.ts +50 -71
- package/templates/app/api/v1/api-keys/route.ts +35 -45
- package/templates/app/api/v1/auth/signup-with-invite/route.ts +14 -14
- package/templates/app/api/v1/billing/cancel/route.ts +7 -6
- package/templates/app/api/v1/billing/change-plan/route.ts +5 -4
- package/templates/app/api/v1/billing/check-action/route.ts +6 -4
- package/templates/app/api/v1/billing/checkout/route.ts +7 -5
- package/templates/app/api/v1/billing/plans/route.ts +8 -12
- package/templates/app/api/v1/billing/portal/route.ts +7 -5
- package/templates/app/api/v1/billing/webhooks/polar/route.ts +1 -1
- package/templates/app/api/v1/devtools/blocks/route.ts +5 -4
- package/templates/app/api/v1/devtools/features/route.ts +5 -4
- package/templates/app/api/v1/devtools/flows/route.ts +5 -4
- package/templates/app/api/v1/devtools/scheduled-actions/route.ts +9 -7
- package/templates/app/api/v1/devtools/scheduled-actions/run/route.ts +5 -4
- package/templates/app/api/v1/devtools/testing/route.ts +5 -4
- package/templates/app/api/v1/media/[id]/route.ts +13 -25
- package/templates/app/api/v1/media/[id]/tags/route.ts +17 -25
- package/templates/app/api/v1/media/check-duplicates/route.ts +5 -7
- package/templates/app/api/v1/media/route.ts +5 -9
- package/templates/app/api/v1/media/upload/route.ts +9 -21
- package/templates/app/api/v1/media-tags/route.ts +9 -13
- package/templates/app/api/v1/patterns/[id]/usages/route.ts +11 -19
- package/templates/app/api/v1/post-categories/[id]/route.ts +9 -7
- package/templates/app/api/v1/post-categories/route.ts +5 -4
- package/templates/app/api/v1/team-invitations/[token]/accept/route.ts +16 -18
- package/templates/app/api/v1/team-invitations/[token]/decline/route.ts +15 -17
- package/templates/app/api/v1/team-invitations/[token]/route.ts +8 -8
- package/templates/app/api/v1/team-invitations/route.ts +9 -11
- package/templates/app/api/v1/teams/[teamId]/invitations/route.ts +41 -32
- package/templates/app/api/v1/teams/[teamId]/invoices/[invoiceNumber]/route.ts +15 -16
- package/templates/app/api/v1/teams/[teamId]/invoices/route.ts +14 -15
- package/templates/app/api/v1/teams/[teamId]/members/[memberId]/route.ts +32 -36
- package/templates/app/api/v1/teams/[teamId]/members/route.ts +33 -33
- package/templates/app/api/v1/teams/[teamId]/route.ts +35 -41
- package/templates/app/api/v1/teams/[teamId]/subscription/route.ts +5 -3
- package/templates/app/api/v1/teams/[teamId]/usage/[limitSlug]/route.ts +10 -6
- package/templates/app/api/v1/teams/route.ts +33 -27
- package/templates/app/api/v1/teams/switch/route.ts +11 -13
- package/templates/app/api/v1/theme/[...path]/route.ts +23 -2
- package/templates/app/api/v1/users/[id]/meta/[key]/route.ts +32 -77
- package/templates/app/api/v1/users/[id]/route.ts +35 -68
- package/templates/app/api/v1/users/route.ts +21 -25
- package/templates/app/dashboard/layout.tsx +2 -6
- package/templates/app/layout.ppr.tsx +9 -14
- package/templates/app/layout.tsx +39 -19
- package/templates/contents/themes/starter/config/app.config.ts +19 -0
- package/templates/contents/themes/starter/config/theme.config.ts +6 -0
- package/templates/contents/themes/starter/styles/globals.css +6 -2
- package/templates/proxy.ts +46 -10
- package/templates/tsconfig.json +4 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/permissions-registry.ts +23 -0
- package/tests/jest/__mocks__/@nextsparkjs/registries/scope-registry.ts +56 -6
- package/tests/jest/__mocks__/next-server.js +24 -0
- package/dist/styles/classes.json +0 -1093
- package/dist/styles/ui.css +0 -2
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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');
|