@podosoft/podokit 0.3.0 → 0.5.0
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/README.md +2 -2
- package/dist/add.d.ts +2 -0
- package/dist/add.js +5 -1
- package/dist/assemble.d.ts +14 -0
- package/dist/assemble.js +60 -0
- package/dist/create.d.ts +2 -0
- package/dist/create.js +7 -0
- package/dist/eject.d.ts +11 -0
- package/dist/eject.js +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +122 -1
- package/dist/inspect.d.ts +43 -0
- package/dist/inspect.js +82 -0
- package/dist/lockfile.d.ts +80 -0
- package/dist/lockfile.js +193 -0
- package/dist/templates/base/dot-env.example +11 -6
- package/dist/templates/fullstack-nest-svelte/README.md +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/package.json +21 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/src/app.module.ts +6 -3
- package/dist/templates/fullstack-nest-svelte/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/fullstack-nest-svelte/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/src/main.ts +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/api/tsconfig.json +14 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/package.json +4 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-content.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/select/select-item.svelte +1 -1
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/lib/server/backend-proxy.ts +44 -5
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/+page.svelte +8 -3
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/fullstack-nest-svelte/dot-env.example +10 -5
- package/dist/templates/fullstack-nest-svelte/dot-gitignore +5 -0
- package/dist/templates/fullstack-nest-svelte/infra/docker/docker-compose.yml +37 -0
- package/dist/templates/fullstack-nest-svelte/infra/docker/sms-sink.mjs +40 -0
- package/dist/templates/fullstack-nest-svelte/package.json +11 -3
- package/dist/templates/fullstack-nest-svelte/tests/README.md +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/fullstack-nest-svelte/tests/package.json +15 -0
- package/dist/templates/fullstack-nest-svelte/tests/playwright.config.ts +25 -0
- package/dist/templates/fullstack-nest-svelte/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/app.d.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/hooks.server.ts +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/app-sidebar.svelte +65 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/data-table.svelte +149 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/language-switch.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/sidebar-user-menu.svelte +44 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-pagination.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/table-toolbar.svelte +93 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/theme-toggle.svelte +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-content.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/collapsible.svelte +11 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/collapsible/index.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/index.ts +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-content.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-ellipsis.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-item.svelte +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-link.svelte +42 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-next.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-prev-button.svelte +31 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination-previous.svelte +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/pagination/pagination.svelte +28 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/index.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-close.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-content.svelte +55 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-description.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-footer.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-header.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-overlay.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-portal.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-title.svelte +17 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet-trigger.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sheet/sheet.svelte +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/constants.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/context.svelte.ts +81 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/index.ts +75 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-content.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-footer.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-action.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-content.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group-label.svelte +33 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-group.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-header.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-input.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-inset.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-action.svelte +37 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-badge.svelte +24 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-button.svelte +102 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-skeleton.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-button.svelte +39 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub-item.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu-sub.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-menu.svelte +21 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-provider.svelte +53 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-rail.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-separator.svelte +19 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar-trigger.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/ui/sidebar/sidebar.svelte +108 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/components/user-avatar.svelte +12 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/hooks/is-mobile.svelte.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/index.ts +27 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/i18n/messages.ts +575 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/lib/server/guards.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.server.ts +30 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/+layout.svelte +50 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/accept-invitation/[id]/+page.svelte +36 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/+page.svelte +45 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.server.ts +5 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/account/+page.svelte +685 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.server.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/audit/+page.svelte +123 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.server.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/organizations/+page.svelte +264 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.server.ts +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/sessions/+page.svelte +156 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.server.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/settings/+page.svelte +322 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.server.ts +7 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(app)/admin/users/+page.svelte +552 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/+layout.svelte +13 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/forgot-password/+page.svelte +61 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.server.ts +14 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/login/+page.svelte +181 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/reset-password/+page.svelte +51 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/signup/+page.svelte +76 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/(auth)/verify-email/+page.svelte +63 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.server.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+layout.svelte +20 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/+page.svelte +22 -0
- package/dist/templates/modules/admin-dashboard/files/apps/web/src/routes/oauth2/consent/+page.svelte +54 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/audit.api.spec.ts +97 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth-config.api.spec.ts +80 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/auth.api.spec.ts +306 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/email.api.spec.ts +120 -0
- package/dist/templates/modules/admin-dashboard/files/tests/api/users-admin.api.spec.ts +134 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/accounts.ts +6 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/hydration.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/mailpit.ts +49 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/sms.ts +35 -0
- package/dist/templates/modules/admin-dashboard/files/tests/helpers/totp.ts +9 -0
- package/dist/templates/modules/admin-dashboard/files/tests/playwright.config.ts +26 -0
- package/dist/templates/modules/admin-dashboard/files/tests/seed.setup.ts +40 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/account.ui.spec.ts +126 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/audit.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/auth.ui.spec.ts +45 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/dashboard.ui.spec.ts +25 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/data-table.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email-admin.ui.spec.ts +60 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/email.ui.spec.ts +42 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/guards.ui.spec.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/i18n.ui.spec.ts +13 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/oidc.ui.spec.ts +57 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/organizations.ui.spec.ts +52 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/passkey.ui.spec.ts +36 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sessions.ui.spec.ts +22 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/settings.ui.spec.ts +34 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/sidebar.ui.spec.ts +16 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/table-toolbar.ui.spec.ts +32 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users-crud.ui.spec.ts +145 -0
- package/dist/templates/modules/admin-dashboard/files/tests/ui/users.ui.spec.ts +51 -0
- package/dist/templates/modules/admin-dashboard/module.manifest.json +47 -0
- package/dist/templates/modules/api-key-auth/module.manifest.json +3 -3
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/README.md +59 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-enabled.ts +16 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-events.ts +34 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-hook.ts +74 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit-log.entity.ts +29 -8
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.controller.ts +7 -2
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.decorator.ts +23 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.interceptor.ts +38 -21
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.module.ts +2 -1
- package/dist/templates/modules/audit-log/files/apps/api/src/audit/audit.service.ts +36 -0
- package/dist/templates/modules/audit-log/files/apps/api/src/migrations/1720300000000-InitAuditLogs.ts +10 -4
- package/dist/templates/modules/audit-log/module.manifest.json +37 -8
- package/dist/templates/modules/auth/files/apps/api/src/account/account.controller.ts +85 -3
- package/dist/templates/modules/auth/files/apps/api/src/account/account.module.ts +2 -1
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth-provider.ts +73 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/auth.ts +199 -31
- package/dist/templates/modules/auth/files/apps/api/src/auth/db.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/feature-gate.ts +56 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/org-permissions.ts +22 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth/permissions.ts +23 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.controller.ts +33 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.entity.ts +26 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.module.ts +12 -0
- package/dist/templates/modules/auth/files/apps/api/src/auth-config/auth-config.service.ts +118 -0
- package/dist/templates/modules/auth/files/apps/api/src/mail/mailer.ts +65 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720400000000-InitAppSettings.ts +38 -0
- package/dist/templates/modules/auth/files/apps/api/src/migrations/1720500000000-InitAuthConfig.ts +27 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/app-setting.entity.ts +16 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/flag-defaults.ts +25 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.module.ts +11 -0
- package/dist/templates/modules/auth/files/apps/api/src/settings/settings.service.ts +57 -0
- package/dist/templates/modules/auth/files/apps/api/src/sms/sms.ts +23 -0
- package/dist/templates/modules/auth/module.manifest.json +54 -20
- package/dist/templates/modules/bullmq/files/apps/api/src/jobs/worker.module.ts +6 -3
- package/dist/templates/modules/bullmq/module.manifest.json +4 -4
- package/dist/templates/modules/file-upload/module.manifest.json +3 -3
- package/dist/templates/modules/job-progress/module.manifest.json +9 -9
- package/dist/templates/modules/logging/module.manifest.json +3 -3
- package/dist/templates/modules/object-storage-s3/files/infra/docker/minio.compose.yml +5 -3
- package/dist/templates/modules/object-storage-s3/module.manifest.json +5 -5
- package/dist/templates/modules/rate-limit/module.manifest.json +2 -2
- package/dist/templates/modules/redis/module.manifest.json +4 -4
- package/dist/templates/modules/sse/module.manifest.json +4 -4
- package/dist/templates/todo/README.md +2 -2
- package/dist/templates/todo/apps/api/package.json +21 -2
- package/dist/templates/todo/apps/api/src/app.module.ts +6 -3
- package/dist/templates/todo/apps/api/src/common/app-exception.ts +4 -12
- package/dist/templates/todo/apps/api/src/config/env.validation.ts +1 -1
- package/dist/templates/todo/apps/api/src/main.ts +1 -1
- package/dist/templates/todo/apps/api/tsconfig.json +14 -5
- package/dist/templates/todo/apps/web/package.json +4 -3
- package/dist/templates/todo/apps/web/src/lib/api.ts +5 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/index.ts +7 -0
- package/dist/templates/todo/apps/web/src/lib/components/ui/switch/switch.svelte +29 -0
- package/dist/templates/todo/apps/web/src/lib/server/api.ts +19 -0
- package/dist/templates/todo/apps/web/src/lib/server/backend-proxy.ts +44 -5
- package/dist/templates/todo/apps/web/src/routes/+page.svelte +19 -18
- package/dist/templates/todo/apps/web/src/routes/api/[...path]/+server.ts +12 -0
- package/dist/templates/todo/apps/web/src/routes/api/auth/[...all]/+server.ts +12 -0
- package/dist/templates/todo/dot-env.example +10 -5
- package/dist/templates/todo/dot-gitignore +5 -0
- package/dist/templates/todo/infra/docker/docker-compose.yml +4 -0
- package/dist/templates/todo/package.json +11 -3
- package/dist/templates/todo/tests/README.md +15 -0
- package/dist/templates/todo/tests/api/health.api.spec.ts +14 -0
- package/dist/templates/todo/tests/package.json +15 -0
- package/dist/templates/todo/tests/playwright.config.ts +25 -0
- package/dist/templates/todo/tests/ui/smoke.ui.spec.ts +8 -0
- package/dist/update.d.ts +49 -0
- package/dist/update.js +158 -0
- package/package.json +2 -2
- package/dist/templates/fullstack-nest-svelte/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/fullstack-nest-svelte/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/api/test/health.e2e-spec.ts +0 -23
- package/dist/templates/todo/apps/web/src/routes/api/health/+server.ts +0 -12
- package/dist/templates/todo/apps/web/src/routes/api/todos/+server.ts +0 -24
- package/dist/templates/todo/apps/web/src/routes/api/todos/[id]/+server.ts +0 -24
|
@@ -1,25 +1,46 @@
|
|
|
1
|
-
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
1
|
+
import { Column, CreateDateColumn, Entity, Index, PrimaryGeneratedColumn } from "typeorm";
|
|
2
2
|
|
|
3
|
+
// An append-only audit trail. Fields follow the actor / action / target model:
|
|
4
|
+
// who did what, to which resource, when — with human-readable labels stored
|
|
5
|
+
// inline so entries stay meaningful even after users are renamed or deleted.
|
|
3
6
|
@Entity("audit_logs")
|
|
4
7
|
export class AuditLog {
|
|
5
8
|
@PrimaryGeneratedColumn("uuid")
|
|
6
9
|
id!: string;
|
|
7
10
|
|
|
11
|
+
// A stable, semantic action code, e.g. "user.create", "auth.login".
|
|
12
|
+
@Index()
|
|
13
|
+
@Column({ type: "varchar", length: 128 })
|
|
14
|
+
action!: string;
|
|
15
|
+
|
|
16
|
+
// Who performed it (denormalized display fields + id).
|
|
8
17
|
@Column({ type: "varchar", nullable: true })
|
|
9
|
-
|
|
18
|
+
actorId!: string | null;
|
|
10
19
|
|
|
11
|
-
@Column({ type: "varchar",
|
|
12
|
-
|
|
20
|
+
@Column({ type: "varchar", nullable: true })
|
|
21
|
+
actorName!: string | null;
|
|
13
22
|
|
|
14
|
-
@Column({ type: "varchar",
|
|
15
|
-
|
|
23
|
+
@Column({ type: "varchar", nullable: true })
|
|
24
|
+
actorEmail!: string | null;
|
|
16
25
|
|
|
17
|
-
|
|
18
|
-
|
|
26
|
+
// What it acted on (optional).
|
|
27
|
+
@Column({ type: "varchar", nullable: true })
|
|
28
|
+
targetType!: string | null;
|
|
29
|
+
|
|
30
|
+
@Column({ type: "varchar", nullable: true })
|
|
31
|
+
targetId!: string | null;
|
|
32
|
+
|
|
33
|
+
@Column({ type: "varchar", nullable: true })
|
|
34
|
+
targetLabel!: string | null;
|
|
19
35
|
|
|
20
36
|
@Column({ type: "varchar", nullable: true })
|
|
21
37
|
ip!: string | null;
|
|
22
38
|
|
|
39
|
+
// Free-form context for custom events.
|
|
40
|
+
@Column({ type: "jsonb", nullable: true })
|
|
41
|
+
metadata!: Record<string, unknown> | null;
|
|
42
|
+
|
|
43
|
+
@Index()
|
|
23
44
|
@CreateDateColumn({ type: "timestamptz" })
|
|
24
45
|
createdAt!: Date;
|
|
25
46
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Controller, Get } from "@nestjs/common";
|
|
1
|
+
import { Controller, ForbiddenException, Get } from "@nestjs/common";
|
|
2
2
|
import { InjectRepository } from "@nestjs/typeorm";
|
|
3
|
+
import { Session, type UserSession } from "@thallesp/nestjs-better-auth";
|
|
3
4
|
import { ApiTags } from "@nestjs/swagger";
|
|
4
5
|
import { Repository } from "typeorm";
|
|
5
6
|
import { AuditLog } from "./audit-log.entity";
|
|
@@ -9,8 +10,12 @@ import { AuditLog } from "./audit-log.entity";
|
|
|
9
10
|
export class AuditController {
|
|
10
11
|
constructor(@InjectRepository(AuditLog) private readonly logs: Repository<AuditLog>) {}
|
|
11
12
|
|
|
13
|
+
// Audit entries are sensitive — admins only.
|
|
12
14
|
@Get()
|
|
13
|
-
recent(): Promise<AuditLog[]> {
|
|
15
|
+
recent(@Session() session: UserSession): Promise<AuditLog[]> {
|
|
16
|
+
if ((session.user as { role?: string | null }).role !== "admin") {
|
|
17
|
+
throw new ForbiddenException("Admins only");
|
|
18
|
+
}
|
|
14
19
|
return this.logs.find({ order: { createdAt: "DESC" }, take: 50 });
|
|
15
20
|
}
|
|
16
21
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SetMetadata } from "@nestjs/common";
|
|
2
|
+
import type { Request } from "express";
|
|
3
|
+
|
|
4
|
+
export const AUDIT_KEY = "podokit:audit";
|
|
5
|
+
|
|
6
|
+
// Optionally derive the target (and extra metadata) from the request + handler
|
|
7
|
+
// result, e.g. (req, result) => ({ type: "todo", id: result.id, label: result.title }).
|
|
8
|
+
export type AuditTarget = {
|
|
9
|
+
type?: string;
|
|
10
|
+
id?: string;
|
|
11
|
+
label?: string;
|
|
12
|
+
metadata?: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
export type AuditMeta = {
|
|
15
|
+
action: string;
|
|
16
|
+
resolve?: (req: Request, result: unknown) => AuditTarget | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Mark a controller handler to be audited under a semantic action code:
|
|
20
|
+
// @Audit("todo.create", (_req, todo) => ({ type: "todo", id: todo.id, label: todo.title }))
|
|
21
|
+
export function Audit(action: string, resolve?: AuditMeta["resolve"]): MethodDecorator {
|
|
22
|
+
return SetMetadata(AUDIT_KEY, { action, resolve } satisfies AuditMeta);
|
|
23
|
+
}
|
|
@@ -1,39 +1,56 @@
|
|
|
1
1
|
import { type CallHandler, type ExecutionContext, Injectable, type NestInterceptor } from "@nestjs/common";
|
|
2
|
-
import {
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
3
|
import { fromNodeHeaders } from "better-auth/node";
|
|
4
|
-
import type { Request
|
|
4
|
+
import type { Request } from "express";
|
|
5
5
|
import { type Observable, tap } from "rxjs";
|
|
6
|
-
import { Repository } from "typeorm";
|
|
7
6
|
import { auth } from "../auth/auth";
|
|
8
|
-
import {
|
|
7
|
+
import { AUDIT_KEY, type AuditMeta } from "./audit.decorator";
|
|
8
|
+
import { AuditService } from "./audit.service";
|
|
9
|
+
import { auditEnabled } from "./audit-enabled";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
11
|
+
// Records handlers explicitly marked with @Audit(...). Nothing is logged unless
|
|
12
|
+
// you opt a route in, so the trail stays meaningful (semantic actions, not raw
|
|
13
|
+
// HTTP). Auth/admin actions are recorded separately by the better-auth hook.
|
|
13
14
|
@Injectable()
|
|
14
15
|
export class AuditInterceptor implements NestInterceptor {
|
|
15
|
-
constructor(
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly reflector: Reflector,
|
|
18
|
+
private readonly audit: AuditService,
|
|
19
|
+
) {}
|
|
16
20
|
|
|
17
21
|
intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
|
|
22
|
+
const meta = this.reflector.get<AuditMeta | undefined>(AUDIT_KEY, context.getHandler());
|
|
23
|
+
if (!meta) return next.handle();
|
|
18
24
|
const req = context.switchToHttp().getRequest<Request>();
|
|
19
|
-
|
|
20
|
-
if (!AUDITED_METHODS.has(req.method) || path.startsWith("/api/auth")) {
|
|
21
|
-
return next.handle();
|
|
22
|
-
}
|
|
23
|
-
const res = context.switchToHttp().getResponse<Response>();
|
|
24
|
-
return next.handle().pipe(tap(() => void this.record(req, res, path)));
|
|
25
|
+
return next.handle().pipe(tap((result) => void this.log(meta, req, result)));
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
private async
|
|
28
|
-
|
|
28
|
+
private async log(meta: AuditMeta, req: Request, result: unknown): Promise<void> {
|
|
29
|
+
if (!(await auditEnabled())) return;
|
|
30
|
+
let actorId: string | null = null;
|
|
31
|
+
let actorName: string | null = null;
|
|
32
|
+
let actorEmail: string | null = null;
|
|
29
33
|
try {
|
|
30
34
|
const session = await auth.api.getSession({ headers: fromNodeHeaders(req.headers) });
|
|
31
|
-
|
|
35
|
+
if (session?.user) {
|
|
36
|
+
actorId = session.user.id;
|
|
37
|
+
actorName = session.user.name ?? null;
|
|
38
|
+
actorEmail = session.user.email ?? null;
|
|
39
|
+
}
|
|
32
40
|
} catch {
|
|
33
|
-
|
|
41
|
+
/* unauthenticated */
|
|
34
42
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
43
|
+
const target = meta.resolve?.(req, result);
|
|
44
|
+
await this.audit.record({
|
|
45
|
+
action: meta.action,
|
|
46
|
+
actorId,
|
|
47
|
+
actorName,
|
|
48
|
+
actorEmail,
|
|
49
|
+
targetType: target?.type ?? null,
|
|
50
|
+
targetId: target?.id ?? null,
|
|
51
|
+
targetLabel: target?.label ?? null,
|
|
52
|
+
ip: req.ip ?? null,
|
|
53
|
+
metadata: target?.metadata ?? null,
|
|
54
|
+
});
|
|
38
55
|
}
|
|
39
56
|
}
|
|
@@ -3,11 +3,12 @@ import { APP_INTERCEPTOR } from "@nestjs/core";
|
|
|
3
3
|
import { TypeOrmModule } from "@nestjs/typeorm";
|
|
4
4
|
import { AuditController } from "./audit.controller";
|
|
5
5
|
import { AuditInterceptor } from "./audit.interceptor";
|
|
6
|
+
import { AuditService } from "./audit.service";
|
|
6
7
|
import { AuditLog } from "./audit-log.entity";
|
|
7
8
|
|
|
8
9
|
@Module({
|
|
9
10
|
imports: [TypeOrmModule.forFeature([AuditLog])],
|
|
10
11
|
controllers: [AuditController],
|
|
11
|
-
providers: [{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
|
|
12
|
+
providers: [AuditService, { provide: APP_INTERCEPTOR, useClass: AuditInterceptor }],
|
|
12
13
|
})
|
|
13
14
|
export class AuditModule {}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Injectable, type OnModuleInit } from "@nestjs/common";
|
|
2
|
+
import { InjectRepository } from "@nestjs/typeorm";
|
|
3
|
+
import { Repository } from "typeorm";
|
|
4
|
+
import { AuditLog } from "./audit-log.entity";
|
|
5
|
+
import { setAuditRecorder, type AuditEntry } from "./audit-events";
|
|
6
|
+
|
|
7
|
+
// The one place audit entries are written. Inject it to record from Nest code,
|
|
8
|
+
// or call recordAudit() from anywhere else — both end up here.
|
|
9
|
+
@Injectable()
|
|
10
|
+
export class AuditService implements OnModuleInit {
|
|
11
|
+
constructor(@InjectRepository(AuditLog) private readonly logs: Repository<AuditLog>) {}
|
|
12
|
+
|
|
13
|
+
onModuleInit(): void {
|
|
14
|
+
setAuditRecorder((entry) => this.record(entry));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async record(entry: AuditEntry): Promise<void> {
|
|
18
|
+
try {
|
|
19
|
+
await this.logs.save(
|
|
20
|
+
this.logs.create({
|
|
21
|
+
action: entry.action,
|
|
22
|
+
actorId: entry.actorId ?? null,
|
|
23
|
+
actorName: entry.actorName ?? null,
|
|
24
|
+
actorEmail: entry.actorEmail ?? null,
|
|
25
|
+
targetType: entry.targetType ?? null,
|
|
26
|
+
targetId: entry.targetId ?? null,
|
|
27
|
+
targetLabel: entry.targetLabel ?? null,
|
|
28
|
+
ip: entry.ip ?? null,
|
|
29
|
+
metadata: entry.metadata ?? null,
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
// An audit write must never break the request it describes.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -8,15 +8,21 @@ export class InitAuditLogs1720300000000 implements MigrationInterface {
|
|
|
8
8
|
await queryRunner.query(`
|
|
9
9
|
CREATE TABLE "audit_logs" (
|
|
10
10
|
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
11
|
+
"action" character varying(128) NOT NULL,
|
|
12
|
+
"actorId" character varying,
|
|
13
|
+
"actorName" character varying,
|
|
14
|
+
"actorEmail" character varying,
|
|
15
|
+
"targetType" character varying,
|
|
16
|
+
"targetId" character varying,
|
|
17
|
+
"targetLabel" character varying,
|
|
15
18
|
"ip" character varying,
|
|
19
|
+
"metadata" jsonb,
|
|
16
20
|
"createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
|
|
17
21
|
CONSTRAINT "PK_audit_logs_id" PRIMARY KEY ("id")
|
|
18
22
|
)
|
|
19
23
|
`);
|
|
24
|
+
await queryRunner.query(`CREATE INDEX "IDX_audit_logs_action" ON "audit_logs" ("action")`);
|
|
25
|
+
await queryRunner.query(`CREATE INDEX "IDX_audit_logs_createdAt" ON "audit_logs" ("createdAt")`);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
async down(queryRunner: QueryRunner): Promise<void> {
|
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audit-log",
|
|
3
|
-
"description": "Audit logging:
|
|
4
|
-
"requires": [
|
|
3
|
+
"description": "Audit logging: records who did each mutating request (who/method/path/status) to an audit_logs table \u2014 a NestJS interceptor for your own API routes plus a better-auth hook for auth/admin actions (login, ban, role changes, ...). Requires auth.",
|
|
4
|
+
"requires": [
|
|
5
|
+
"auth"
|
|
6
|
+
],
|
|
5
7
|
"targetApp": "api",
|
|
6
|
-
"env": [
|
|
8
|
+
"env": [
|
|
9
|
+
"# audit-log: on/off is managed on the admin Settings page (stored in the DB,",
|
|
10
|
+
"# applied live, no restart). This env var is the fallback default used until an",
|
|
11
|
+
"# admin sets it \u2014 leave it 'true' so the trail is on out of the box; the DB value",
|
|
12
|
+
"# (Settings page) overrides it once set.",
|
|
13
|
+
"AUDIT_LOG_ENABLED=true"
|
|
14
|
+
],
|
|
7
15
|
"inject": [
|
|
8
|
-
{
|
|
9
|
-
|
|
16
|
+
{
|
|
17
|
+
"file": "apps/api/src/app.module.ts",
|
|
18
|
+
"marker": "// podokit:end:imports",
|
|
19
|
+
"text": "import { AuditModule } from \"./audit/audit.module\";"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"file": "apps/api/src/app.module.ts",
|
|
23
|
+
"marker": "// podokit:end:module-imports",
|
|
24
|
+
"text": "AuditModule,"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"file": "apps/api/src/auth/auth.ts",
|
|
28
|
+
"marker": "// podokit:end:auth-imports",
|
|
29
|
+
"text": "import { auditAfterHook } from \"../audit/audit-hook\";"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"file": "apps/api/src/auth/auth.ts",
|
|
33
|
+
"marker": "// podokit:end:auth-hooks",
|
|
34
|
+
"text": "hooks.after = auditAfterHook;"
|
|
35
|
+
}
|
|
10
36
|
],
|
|
11
37
|
"instructions": [
|
|
12
38
|
"Run migrations to create the audit_logs table: npm run migration:run -w <app>-api",
|
|
13
|
-
"
|
|
14
|
-
"List recent entries (
|
|
15
|
-
]
|
|
39
|
+
"Auth/admin actions (login, ban, role changes, session revocation, user CRUD) are recorded via a better-auth hook; your own mutating NestJS routes are recorded by the interceptor.",
|
|
40
|
+
"List recent entries (admin session required): curl -b cookies.txt localhost:5002/audit-logs"
|
|
41
|
+
],
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@podosoft/podokit-auth": "^0.1.0"
|
|
44
|
+
}
|
|
16
45
|
}
|
|
@@ -1,13 +1,95 @@
|
|
|
1
|
-
import { Controller, Get } from "@nestjs/common";
|
|
2
|
-
import {
|
|
1
|
+
import { Body, Controller, ForbiddenException, Get, Post, Put, Req } from "@nestjs/common";
|
|
2
|
+
import type { Request } from "express";
|
|
3
|
+
import { fromNodeHeaders } from "better-auth/node";
|
|
4
|
+
import { Public, Session, type UserSession } from "@thallesp/nestjs-better-auth";
|
|
3
5
|
import { ApiTags } from "@nestjs/swagger";
|
|
6
|
+
import { FEATURE_FLAGS, SettingsService, type FeatureFlag } from "../settings/settings.service";
|
|
7
|
+
import { ROLE_NAMES } from "../auth/permissions";
|
|
8
|
+
import { auth } from "../auth/auth";
|
|
9
|
+
import { pool } from "../auth/db";
|
|
10
|
+
import { createConfigStore } from "@podosoft/podokit-auth";
|
|
11
|
+
import type { Capabilities } from "@podosoft/podokit-contracts";
|
|
12
|
+
|
|
13
|
+
// Reads OAuth/server-toggle state (DB-first, env fallback) for capabilities.
|
|
14
|
+
const configStore = createConfigStore(pool);
|
|
15
|
+
|
|
16
|
+
function isAdmin(session: UserSession): boolean {
|
|
17
|
+
const role = session.user?.role;
|
|
18
|
+
const roles = Array.isArray(role) ? role : (role ?? "").split(",").map((r: string) => r.trim());
|
|
19
|
+
return roles.includes("admin");
|
|
20
|
+
}
|
|
4
21
|
|
|
5
|
-
// Protected by the global AuthGuard. Use @Session() to read the current user.
|
|
6
22
|
@ApiTags("account")
|
|
7
23
|
@Controller("account")
|
|
8
24
|
export class AccountController {
|
|
25
|
+
constructor(private readonly settings: SettingsService) {}
|
|
26
|
+
|
|
9
27
|
@Get("me")
|
|
10
28
|
me(@Session() session: UserSession) {
|
|
11
29
|
return session.user;
|
|
12
30
|
}
|
|
31
|
+
|
|
32
|
+
// Which optional auth features are enabled, so the UI can show/hide sections.
|
|
33
|
+
// Public so the login page (unauthenticated) can offer available sign-in methods.
|
|
34
|
+
@Public()
|
|
35
|
+
@Get("capabilities")
|
|
36
|
+
async capabilities(): Promise<Capabilities> {
|
|
37
|
+
const flags = this.settings.flags();
|
|
38
|
+
// OAuth providers + server-enforced toggles are DB-backed (env fallback) and
|
|
39
|
+
// applied live, so read them from the config store rather than process.env.
|
|
40
|
+
const snapshot = await configStore.capabilitiesSnapshot();
|
|
41
|
+
return {
|
|
42
|
+
twoFactor: flags.twoFactor,
|
|
43
|
+
magicLink: flags.magicLink,
|
|
44
|
+
emailOtp: flags.emailOtp,
|
|
45
|
+
username: flags.username,
|
|
46
|
+
multiSession: flags.multiSession,
|
|
47
|
+
phoneNumber: flags.phoneNumber,
|
|
48
|
+
apiKey: flags.apiKey,
|
|
49
|
+
passkey: flags.passkey,
|
|
50
|
+
organization: flags.organization,
|
|
51
|
+
oidcProvider: flags.oidcProvider,
|
|
52
|
+
providers: snapshot.providers,
|
|
53
|
+
deleteAccount: snapshot.allowDelete,
|
|
54
|
+
emailVerification: snapshot.requireEmailVerification,
|
|
55
|
+
passwordBreachCheck: snapshot.passwordBreachCheck,
|
|
56
|
+
auditLog: snapshot.auditLog,
|
|
57
|
+
roles: ROLE_NAMES,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Admin-only: toggle feature flags. Stored in the DB and reflected immediately in
|
|
62
|
+
// capabilities (UI). Server-enforced behaviours (email verification, breach check)
|
|
63
|
+
// are read by the auth server at startup, so those take effect on the next restart.
|
|
64
|
+
@Put("settings")
|
|
65
|
+
async updateSettings(
|
|
66
|
+
@Session() session: UserSession,
|
|
67
|
+
@Body() body: Partial<Record<FeatureFlag, boolean>>,
|
|
68
|
+
): Promise<Record<FeatureFlag, boolean>> {
|
|
69
|
+
if (!isAdmin(session)) throw new ForbiddenException("Admins only");
|
|
70
|
+
const update: Partial<Record<FeatureFlag, boolean>> = {};
|
|
71
|
+
for (const flag of FEATURE_FLAGS) {
|
|
72
|
+
if (typeof body?.[flag] === "boolean") update[flag] = body[flag];
|
|
73
|
+
}
|
|
74
|
+
return this.settings.setMany(update);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Add an existing user to an organization with a role (e.g. "manager"), so several
|
|
78
|
+
// members can hold it. better-auth's addMember is server-only; this exposes it and
|
|
79
|
+
// delegates org-level authorization to better-auth via the caller's session headers.
|
|
80
|
+
@Post("org-member")
|
|
81
|
+
async addOrgMember(
|
|
82
|
+
@Req() req: Request,
|
|
83
|
+
@Body() body: { organizationId: string; userId: string; role: string },
|
|
84
|
+
): Promise<unknown> {
|
|
85
|
+
// addMember is a server-only better-auth endpoint (not on the typed public api);
|
|
86
|
+
// it exists at runtime, so reach it through a narrow cast.
|
|
87
|
+
const api = auth.api as unknown as {
|
|
88
|
+
addMember: (opts: { body: { organizationId: string; userId: string; role: string }; headers: Headers }) => Promise<unknown>;
|
|
89
|
+
};
|
|
90
|
+
return api.addMember({
|
|
91
|
+
body: { organizationId: body.organizationId, userId: body.userId, role: body.role },
|
|
92
|
+
headers: fromNodeHeaders(req.headers),
|
|
93
|
+
});
|
|
94
|
+
}
|
|
13
95
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Module } from "@nestjs/common";
|
|
2
|
+
import { SettingsModule } from "../settings/settings.module";
|
|
2
3
|
import { AccountController } from "./account.controller";
|
|
3
4
|
|
|
4
|
-
@Module({ controllers: [AccountController] })
|
|
5
|
+
@Module({ imports: [SettingsModule], controllers: [AccountController] })
|
|
5
6
|
export class AccountModule {}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { auth as bootstrapAuth, buildAuth } from "./auth";
|
|
2
|
+
import { pool } from "./db";
|
|
3
|
+
import { createConfigStore } from "@podosoft/podokit-auth";
|
|
4
|
+
|
|
5
|
+
// Runtime auth instance that rebuilds when the DB config changes, so admin edits
|
|
6
|
+
// (OAuth credentials, SMTP, server-enforced toggles) apply WITHOUT a restart.
|
|
7
|
+
//
|
|
8
|
+
// @thallesp/nestjs-better-auth reads `auth.handler` exactly once (via toNodeHandler)
|
|
9
|
+
// and `auth.api` per request. We hand it a Proxy: `.handler` is a STABLE delegating
|
|
10
|
+
// function (so the adapter's captured reference stays valid) that redispatches each
|
|
11
|
+
// request to the currently-built instance; every other access forwards to the
|
|
12
|
+
// current instance too. The guard/session behavior is unchanged (session tokens are
|
|
13
|
+
// signed with the stable BETTER_AUTH_SECRET; trustedOrigins is baked into every build).
|
|
14
|
+
|
|
15
|
+
const store = createConfigStore(pool);
|
|
16
|
+
const TTL_MS = 3_000;
|
|
17
|
+
|
|
18
|
+
let current = bootstrapAuth;
|
|
19
|
+
let currentVersion = "env"; // matches envAuthConfig() bootstrap
|
|
20
|
+
let checkedAt = 0;
|
|
21
|
+
let inflight: Promise<void> | null = null;
|
|
22
|
+
|
|
23
|
+
export function getAuth(): typeof bootstrapAuth {
|
|
24
|
+
return current;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Rebuild the instance if the DB config version changed. Cheap and throttled to
|
|
28
|
+
* TTL; single-flight so concurrent requests trigger at most one rebuild; keeps the
|
|
29
|
+
* last-good instance if loading/building fails, so auth never goes down. */
|
|
30
|
+
export async function refreshIfStale(): Promise<void> {
|
|
31
|
+
if (checkedAt !== 0 && Date.now() - checkedAt < TTL_MS) return;
|
|
32
|
+
checkedAt = Date.now();
|
|
33
|
+
let latest: string;
|
|
34
|
+
try {
|
|
35
|
+
latest = await store.currentVersion();
|
|
36
|
+
} catch {
|
|
37
|
+
return; // DB blip — keep serving the last-good instance
|
|
38
|
+
}
|
|
39
|
+
if (latest === currentVersion) return;
|
|
40
|
+
if (inflight) return inflight;
|
|
41
|
+
inflight = (async () => {
|
|
42
|
+
try {
|
|
43
|
+
const config = await store.load();
|
|
44
|
+
current = buildAuth(config);
|
|
45
|
+
currentVersion = config.version;
|
|
46
|
+
} catch (err) {
|
|
47
|
+
console.error("[auth-config] rebuild failed; keeping last-good auth instance:", err instanceof Error ? err.message : err);
|
|
48
|
+
} finally {
|
|
49
|
+
inflight = null;
|
|
50
|
+
}
|
|
51
|
+
})();
|
|
52
|
+
return inflight;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Stable handler reference the adapter captures once; redispatches per request.
|
|
56
|
+
const dynamicHandler: typeof bootstrapAuth.handler = async (request) => {
|
|
57
|
+
await refreshIfStale();
|
|
58
|
+
return current.handler(request);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const authRuntime: typeof bootstrapAuth = new Proxy(bootstrapAuth, {
|
|
62
|
+
get(_target, prop) {
|
|
63
|
+
if (prop === "handler") return dynamicHandler;
|
|
64
|
+
const value = Reflect.get(current, prop, current);
|
|
65
|
+
return typeof value === "function" ? value.bind(current) : value;
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
/** Optional boot preload so the first request doesn't pay the initial DB read. */
|
|
70
|
+
export async function primeAuth(): Promise<void> {
|
|
71
|
+
checkedAt = 0;
|
|
72
|
+
await refreshIfStale();
|
|
73
|
+
}
|