@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
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
handleCorsPreflightRequest,
|
|
8
8
|
addCorsHeaders,
|
|
9
9
|
} from '@nextsparkjs/core/lib/api/helpers'
|
|
10
|
-
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
10
|
+
import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
11
11
|
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
12
12
|
import { updateMemberRoleSchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
13
13
|
import { MembershipService } from '@nextsparkjs/core/lib/services'
|
|
@@ -15,8 +15,8 @@ import { validateRoleTransition, canManageRole } from '@nextsparkjs/core/lib/tea
|
|
|
15
15
|
import type { TeamMember, TeamRole } from '@nextsparkjs/core/lib/teams/types'
|
|
16
16
|
|
|
17
17
|
// Handle CORS preflight
|
|
18
|
-
export async function OPTIONS() {
|
|
19
|
-
return handleCorsPreflightRequest()
|
|
18
|
+
export async function OPTIONS(request: NextRequest) {
|
|
19
|
+
return handleCorsPreflightRequest(request)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// PATCH /api/v1/teams/:teamId/members/:memberId - Update member role
|
|
@@ -26,14 +26,12 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
26
26
|
{ params }: { params: Promise<{ teamId: string; memberId: string }> }
|
|
27
27
|
): Promise<NextResponse> => {
|
|
28
28
|
try {
|
|
29
|
-
// Authenticate using dual auth
|
|
30
|
-
|
|
29
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
30
|
+
// point, which fails closed for keys that lack it (#93).
|
|
31
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
|
|
31
32
|
|
|
32
33
|
if (!authResult.success) {
|
|
33
|
-
return
|
|
34
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
35
|
-
{ status: 401 }
|
|
36
|
-
)
|
|
34
|
+
return createAuthFailureResponse(authResult)
|
|
37
35
|
}
|
|
38
36
|
|
|
39
37
|
if (authResult.rateLimitResponse) {
|
|
@@ -45,17 +43,17 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
45
43
|
// Validate parameters
|
|
46
44
|
if (!teamId || teamId.trim() === '') {
|
|
47
45
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
48
|
-
return addCorsHeaders(response)
|
|
46
|
+
return addCorsHeaders(response, req)
|
|
49
47
|
}
|
|
50
48
|
|
|
51
49
|
if (!memberId || memberId.trim() === '') {
|
|
52
50
|
const response = createApiError('Member ID is required', 400, null, 'MISSING_MEMBER_ID')
|
|
53
|
-
return addCorsHeaders(response)
|
|
51
|
+
return addCorsHeaders(response, req)
|
|
54
52
|
}
|
|
55
53
|
|
|
56
54
|
// Check if user has permission to update member roles using MembershipService
|
|
57
55
|
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
58
|
-
const actionResult = membership.canPerformAction('members.update_role')
|
|
56
|
+
const actionResult = membership.canPerformAction('team.members.update_role')
|
|
59
57
|
|
|
60
58
|
if (!actionResult.allowed) {
|
|
61
59
|
const response = NextResponse.json(
|
|
@@ -67,7 +65,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
67
65
|
},
|
|
68
66
|
{ status: 403 }
|
|
69
67
|
)
|
|
70
|
-
return addCorsHeaders(response)
|
|
68
|
+
return addCorsHeaders(response, req)
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
// Get user's role for role hierarchy validation
|
|
@@ -82,7 +80,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
82
80
|
|
|
83
81
|
if (!targetMember) {
|
|
84
82
|
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
85
|
-
return addCorsHeaders(response)
|
|
83
|
+
return addCorsHeaders(response, req)
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
const body = await req.json()
|
|
@@ -93,7 +91,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
93
91
|
|
|
94
92
|
if (!transitionValidation.allowed) {
|
|
95
93
|
const response = createApiError(transitionValidation.reason || 'Invalid role transition', 403, null, 'INVALID_ROLE_TRANSITION')
|
|
96
|
-
return addCorsHeaders(response)
|
|
94
|
+
return addCorsHeaders(response, req)
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
// Check if actor can manage the target role
|
|
@@ -104,7 +102,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
104
102
|
null,
|
|
105
103
|
'INSUFFICIENT_PERMISSIONS'
|
|
106
104
|
)
|
|
107
|
-
return addCorsHeaders(response)
|
|
105
|
+
return addCorsHeaders(response, req)
|
|
108
106
|
}
|
|
109
107
|
|
|
110
108
|
// Update member role
|
|
@@ -119,7 +117,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
119
117
|
|
|
120
118
|
if (result.rows.length === 0) {
|
|
121
119
|
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
122
|
-
return addCorsHeaders(response)
|
|
120
|
+
return addCorsHeaders(response, req)
|
|
123
121
|
}
|
|
124
122
|
|
|
125
123
|
// Fetch member with user details
|
|
@@ -143,17 +141,17 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
143
141
|
)
|
|
144
142
|
|
|
145
143
|
const response = createApiResponse(memberWithUser)
|
|
146
|
-
return addCorsHeaders(response)
|
|
144
|
+
return addCorsHeaders(response, req)
|
|
147
145
|
} catch (error) {
|
|
148
146
|
if (error instanceof Error && error.name === 'ZodError') {
|
|
149
147
|
const zodError = error as { issues?: unknown[] }
|
|
150
148
|
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
151
|
-
return addCorsHeaders(response)
|
|
149
|
+
return addCorsHeaders(response, req)
|
|
152
150
|
}
|
|
153
151
|
|
|
154
152
|
console.error('Error updating member role:', error)
|
|
155
153
|
const response = createApiError('Internal server error', 500)
|
|
156
|
-
return addCorsHeaders(response)
|
|
154
|
+
return addCorsHeaders(response, req)
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
157
|
), 'write')
|
|
@@ -165,14 +163,12 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
165
163
|
{ params }: { params: Promise<{ teamId: string; memberId: string }> }
|
|
166
164
|
): Promise<NextResponse> => {
|
|
167
165
|
try {
|
|
168
|
-
// Authenticate using dual auth
|
|
169
|
-
|
|
166
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
167
|
+
// point, which fails closed for keys that lack it (#93).
|
|
168
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
|
|
170
169
|
|
|
171
170
|
if (!authResult.success) {
|
|
172
|
-
return
|
|
173
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
174
|
-
{ status: 401 }
|
|
175
|
-
)
|
|
171
|
+
return createAuthFailureResponse(authResult)
|
|
176
172
|
}
|
|
177
173
|
|
|
178
174
|
if (authResult.rateLimitResponse) {
|
|
@@ -184,17 +180,17 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
184
180
|
// Validate parameters
|
|
185
181
|
if (!teamId || teamId.trim() === '') {
|
|
186
182
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
187
|
-
return addCorsHeaders(response)
|
|
183
|
+
return addCorsHeaders(response, req)
|
|
188
184
|
}
|
|
189
185
|
|
|
190
186
|
if (!memberId || memberId.trim() === '') {
|
|
191
187
|
const response = createApiError('Member ID is required', 400, null, 'MISSING_MEMBER_ID')
|
|
192
|
-
return addCorsHeaders(response)
|
|
188
|
+
return addCorsHeaders(response, req)
|
|
193
189
|
}
|
|
194
190
|
|
|
195
191
|
// Check if user has permission to remove members using MembershipService
|
|
196
192
|
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
197
|
-
const actionResult = membership.canPerformAction('members.remove')
|
|
193
|
+
const actionResult = membership.canPerformAction('team.members.remove')
|
|
198
194
|
|
|
199
195
|
if (!actionResult.allowed) {
|
|
200
196
|
const response = NextResponse.json(
|
|
@@ -206,7 +202,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
206
202
|
},
|
|
207
203
|
{ status: 403 }
|
|
208
204
|
)
|
|
209
|
-
return addCorsHeaders(response)
|
|
205
|
+
return addCorsHeaders(response, req)
|
|
210
206
|
}
|
|
211
207
|
|
|
212
208
|
// Get user's role for role hierarchy validation
|
|
@@ -221,13 +217,13 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
221
217
|
|
|
222
218
|
if (!targetMember) {
|
|
223
219
|
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
224
|
-
return addCorsHeaders(response)
|
|
220
|
+
return addCorsHeaders(response, req)
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
// Cannot remove the owner
|
|
228
224
|
if (targetMember.role === 'owner') {
|
|
229
225
|
const response = createApiError('Cannot remove the team owner', 403, null, 'CANNOT_REMOVE_OWNER')
|
|
230
|
-
return addCorsHeaders(response)
|
|
226
|
+
return addCorsHeaders(response, req)
|
|
231
227
|
}
|
|
232
228
|
|
|
233
229
|
// Check if actor can manage the target role
|
|
@@ -238,7 +234,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
238
234
|
null,
|
|
239
235
|
'INSUFFICIENT_PERMISSIONS'
|
|
240
236
|
)
|
|
241
|
-
return addCorsHeaders(response)
|
|
237
|
+
return addCorsHeaders(response, req)
|
|
242
238
|
}
|
|
243
239
|
|
|
244
240
|
// Remove member
|
|
@@ -250,15 +246,15 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
250
246
|
|
|
251
247
|
if (result.rows.length === 0) {
|
|
252
248
|
const response = createApiError('Member not found', 404, null, 'MEMBER_NOT_FOUND')
|
|
253
|
-
return addCorsHeaders(response)
|
|
249
|
+
return addCorsHeaders(response, req)
|
|
254
250
|
}
|
|
255
251
|
|
|
256
252
|
const response = createApiResponse({ deleted: true, id: memberId })
|
|
257
|
-
return addCorsHeaders(response)
|
|
253
|
+
return addCorsHeaders(response, req)
|
|
258
254
|
} catch (error) {
|
|
259
255
|
console.error('Error removing member:', error)
|
|
260
256
|
const response = createApiError('Internal server error', 500)
|
|
261
|
-
return addCorsHeaders(response)
|
|
257
|
+
return addCorsHeaders(response, req)
|
|
262
258
|
}
|
|
263
259
|
}
|
|
264
260
|
), 'write')
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
handleCorsPreflightRequest,
|
|
9
9
|
addCorsHeaders,
|
|
10
10
|
} from '@nextsparkjs/core/lib/api/helpers'
|
|
11
|
-
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
11
|
+
import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
12
12
|
import { isSuperAdmin } from '@nextsparkjs/core/lib/api/auth/permissions'
|
|
13
13
|
import { checkRateLimit, withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
14
14
|
import { RATE_LIMITS } from '@nextsparkjs/core/lib/api/keys'
|
|
@@ -21,22 +21,20 @@ import { sendTeamInvitationEmail } from '@nextsparkjs/core/lib/email/send'
|
|
|
21
21
|
import { I18N_CONFIG } from '@nextsparkjs/core/lib/config'
|
|
22
22
|
|
|
23
23
|
// Handle CORS preflight
|
|
24
|
-
export async function OPTIONS() {
|
|
25
|
-
return handleCorsPreflightRequest()
|
|
24
|
+
export async function OPTIONS(request: NextRequest) {
|
|
25
|
+
return handleCorsPreflightRequest(request)
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// GET /api/v1/teams/:teamId/members - List team members
|
|
29
29
|
export const GET = withRateLimitTier(withApiLogging(
|
|
30
30
|
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
31
31
|
try {
|
|
32
|
-
// Authenticate using dual auth
|
|
33
|
-
|
|
32
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
33
|
+
// point, which fails closed for keys that lack it (#93).
|
|
34
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
|
|
34
35
|
|
|
35
36
|
if (!authResult.success) {
|
|
36
|
-
return
|
|
37
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
38
|
-
{ status: 401 }
|
|
39
|
-
)
|
|
37
|
+
return createAuthFailureResponse(authResult)
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
if (authResult.rateLimitResponse) {
|
|
@@ -48,19 +46,23 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
48
46
|
// Validate that teamId is not empty
|
|
49
47
|
if (!teamId || teamId.trim() === '') {
|
|
50
48
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
51
|
-
return addCorsHeaders(response)
|
|
49
|
+
return addCorsHeaders(response, req)
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
// Superadmins can view members of any team
|
|
55
53
|
const userIsSuperAdmin = isSuperAdmin(authResult)
|
|
56
54
|
|
|
57
|
-
//
|
|
55
|
+
// Listing the roster is gated by the 'team.members.view' permission, not
|
|
56
|
+
// bare membership — membership only proves you belong to the team, not
|
|
57
|
+
// that your role is allowed to see who else does (a theme may grant
|
|
58
|
+
// 'team.members.view' to a subset of roles; see GHSA-rw2j-9mxg-rx98).
|
|
58
59
|
if (!userIsSuperAdmin) {
|
|
59
|
-
const
|
|
60
|
+
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
61
|
+
const actionResult = membership.canPerformAction('team.members.view')
|
|
60
62
|
|
|
61
|
-
if (!
|
|
63
|
+
if (!actionResult.allowed) {
|
|
62
64
|
const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
|
|
63
|
-
return addCorsHeaders(response)
|
|
65
|
+
return addCorsHeaders(response, req)
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -141,11 +143,11 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
141
143
|
const paginationMeta = createPaginationMeta(page, limit, total)
|
|
142
144
|
|
|
143
145
|
const response = createApiResponse(members, paginationMeta)
|
|
144
|
-
return addCorsHeaders(response)
|
|
146
|
+
return addCorsHeaders(response, req)
|
|
145
147
|
} catch (error) {
|
|
146
148
|
console.error('Error fetching team members:', error)
|
|
147
149
|
const response = createApiError('Internal server error', 500)
|
|
148
|
-
return addCorsHeaders(response)
|
|
150
|
+
return addCorsHeaders(response, req)
|
|
149
151
|
}
|
|
150
152
|
}
|
|
151
153
|
), 'read')
|
|
@@ -154,14 +156,12 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
154
156
|
export const POST = withRateLimitTier(withApiLogging(
|
|
155
157
|
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
156
158
|
try {
|
|
157
|
-
// Authenticate using dual auth
|
|
158
|
-
|
|
159
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
160
|
+
// point, which fails closed for keys that lack it (#93).
|
|
161
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
|
|
159
162
|
|
|
160
163
|
if (!authResult.success) {
|
|
161
|
-
return
|
|
162
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
163
|
-
{ status: 401 }
|
|
164
|
-
)
|
|
164
|
+
return createAuthFailureResponse(authResult)
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
if (authResult.rateLimitResponse) {
|
|
@@ -180,7 +180,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
180
180
|
{ retryAfter: Math.ceil((rateLimit.resetTime - Date.now()) / 1000) },
|
|
181
181
|
'RATE_LIMIT_EXCEEDED'
|
|
182
182
|
)
|
|
183
|
-
return addCorsHeaders(response)
|
|
183
|
+
return addCorsHeaders(response, req)
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
const { teamId } = await params
|
|
@@ -188,12 +188,12 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
188
188
|
// Validate that teamId is not empty
|
|
189
189
|
if (!teamId || teamId.trim() === '') {
|
|
190
190
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
191
|
-
return addCorsHeaders(response)
|
|
191
|
+
return addCorsHeaders(response, req)
|
|
192
192
|
}
|
|
193
193
|
|
|
194
194
|
// Check if user has permission to invite members using MembershipService
|
|
195
195
|
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
196
|
-
const actionResult = membership.canPerformAction('members.invite')
|
|
196
|
+
const actionResult = membership.canPerformAction('team.members.invite')
|
|
197
197
|
|
|
198
198
|
if (!actionResult.allowed) {
|
|
199
199
|
const response = NextResponse.json(
|
|
@@ -205,7 +205,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
205
205
|
},
|
|
206
206
|
{ status: 403 }
|
|
207
207
|
)
|
|
208
|
-
return addCorsHeaders(response)
|
|
208
|
+
return addCorsHeaders(response, req)
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
// Get user's role for role hierarchy validation
|
|
@@ -223,7 +223,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
223
223
|
null,
|
|
224
224
|
'ROLE_HIERARCHY_VIOLATION'
|
|
225
225
|
)
|
|
226
|
-
return addCorsHeaders(response)
|
|
226
|
+
return addCorsHeaders(response, req)
|
|
227
227
|
}
|
|
228
228
|
|
|
229
229
|
// Check if user already exists and is already a member
|
|
@@ -243,7 +243,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
243
243
|
|
|
244
244
|
if (existingMember) {
|
|
245
245
|
const response = createApiError('User is already a member of this team', 409, null, 'ALREADY_MEMBER')
|
|
246
|
-
return addCorsHeaders(response)
|
|
246
|
+
return addCorsHeaders(response, req)
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
249
|
|
|
@@ -262,7 +262,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
262
262
|
null,
|
|
263
263
|
'INVITATION_EXISTS'
|
|
264
264
|
)
|
|
265
|
-
return addCorsHeaders(response)
|
|
265
|
+
return addCorsHeaders(response, req)
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
// Get team info for email
|
|
@@ -274,7 +274,7 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
274
274
|
|
|
275
275
|
if (!team) {
|
|
276
276
|
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
277
|
-
return addCorsHeaders(response)
|
|
277
|
+
return addCorsHeaders(response, req)
|
|
278
278
|
}
|
|
279
279
|
|
|
280
280
|
// Create invitation
|
|
@@ -333,17 +333,17 @@ export const POST = withRateLimitTier(withApiLogging(
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
const response = createApiResponse(invitation, { created: true }, 201)
|
|
336
|
-
return addCorsHeaders(response)
|
|
336
|
+
return addCorsHeaders(response, req)
|
|
337
337
|
} catch (error) {
|
|
338
338
|
if (error instanceof Error && error.name === 'ZodError') {
|
|
339
339
|
const zodError = error as { issues?: unknown[] }
|
|
340
340
|
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
341
|
-
return addCorsHeaders(response)
|
|
341
|
+
return addCorsHeaders(response, req)
|
|
342
342
|
}
|
|
343
343
|
|
|
344
344
|
console.error('Error creating invitation:', error)
|
|
345
345
|
const response = createApiError('Internal server error', 500)
|
|
346
|
-
return addCorsHeaders(response)
|
|
346
|
+
return addCorsHeaders(response, req)
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
349
|
), 'write')
|
|
@@ -7,29 +7,27 @@ import {
|
|
|
7
7
|
handleCorsPreflightRequest,
|
|
8
8
|
addCorsHeaders,
|
|
9
9
|
} from '@nextsparkjs/core/lib/api/helpers'
|
|
10
|
-
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
10
|
+
import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth'
|
|
11
11
|
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit'
|
|
12
12
|
import { updateTeamSchema } from '@nextsparkjs/core/lib/teams/schema'
|
|
13
13
|
import { TeamService, MembershipService } from '@nextsparkjs/core/lib/services'
|
|
14
14
|
import type { Team, TeamRole } from '@nextsparkjs/core/lib/teams/types'
|
|
15
15
|
|
|
16
16
|
// Handle CORS preflight
|
|
17
|
-
export async function OPTIONS() {
|
|
18
|
-
return handleCorsPreflightRequest()
|
|
17
|
+
export async function OPTIONS(request: NextRequest) {
|
|
18
|
+
return handleCorsPreflightRequest(request)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// GET /api/v1/teams/:teamId - Get team details
|
|
22
22
|
export const GET = withRateLimitTier(withApiLogging(
|
|
23
23
|
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
24
24
|
try {
|
|
25
|
-
// Authenticate using dual auth
|
|
26
|
-
|
|
25
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
26
|
+
// point, which fails closed for keys that lack it (#93).
|
|
27
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:read' })
|
|
27
28
|
|
|
28
29
|
if (!authResult.success) {
|
|
29
|
-
return
|
|
30
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
31
|
-
{ status: 401 }
|
|
32
|
-
)
|
|
30
|
+
return createAuthFailureResponse(authResult)
|
|
33
31
|
}
|
|
34
32
|
|
|
35
33
|
if (authResult.rateLimitResponse) {
|
|
@@ -41,7 +39,7 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
41
39
|
// Validate that teamId is not empty
|
|
42
40
|
if (!teamId || teamId.trim() === '') {
|
|
43
41
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
44
|
-
return addCorsHeaders(response)
|
|
42
|
+
return addCorsHeaders(response, req)
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
// Check if user is a member of the team
|
|
@@ -53,7 +51,7 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
53
51
|
|
|
54
52
|
if (!userRole) {
|
|
55
53
|
const response = createApiError('Team not found or access denied', 404, null, 'TEAM_NOT_FOUND')
|
|
56
|
-
return addCorsHeaders(response)
|
|
54
|
+
return addCorsHeaders(response, req)
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
// Fetch team with member count
|
|
@@ -72,7 +70,7 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
72
70
|
|
|
73
71
|
if (!team) {
|
|
74
72
|
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
75
|
-
return addCorsHeaders(response)
|
|
73
|
+
return addCorsHeaders(response, req)
|
|
76
74
|
}
|
|
77
75
|
|
|
78
76
|
const responseData = {
|
|
@@ -81,11 +79,11 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
const response = createApiResponse(responseData)
|
|
84
|
-
return addCorsHeaders(response)
|
|
82
|
+
return addCorsHeaders(response, req)
|
|
85
83
|
} catch (error) {
|
|
86
84
|
console.error('Error fetching team:', error)
|
|
87
85
|
const response = createApiError('Internal server error', 500)
|
|
88
|
-
return addCorsHeaders(response)
|
|
86
|
+
return addCorsHeaders(response, req)
|
|
89
87
|
}
|
|
90
88
|
}
|
|
91
89
|
), 'read')
|
|
@@ -94,14 +92,12 @@ export const GET = withRateLimitTier(withApiLogging(
|
|
|
94
92
|
export const PATCH = withRateLimitTier(withApiLogging(
|
|
95
93
|
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
96
94
|
try {
|
|
97
|
-
// Authenticate using dual auth
|
|
98
|
-
|
|
95
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
96
|
+
// point, which fails closed for keys that lack it (#93).
|
|
97
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:write' })
|
|
99
98
|
|
|
100
99
|
if (!authResult.success) {
|
|
101
|
-
return
|
|
102
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
103
|
-
{ status: 401 }
|
|
104
|
-
)
|
|
100
|
+
return createAuthFailureResponse(authResult)
|
|
105
101
|
}
|
|
106
102
|
|
|
107
103
|
if (authResult.rateLimitResponse) {
|
|
@@ -113,12 +109,12 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
113
109
|
// Validate that teamId is not empty
|
|
114
110
|
if (!teamId || teamId.trim() === '') {
|
|
115
111
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
116
|
-
return addCorsHeaders(response)
|
|
112
|
+
return addCorsHeaders(response, req)
|
|
117
113
|
}
|
|
118
114
|
|
|
119
115
|
// Check if user has permission to edit team using MembershipService
|
|
120
116
|
const membership = await MembershipService.get(authResult.user!.id, teamId)
|
|
121
|
-
const actionResult = membership.canPerformAction('
|
|
117
|
+
const actionResult = membership.canPerformAction('team.edit')
|
|
122
118
|
|
|
123
119
|
if (!actionResult.allowed) {
|
|
124
120
|
const response = NextResponse.json(
|
|
@@ -130,7 +126,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
130
126
|
},
|
|
131
127
|
{ status: 403 }
|
|
132
128
|
)
|
|
133
|
-
return addCorsHeaders(response)
|
|
129
|
+
return addCorsHeaders(response, req)
|
|
134
130
|
}
|
|
135
131
|
|
|
136
132
|
const body = await req.json()
|
|
@@ -141,7 +137,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
141
137
|
const slugAvailable = await TeamService.isSlugAvailable(validatedData.slug, teamId)
|
|
142
138
|
if (!slugAvailable) {
|
|
143
139
|
const response = createApiError('Team slug already exists', 409, null, 'SLUG_EXISTS')
|
|
144
|
-
return addCorsHeaders(response)
|
|
140
|
+
return addCorsHeaders(response, req)
|
|
145
141
|
}
|
|
146
142
|
}
|
|
147
143
|
|
|
@@ -177,7 +173,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
177
173
|
|
|
178
174
|
if (updates.length === 0) {
|
|
179
175
|
const response = createApiError('No fields to update', 400, null, 'NO_FIELDS')
|
|
180
|
-
return addCorsHeaders(response)
|
|
176
|
+
return addCorsHeaders(response, req)
|
|
181
177
|
}
|
|
182
178
|
|
|
183
179
|
updates.push(`"updatedAt" = CURRENT_TIMESTAMP`)
|
|
@@ -194,7 +190,7 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
194
190
|
|
|
195
191
|
if (result.rows.length === 0) {
|
|
196
192
|
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
197
|
-
return addCorsHeaders(response)
|
|
193
|
+
return addCorsHeaders(response, req)
|
|
198
194
|
}
|
|
199
195
|
|
|
200
196
|
// Fetch team with member count
|
|
@@ -216,17 +212,17 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
216
212
|
}
|
|
217
213
|
|
|
218
214
|
const response = createApiResponse(responseData)
|
|
219
|
-
return addCorsHeaders(response)
|
|
215
|
+
return addCorsHeaders(response, req)
|
|
220
216
|
} catch (error) {
|
|
221
217
|
if (error instanceof Error && error.name === 'ZodError') {
|
|
222
218
|
const zodError = error as { issues?: unknown[] }
|
|
223
219
|
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
|
|
224
|
-
return addCorsHeaders(response)
|
|
220
|
+
return addCorsHeaders(response, req)
|
|
225
221
|
}
|
|
226
222
|
|
|
227
223
|
console.error('Error updating team:', error)
|
|
228
224
|
const response = createApiError('Internal server error', 500)
|
|
229
|
-
return addCorsHeaders(response)
|
|
225
|
+
return addCorsHeaders(response, req)
|
|
230
226
|
}
|
|
231
227
|
}
|
|
232
228
|
), 'write')
|
|
@@ -235,14 +231,12 @@ export const PATCH = withRateLimitTier(withApiLogging(
|
|
|
235
231
|
export const DELETE = withRateLimitTier(withApiLogging(
|
|
236
232
|
async (req: NextRequest, { params }: { params: Promise<{ teamId: string }> }): Promise<NextResponse> => {
|
|
237
233
|
try {
|
|
238
|
-
// Authenticate using dual auth
|
|
239
|
-
|
|
234
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
235
|
+
// point, which fails closed for keys that lack it (#93).
|
|
236
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'teams:delete' })
|
|
240
237
|
|
|
241
238
|
if (!authResult.success) {
|
|
242
|
-
return
|
|
243
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
244
|
-
{ status: 401 }
|
|
245
|
-
)
|
|
239
|
+
return createAuthFailureResponse(authResult)
|
|
246
240
|
}
|
|
247
241
|
|
|
248
242
|
if (authResult.rateLimitResponse) {
|
|
@@ -254,7 +248,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
254
248
|
// Validate that teamId is not empty
|
|
255
249
|
if (!teamId || teamId.trim() === '') {
|
|
256
250
|
const response = createApiError('Team ID is required', 400, null, 'MISSING_TEAM_ID')
|
|
257
|
-
return addCorsHeaders(response)
|
|
251
|
+
return addCorsHeaders(response, req)
|
|
258
252
|
}
|
|
259
253
|
|
|
260
254
|
// Use the TeamService.delete (handles owner check and personal team protection)
|
|
@@ -262,7 +256,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
262
256
|
await TeamService.delete(teamId, authResult.user!.id)
|
|
263
257
|
|
|
264
258
|
const response = createApiResponse({ deleted: true, id: teamId })
|
|
265
|
-
return addCorsHeaders(response)
|
|
259
|
+
return addCorsHeaders(response, req)
|
|
266
260
|
} catch (error) {
|
|
267
261
|
if (error instanceof Error && error.message === 'Only team owner can delete the team') {
|
|
268
262
|
const response = createApiError(
|
|
@@ -271,17 +265,17 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
271
265
|
null,
|
|
272
266
|
'INSUFFICIENT_PERMISSIONS'
|
|
273
267
|
)
|
|
274
|
-
return addCorsHeaders(response)
|
|
268
|
+
return addCorsHeaders(response, req)
|
|
275
269
|
}
|
|
276
270
|
|
|
277
271
|
if (error instanceof Error && error.message === 'Team not found') {
|
|
278
272
|
const response = createApiError('Team not found', 404, null, 'TEAM_NOT_FOUND')
|
|
279
|
-
return addCorsHeaders(response)
|
|
273
|
+
return addCorsHeaders(response, req)
|
|
280
274
|
}
|
|
281
275
|
|
|
282
276
|
if (error instanceof Error && error.message === 'Personal teams cannot be deleted') {
|
|
283
277
|
const response = createApiError('Personal teams cannot be deleted', 403, null, 'PERSONAL_TEAM_DELETE_FORBIDDEN')
|
|
284
|
-
return addCorsHeaders(response)
|
|
278
|
+
return addCorsHeaders(response, req)
|
|
285
279
|
}
|
|
286
280
|
|
|
287
281
|
throw error
|
|
@@ -289,7 +283,7 @@ export const DELETE = withRateLimitTier(withApiLogging(
|
|
|
289
283
|
} catch (error) {
|
|
290
284
|
console.error('Error deleting team:', error)
|
|
291
285
|
const response = createApiError('Internal server error', 500)
|
|
292
|
-
return addCorsHeaders(response)
|
|
286
|
+
return addCorsHeaders(response, req)
|
|
293
287
|
}
|
|
294
288
|
}
|
|
295
289
|
), 'write')
|
|
@@ -14,16 +14,18 @@ interface RouteParams {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export const GET = withRateLimitTier(async function GET(request: NextRequest, props: RouteParams) {
|
|
17
|
-
// Authenticate request
|
|
18
|
-
|
|
17
|
+
// Authenticate request; the API-key scope is declared at the entry point,
|
|
18
|
+
// which fails closed for keys that lack it (#93).
|
|
19
|
+
const { auth, rateLimitResponse, errorResponse } = await validateAndAuthenticateRequest(request, { requiredScope: 'billing:read' })
|
|
19
20
|
if (rateLimitResponse) return rateLimitResponse
|
|
21
|
+
if (!auth) return errorResponse ?? createApiError('Authentication required', 401, undefined, 'AUTHENTICATION_REQUIRED')
|
|
20
22
|
|
|
21
23
|
const { teamId } = await props.params
|
|
22
24
|
|
|
23
25
|
try {
|
|
24
26
|
// Check if user has permission to view subscription using MembershipService
|
|
25
27
|
const membership = await MembershipService.get(auth.userId, teamId)
|
|
26
|
-
const actionResult = membership.canPerformAction('billing.view')
|
|
28
|
+
const actionResult = membership.canPerformAction('team.billing.view')
|
|
27
29
|
|
|
28
30
|
if (!actionResult.allowed) {
|
|
29
31
|
return NextResponse.json(
|