@nextsparkjs/core 0.1.0-beta.185 → 0.1.0-beta.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/app/guards/DeveloperGuard.d.ts.map +1 -1
- package/dist/components/app/guards/DeveloperGuard.js +8 -4
- package/dist/components/app/guards/SuperAdminGuard.d.ts.map +1 -1
- package/dist/components/app/guards/SuperAdminGuard.js +8 -4
- 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/devtools/api-explorer/ApiExplorer.js +4 -4
- 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/patterns/pages/PatternEditPage.d.ts +5 -1
- package/dist/components/patterns/pages/PatternEditPage.d.ts.map +1 -1
- package/dist/components/patterns/pages/PatternEditPage.js +16 -5
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts +2 -1
- package/dist/components/patterns/pages/PatternsCreatePage.d.ts.map +1 -1
- package/dist/components/patterns/pages/PatternsCreatePage.js +17 -5
- 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 +29 -0
- 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/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 +38 -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 +15 -0
- package/dist/lib/api/auth.d.ts.map +1 -1
- package/dist/lib/api/auth.js +17 -4
- package/dist/lib/api/entities.js +2 -1
- 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 -138
- package/dist/lib/api/entity-handler.js +2 -2
- 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/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 +1639 -0
- package/dist/lib/auth-client.d.ts.map +1 -0
- package/dist/lib/auth-client.js +13 -1
- package/dist/lib/auth.d.ts.map +1 -1
- package/dist/lib/auth.js +45 -11
- 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/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/email/templates.d.ts +3 -3
- package/dist/lib/email/templates.d.ts.map +1 -1
- package/dist/lib/entities/api-generator.js +5 -5
- 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 +20 -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 +8 -4
- package/dist/lib/mcp/audit.d.ts.map +1 -1
- 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/api-docs.service.d.ts +5 -1
- package/dist/lib/services/api-docs.service.d.ts.map +1 -1
- package/dist/lib/services/api-presets.service.d.ts +6 -1
- package/dist/lib/services/api-presets.service.d.ts.map +1 -1
- package/dist/lib/services/feature.service.d.ts +58 -4
- package/dist/lib/services/feature.service.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/permission.service.d.ts +10 -4
- package/dist/lib/services/permission.service.d.ts.map +1 -1
- package/dist/lib/services/plugin.service.d.ts +1 -1
- package/dist/lib/services/plugin.service.d.ts.map +1 -1
- 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/template-resolver.d.ts +3 -3
- 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/lib/utils/cors.d.ts +13 -0
- package/dist/lib/utils/cors.d.ts.map +1 -1
- package/dist/lib/utils/cors.js +20 -0
- package/dist/messages/de/auth.json +25 -2
- package/dist/messages/de/index.d.ts +83 -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/de/navigation.json +3 -1
- package/dist/messages/en/auth.json +25 -2
- package/dist/messages/en/index.d.ts +83 -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/en/navigation.json +3 -1
- package/dist/messages/es/auth.json +25 -2
- package/dist/messages/es/index.d.ts +83 -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/es/navigation.json +3 -1
- package/dist/messages/fr/auth.json +25 -2
- package/dist/messages/fr/index.d.ts +83 -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/fr/navigation.json +3 -1
- package/dist/messages/it/auth.json +25 -2
- package/dist/messages/it/index.d.ts +83 -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/it/navigation.json +3 -1
- package/dist/messages/pt/auth.json +25 -2
- package/dist/messages/pt/index.d.ts +83 -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/messages/pt/navigation.json +3 -1
- 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/nextspark-registries.d.ts +580 -236
- 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 +4 -1
- 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 +21 -43
- 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 +15 -15
- package/dist/templates/app/layout.tsx +46 -20
- 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 +14 -11
- package/scripts/build/registry/__tests__/scope-registry.test.mjs +18 -0
- package/scripts/build/registry/generators/scope-registry.mjs +9 -2
- 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 +4 -1
- 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 +21 -43
- 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 +15 -15
- package/templates/app/layout.tsx +46 -20
- 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__/next-server.js +24 -0
- package/dist/components/teams/TeamSwitchModal.d.js +0 -0
- package/dist/components/ui/accordion.d.js +0 -0
- package/dist/components/ui/avatar.d.js +0 -0
- package/dist/components/ui/badge.d.js +0 -0
- package/dist/components/ui/button.d.js +0 -0
- package/dist/components/ui/card.d.js +0 -0
- package/dist/components/ui/dialog.d.js +0 -0
- package/dist/components/ui/dropdown-menu.d.js +0 -0
- package/dist/components/ui/input.d.js +0 -0
- package/dist/components/ui/progress.d.js +0 -0
- package/dist/components/ui/scroll-area.d.js +0 -0
- package/dist/components/ui/select.d.js +0 -0
- package/dist/components/ui/separator.d.js +0 -0
- package/dist/components/ui/table.d.js +0 -0
- package/dist/components/ui/textarea.d.js +0 -0
- package/dist/contexts/TeamContext.d.js +0 -0
- package/dist/hooks/useAuth.d.js +0 -0
- package/dist/hooks/useLastAuthMethod.d.js +0 -0
- package/dist/hooks/useOrigin.d.js +0 -0
- package/dist/lib/api/auth/dual-auth.d.js +0 -0
- package/dist/lib/api/auth.d.js +0 -0
- package/dist/lib/api/cache.d.js +0 -0
- package/dist/lib/api/keys.d.js +0 -0
- package/dist/lib/auth-client.d.js +0 -0
- package/dist/lib/auth-context.d.js +0 -0
- package/dist/lib/auth.d.js +0 -0
- package/dist/lib/billing/config-types.d.js +0 -0
- package/dist/lib/billing/enforcement.d.js +0 -0
- package/dist/lib/billing/helpers.d.js +0 -0
- package/dist/lib/billing/jobs.d.js +0 -0
- package/dist/lib/billing/types.d.js +0 -0
- package/dist/lib/blocks/loader.d.js +0 -0
- package/dist/lib/config/app.config.d.js +0 -0
- package/dist/lib/config/config-loader.d.js +0 -0
- package/dist/lib/config/config-sync.d.js +0 -0
- package/dist/lib/config/config-types.d.js +0 -0
- package/dist/lib/config/config-utils.d.js +0 -0
- package/dist/lib/config/dashboard-types.d.js +0 -0
- package/dist/lib/config/dashboard.config.d.js +0 -0
- package/dist/lib/config/icon-map.d.js +0 -0
- package/dist/lib/config/index.d.js +0 -7
- package/dist/lib/config/nextspark-types.d.js +0 -0
- package/dist/lib/config/roles-merge.d.js +0 -0
- package/dist/lib/config/types.d.js +0 -0
- package/dist/lib/db.d.js +0 -0
- package/dist/lib/email/factory.d.js +0 -0
- package/dist/lib/email/index.d.js +0 -3
- package/dist/lib/email/providers/console.d.js +0 -0
- package/dist/lib/email/providers/resend.d.js +0 -0
- package/dist/lib/email/templates.d.js +0 -0
- package/dist/lib/email/types.d.js +0 -0
- package/dist/lib/entities/types.d.js +0 -0
- package/dist/lib/permissions/system.d.js +0 -0
- package/dist/lib/permissions/types.d.js +0 -0
- package/dist/lib/plugins/hook-system.d.js +0 -0
- package/dist/lib/scheduled-actions/cleanup.d.js +0 -0
- package/dist/lib/scheduled-actions/index.d.js +0 -19
- package/dist/lib/scheduled-actions/initializer.d.js +0 -1
- package/dist/lib/scheduled-actions/processor.d.js +0 -0
- package/dist/lib/scheduled-actions/registry.d.js +0 -0
- package/dist/lib/scheduled-actions/scheduler.d.js +0 -0
- package/dist/lib/scheduled-actions/types.d.js +0 -0
- package/dist/lib/services/api-routes.service.d.js +0 -0
- package/dist/lib/services/entity-type.service.d.js +0 -0
- package/dist/lib/services/index.d.js +0 -40
- package/dist/lib/services/invoice.service.d.js +0 -0
- package/dist/lib/services/membership.service.d.js +0 -0
- package/dist/lib/services/meta.service.d.js +0 -0
- package/dist/lib/services/middleware.service.d.js +0 -0
- package/dist/lib/services/namespace.service.d.js +0 -0
- package/dist/lib/services/permission.service.d.js +0 -0
- package/dist/lib/services/plan.service.d.js +0 -0
- package/dist/lib/services/plugin.service.d.js +0 -0
- package/dist/lib/services/route-handler.service.d.js +0 -0
- package/dist/lib/services/scope.service.d.js +0 -0
- package/dist/lib/services/subscription.service.d.js +0 -0
- package/dist/lib/services/team-member.service.d.js +0 -0
- package/dist/lib/services/team.service.d.js +0 -0
- package/dist/lib/services/theme.service.d.js +0 -0
- package/dist/lib/services/usage.service.d.js +0 -0
- package/dist/lib/services/user-flags.service.d.js +0 -0
- package/dist/lib/services/user.service.d.js +0 -0
- package/dist/lib/teams/helpers.d.js +0 -0
- package/dist/lib/teams/permissions.d.js +0 -0
- package/dist/lib/teams/types.d.js +0 -0
- package/dist/lib/utils/config-merge.d.js +0 -0
- package/dist/lib/utils/file-logger.d.js +0 -0
- package/dist/lib/utils.d.js +0 -0
- package/dist/styles/classes.json +0 -1093
- package/dist/styles/ui.css +0 -2
- package/dist/types/auth.d.js +0 -0
- package/dist/types/blocks.d.js +0 -0
- package/dist/types/meta.types.d.js +0 -0
- package/dist/types/plugin.d.js +0 -0
- package/dist/types/testing.types.d.js +0 -0
- package/dist/types/theme.d.js +0 -0
- package/dist/types/user.types.d.js +0 -0
- package/scripts/build/build-ui-css.mjs +0 -351
- /package/dist/{components/app/layouts/PublicFooter.d.js → lib/auth/security-notifications/types.js} +0 -0
- /package/dist/{components/app/layouts/PublicNavbar.d.js → nextspark-registries.d.js} +0 -0
- /package/dist/{components/app/misc/ThemeToggle.d.js → types/use-intl-react-shim.d.js} +0 -0
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
handleEntityMetadataInResponse,
|
|
15
15
|
processEntityMetadata
|
|
16
16
|
} from '@nextsparkjs/core/lib/api/helpers';
|
|
17
|
-
import { authenticateRequest } from '@nextsparkjs/core/lib/api/auth/dual-auth';
|
|
17
|
+
import { authenticateRequest, createAuthFailureResponse } from '@nextsparkjs/core/lib/api/auth/dual-auth';
|
|
18
18
|
import { hasAdminPermission } from '@nextsparkjs/core/lib/api/auth/permissions';
|
|
19
19
|
import { z } from 'zod';
|
|
20
20
|
import { withRateLimitTier } from '@nextsparkjs/core/lib/api/rate-limit';
|
|
@@ -34,21 +34,19 @@ const createUserSchema = z.object({
|
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
// Handle CORS preflight
|
|
37
|
-
export async function OPTIONS() {
|
|
38
|
-
return handleCorsPreflightRequest();
|
|
37
|
+
export async function OPTIONS(request: NextRequest) {
|
|
38
|
+
return handleCorsPreflightRequest(request);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
// GET /api/v1/users - List users with dual auth
|
|
42
42
|
export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
|
|
43
43
|
try {
|
|
44
|
-
// Authenticate using dual auth
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
45
|
+
// point, which fails closed for keys that lack it (#93).
|
|
46
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'users:read' });
|
|
47
|
+
|
|
47
48
|
if (!authResult.success) {
|
|
48
|
-
return
|
|
49
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
50
|
-
{ status: 401 }
|
|
51
|
-
);
|
|
49
|
+
return createAuthFailureResponse(authResult);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
52
|
if (authResult.rateLimitResponse) {
|
|
@@ -58,7 +56,7 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
|
|
|
58
56
|
// SECURITY: Only superadmins can list all users
|
|
59
57
|
if (!hasAdminPermission(authResult, 'users:read')) {
|
|
60
58
|
const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
|
|
61
|
-
return addCorsHeaders(response);
|
|
59
|
+
return addCorsHeaders(response, req);
|
|
62
60
|
}
|
|
63
61
|
|
|
64
62
|
const metaParams = parseMetaParams(req);
|
|
@@ -103,25 +101,23 @@ export const GET = withRateLimitTier(withApiLogging(async (req: NextRequest): Pr
|
|
|
103
101
|
const usersWithMeta = await includeEntityMetadata('user', users as { id: string }[], metaParams, authResult.user!.id);
|
|
104
102
|
|
|
105
103
|
const response = createApiResponse(usersWithMeta, paginationMeta);
|
|
106
|
-
return addCorsHeaders(response);
|
|
104
|
+
return addCorsHeaders(response, req);
|
|
107
105
|
} catch (error) {
|
|
108
106
|
console.error('Error fetching users:', error);
|
|
109
107
|
const response = createApiError('Internal server error', 500);
|
|
110
|
-
return addCorsHeaders(response);
|
|
108
|
+
return addCorsHeaders(response, req);
|
|
111
109
|
}
|
|
112
110
|
}), 'read');
|
|
113
111
|
|
|
114
112
|
// POST /api/v1/users - Create user with dual auth
|
|
115
113
|
export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
|
|
116
114
|
try {
|
|
117
|
-
// Authenticate using dual auth
|
|
118
|
-
|
|
119
|
-
|
|
115
|
+
// Authenticate using dual auth; the API-key scope is declared at the entry
|
|
116
|
+
// point, which fails closed for keys that lack it (#93).
|
|
117
|
+
const authResult = await authenticateRequest(req, { requiredScope: 'users:write' });
|
|
118
|
+
|
|
120
119
|
if (!authResult.success) {
|
|
121
|
-
return
|
|
122
|
-
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
|
|
123
|
-
{ status: 401 }
|
|
124
|
-
);
|
|
120
|
+
return createAuthFailureResponse(authResult);
|
|
125
121
|
}
|
|
126
122
|
|
|
127
123
|
if (authResult.rateLimitResponse) {
|
|
@@ -131,7 +127,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
|
|
|
131
127
|
// SECURITY: Only superadmins can create users
|
|
132
128
|
if (!hasAdminPermission(authResult, 'users:write')) {
|
|
133
129
|
const response = createApiError('Insufficient permissions. Superadmin access required.', 403);
|
|
134
|
-
return addCorsHeaders(response);
|
|
130
|
+
return addCorsHeaders(response, req);
|
|
135
131
|
}
|
|
136
132
|
|
|
137
133
|
const body = await req.json();
|
|
@@ -147,7 +143,7 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
|
|
|
147
143
|
|
|
148
144
|
if (existingUser.length > 0) {
|
|
149
145
|
const response = createApiError('Email already exists', 409, null, 'EMAIL_EXISTS');
|
|
150
|
-
return addCorsHeaders(response);
|
|
146
|
+
return addCorsHeaders(response, req);
|
|
151
147
|
}
|
|
152
148
|
|
|
153
149
|
const newUserId = globalThis.crypto.randomUUID();
|
|
@@ -183,16 +179,16 @@ export const POST = withRateLimitTier(withApiLogging(async (req: NextRequest): P
|
|
|
183
179
|
const responseData = await handleEntityMetadataInResponse('user', createdUser as { id: string }, metadataWasProvided, authResult.user!.id);
|
|
184
180
|
|
|
185
181
|
const response = createApiResponse(responseData, { created: true }, 201);
|
|
186
|
-
return addCorsHeaders(response);
|
|
182
|
+
return addCorsHeaders(response, req);
|
|
187
183
|
} catch (error) {
|
|
188
184
|
if (error instanceof z.ZodError) {
|
|
189
185
|
const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR');
|
|
190
|
-
return addCorsHeaders(response);
|
|
186
|
+
return addCorsHeaders(response, req);
|
|
191
187
|
}
|
|
192
188
|
|
|
193
189
|
console.error('Error creating user:', error);
|
|
194
190
|
const response = createApiError('Internal server error', 500);
|
|
195
|
-
return addCorsHeaders(response);
|
|
191
|
+
return addCorsHeaders(response, req);
|
|
196
192
|
}
|
|
197
193
|
}), 'write');
|
|
198
194
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useAuth } from '@nextsparkjs/core/hooks/useAuth'
|
|
4
4
|
import { useRouter } from 'next/navigation'
|
|
5
5
|
import { useEffect, Suspense } from 'react'
|
|
6
|
-
import {
|
|
6
|
+
import { DashboardAuthSkeleton } from '@nextsparkjs/core/components/dashboard/layouts/DashboardAuthSkeleton'
|
|
7
7
|
import { DashboardProviders } from '@nextsparkjs/core/providers/DashboardProviders'
|
|
8
8
|
import { DashboardTranslationPreloader } from '@nextsparkjs/core/lib/i18n/DashboardTranslationPreloader'
|
|
9
9
|
import { TranslationDebugger } from '@nextsparkjs/core/utils/dev/TranslationDebugger'
|
|
@@ -35,11 +35,7 @@ function DashboardLayoutContent({ children }: { children: React.ReactNode }) {
|
|
|
35
35
|
}, [user, isLoading, router])
|
|
36
36
|
|
|
37
37
|
if (isLoading) {
|
|
38
|
-
return
|
|
39
|
-
<div className="min-h-screen flex items-center justify-center">
|
|
40
|
-
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
|
|
41
|
-
</div>
|
|
42
|
-
)
|
|
38
|
+
return <DashboardAuthSkeleton />
|
|
43
39
|
}
|
|
44
40
|
|
|
45
41
|
if (!user) {
|
|
@@ -55,27 +55,22 @@ export const metadata: Metadata = getMetadataOrDefault(
|
|
|
55
55
|
defaultMetadata
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
export default function RootLayout({
|
|
58
|
+
export default async function RootLayout({
|
|
59
59
|
children,
|
|
60
60
|
}: Readonly<{
|
|
61
61
|
children: React.ReactNode
|
|
62
62
|
}>) {
|
|
63
|
+
const billingHints = await getBillingResourceHints()
|
|
64
|
+
|
|
63
65
|
return (
|
|
64
66
|
<html lang={DEFAULT_LOCALE} suppressHydrationWarning>
|
|
65
67
|
<head>
|
|
66
|
-
{(() =>
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
))}
|
|
73
|
-
{[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
|
|
74
|
-
<link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
|
|
75
|
-
))}
|
|
76
|
-
</>
|
|
77
|
-
)
|
|
78
|
-
})()}
|
|
68
|
+
{billingHints.preconnect.map((domain) => (
|
|
69
|
+
<link key={`pre-${domain}`} rel="preconnect" href={domain} />
|
|
70
|
+
))}
|
|
71
|
+
{[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
|
|
72
|
+
<link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
|
|
73
|
+
))}
|
|
79
74
|
</head>
|
|
80
75
|
<body
|
|
81
76
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
@@ -84,7 +79,12 @@ export default function RootLayout({
|
|
|
84
79
|
<NextThemeProvider
|
|
85
80
|
attribute="class"
|
|
86
81
|
defaultTheme={DEFAULT_THEME_MODE}
|
|
87
|
-
|
|
82
|
+
// Always true here: this layout never passes forcedTheme, so
|
|
83
|
+
// ThemeToggle is unconditionally visible and offers "System" as one
|
|
84
|
+
// of its 3 choices — gating on DEFAULT_THEME_MODE === 'system' had
|
|
85
|
+
// the same bug as the non-PPR layout (see #175): a build-time
|
|
86
|
+
// default of 'light'/'dark' silently broke the "System" option.
|
|
87
|
+
enableSystem
|
|
88
88
|
disableTransitionOnChange
|
|
89
89
|
>
|
|
90
90
|
<CustomThemeProvider>
|
|
@@ -15,11 +15,15 @@ import { getBillingResourceHints } from "@nextsparkjs/core/lib/billing/gateways/
|
|
|
15
15
|
import { ThemeProvider as NextThemeProvider } from "@nextsparkjs/core/providers/theme-provider"
|
|
16
16
|
import { ThemeProvider as CustomThemeProvider } from "@nextsparkjs/core/lib/theme/ThemeProvider"
|
|
17
17
|
import { Toaster } from "@nextsparkjs/core/components/ui/sonner"
|
|
18
|
+
import { QueryProvider } from "@nextsparkjs/core/providers/query-provider"
|
|
19
|
+
import { TeamProvider } from "@nextsparkjs/core/contexts/TeamContext"
|
|
20
|
+
import { SubscriptionProvider } from "@nextsparkjs/core/contexts/SubscriptionContext"
|
|
18
21
|
import { getUserLocale } from '@nextsparkjs/core/lib/locale'
|
|
19
22
|
import { TranslationContextManager } from "@nextsparkjs/core/providers/TranslationContextManager"
|
|
23
|
+
import { SessionCookieRefresher } from "@nextsparkjs/core/components/auth/SessionCookieRefresher"
|
|
20
24
|
import { PluginService } from '@nextsparkjs/core/lib/services'
|
|
21
25
|
import { getMetadataOrDefault } from '@nextsparkjs/core/lib/template-resolver'
|
|
22
|
-
import {
|
|
26
|
+
import { getThemeSettings } from '@nextsparkjs/core/lib/theme/get-default-theme-mode'
|
|
23
27
|
|
|
24
28
|
const geistSans = Geist({
|
|
25
29
|
variable: "--font-geist-sans",
|
|
@@ -55,24 +59,18 @@ export default async function RootLayout({
|
|
|
55
59
|
|
|
56
60
|
const locale = await getUserLocale()
|
|
57
61
|
const messages = await getMessages({ locale })
|
|
58
|
-
const
|
|
62
|
+
const { defaultMode, allowUserToggle, forcedThemeRoutes } = await getThemeSettings()
|
|
63
|
+
const billingHints = await getBillingResourceHints()
|
|
59
64
|
|
|
60
65
|
return (
|
|
61
66
|
<html lang={locale} suppressHydrationWarning>
|
|
62
67
|
<head>
|
|
63
|
-
{(() =>
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
))}
|
|
70
|
-
{[...hints.preconnect, ...hints.dnsPrefetch].map((domain) => (
|
|
71
|
-
<link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
|
|
72
|
-
))}
|
|
73
|
-
</>
|
|
74
|
-
)
|
|
75
|
-
})()}
|
|
68
|
+
{billingHints.preconnect.map((domain) => (
|
|
69
|
+
<link key={`pre-${domain}`} rel="preconnect" href={domain} />
|
|
70
|
+
))}
|
|
71
|
+
{[...billingHints.preconnect, ...billingHints.dnsPrefetch].map((domain) => (
|
|
72
|
+
<link key={`dns-${domain}`} rel="dns-prefetch" href={domain} />
|
|
73
|
+
))}
|
|
76
74
|
</head>
|
|
77
75
|
<body
|
|
78
76
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
@@ -80,14 +78,42 @@ export default async function RootLayout({
|
|
|
80
78
|
<NextIntlClientProvider locale={locale} messages={messages}>
|
|
81
79
|
<NextThemeProvider
|
|
82
80
|
attribute="class"
|
|
83
|
-
defaultTheme={
|
|
84
|
-
|
|
81
|
+
defaultTheme={defaultMode}
|
|
82
|
+
// When allowUserToggle is false, force the theme and ignore localStorage/system
|
|
83
|
+
forcedTheme={!allowUserToggle ? defaultMode : undefined}
|
|
84
|
+
// Detect OS preference whenever the user can toggle at all. ThemeToggle
|
|
85
|
+
// always offers "System" as one of its 3 choices when toggling is
|
|
86
|
+
// allowed, so the provider must be able to resolve it whenever that's
|
|
87
|
+
// true — gating on `defaultMode === 'system'` looks equivalent at
|
|
88
|
+
// first render, but defaultMode is the user's last SAVED preference
|
|
89
|
+
// for a logged-in user (see getThemeSettings), not a static "is
|
|
90
|
+
// system-matching enabled" flag. That made "System" silently write a
|
|
91
|
+
// literal, unmatched `class="system"` for anyone whose saved
|
|
92
|
+
// preference (or the theme's own config default) wasn't already
|
|
93
|
+
// 'system' — see #175.
|
|
94
|
+
enableSystem={allowUserToggle}
|
|
95
|
+
// Force a theme on the routes declared in theme.config.ts (forcedThemeRoutes)
|
|
96
|
+
forcedThemeRoutes={forcedThemeRoutes}
|
|
85
97
|
disableTransitionOnChange
|
|
86
98
|
>
|
|
87
99
|
<CustomThemeProvider>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
{/* Team providers live here (not only in DashboardProviders) so the activeTeamId
|
|
101
|
+
cookie is synced from any route, matching layout.ppr.tsx — see #115.
|
|
102
|
+
DashboardProviders skips re-mounting them when already present. */}
|
|
103
|
+
<QueryProvider>
|
|
104
|
+
<TeamProvider>
|
|
105
|
+
<SubscriptionProvider>
|
|
106
|
+
<TranslationContextManager />
|
|
107
|
+
{/* Real session-cookie renewal for installed PWAs: the render-time
|
|
108
|
+
session reads above (getUserLocale/getThemeSettings) cannot
|
|
109
|
+
write cookies, so the rolling refresh is triggered from the
|
|
110
|
+
client through the auth Route Handler instead. */}
|
|
111
|
+
<SessionCookieRefresher />
|
|
112
|
+
<main>{children}</main>
|
|
113
|
+
<Suspense><Toaster position="bottom-left" /></Suspense>
|
|
114
|
+
</SubscriptionProvider>
|
|
115
|
+
</TeamProvider>
|
|
116
|
+
</QueryProvider>
|
|
91
117
|
</CustomThemeProvider>
|
|
92
118
|
</NextThemeProvider>
|
|
93
119
|
</NextIntlClientProvider>
|
|
@@ -115,6 +115,25 @@ export const APP_CONFIG_OVERRIDES = {
|
|
|
115
115
|
enabled: false,
|
|
116
116
|
},
|
|
117
117
|
},
|
|
118
|
+
/**
|
|
119
|
+
* Login methods, in priority order. Core default = passwordless preset:
|
|
120
|
+
* a one-time code by email + Google, no password field (needs an email
|
|
121
|
+
* provider — RESEND_API_KEY — and GOOGLE_CLIENT_ID/SECRET for Google).
|
|
122
|
+
*
|
|
123
|
+
* Uncomment to switch to the classic email + password preset, or list both
|
|
124
|
+
* (the first email method is the form shown first):
|
|
125
|
+
*/
|
|
126
|
+
// methods: ['email-password', 'google'],
|
|
127
|
+
// methods: ['email-otp', 'email-password', 'google'],
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Session duration / renewal in seconds (defaults: 7 days, renewed daily).
|
|
131
|
+
* Long-lived sessions for an installed PWA, for example:
|
|
132
|
+
*/
|
|
133
|
+
// session: {
|
|
134
|
+
// expiresIn: 60 * 60 * 24 * 90, // 90 days
|
|
135
|
+
// updateAge: 60 * 60 * 24 * 7, // renew weekly
|
|
136
|
+
// },
|
|
118
137
|
/**
|
|
119
138
|
* Whether Better Auth automatically sends the verification email on signup.
|
|
120
139
|
*
|
|
@@ -22,6 +22,12 @@ export const starterThemeConfig: ThemeConfig = {
|
|
|
22
22
|
// ==========================================
|
|
23
23
|
plugins: [],
|
|
24
24
|
|
|
25
|
+
// ==========================================
|
|
26
|
+
// THEME MODE (Optional)
|
|
27
|
+
// ==========================================
|
|
28
|
+
// defaultMode: 'system', // 'light' | 'dark' | 'system'
|
|
29
|
+
// forcedThemeRoutes: { '/login': 'light' }, // force a theme on route prefixes (longest wins)
|
|
30
|
+
|
|
25
31
|
// ==========================================
|
|
26
32
|
// STYLES CONFIGURATION
|
|
27
33
|
// ==========================================
|
|
@@ -200,8 +200,12 @@
|
|
|
200
200
|
/* =============================================
|
|
201
201
|
BASE STYLES
|
|
202
202
|
============================================= */
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
/* Must live in @layer base: un-layered rules beat every Tailwind v4 utility
|
|
204
|
+
(border-primary, border-destructive, ...) regardless of specificity. */
|
|
205
|
+
@layer base {
|
|
206
|
+
* {
|
|
207
|
+
border-color: var(--border);
|
|
208
|
+
}
|
|
205
209
|
}
|
|
206
210
|
|
|
207
211
|
body {
|
package/dist/templates/proxy.ts
CHANGED
|
@@ -64,9 +64,48 @@ function isPublicPath(pathname: string): boolean {
|
|
|
64
64
|
)
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Identity headers this proxy injects for downstream server code.
|
|
69
|
+
*
|
|
70
|
+
* SECURITY (#87): these headers are trusted by RSC layouts and permission
|
|
71
|
+
* checks, so an inbound value must NEVER reach the app. They are stripped from
|
|
72
|
+
* every request before any path branching and re-added only from the verified
|
|
73
|
+
* session. Gating the strip on protected prefixes is not enough: Next.js
|
|
74
|
+
* dispatches Server Actions by the `Next-Action` header, not by the URL, so an
|
|
75
|
+
* action can be POSTed to a public path with a forged `x-user-id`.
|
|
76
|
+
*/
|
|
77
|
+
const TRUSTED_IDENTITY_HEADERS = ['x-user-id', 'x-user-email', 'x-pathname'] as const
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build the request headers forwarded to the app: inbound copy minus every
|
|
81
|
+
* trusted identity header, plus the real pathname.
|
|
82
|
+
*/
|
|
83
|
+
function sanitizeRequestHeaders(request: NextRequest): Headers {
|
|
84
|
+
const headers = new Headers(request.headers)
|
|
85
|
+
for (const name of TRUSTED_IDENTITY_HEADERS) {
|
|
86
|
+
headers.delete(name)
|
|
87
|
+
}
|
|
88
|
+
headers.set('x-pathname', request.nextUrl.pathname)
|
|
89
|
+
return headers
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Continue to the app with the (sanitized) request headers.
|
|
94
|
+
* Every pass-through in this proxy MUST go through here so the strip applies
|
|
95
|
+
* to public paths, /api/v1 and unmatched routes alike.
|
|
96
|
+
*/
|
|
97
|
+
function passThrough(requestHeaders: Headers): NextResponse {
|
|
98
|
+
return NextResponse.next({
|
|
99
|
+
request: { headers: requestHeaders },
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
67
103
|
export async function proxy(request: NextRequest) {
|
|
68
104
|
const { pathname } = request.nextUrl
|
|
69
105
|
|
|
106
|
+
// 0. Strip forgeable identity headers before ANY branching (see #87)
|
|
107
|
+
const requestHeaders = sanitizeRequestHeaders(request)
|
|
108
|
+
|
|
70
109
|
// 1. Check for theme middleware override
|
|
71
110
|
const activeTheme = process.env.NEXT_PUBLIC_ACTIVE_THEME
|
|
72
111
|
if (activeTheme && hasThemeMiddleware(activeTheme)) {
|
|
@@ -114,17 +153,17 @@ export async function proxy(request: NextRequest) {
|
|
|
114
153
|
return NextResponse.redirect(loginUrl)
|
|
115
154
|
}
|
|
116
155
|
}
|
|
117
|
-
return
|
|
156
|
+
return passThrough(requestHeaders)
|
|
118
157
|
}
|
|
119
158
|
|
|
120
159
|
// 4. Allow public paths
|
|
121
160
|
if (isPublicPath(pathname)) {
|
|
122
|
-
return
|
|
161
|
+
return passThrough(requestHeaders)
|
|
123
162
|
}
|
|
124
163
|
|
|
125
164
|
// 5. API v1 routes handle their own dual authentication
|
|
126
165
|
if (pathname.startsWith('/api/v1')) {
|
|
127
|
-
return
|
|
166
|
+
return passThrough(requestHeaders)
|
|
128
167
|
}
|
|
129
168
|
|
|
130
169
|
// 6. Protected routes - require authentication and inject user headers
|
|
@@ -162,20 +201,17 @@ export async function proxy(request: NextRequest) {
|
|
|
162
201
|
}
|
|
163
202
|
}
|
|
164
203
|
|
|
165
|
-
// Inject user headers for downstream use
|
|
204
|
+
// Inject user headers for downstream use, ONLY from the verified session
|
|
205
|
+
// (requestHeaders already had any inbound values stripped).
|
|
166
206
|
// IMPORTANT: EntityPermissionLayout depends on these headers
|
|
167
|
-
const requestHeaders = new Headers(request.headers)
|
|
168
207
|
if (session.user?.id) {
|
|
169
208
|
requestHeaders.set('x-user-id', session.user.id)
|
|
170
209
|
}
|
|
171
210
|
if (session.user?.email) {
|
|
172
211
|
requestHeaders.set('x-user-email', session.user.email)
|
|
173
212
|
}
|
|
174
|
-
requestHeaders.set('x-pathname', pathname)
|
|
175
213
|
|
|
176
|
-
return
|
|
177
|
-
request: { headers: requestHeaders },
|
|
178
|
-
})
|
|
214
|
+
return passThrough(requestHeaders)
|
|
179
215
|
} catch (error) {
|
|
180
216
|
console.error('Proxy error:', error)
|
|
181
217
|
const loginUrl = new URL('/login', request.url)
|
|
@@ -184,7 +220,7 @@ export async function proxy(request: NextRequest) {
|
|
|
184
220
|
}
|
|
185
221
|
}
|
|
186
222
|
|
|
187
|
-
return
|
|
223
|
+
return passThrough(requestHeaders)
|
|
188
224
|
}
|
|
189
225
|
|
|
190
226
|
export const config = {
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
],
|
|
57
57
|
"exclude": [
|
|
58
58
|
"node_modules",
|
|
59
|
+
// PPR template variants (layout.ppr.tsx) are copied over layout.tsx when a project
|
|
60
|
+
// enables cacheComponents; in place they import registry exports that only exist
|
|
61
|
+
// in PPR mode, so they are never type-checked as-is.
|
|
62
|
+
"**/*.ppr.tsx",
|
|
59
63
|
"**/*.test.ts",
|
|
60
64
|
"**/*.test.tsx",
|
|
61
65
|
"**/*.spec.ts",
|
package/dist/types/theme.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export interface ThemeConfig {
|
|
|
17
17
|
enabled?: boolean;
|
|
18
18
|
/** Default theme mode - controls next-themes behavior */
|
|
19
19
|
defaultMode?: 'light' | 'dark' | 'system';
|
|
20
|
+
/**
|
|
21
|
+
* Force a theme on specific routes, ignoring user and system preference.
|
|
22
|
+
* Keys are route prefixes matched at segment boundaries (the longest match
|
|
23
|
+
* wins); values are the theme to force. The root ThemeProvider passes the
|
|
24
|
+
* resolved value to next-themes' `forcedTheme`, so the route renders with
|
|
25
|
+
* that theme from the first paint and on client-side navigations.
|
|
26
|
+
* @example { '/login': 'light', '/signup': 'light', '/embed': 'dark' }
|
|
27
|
+
*/
|
|
28
|
+
forcedThemeRoutes?: Record<string, 'light' | 'dark'>;
|
|
20
29
|
dependencies?: string[];
|
|
21
30
|
plugins?: string[];
|
|
22
31
|
styles?: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/types/theme.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"theme.d.ts","sourceRoot":"","sources":["../../src/types/theme.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
CREATE TABLE IF NOT EXISTS "api_audit_log" (
|
|
5
5
|
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
6
|
-
|
|
6
|
+
-- NULL for session-authenticated requests (see 025_api_audit_log_nullable_api_key.sql)
|
|
7
|
+
"apiKeyId" TEXT REFERENCES public."api_key"(id) ON DELETE CASCADE,
|
|
7
8
|
"userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
|
|
8
9
|
endpoint TEXT NOT NULL,
|
|
9
10
|
method TEXT NOT NULL,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
-- Migration: 025_api_audit_log_nullable_api_key.sql
|
|
2
|
+
-- Description: Allow api_audit_log rows without an API key (#105)
|
|
3
|
+
--
|
|
4
|
+
-- The generic entity routes (/api/v1/[entity], /api/v1/[entity]/[id]) now
|
|
5
|
+
-- write an audit row for every authenticated request, whether it was
|
|
6
|
+
-- authenticated with an API key or with a browser session. Session requests
|
|
7
|
+
-- have no key, so "apiKeyId" must accept NULL. The foreign key stays as-is:
|
|
8
|
+
-- a FK only constrains non-null values.
|
|
9
|
+
--
|
|
10
|
+
-- Idempotent: DROP NOT NULL on an already-nullable column is a no-op.
|
|
11
|
+
|
|
12
|
+
ALTER TABLE "api_audit_log" ALTER COLUMN "apiKeyId" DROP NOT NULL;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
-- Migration: 026_login_events.sql
|
|
2
|
+
-- Description: login_events — per-user device/login history for built-in
|
|
3
|
+
-- security email notifications (new-device login alerts).
|
|
4
|
+
-- Date: 2026-09-01
|
|
5
|
+
-- Issue: #75
|
|
6
|
+
--
|
|
7
|
+
-- Why 026 (and not 022 as the issue text proposed, nor 025):
|
|
8
|
+
-- The issue was written when the migration sequence ended earlier. By the
|
|
9
|
+
-- time this branch was authored the sequence already went through
|
|
10
|
+
-- 024_teams_soft_delete.sql (022 and 023 are taken by rls_runtime_roles and
|
|
11
|
+
-- the invitations index), so this migration was originally 025. It was
|
|
12
|
+
-- renumbered to 026 when merging to main, because 025 had in the meantime
|
|
13
|
+
-- been taken by 025_api_audit_log_nullable_api_key.sql (issue #105).
|
|
14
|
+
--
|
|
15
|
+
-- What this backs:
|
|
16
|
+
-- The security-notifications dispatcher records one row per successful sign-in
|
|
17
|
+
-- with a device fingerprint = sha256(normalizedUA | ip/24). A sign-in whose
|
|
18
|
+
-- (userId, deviceFingerprint) pair has never been seen is flagged isNew=true
|
|
19
|
+
-- and triggers a "new device" email via the auth:security-notification
|
|
20
|
+
-- scheduled action. Known fingerprints are still logged (isNew=false) but send
|
|
21
|
+
-- no email. The table is also the substrate for a future login-history UI
|
|
22
|
+
-- (out of scope here — see issue #75 "Out of scope").
|
|
23
|
+
--
|
|
24
|
+
-- Append-only: rows are never updated, so there is no "updatedAt"/set_updated_at
|
|
25
|
+
-- trigger (unlike mutable entity tables). city/country are nullable for a future
|
|
26
|
+
-- geolocation enhancement (also out of scope).
|
|
27
|
+
|
|
28
|
+
-- ============================================
|
|
29
|
+
-- TABLE
|
|
30
|
+
-- ============================================
|
|
31
|
+
DROP TABLE IF EXISTS public."login_events" CASCADE;
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS public."login_events" (
|
|
34
|
+
-- Primary Key (TEXT not UUID — Better Auth convention)
|
|
35
|
+
id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
|
|
36
|
+
|
|
37
|
+
-- Relational Fields
|
|
38
|
+
"userId" TEXT NOT NULL REFERENCES public."users"(id) ON DELETE CASCADE,
|
|
39
|
+
|
|
40
|
+
-- Entity-specific fields
|
|
41
|
+
"ipAddress" TEXT,
|
|
42
|
+
"userAgent" TEXT,
|
|
43
|
+
"deviceFingerprint" TEXT NOT NULL,
|
|
44
|
+
city TEXT,
|
|
45
|
+
country TEXT,
|
|
46
|
+
"isNew" BOOLEAN NOT NULL DEFAULT false,
|
|
47
|
+
|
|
48
|
+
-- System fields (append-only: created only, never updated)
|
|
49
|
+
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
COMMENT ON TABLE public."login_events" IS 'Per-user login/device history backing built-in new-device security email notifications (issue #75).';
|
|
53
|
+
COMMENT ON COLUMN public."login_events"."userId" IS 'Owner of the login event.';
|
|
54
|
+
COMMENT ON COLUMN public."login_events"."ipAddress" IS 'Best-effort client IP captured at sign-in (may be a proxy address).';
|
|
55
|
+
COMMENT ON COLUMN public."login_events"."userAgent" IS 'Raw User-Agent header captured at sign-in.';
|
|
56
|
+
COMMENT ON COLUMN public."login_events"."deviceFingerprint" IS 'sha256(normalizedUA | ip/24). Stable across patch-version UA churn and within a /24 network so routine re-logins from the same device do not re-alert.';
|
|
57
|
+
COMMENT ON COLUMN public."login_events".city IS 'Reserved for future geolocation enrichment (nullable, unused today).';
|
|
58
|
+
COMMENT ON COLUMN public."login_events".country IS 'Reserved for future geolocation enrichment (nullable, unused today).';
|
|
59
|
+
COMMENT ON COLUMN public."login_events"."isNew" IS 'True when this (userId, deviceFingerprint) pair had not been seen before this row → a new-device email was queued.';
|
|
60
|
+
|
|
61
|
+
-- ============================================
|
|
62
|
+
-- INDEXES
|
|
63
|
+
-- ============================================
|
|
64
|
+
-- Most-recent-first history lookups per user (login-history UI, TTL checks).
|
|
65
|
+
CREATE INDEX IF NOT EXISTS idx_login_events_user_created
|
|
66
|
+
ON public."login_events" ("userId", "createdAt" DESC);
|
|
67
|
+
|
|
68
|
+
-- "Have we seen this fingerprint for this user?" — the new-device decision.
|
|
69
|
+
CREATE INDEX IF NOT EXISTS idx_login_events_user_fingerprint
|
|
70
|
+
ON public."login_events" ("userId", "deviceFingerprint");
|
|
71
|
+
|
|
72
|
+
-- ============================================
|
|
73
|
+
-- RLS
|
|
74
|
+
-- ============================================
|
|
75
|
+
-- A user may read ONLY their own login events; superadmin/developer bypass via
|
|
76
|
+
-- can_bypass_rls() (defined in 001). Writes are service-only: the dispatcher
|
|
77
|
+
-- inserts with no user GUC (service/bypass pool), matching how session/account
|
|
78
|
+
-- rows are written in 002. Mirrors the "self read + service write" pattern.
|
|
79
|
+
ALTER TABLE public."login_events" ENABLE ROW LEVEL SECURITY;
|
|
80
|
+
|
|
81
|
+
DROP POLICY IF EXISTS "Login events self read" ON public."login_events";
|
|
82
|
+
DROP POLICY IF EXISTS "Login events service write" ON public."login_events";
|
|
83
|
+
|
|
84
|
+
CREATE POLICY "Login events self read" ON public."login_events"
|
|
85
|
+
FOR SELECT TO authenticated
|
|
86
|
+
USING (public.can_bypass_rls() OR "userId" = public.get_auth_user_id());
|
|
87
|
+
|
|
88
|
+
CREATE POLICY "Login events service write" ON public."login_events"
|
|
89
|
+
FOR ALL TO authenticated
|
|
90
|
+
USING (public.can_bypass_rls())
|
|
91
|
+
WITH CHECK (public.can_bypass_rls());
|