create-windy 0.2.33 → 0.3.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/dist/cli.js +173 -17
- package/package.json +1 -1
- package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +9 -0
- package/template/README.md +3 -1
- package/template/apps/agent-server/Dockerfile +1 -0
- package/template/apps/agent-server/README.md +28 -5
- package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
- package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
- package/template/apps/agent-server/pyproject.toml +1 -1
- package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
- package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
- package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
- package/template/apps/agent-server/src/southwind_agent_server/app.py +63 -3
- package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
- package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
- package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
- package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
- package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
- package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
- package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
- package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
- package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
- package/template/apps/agent-server/tests/test_api.py +16 -1
- package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
- package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
- package/template/apps/server/package.json +4 -1
- package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
- package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
- package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
- package/template/apps/server/src/agent/remote-runtime.ts +1 -0
- package/template/apps/server/src/agent/run-contracts.ts +2 -0
- package/template/apps/server/src/agent/run-facade.test.ts +52 -2
- package/template/apps/server/src/agent/run-facade.ts +38 -9
- package/template/apps/server/src/agent/tool-registry.ts +2 -6
- package/template/apps/server/src/application-services.ts +50 -0
- package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
- package/template/apps/server/src/audit/search-summary.ts +2 -6
- package/template/apps/server/src/auth/credential-restriction.ts +1 -0
- package/template/apps/server/src/auth/password.ts +30 -21
- package/template/apps/server/src/auth/routes.test.ts +44 -3
- package/template/apps/server/src/auth/routes.ts +6 -1
- package/template/apps/server/src/auth/runtime.ts +4 -1
- package/template/apps/server/src/auth/service.ts +25 -7
- package/template/apps/server/src/configuration/bootstrap.ts +20 -1
- package/template/apps/server/src/configuration/definition.test.ts +71 -0
- package/template/apps/server/src/configuration/definition.ts +242 -12
- package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
- package/template/apps/server/src/configuration/repository.ts +27 -2
- package/template/apps/server/src/configuration/routes.ts +4 -1
- package/template/apps/server/src/configuration/service.test.ts +100 -0
- package/template/apps/server/src/configuration/service.ts +50 -10
- package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
- package/template/apps/server/src/data-governance/export/service.ts +2 -6
- package/template/apps/server/src/example-modules.ts +4 -1
- package/template/apps/server/src/guards.ts +18 -5
- package/template/apps/server/src/http/request-source.test.ts +85 -0
- package/template/apps/server/src/http/request-source.ts +90 -0
- package/template/apps/server/src/index.ts +45 -45
- package/template/apps/server/src/license/routes.ts +2 -0
- package/template/apps/server/src/license/runtime-service.ts +10 -11
- package/template/apps/server/src/module-bootstrap.ts +2 -2
- package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
- package/template/apps/server/src/module-host/initialize.ts +30 -44
- package/template/apps/server/src/module-host/request-context.ts +29 -23
- package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
- package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
- package/template/apps/server/src/module-host/route-declaration.ts +45 -0
- package/template/apps/server/src/module-host/route-installer.ts +27 -3
- package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
- package/template/apps/server/src/module-host/storage-port.ts +134 -1
- package/template/apps/server/src/module-host/storage-scope.ts +89 -0
- package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
- package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
- package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
- package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
- package/template/apps/server/src/module-host/user-directory-port.ts +139 -0
- package/template/apps/server/src/module-host.ts +4 -0
- package/template/apps/server/src/notification/channel-adapter.ts +46 -0
- package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
- package/template/apps/server/src/notification/delivery-policy.ts +37 -0
- package/template/apps/server/src/notification/delivery-repository.ts +198 -0
- package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
- package/template/apps/server/src/notification/delivery-worker.ts +88 -0
- package/template/apps/server/src/notification/drizzle-management.ts +180 -0
- package/template/apps/server/src/notification/drizzle-query.ts +160 -0
- package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
- package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
- package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
- package/template/apps/server/src/notification/extension-routes.ts +111 -0
- package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
- package/template/apps/server/src/notification/memory-support.ts +72 -0
- package/template/apps/server/src/notification/preference-repository.ts +95 -0
- package/template/apps/server/src/notification/recipient-directory.ts +104 -0
- package/template/apps/server/src/notification/repository.test.ts +171 -0
- package/template/apps/server/src/notification/repository.ts +179 -32
- package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
- package/template/apps/server/src/notification/route-support.ts +225 -0
- package/template/apps/server/src/notification/routes.test.ts +182 -0
- package/template/apps/server/src/notification/routes.ts +225 -132
- package/template/apps/server/src/notification/service.test.ts +104 -0
- package/template/apps/server/src/notification/service.ts +140 -0
- package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
- package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
- package/template/apps/server/src/persistence.integration.test.ts +41 -2
- package/template/apps/server/src/route-guards.ts +1 -0
- package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
- package/template/apps/server/src/runtime-development.ts +2 -0
- package/template/apps/server/src/runtime.test.ts +23 -9
- package/template/apps/server/src/runtime.ts +39 -2
- package/template/apps/server/src/scheduler/audit.ts +2 -11
- package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
- package/template/apps/server/src/scheduler/definitions.ts +7 -0
- package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
- package/template/apps/server/src/scheduler/routes.ts +2 -7
- package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
- package/template/apps/server/src/search/opensearch-client.ts +145 -0
- package/template/apps/server/src/search/service.ts +2 -6
- package/template/apps/server/src/settings/routes.ts +15 -3
- package/template/apps/server/src/settings/runtime.ts +10 -1
- package/template/apps/server/src/storage/runtime.ts +13 -0
- package/template/apps/server/src/system/audit-query.ts +33 -4
- package/template/apps/server/src/system/audit-routes.test.ts +58 -4
- package/template/apps/server/src/system/audit-routes.ts +6 -5
- package/template/apps/server/src/system/built-in-roles.ts +1 -0
- package/template/apps/server/src/system/department-organization.ts +320 -0
- package/template/apps/server/src/system/department-routes.test.ts +207 -48
- package/template/apps/server/src/system/department-routes.ts +114 -33
- package/template/apps/server/src/system/drizzle-repository.ts +15 -7
- package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
- package/template/apps/server/src/system/drizzle-system.ts +37 -9
- package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
- package/template/apps/server/src/system/identity-route-config.ts +7 -2
- package/template/apps/server/src/system/mutation-runner.ts +23 -0
- package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
- package/template/apps/server/src/system/resource-access-response.ts +12 -6
- package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
- package/template/apps/server/src/system/route-helpers.ts +2 -5
- package/template/apps/server/src/system/route-types.ts +12 -3
- package/template/apps/server/src/system/routes-validation.test.ts +26 -0
- package/template/apps/server/src/system/routes.test.ts +10 -2
- package/template/apps/server/src/system/routes.ts +70 -36
- package/template/apps/server/src/system/seed.ts +7 -0
- package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
- package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
- package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
- package/template/apps/server/src/work-order/search-provider.ts +60 -1
- package/template/apps/server/src/work-order/service.ts +42 -10
- package/template/apps/web/src/app/error-pages.ts +9 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
- package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
- package/template/apps/web/src/components/common/FormDialog.vue +16 -2
- package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
- package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
- package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
- package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
- package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
- package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
- package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
- package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
- package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
- package/template/apps/web/src/lib/date-time.ts +22 -10
- package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
- package/template/apps/web/src/main.ts +14 -1
- package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
- package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
- package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
- package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
- package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
- package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
- package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
- package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
- package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
- package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
- package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
- package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
- package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
- package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
- package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
- package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
- package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
- package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
- package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
- package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
- package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
- package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
- package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
- package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
- package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
- package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
- package/template/apps/web/src/pages/system/resource-config.ts +12 -4
- package/template/apps/web/src/router/error-navigation.ts +50 -0
- package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
- package/template/apps/web/src/router/index.ts +71 -0
- package/template/apps/web/src/router/index.webtest.ts +29 -0
- package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
- package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
- package/template/apps/web/src/services/auth-api.ts +5 -0
- package/template/apps/web/src/services/http.ts +43 -6
- package/template/apps/web/src/services/http.webtest.ts +70 -0
- package/template/apps/web/src/services/notification-api.ts +85 -2
- package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
- package/template/apps/web/src/services/search-api.ts +39 -0
- package/template/apps/web/src/services/system-api.ts +18 -0
- package/template/apps/web/vitest.config.ts +1 -0
- package/template/docker-compose.yml +68 -0
- package/template/docs/architecture/ai-egress.md +49 -7
- package/template/docs/architecture/ai-runtime.md +34 -6
- package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
- package/template/docs/architecture/object-storage.md +22 -2
- package/template/docs/architecture/search-provider-protocol.md +38 -2
- package/template/docs/development/custom-error-pages.md +78 -0
- package/template/docs/development/custom-login-page.md +3 -3
- package/template/docs/platform/agent-runtime.md +76 -7
- package/template/docs/platform/audit-reliability.md +46 -2
- package/template/docs/platform/business-user-directory.md +82 -0
- package/template/docs/platform/module-configuration.md +13 -4
- package/template/docs/platform/notifications.md +67 -11
- package/template/docs/platform/organization-membership.md +17 -3
- package/template/docs/platform/system-crud-api.md +16 -0
- package/template/docs/platform/web-system-crud.md +6 -2
- package/template/package.json +4 -0
- package/template/packages/config/index.test.ts +45 -0
- package/template/packages/config/package.json +1 -1
- package/template/packages/config/src/platform.ts +169 -48
- package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
- package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
- package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
- package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
- package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
- package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
- package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
- package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
- package/template/packages/database/drizzle/meta/_journal.json +28 -0
- package/template/packages/database/package.json +1 -1
- package/template/packages/database/src/schema/identity.ts +7 -0
- package/template/packages/database/src/schema/notifications.ts +135 -0
- package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
- package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
- package/template/packages/modules/package.json +1 -1
- package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
- package/template/packages/modules/src/ai-operation-composition.ts +115 -0
- package/template/packages/modules/src/manifest.ts +26 -0
- package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
- package/template/packages/modules/src/system-api-permissions.ts +54 -0
- package/template/packages/modules/src/system-audit-actions.ts +15 -0
- package/template/packages/modules/src/system-features.ts +6 -1
- package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
- package/template/packages/modules/src/system-module-catalog.ts +28 -4
- package/template/packages/modules/src/system-modules.test.ts +13 -1
- package/template/packages/modules/src/system-modules.ts +12 -0
- package/template/packages/modules/src/system-permissions.ts +11 -5
- package/template/packages/server-sdk/index.ts +11 -0
- package/template/packages/server-sdk/package.json +3 -2
- package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
- package/template/packages/server-sdk/src/module-host.ts +8 -0
- package/template/packages/server-sdk/src/request-context.ts +2 -0
- package/template/packages/server-sdk/src/storage-port.ts +66 -0
- package/template/packages/server-sdk/src/tool-registration.ts +6 -0
- package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.ts +25 -0
- package/template/packages/shared/src/module-configuration.ts +5 -0
- package/template/packages/shared/src/notification.ts +92 -1
- package/template/packages/shared/src/password.ts +7 -2
- package/template/packages/shared/src/platform-settings.ts +2 -0
- package/template/packages/storage/package.json +1 -1
- package/template/packages/storage/src/upload-service.test.ts +16 -0
- package/template/packages/storage/src/upload-service.ts +5 -1
- package/template/scripts/notification-postgres-regression.ts +27 -0
|
@@ -20,6 +20,7 @@ import { DrizzleSchedulerRepository } from "./scheduler/drizzle-repository.js";
|
|
|
20
20
|
import { InMemorySchedulerRepository } from "./scheduler/repository.js";
|
|
21
21
|
import type { ScheduledTaskDefinition } from "./scheduler/types.js";
|
|
22
22
|
import type { createStorageRuntime } from "./storage/runtime.js";
|
|
23
|
+
import type { PlatformConfig } from "@southwind-ai/config";
|
|
23
24
|
import type { DurableHandlerRegistry } from "@southwind-ai/jobs";
|
|
24
25
|
import { collectActiveBlobRefs } from "./storage/blob-reference-registry.js";
|
|
25
26
|
import type { createSystemRepositories } from "./system/seed.js";
|
|
@@ -33,6 +34,17 @@ import {
|
|
|
33
34
|
} from "./workflow/effects.js";
|
|
34
35
|
import { InMemoryWorkflowRepository } from "./workflow/repository.js";
|
|
35
36
|
import { WorkflowService } from "./workflow/service.js";
|
|
37
|
+
import { NotificationChannelRegistry } from "./notification/channel-adapter.js";
|
|
38
|
+
import { DrizzleNotificationDeliveryRepository } from "./notification/delivery-repository.js";
|
|
39
|
+
import { NotificationDeliveryWorker } from "./notification/delivery-worker.js";
|
|
40
|
+
import { InMemoryNotificationDeliveryRepository } from "./notification/memory-delivery-repository.js";
|
|
41
|
+
import {
|
|
42
|
+
DrizzleNotificationPreferenceRepository,
|
|
43
|
+
InMemoryNotificationPreferenceRepository,
|
|
44
|
+
} from "./notification/preference-repository.js";
|
|
45
|
+
import { SystemNotificationRecipientDirectory } from "./notification/recipient-directory.js";
|
|
46
|
+
import { NotificationService } from "./notification/service.js";
|
|
47
|
+
import { SmtpNotificationChannelAdapter } from "./notification/smtp-adapter.js";
|
|
36
48
|
|
|
37
49
|
interface ApplicationServicesInput {
|
|
38
50
|
persistence: ReturnType<typeof createServerPersistence>;
|
|
@@ -42,6 +54,7 @@ interface ApplicationServicesInput {
|
|
|
42
54
|
storage: ReturnType<typeof createStorageRuntime>;
|
|
43
55
|
moduleTasks: ScheduledTaskDefinition[];
|
|
44
56
|
bulkRetentionDays: number;
|
|
57
|
+
mailConfig?: PlatformConfig["mail"];
|
|
45
58
|
installModuleDurableHandlers?: (registry: DurableHandlerRegistry) => void;
|
|
46
59
|
}
|
|
47
60
|
|
|
@@ -52,6 +65,32 @@ export async function createApplicationServices(
|
|
|
52
65
|
const notifications = persistence
|
|
53
66
|
? new DrizzleNotificationRepository(persistence.db)
|
|
54
67
|
: new InMemoryNotificationRepository();
|
|
68
|
+
const notificationPreferences = persistence
|
|
69
|
+
? new DrizzleNotificationPreferenceRepository(persistence.db)
|
|
70
|
+
: new InMemoryNotificationPreferenceRepository();
|
|
71
|
+
const notificationDeliveries = persistence
|
|
72
|
+
? new DrizzleNotificationDeliveryRepository(persistence.db)
|
|
73
|
+
: new InMemoryNotificationDeliveryRepository();
|
|
74
|
+
const notificationRecipients = new SystemNotificationRecipientDirectory(
|
|
75
|
+
input.systemRepositories,
|
|
76
|
+
);
|
|
77
|
+
const notificationChannels = new NotificationChannelRegistry(
|
|
78
|
+
input.mailConfig?.enabled
|
|
79
|
+
? [new SmtpNotificationChannelAdapter(input.mailConfig)]
|
|
80
|
+
: [],
|
|
81
|
+
);
|
|
82
|
+
const notificationService = new NotificationService(
|
|
83
|
+
notifications,
|
|
84
|
+
notificationPreferences,
|
|
85
|
+
notificationDeliveries,
|
|
86
|
+
notificationRecipients,
|
|
87
|
+
{ mandatoryCategories: ["安全"] },
|
|
88
|
+
);
|
|
89
|
+
const notificationDeliveryWorker = new NotificationDeliveryWorker(
|
|
90
|
+
notificationDeliveries,
|
|
91
|
+
notificationRecipients,
|
|
92
|
+
notificationChannels,
|
|
93
|
+
);
|
|
55
94
|
const deferredTimeout = new DeferredWorkflowTimeoutScheduler();
|
|
56
95
|
const workflow = new WorkflowService(
|
|
57
96
|
persistence
|
|
@@ -89,6 +128,15 @@ export async function createApplicationServices(
|
|
|
89
128
|
const result = await governedExportDispatch.runNext?.();
|
|
90
129
|
return { claimed: Boolean(result && result.status !== "idle") };
|
|
91
130
|
},
|
|
131
|
+
notificationDispatch: async () => {
|
|
132
|
+
const scheduled = await notificationService.dispatchScheduled();
|
|
133
|
+
const delivery = await notificationDeliveryWorker.runNext();
|
|
134
|
+
return {
|
|
135
|
+
claimed: scheduled.claimed + delivery.claimed > 0,
|
|
136
|
+
scheduled,
|
|
137
|
+
delivery,
|
|
138
|
+
};
|
|
139
|
+
},
|
|
92
140
|
}),
|
|
93
141
|
...input.moduleTasks,
|
|
94
142
|
];
|
|
@@ -149,6 +197,8 @@ export async function createApplicationServices(
|
|
|
149
197
|
return {
|
|
150
198
|
...services,
|
|
151
199
|
notifications,
|
|
200
|
+
notificationService,
|
|
201
|
+
notificationDeliveryWorker,
|
|
152
202
|
workflow,
|
|
153
203
|
governedExports,
|
|
154
204
|
governedExportService,
|
|
@@ -74,6 +74,7 @@ export class DrizzleAuditLogReader {
|
|
|
74
74
|
|
|
75
75
|
function auditWhere(query: AuditEventListQuery): SQL | undefined {
|
|
76
76
|
const clauses = [
|
|
77
|
+
query.q ? keywordClause(query.q) : undefined,
|
|
77
78
|
query.includeScheduler
|
|
78
79
|
? undefined
|
|
79
80
|
: ne(auditLogs.actorId, "system_scheduler"),
|
|
@@ -91,6 +92,30 @@ function auditWhere(query: AuditEventListQuery): SQL | undefined {
|
|
|
91
92
|
return clauses.length ? and(...clauses) : undefined;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
function keywordClause(query: string): SQL {
|
|
96
|
+
const pattern = `%${escapeLike(query.toLowerCase())}%`;
|
|
97
|
+
return sql`lower(concat_ws(' ',
|
|
98
|
+
${auditLogs.action},
|
|
99
|
+
${auditLogs.actorId},
|
|
100
|
+
${auditLogs.resource},
|
|
101
|
+
${auditLogs.resourceId},
|
|
102
|
+
${auditLogs.ip},
|
|
103
|
+
coalesce(${auditLogs.metadata} ->> 'actorName', ''),
|
|
104
|
+
coalesce(${auditLogs.metadata} ->> 'targetLabel', ''),
|
|
105
|
+
coalesce(${auditLogs.metadata} ->> 'message', ''),
|
|
106
|
+
coalesce(${auditLogs.metadata} ->> 'module', ''),
|
|
107
|
+
coalesce(${auditLogs.metadata} ->> 'requestId', ''),
|
|
108
|
+
coalesce(${auditLogs.metadata}::text, '')
|
|
109
|
+
)) like ${pattern} escape '\\'`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function escapeLike(value: string): string {
|
|
113
|
+
return value
|
|
114
|
+
.replaceAll("\\", "\\\\")
|
|
115
|
+
.replaceAll("%", "\\%")
|
|
116
|
+
.replaceAll("_", "\\_");
|
|
117
|
+
}
|
|
118
|
+
|
|
94
119
|
function rowToAuditEvent(row: typeof auditLogs.$inferSelect): AuditEvent {
|
|
95
120
|
const metadata = readMetadata(row.metadata);
|
|
96
121
|
const outcome = readString(metadata.outcome, "success") as AuditOutcome;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuditEvent } from "@southwind-ai/shared";
|
|
2
2
|
import { randomUUIDv7 } from "bun";
|
|
3
|
-
import type
|
|
3
|
+
import { auditActor, type RequestGuardContext } from "../guards.js";
|
|
4
4
|
|
|
5
5
|
export function searchAuditMetadata(
|
|
6
6
|
query: string,
|
|
@@ -26,11 +26,7 @@ export function searchAuditEvent(
|
|
|
26
26
|
id: randomUUIDv7(),
|
|
27
27
|
action: "data.search",
|
|
28
28
|
outcome: "success",
|
|
29
|
-
actor:
|
|
30
|
-
id: context.actor.id,
|
|
31
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
32
|
-
name: context.actor.name,
|
|
33
|
-
},
|
|
29
|
+
actor: auditActor(context),
|
|
34
30
|
target: { type: resourceName },
|
|
35
31
|
requestId: context.requestId,
|
|
36
32
|
metadata: searchAuditMetadata(query, matched),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import argon2, { argon2id } from "argon2";
|
|
2
|
-
import { PASSWORD_POLICY } from "@southwind-ai/shared";
|
|
2
|
+
import { PASSWORD_POLICY, type PasswordPolicy } from "@southwind-ai/shared";
|
|
3
3
|
|
|
4
4
|
export { PASSWORD_POLICY } from "@southwind-ai/shared";
|
|
5
5
|
|
|
@@ -15,30 +15,39 @@ export interface PasswordHasher {
|
|
|
15
15
|
verify(hash: string, password: string): Promise<boolean>;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export const argon2PasswordHasher
|
|
19
|
-
async hash(password) {
|
|
20
|
-
assertPasswordPolicy(password);
|
|
21
|
-
return argon2.hash(password, ARGON2_OPTIONS);
|
|
22
|
-
},
|
|
23
|
-
async verify(hash, password) {
|
|
24
|
-
if (!password || password.length > PASSWORD_POLICY.maxLength) {
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
return await argon2.verify(hash, password);
|
|
29
|
-
} catch {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
},
|
|
33
|
-
};
|
|
18
|
+
export const argon2PasswordHasher = createArgon2PasswordHasher();
|
|
34
19
|
|
|
35
|
-
export function
|
|
20
|
+
export function createArgon2PasswordHasher(
|
|
21
|
+
policyProvider: () => PasswordPolicy = () => PASSWORD_POLICY,
|
|
22
|
+
): PasswordHasher {
|
|
23
|
+
return {
|
|
24
|
+
async hash(password) {
|
|
25
|
+
assertPasswordPolicy(password, policyProvider());
|
|
26
|
+
return argon2.hash(password, ARGON2_OPTIONS);
|
|
27
|
+
},
|
|
28
|
+
async verify(hash, password) {
|
|
29
|
+
if (!password || password.length > 512) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
return await argon2.verify(hash, password);
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function assertPasswordPolicy(
|
|
42
|
+
password: string,
|
|
43
|
+
policy: PasswordPolicy = PASSWORD_POLICY,
|
|
44
|
+
): void {
|
|
36
45
|
if (
|
|
37
|
-
password.length <
|
|
38
|
-
password.length >
|
|
46
|
+
password.length < policy.minLength ||
|
|
47
|
+
password.length > policy.maxLength
|
|
39
48
|
) {
|
|
40
49
|
throw new Error(
|
|
41
|
-
`密码长度必须为 ${
|
|
50
|
+
`密码长度必须为 ${policy.minLength}-${policy.maxLength} 个字符`,
|
|
42
51
|
);
|
|
43
52
|
}
|
|
44
53
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeAll, describe, expect, test } from "bun:test";
|
|
2
|
-
import type { AuditEvent } from "@southwind-ai/shared";
|
|
2
|
+
import type { AuditEvent, PasswordPolicy } from "@southwind-ai/shared";
|
|
3
3
|
import { Elysia } from "elysia";
|
|
4
4
|
import { createServerRuntime } from "../runtime.js";
|
|
5
5
|
import { RepositoryActorResolver } from "./actor-resolver.js";
|
|
@@ -15,6 +15,37 @@ beforeAll(async () => {
|
|
|
15
15
|
});
|
|
16
16
|
|
|
17
17
|
describe("platform auth API", () => {
|
|
18
|
+
test("密码策略端点返回当前运行时策略且禁止缓存", async () => {
|
|
19
|
+
let policy: PasswordPolicy = { minLength: 16, maxLength: 64 };
|
|
20
|
+
const fixture = authFixture([activeUser()], () => policy);
|
|
21
|
+
|
|
22
|
+
const first = await fixture.app.handle(
|
|
23
|
+
new Request("http://localhost/api/auth/password/policy"),
|
|
24
|
+
);
|
|
25
|
+
expect(first.headers.get("cache-control")).toBe("no-store");
|
|
26
|
+
await expect(first.json()).resolves.toEqual(policy);
|
|
27
|
+
|
|
28
|
+
policy = { minLength: 20, maxLength: 80 };
|
|
29
|
+
const second = await fixture.app.handle(
|
|
30
|
+
new Request("http://localhost/api/auth/password/policy"),
|
|
31
|
+
);
|
|
32
|
+
await expect(second.json()).resolves.toEqual(policy);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("会话有效期配置即时影响后续签发会话", async () => {
|
|
36
|
+
let ttlSeconds = 60;
|
|
37
|
+
const fixture = authFixture([activeUser()], undefined, () => ttlSeconds);
|
|
38
|
+
const first = await login(fixture.app, "admin");
|
|
39
|
+
|
|
40
|
+
ttlSeconds = 120;
|
|
41
|
+
const second = await login(fixture.app, "admin");
|
|
42
|
+
|
|
43
|
+
expect(
|
|
44
|
+
new Date(second.expiresAt).getTime() -
|
|
45
|
+
new Date(first.expiresAt).getTime(),
|
|
46
|
+
).toBeGreaterThan(59_000);
|
|
47
|
+
});
|
|
48
|
+
|
|
18
49
|
test("会话探测只返回当前 Actor,不暴露 Token", async () => {
|
|
19
50
|
const fixture = authFixture([activeUser()]);
|
|
20
51
|
const anonymous = await fixture.app.handle(
|
|
@@ -278,7 +309,11 @@ describe("platform auth API", () => {
|
|
|
278
309
|
});
|
|
279
310
|
});
|
|
280
311
|
|
|
281
|
-
function authFixture(
|
|
312
|
+
function authFixture(
|
|
313
|
+
users: AuthUserRecord[],
|
|
314
|
+
passwordPolicy?: () => PasswordPolicy,
|
|
315
|
+
sessionTtlSeconds?: () => number,
|
|
316
|
+
) {
|
|
282
317
|
const repository = new InMemoryAuthRepository(users);
|
|
283
318
|
const resolver = new RepositoryActorResolver(repository, {
|
|
284
319
|
sessionCookieName: "windy.sid",
|
|
@@ -286,6 +321,8 @@ function authFixture(users: AuthUserRecord[]) {
|
|
|
286
321
|
const events: AuditEvent[] = [];
|
|
287
322
|
const runtime = createServerRuntime({}, { actorResolver: resolver });
|
|
288
323
|
const service = new AuthService(repository, {
|
|
324
|
+
passwordPolicy,
|
|
325
|
+
sessionTtlSeconds,
|
|
289
326
|
auditWriter: {
|
|
290
327
|
write(event) {
|
|
291
328
|
events.push(event);
|
|
@@ -326,7 +363,11 @@ async function login(
|
|
|
326
363
|
}),
|
|
327
364
|
);
|
|
328
365
|
expect(response.status).toBe(200);
|
|
329
|
-
return (await response.json()) as {
|
|
366
|
+
return (await response.json()) as {
|
|
367
|
+
token: string;
|
|
368
|
+
sessionId: string;
|
|
369
|
+
expiresAt: string;
|
|
370
|
+
};
|
|
330
371
|
}
|
|
331
372
|
|
|
332
373
|
function readCookieValue(header: string, name: string): string {
|
|
@@ -53,6 +53,10 @@ export function registerAuthRoutes(
|
|
|
53
53
|
registerLoginRoutes(app, authService, options);
|
|
54
54
|
registerOwnSessionRoutes(app, authService, options);
|
|
55
55
|
|
|
56
|
+
app.get("/auth/password/policy", () =>
|
|
57
|
+
json(authService.getPasswordPolicy(), 200, { "cache-control": "no-store" }),
|
|
58
|
+
);
|
|
59
|
+
|
|
56
60
|
app.post("/auth/logout", async (rawContext) => {
|
|
57
61
|
const context = rawContext as AuthRouteContext;
|
|
58
62
|
const token = readRequestSessionToken(context.request, cookieName);
|
|
@@ -112,10 +116,11 @@ export function registerAuthRoutes(
|
|
|
112
116
|
INVALID_PASSWORD: 400,
|
|
113
117
|
PASSWORD_UNCHANGED: 400,
|
|
114
118
|
} as const;
|
|
119
|
+
const policy = authService.getPasswordPolicy();
|
|
115
120
|
const messages = {
|
|
116
121
|
USER_NOT_FOUND: "当前账号不存在",
|
|
117
122
|
INVALID_CURRENT_PASSWORD: "当前密码错误",
|
|
118
|
-
INVALID_PASSWORD:
|
|
123
|
+
INVALID_PASSWORD: `新密码必须为 ${policy.minLength}-${policy.maxLength} 个字符`,
|
|
119
124
|
PASSWORD_UNCHANGED: "新密码不能与当前密码相同",
|
|
120
125
|
} as const;
|
|
121
126
|
return json(
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { AuditEventReader, AuditEventWriter } from "../runtime.js";
|
|
2
|
+
import type { PasswordPolicy } from "@southwind-ai/shared";
|
|
2
3
|
import { InMemoryAuthRepository, type AuthRepository } from "./repository.js";
|
|
3
4
|
import { AuthService } from "./service.js";
|
|
4
5
|
|
|
5
6
|
interface AuthRuntimeInput {
|
|
6
7
|
repository?: AuthRepository;
|
|
7
|
-
sessionTtlSeconds: number;
|
|
8
|
+
sessionTtlSeconds: number | (() => number);
|
|
9
|
+
passwordPolicy?: () => PasswordPolicy;
|
|
8
10
|
auditWriter: AuditEventWriter["write"];
|
|
9
11
|
auditReader?: AuditEventReader;
|
|
10
12
|
}
|
|
@@ -13,6 +15,7 @@ export function createAuthRuntime(input: AuthRuntimeInput) {
|
|
|
13
15
|
const authRepository = input.repository ?? new InMemoryAuthRepository();
|
|
14
16
|
const authService = new AuthService(authRepository, {
|
|
15
17
|
sessionTtlSeconds: input.sessionTtlSeconds,
|
|
18
|
+
passwordPolicy: input.passwordPolicy,
|
|
16
19
|
auditWriter: { write: input.auditWriter },
|
|
17
20
|
auditReader: input.auditReader,
|
|
18
21
|
});
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
2
|
import { randomUUIDv7 } from "bun";
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
PASSWORD_POLICY,
|
|
5
|
+
type AuditEvent,
|
|
6
|
+
type PasswordPolicy,
|
|
7
|
+
} from "@southwind-ai/shared";
|
|
4
8
|
import type { RequestActor } from "../guards.js";
|
|
5
9
|
import type { AuditEventReader, AuditEventWriter } from "../runtime.js";
|
|
6
10
|
import { hashSessionToken } from "./actor-resolver.js";
|
|
@@ -8,7 +12,7 @@ import {
|
|
|
8
12
|
AuthenticationProviderRegistry,
|
|
9
13
|
LocalArgon2AuthenticationAdapter,
|
|
10
14
|
} from "./authentication-provider.js";
|
|
11
|
-
import {
|
|
15
|
+
import { createArgon2PasswordHasher, type PasswordHasher } from "./password.js";
|
|
12
16
|
import type { AuthRepository, AuthSessionRecord } from "./repository.js";
|
|
13
17
|
import {
|
|
14
18
|
CredentialService,
|
|
@@ -42,7 +46,8 @@ export type RevokeResult =
|
|
|
42
46
|
| { ok: false; code: "SESSION_NOT_FOUND" | "FORBIDDEN" };
|
|
43
47
|
|
|
44
48
|
export interface AuthServiceOptions {
|
|
45
|
-
sessionTtlSeconds?: number;
|
|
49
|
+
sessionTtlSeconds?: number | (() => number);
|
|
50
|
+
passwordPolicy?: () => PasswordPolicy;
|
|
46
51
|
now?: () => Date;
|
|
47
52
|
tokenFactory?: () => string;
|
|
48
53
|
passwordHasher?: PasswordHasher;
|
|
@@ -52,7 +57,8 @@ export interface AuthServiceOptions {
|
|
|
52
57
|
}
|
|
53
58
|
|
|
54
59
|
export class AuthService {
|
|
55
|
-
private readonly sessionTtlSeconds: number;
|
|
60
|
+
private readonly sessionTtlSeconds: () => number;
|
|
61
|
+
private readonly passwordPolicy: () => PasswordPolicy;
|
|
56
62
|
private readonly now: () => Date;
|
|
57
63
|
private readonly tokenFactory: () => string;
|
|
58
64
|
private readonly passwordHasher: PasswordHasher;
|
|
@@ -64,11 +70,17 @@ export class AuthService {
|
|
|
64
70
|
private readonly repository: AuthRepository,
|
|
65
71
|
private readonly options: AuthServiceOptions,
|
|
66
72
|
) {
|
|
67
|
-
|
|
73
|
+
const sessionTtlSeconds = options.sessionTtlSeconds;
|
|
74
|
+
this.sessionTtlSeconds =
|
|
75
|
+
typeof sessionTtlSeconds === "function"
|
|
76
|
+
? sessionTtlSeconds
|
|
77
|
+
: () => sessionTtlSeconds || 60 * 60 * 8;
|
|
78
|
+
this.passwordPolicy = options.passwordPolicy || (() => PASSWORD_POLICY);
|
|
68
79
|
this.now = options.now || (() => new Date());
|
|
69
80
|
this.tokenFactory =
|
|
70
81
|
options.tokenFactory || (() => randomBytes(32).toString("base64url"));
|
|
71
|
-
this.passwordHasher =
|
|
82
|
+
this.passwordHasher =
|
|
83
|
+
options.passwordHasher || createArgon2PasswordHasher(this.passwordPolicy);
|
|
72
84
|
this.authenticationProviders =
|
|
73
85
|
options.authenticationProviders ||
|
|
74
86
|
new AuthenticationProviderRegistry(
|
|
@@ -140,7 +152,9 @@ export class AuthService {
|
|
|
140
152
|
tokenHash: hashSessionToken(token),
|
|
141
153
|
status: "active",
|
|
142
154
|
issuedAt,
|
|
143
|
-
expiresAt: new Date(
|
|
155
|
+
expiresAt: new Date(
|
|
156
|
+
issuedAt.getTime() + this.sessionTtlSeconds() * 1_000,
|
|
157
|
+
),
|
|
144
158
|
ip: metadata.ip,
|
|
145
159
|
userAgent: metadata.userAgent,
|
|
146
160
|
};
|
|
@@ -211,6 +225,10 @@ export class AuthService {
|
|
|
211
225
|
);
|
|
212
226
|
}
|
|
213
227
|
|
|
228
|
+
getPasswordPolicy(): PasswordPolicy {
|
|
229
|
+
return { ...this.passwordPolicy() };
|
|
230
|
+
}
|
|
231
|
+
|
|
214
232
|
async revokeSession(
|
|
215
233
|
sessionId: string,
|
|
216
234
|
actor: RequestActor,
|
|
@@ -3,6 +3,10 @@ import type { FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
|
3
3
|
import type { FoundationSnapshot } from "../foundation.js";
|
|
4
4
|
import type { createServerRuntime } from "../runtime.js";
|
|
5
5
|
import type { RouteApp } from "../system/route-types.js";
|
|
6
|
+
// @windy-module system.storage begin
|
|
7
|
+
import type { UploadService } from "@southwind-ai/storage";
|
|
8
|
+
import { createPlatformUploadPolicies } from "../storage/runtime.js";
|
|
9
|
+
// @windy-module system.storage end
|
|
6
10
|
import {
|
|
7
11
|
createRedisHealthProbe,
|
|
8
12
|
type RedisHealthProbe,
|
|
@@ -21,6 +25,9 @@ export async function bootstrapModuleConfiguration(
|
|
|
21
25
|
): Promise<{
|
|
22
26
|
service: ModuleConfigurationService;
|
|
23
27
|
bindFoundation(snapshot: FoundationSnapshot): void;
|
|
28
|
+
// @windy-module system.storage begin
|
|
29
|
+
bindStorage(uploads: Pick<UploadService, "setPolicy">): void;
|
|
30
|
+
// @windy-module system.storage end
|
|
24
31
|
redisHealthProbe: RedisHealthProbe;
|
|
25
32
|
register(
|
|
26
33
|
app: Pick<RouteApp, "get" | "post">,
|
|
@@ -37,12 +44,24 @@ export async function bootstrapModuleConfiguration(
|
|
|
37
44
|
return {
|
|
38
45
|
service,
|
|
39
46
|
bindFoundation(snapshot) {
|
|
40
|
-
|
|
47
|
+
snapshot.platform.enableAI = config.enableAI;
|
|
48
|
+
adapter.addListener({
|
|
41
49
|
applied(current) {
|
|
42
50
|
snapshot.platform.enableAI = current.enableAI;
|
|
43
51
|
},
|
|
44
52
|
});
|
|
45
53
|
},
|
|
54
|
+
// @windy-module system.storage begin
|
|
55
|
+
bindStorage(uploads: Pick<UploadService, "setPolicy">) {
|
|
56
|
+
adapter.addListener({
|
|
57
|
+
applied(current) {
|
|
58
|
+
for (const policy of createPlatformUploadPolicies(current)) {
|
|
59
|
+
uploads.setPolicy(policy);
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
// @windy-module system.storage end
|
|
46
65
|
redisHealthProbe: createLiveRedisHealthProbe(config),
|
|
47
66
|
register(app, runtime, features) {
|
|
48
67
|
registerModuleConfigurationRoutes(app, service, runtime, features);
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { loadPlatformConfig } from "@southwind-ai/config";
|
|
3
|
+
import { PlatformConfigApplyAdapter } from "./definition.js";
|
|
4
|
+
|
|
5
|
+
describe("平台类型化运行配置", () => {
|
|
6
|
+
test("白名单不包含 SMTP Secret 且旧版本缺失字段由环境基线补齐", () => {
|
|
7
|
+
const config = loadPlatformConfig({
|
|
8
|
+
MAIL_SMTP_URL: "smtp://secret-marker@example.invalid",
|
|
9
|
+
});
|
|
10
|
+
const definition = new PlatformConfigApplyAdapter(config).definition();
|
|
11
|
+
|
|
12
|
+
expect(definition.fields.map((field) => field.path)).not.toContain(
|
|
13
|
+
"mail.smtpUrl",
|
|
14
|
+
);
|
|
15
|
+
expect(JSON.stringify(definition.baseValues())).not.toContain(
|
|
16
|
+
"secret-marker",
|
|
17
|
+
);
|
|
18
|
+
expect(
|
|
19
|
+
definition.validate({
|
|
20
|
+
enableAI: true,
|
|
21
|
+
"cache.healthTimeoutMs": 500,
|
|
22
|
+
}),
|
|
23
|
+
).toMatchObject({
|
|
24
|
+
enableAI: true,
|
|
25
|
+
"cache.healthTimeoutMs": 500,
|
|
26
|
+
"security.passwordMinLength": 12,
|
|
27
|
+
"security.passwordMaxLength": 128,
|
|
28
|
+
"regional.timeZone": "Asia/Shanghai",
|
|
29
|
+
});
|
|
30
|
+
expect(() =>
|
|
31
|
+
definition.validate({
|
|
32
|
+
...definition.baseValues(),
|
|
33
|
+
"mail.smtpUrl": "smtp://must-not-persist",
|
|
34
|
+
}),
|
|
35
|
+
).toThrow();
|
|
36
|
+
expect(() =>
|
|
37
|
+
definition.validate({
|
|
38
|
+
...definition.baseValues(),
|
|
39
|
+
"mail.enabled": true,
|
|
40
|
+
}),
|
|
41
|
+
).toThrow();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("即时字段应用到共享运行配置对象", async () => {
|
|
45
|
+
const config = loadPlatformConfig({});
|
|
46
|
+
const definition = new PlatformConfigApplyAdapter(config).definition();
|
|
47
|
+
|
|
48
|
+
await definition.apply(
|
|
49
|
+
definition.validate({
|
|
50
|
+
...definition.baseValues(),
|
|
51
|
+
"security.sessionTtlSeconds": 3_600,
|
|
52
|
+
"security.passwordMinLength": 16,
|
|
53
|
+
"security.passwordMaxLength": 64,
|
|
54
|
+
"storage.bulkDataMaxByteSize": 1_048_576,
|
|
55
|
+
"storage.brandingLogoMaxByteSize": 262_144,
|
|
56
|
+
"regional.timeZone": "UTC",
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
expect(config.security).toMatchObject({
|
|
61
|
+
sessionTtlSeconds: 3_600,
|
|
62
|
+
passwordMinLength: 16,
|
|
63
|
+
passwordMaxLength: 64,
|
|
64
|
+
});
|
|
65
|
+
expect(config.storage).toEqual({
|
|
66
|
+
bulkDataMaxByteSize: 1_048_576,
|
|
67
|
+
brandingLogoMaxByteSize: 262_144,
|
|
68
|
+
});
|
|
69
|
+
expect(config.regional.timeZone).toBe("UTC");
|
|
70
|
+
});
|
|
71
|
+
});
|