create-windy 0.2.34 → 0.3.1
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 +26 -3
- package/dist/cli.js +8121 -3746
- package/package.json +3 -3
- package/template/.windy-template.json +2 -2
- package/template/AGENTS.md +5 -0
- package/template/README.md +28 -4
- package/template/_gitignore +4 -0
- package/template/apps/agent-server/Dockerfile +1 -0
- package/template/apps/agent-server/README.md +18 -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 +1 -2
- package/template/apps/agent-server/tests/__init__.py +1 -0
- package/template/apps/agent-server/tests/openai_provider_test_support.py +131 -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_openai_provider.py +7 -123
- package/template/apps/server/package.json +4 -1
- package/template/apps/server/src/agent/run-facade.ts +2 -7
- 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-support.ts +94 -0
- package/template/apps/server/src/auth/routes.test.ts +51 -78
- 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 +40 -40
- 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/mcp/adapter.test.ts +3 -2
- package/template/apps/server/src/mcp/adapter.ts +9 -9
- package/template/apps/server/src/mcp/routes.ts +4 -3
- package/template/apps/server/src/module-bootstrap.ts +2 -2
- package/template/apps/server/src/module-host/request-context.ts +6 -6
- package/template/apps/server/src/module-host/storage-scope.ts +2 -9
- package/template/apps/server/src/module-host/user-directory-port.ts +2 -6
- 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/platform-http-runtime.ts +87 -0
- 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 +1 -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/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-configs.ts +319 -0
- package/template/apps/server/src/system/drizzle-system.ts +59 -311
- 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/resource-routes.ts +270 -0
- 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 +35 -267
- 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/SystemResourceLoadError.vue +21 -0
- package/template/apps/web/src/pages/system/components/SystemResourceLoadError.webtest.ts +17 -0
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +96 -108
- 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 +66 -0
- package/template/docs/architecture/ai-runtime.md +14 -5
- package/template/docs/architecture/mcp-business-capabilities.md +107 -0
- 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 +32 -6
- package/template/docs/platform/agent-tools.md +4 -0
- package/template/docs/platform/audit-reliability.md +46 -2
- 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/composition-fixtures.test-support.ts +164 -0
- package/template/packages/modules/src/composition.test.ts +5 -158
- package/template/packages/modules/src/system-api-operational.ts +234 -0
- package/template/packages/modules/src/system-api-permissions.ts +14 -181
- package/template/packages/modules/src/system-audit-actions.ts +10 -0
- package/template/packages/modules/src/system-features.ts +1 -1
- package/template/packages/modules/src/system-module-catalog.ts +2 -1
- package/template/packages/modules/src/system-module-tasks.ts +86 -0
- package/template/packages/modules/src/system-modules-access.test.ts +60 -0
- package/template/packages/modules/src/system-modules.test.ts +13 -54
- package/template/packages/modules/src/system-modules.ts +2 -72
- package/template/packages/modules/src/system-permissions.ts +9 -5
- package/template/packages/server-sdk/package.json +1 -1
- package/template/packages/shared/index.ts +1 -0
- package/template/packages/shared/package.json +1 -1
- package/template/packages/shared/src/audit.ts +25 -0
- package/template/packages/shared/src/mcp.test.ts +12 -0
- package/template/packages/shared/src/mcp.ts +7 -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
|
@@ -17,6 +17,7 @@ import { createWorkOrderRepository } from "./work-order/repository.js";
|
|
|
17
17
|
import { createDrizzleWorkOrderRepository } from "./work-order/drizzle-repository.js";
|
|
18
18
|
import { createWorkOrderRouteDefinitions } from "./work-order/routes.js";
|
|
19
19
|
import { WorkOrderSearchProvider } from "./work-order/search-provider.js";
|
|
20
|
+
import { createWorkOrderOpenSearchIndex } from "./work-order/opensearch-index.js";
|
|
20
21
|
import { WorkOrderService } from "./work-order/service.js";
|
|
21
22
|
|
|
22
23
|
export interface WorkOrderModuleDependencies {
|
|
@@ -42,6 +43,7 @@ export interface WorkOrderModule {
|
|
|
42
43
|
*/
|
|
43
44
|
export function createWorkOrderModule(): WorkOrderModule {
|
|
44
45
|
let service: WorkOrderService | undefined;
|
|
46
|
+
const searchIndex = createWorkOrderOpenSearchIndex(process.env);
|
|
45
47
|
const requireService = () => {
|
|
46
48
|
if (!service) throw new Error("示例工单模块尚未挂载");
|
|
47
49
|
return service;
|
|
@@ -52,7 +54,7 @@ export function createWorkOrderModule(): WorkOrderModule {
|
|
|
52
54
|
requireService().operationalSnapshot(
|
|
53
55
|
platformDataActor("task_work_order_snapshot", "工单运营快照任务"),
|
|
54
56
|
),
|
|
55
|
-
searchProvider: new WorkOrderSearchProvider(requireService),
|
|
57
|
+
searchProvider: new WorkOrderSearchProvider(requireService, searchIndex),
|
|
56
58
|
});
|
|
57
59
|
return {
|
|
58
60
|
...serverModule,
|
|
@@ -67,6 +69,7 @@ export function createWorkOrderModule(): WorkOrderModule {
|
|
|
67
69
|
? createDrizzleWorkOrderRepository(dependencies.db)
|
|
68
70
|
: createWorkOrderRepository()),
|
|
69
71
|
dependencies.departments,
|
|
72
|
+
searchIndex,
|
|
70
73
|
);
|
|
71
74
|
},
|
|
72
75
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ApiPermissionBinding,
|
|
3
|
+
AuditActor,
|
|
3
4
|
AuditEvent,
|
|
4
5
|
EntitlementKey,
|
|
5
6
|
FeatureFlagDefinition,
|
|
@@ -33,6 +34,7 @@ export interface RequestGuardContext {
|
|
|
33
34
|
actor: RequestActor;
|
|
34
35
|
startedAt: string;
|
|
35
36
|
clientIp?: string;
|
|
37
|
+
userAgent?: string;
|
|
36
38
|
license?: {
|
|
37
39
|
versionKey?: LicenseVersionKey;
|
|
38
40
|
/** V1 兼容字段。新 License 不再包含 Entitlement。 */
|
|
@@ -61,6 +63,7 @@ export interface GuardDecision {
|
|
|
61
63
|
|
|
62
64
|
export interface RequestGuardContextOptions {
|
|
63
65
|
clientIp?: string;
|
|
66
|
+
userAgent?: string;
|
|
64
67
|
resolveActor?: (request: Request) => RequestActor | undefined;
|
|
65
68
|
resolveLicense?: (
|
|
66
69
|
request: Request,
|
|
@@ -86,10 +89,24 @@ export function createRequestGuardContext(
|
|
|
86
89
|
actor: options.resolveActor?.(request) || anonymousActor(),
|
|
87
90
|
startedAt: new Date().toISOString(),
|
|
88
91
|
clientIp: options.clientIp,
|
|
92
|
+
userAgent: options.userAgent,
|
|
89
93
|
license: options.resolveLicense?.(request),
|
|
90
94
|
};
|
|
91
95
|
}
|
|
92
96
|
|
|
97
|
+
export function auditActor(
|
|
98
|
+
context: Pick<RequestGuardContext, "actor" | "clientIp" | "userAgent">,
|
|
99
|
+
): AuditActor {
|
|
100
|
+
return {
|
|
101
|
+
id: context.actor.id,
|
|
102
|
+
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
103
|
+
name: context.actor.name,
|
|
104
|
+
roleCodes: context.actor.roleCodes,
|
|
105
|
+
ip: context.clientIp,
|
|
106
|
+
userAgent: context.userAgent,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
export function evaluatePermissionGuard(
|
|
94
111
|
context: RequestGuardContext,
|
|
95
112
|
binding: PermissionGuardRequirement,
|
|
@@ -207,11 +224,7 @@ function deniedDecision(
|
|
|
207
224
|
id: randomUUIDv7(),
|
|
208
225
|
action: "security.denied",
|
|
209
226
|
outcome: "denied",
|
|
210
|
-
actor:
|
|
211
|
-
id: context.actor.id,
|
|
212
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
213
|
-
name: context.actor.name,
|
|
214
|
-
},
|
|
227
|
+
actor: auditActor(context),
|
|
215
228
|
target: {
|
|
216
229
|
type: "guard",
|
|
217
230
|
id: targetId,
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
requestSourcePolicyFromEnv,
|
|
4
|
+
resolveRequestSource,
|
|
5
|
+
} from "./request-source.js";
|
|
6
|
+
|
|
7
|
+
describe("平台请求来源信任边界", () => {
|
|
8
|
+
test("默认使用直接对端并忽略客户端伪造的转发头", () => {
|
|
9
|
+
const source = resolveRequestSource(
|
|
10
|
+
request("203.0.113.66"),
|
|
11
|
+
"198.51.100.10",
|
|
12
|
+
requestSourcePolicyFromEnv({}),
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(source).toEqual({
|
|
16
|
+
clientIp: "198.51.100.10",
|
|
17
|
+
userAgent: "Windy-Test/1.0",
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("仅显式启用且直接对端受信时采用单一合法转发来源", () => {
|
|
22
|
+
const policy = requestSourcePolicyFromEnv({
|
|
23
|
+
WINDY_TRUST_PROXY: "true",
|
|
24
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
expect(
|
|
28
|
+
resolveRequestSource(request("203.0.113.66"), "::1", policy).clientIp,
|
|
29
|
+
).toBe("203.0.113.66");
|
|
30
|
+
expect(
|
|
31
|
+
resolveRequestSource(request("203.0.113.67"), "10.0.0.8", policy)
|
|
32
|
+
.clientIp,
|
|
33
|
+
).toBe("203.0.113.67");
|
|
34
|
+
expect(
|
|
35
|
+
resolveRequestSource(request("203.0.113.68"), "10.0.0.9", policy)
|
|
36
|
+
.clientIp,
|
|
37
|
+
).toBe("10.0.0.9");
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("多段或非法转发来源退回直接对端", () => {
|
|
41
|
+
const policy = requestSourcePolicyFromEnv({
|
|
42
|
+
WINDY_TRUST_PROXY: "true",
|
|
43
|
+
});
|
|
44
|
+
for (const forwardedFor of [
|
|
45
|
+
"203.0.113.66, 198.51.100.9",
|
|
46
|
+
"not-an-ip",
|
|
47
|
+
"203.0.113.66:443",
|
|
48
|
+
]) {
|
|
49
|
+
expect(
|
|
50
|
+
resolveRequestSource(request(forwardedFor), "127.0.0.1", policy)
|
|
51
|
+
.clientIp,
|
|
52
|
+
).toBe("127.0.0.1");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("代理配置默认关闭且错误配置 fail closed", () => {
|
|
57
|
+
expect(requestSourcePolicyFromEnv({})).toEqual({
|
|
58
|
+
trustProxy: false,
|
|
59
|
+
trustedProxyIps: [],
|
|
60
|
+
});
|
|
61
|
+
expect(() =>
|
|
62
|
+
requestSourcePolicyFromEnv({
|
|
63
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.8",
|
|
64
|
+
}),
|
|
65
|
+
).toThrow("WINDY_TRUST_PROXY=true");
|
|
66
|
+
expect(() =>
|
|
67
|
+
requestSourcePolicyFromEnv({ WINDY_TRUST_PROXY: "yes" }),
|
|
68
|
+
).toThrow("只允许 true 或 false");
|
|
69
|
+
expect(() =>
|
|
70
|
+
requestSourcePolicyFromEnv({
|
|
71
|
+
WINDY_TRUST_PROXY: "true",
|
|
72
|
+
WINDY_TRUSTED_PROXY_IPS: "10.0.0.999",
|
|
73
|
+
}),
|
|
74
|
+
).toThrow("无效 IP");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
function request(forwardedFor: string): Request {
|
|
79
|
+
return new Request("https://windy.example.test/api/system/users", {
|
|
80
|
+
headers: {
|
|
81
|
+
"user-agent": "Windy-Test/1.0",
|
|
82
|
+
"x-forwarded-for": forwardedFor,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
|
|
3
|
+
export interface RequestSourcePolicy {
|
|
4
|
+
trustProxy: boolean;
|
|
5
|
+
trustedProxyIps: readonly string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RequestSource {
|
|
9
|
+
clientIp?: string;
|
|
10
|
+
userAgent?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function requestSourcePolicyFromEnv(
|
|
14
|
+
env: Record<string, string | undefined>,
|
|
15
|
+
): RequestSourcePolicy {
|
|
16
|
+
const trustProxy = readBoolean(env.WINDY_TRUST_PROXY);
|
|
17
|
+
const configuredIps = (env.WINDY_TRUSTED_PROXY_IPS || "")
|
|
18
|
+
.split(",")
|
|
19
|
+
.map((value) => value.trim())
|
|
20
|
+
.filter(Boolean);
|
|
21
|
+
if (!trustProxy && configuredIps.length) {
|
|
22
|
+
throw new Error(
|
|
23
|
+
"配置 WINDY_TRUSTED_PROXY_IPS 时必须显式设置 WINDY_TRUST_PROXY=true",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
trustProxy,
|
|
28
|
+
trustedProxyIps: configuredIps.map((value) => {
|
|
29
|
+
const normalized = normalizeIp(value);
|
|
30
|
+
if (!normalized) {
|
|
31
|
+
throw new Error(`WINDY_TRUSTED_PROXY_IPS 包含无效 IP:${value}`);
|
|
32
|
+
}
|
|
33
|
+
return normalized;
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveRequestSource(
|
|
39
|
+
request: Request,
|
|
40
|
+
peerIp: string | undefined,
|
|
41
|
+
policy: RequestSourcePolicy,
|
|
42
|
+
): RequestSource {
|
|
43
|
+
const normalizedPeerIp = normalizeIp(peerIp);
|
|
44
|
+
const forwardedIp = resolveForwardedIp(request, normalizedPeerIp, policy);
|
|
45
|
+
return {
|
|
46
|
+
clientIp: forwardedIp || normalizedPeerIp,
|
|
47
|
+
userAgent: normalizeUserAgent(request.headers.get("user-agent")),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function resolveForwardedIp(
|
|
52
|
+
request: Request,
|
|
53
|
+
peerIp: string | undefined,
|
|
54
|
+
policy: RequestSourcePolicy,
|
|
55
|
+
): string | undefined {
|
|
56
|
+
if (!policy.trustProxy || !peerIp || !isTrustedProxy(peerIp, policy)) {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
const header = request.headers.get("x-forwarded-for")?.trim();
|
|
60
|
+
if (!header || header.includes(",")) return undefined;
|
|
61
|
+
return normalizeIp(header);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isTrustedProxy(peerIp: string, policy: RequestSourcePolicy): boolean {
|
|
65
|
+
if (isLoopback(peerIp)) return true;
|
|
66
|
+
return policy.trustedProxyIps.some((value) => normalizeIp(value) === peerIp);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isLoopback(ip: string): boolean {
|
|
70
|
+
return ip === "::1" || (isIP(ip) === 4 && ip.startsWith("127."));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeIp(value: string | undefined): string | undefined {
|
|
74
|
+
const candidate = value?.trim().toLowerCase();
|
|
75
|
+
if (!candidate) return undefined;
|
|
76
|
+
const mapped = candidate.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)?.[1];
|
|
77
|
+
const normalized = mapped && isIP(mapped) === 4 ? mapped : candidate;
|
|
78
|
+
return isIP(normalized) ? normalized : undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeUserAgent(value: string | null): string | undefined {
|
|
82
|
+
const normalized = value?.trim();
|
|
83
|
+
return normalized ? normalized.slice(0, 1_024) : undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readBoolean(value: string | undefined): boolean {
|
|
87
|
+
if (value === undefined || value === "" || value === "false") return false;
|
|
88
|
+
if (value === "true") return true;
|
|
89
|
+
throw new Error("WINDY_TRUST_PROXY 只允许 true 或 false");
|
|
90
|
+
}
|
|
@@ -26,10 +26,7 @@ import { GovernedToolRegistry } from "./agent/tool-registry.js";
|
|
|
26
26
|
import { PlatformMcpAdapter } from "./mcp/adapter.js";
|
|
27
27
|
import { registerMcpRoutes } from "./mcp/routes.js";
|
|
28
28
|
// @windy-module system.agent end
|
|
29
|
-
import { registerAuthRoutes, registerAuthSessionRoute } from "./auth/routes.js";
|
|
30
29
|
import { createAuthRuntime } from "./auth/runtime.js";
|
|
31
|
-
import { createPlatformHttpApp } from "./http-app.js";
|
|
32
|
-
import { bootstrapAdminPassword } from "./auth/bootstrap-admin.js";
|
|
33
30
|
import { createServerPersistence } from "./persistence.js";
|
|
34
31
|
// @windy-module system.license begin
|
|
35
32
|
import { registerPlatformLicenseRoutes } from "./license/routes.js";
|
|
@@ -51,7 +48,6 @@ import { SchedulerWorker } from "./scheduler/worker.js";
|
|
|
51
48
|
// @windy-module system.settings begin
|
|
52
49
|
import { createPlatformSettingsRuntime } from "./settings/runtime.js";
|
|
53
50
|
// @windy-module system.settings end
|
|
54
|
-
import { registerProfileRoutes } from "./profile/routes.js";
|
|
55
51
|
// @windy-module system.notification begin
|
|
56
52
|
import { registerNotificationRoutes } from "./notification/routes.js";
|
|
57
53
|
// @windy-module system.notification end
|
|
@@ -76,11 +72,13 @@ import { createSearchHost } from "./search/host.js";
|
|
|
76
72
|
// @windy-module system.workflow begin
|
|
77
73
|
import { registerWorkflowRoutes } from "./workflow/routes.js";
|
|
78
74
|
// @windy-module system.workflow end
|
|
79
|
-
import {
|
|
80
|
-
createPlatformObservability,
|
|
81
|
-
logPlatformStartup,
|
|
82
|
-
} from "./observability/bootstrap.js";
|
|
83
75
|
import { bootstrapServerModules } from "./module-bootstrap.js";
|
|
76
|
+
import {
|
|
77
|
+
createHttpObservability,
|
|
78
|
+
createPlatformServerApp,
|
|
79
|
+
listenPlatformServer,
|
|
80
|
+
logServerStartup,
|
|
81
|
+
} from "./platform-http-runtime.js";
|
|
84
82
|
|
|
85
83
|
const logger = pino({ name: "windy-server" });
|
|
86
84
|
const platformConfig = loadPlatformConfig();
|
|
@@ -128,20 +126,17 @@ const runtime = createServerRuntime(process.env, {
|
|
|
128
126
|
auditWriter: persistence?.auditWriter,
|
|
129
127
|
// @windy-module system.audit end
|
|
130
128
|
});
|
|
131
|
-
const observability =
|
|
132
|
-
databaseCheck: persistence
|
|
133
|
-
? async () => void (await persistence.pool.query("select 1"))
|
|
134
|
-
: undefined,
|
|
135
|
-
databaseRequired: process.env.NODE_ENV === "production",
|
|
136
|
-
auditStatus: () => runtime.auditSink.status(),
|
|
137
|
-
logger,
|
|
138
|
-
});
|
|
129
|
+
const observability = createHttpObservability(runtime, persistence, logger);
|
|
139
130
|
// @windy-module system.license begin
|
|
140
131
|
platformLicenseRuntime.setAuditSink(runtime.auditSink);
|
|
141
132
|
// @windy-module system.license end
|
|
142
133
|
const { authRepository, authService } = createAuthRuntime({
|
|
143
134
|
repository: persistence?.authRepository,
|
|
144
|
-
sessionTtlSeconds: platformConfig.security.sessionTtlSeconds,
|
|
135
|
+
sessionTtlSeconds: () => platformConfig.security.sessionTtlSeconds,
|
|
136
|
+
passwordPolicy: () => ({
|
|
137
|
+
minLength: platformConfig.security.passwordMinLength,
|
|
138
|
+
maxLength: platformConfig.security.passwordMaxLength,
|
|
139
|
+
}),
|
|
145
140
|
auditWriter: (event) => runtime.auditSink.recordCriticalEvent(event),
|
|
146
141
|
// @windy-module system.audit begin
|
|
147
142
|
auditReader: persistence?.auditReader,
|
|
@@ -153,12 +148,17 @@ const storageRuntime = createStorageRuntime(
|
|
|
153
148
|
persistence?.db,
|
|
154
149
|
moduleHosts.uploadPolicies,
|
|
155
150
|
);
|
|
151
|
+
configurationRuntime.bindStorage(storageRuntime.uploads);
|
|
156
152
|
// @windy-module system.storage end
|
|
157
153
|
// @windy-module system.settings begin
|
|
158
|
-
const platformSettings = createPlatformSettingsRuntime(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
154
|
+
const platformSettings = createPlatformSettingsRuntime(
|
|
155
|
+
persistence?.db,
|
|
156
|
+
{
|
|
157
|
+
softwareName: platformConfig.name,
|
|
158
|
+
logoUrl: platformConfig.logoUrl,
|
|
159
|
+
},
|
|
160
|
+
() => ({ timeZone: platformConfig.regional.timeZone }),
|
|
161
|
+
);
|
|
162
162
|
// @windy-module system.settings end
|
|
163
163
|
// @windy-module system.search begin
|
|
164
164
|
const searchHost = createSearchHost(
|
|
@@ -177,6 +177,7 @@ const {
|
|
|
177
177
|
scheduler,
|
|
178
178
|
taskDefinitions: scheduledTaskDefinitions,
|
|
179
179
|
notifications: notificationRepository,
|
|
180
|
+
notificationService,
|
|
180
181
|
workflow: workflowService,
|
|
181
182
|
governedExportService,
|
|
182
183
|
durable: durableJobRuntime,
|
|
@@ -188,6 +189,7 @@ const {
|
|
|
188
189
|
storage: storageRuntime,
|
|
189
190
|
moduleTasks: moduleTaskDefinitions,
|
|
190
191
|
bulkRetentionDays: readPositiveInt(process.env.BULK_DATA_RETENTION_DAYS, 7),
|
|
192
|
+
mailConfig: platformConfig.mail,
|
|
191
193
|
installModuleDurableHandlers: moduleHosts.installDurableHandlers,
|
|
192
194
|
});
|
|
193
195
|
const schedulerWorker = new SchedulerWorker(
|
|
@@ -197,20 +199,17 @@ const schedulerWorker = new SchedulerWorker(
|
|
|
197
199
|
(result) => observability.metrics.observeSchedulerTick(result),
|
|
198
200
|
);
|
|
199
201
|
// @windy-module system.scheduler end
|
|
200
|
-
await
|
|
202
|
+
const app = await createPlatformServerApp({
|
|
203
|
+
runtime,
|
|
204
|
+
persistence,
|
|
205
|
+
platformConfig,
|
|
206
|
+
authRepository,
|
|
201
207
|
authService,
|
|
202
|
-
authRepository: persistence?.authRepository,
|
|
203
208
|
repositories: systemRepositories,
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
observability.register(app);
|
|
208
|
-
registerAuthSessionRoute(app);
|
|
209
|
-
registerAuthRoutes(app, authService, {
|
|
210
|
-
sessionCookieName: platformConfig.security.sessionCookieName,
|
|
211
|
-
secureCookie: process.env.NODE_ENV === "production",
|
|
209
|
+
features,
|
|
210
|
+
logger,
|
|
211
|
+
observability,
|
|
212
212
|
});
|
|
213
|
-
registerProfileRoutes(app, authRepository, runtime, features);
|
|
214
213
|
|
|
215
214
|
// @windy-module system.license begin
|
|
216
215
|
registerPlatformLicenseRoutes(app, platformLicenseRuntime);
|
|
@@ -220,7 +219,13 @@ registerPlatformLicenseRoutes(app, platformLicenseRuntime);
|
|
|
220
219
|
platformSettings.register(app, runtime, features);
|
|
221
220
|
// @windy-module system.settings end
|
|
222
221
|
// @windy-module system.notification begin
|
|
223
|
-
registerNotificationRoutes(
|
|
222
|
+
registerNotificationRoutes(
|
|
223
|
+
app,
|
|
224
|
+
notificationRepository,
|
|
225
|
+
runtime,
|
|
226
|
+
features,
|
|
227
|
+
notificationService,
|
|
228
|
+
);
|
|
224
229
|
// @windy-module system.notification end
|
|
225
230
|
// @windy-module system.workflow begin
|
|
226
231
|
registerWorkflowRoutes(app, workflowService, runtime);
|
|
@@ -337,13 +342,8 @@ validateInstalledRuntime({
|
|
|
337
342
|
hosts: moduleHosts,
|
|
338
343
|
});
|
|
339
344
|
|
|
340
|
-
const port =
|
|
341
|
-
app.listen(port);
|
|
345
|
+
const port = listenPlatformServer(app);
|
|
342
346
|
// @windy-module system.scheduler begin
|
|
343
347
|
schedulerWorker.start();
|
|
344
348
|
// @windy-module system.scheduler end
|
|
345
|
-
|
|
346
|
-
featureSync,
|
|
347
|
-
persistenceEnabled: Boolean(persistence),
|
|
348
|
-
port,
|
|
349
|
-
});
|
|
349
|
+
logServerStartup(logger, featureSync, Boolean(persistence), port);
|
|
@@ -52,6 +52,8 @@ export function registerPlatformLicenseRoutes(
|
|
|
52
52
|
const installed = await licenseRuntime.install(rawLicense, {
|
|
53
53
|
actor: context.guardContext.actor,
|
|
54
54
|
requestId: context.guardContext.requestId,
|
|
55
|
+
clientIp: context.guardContext.clientIp,
|
|
56
|
+
userAgent: context.guardContext.userAgent,
|
|
55
57
|
});
|
|
56
58
|
if (installed.result.status === "rejected") {
|
|
57
59
|
return Response.json(
|
|
@@ -17,7 +17,11 @@ import type {
|
|
|
17
17
|
LicenseFailureReason,
|
|
18
18
|
SignedLicense,
|
|
19
19
|
} from "@southwind-ai/shared";
|
|
20
|
-
import
|
|
20
|
+
import {
|
|
21
|
+
auditActor,
|
|
22
|
+
type RequestActor,
|
|
23
|
+
type RequestGuardContext,
|
|
24
|
+
} from "../guards.js";
|
|
21
25
|
import type { InMemoryAuditSink } from "../runtime.js";
|
|
22
26
|
import type {
|
|
23
27
|
LicenseEntitlementResolver,
|
|
@@ -57,10 +61,10 @@ export interface PlatformLicenseRuntimeOptions {
|
|
|
57
61
|
now?: () => Date;
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
export
|
|
61
|
-
|
|
62
|
-
requestId
|
|
63
|
-
|
|
64
|
+
export type LicenseInstallActorContext = Pick<
|
|
65
|
+
RequestGuardContext,
|
|
66
|
+
"actor" | "requestId" | "clientIp" | "userAgent"
|
|
67
|
+
>;
|
|
64
68
|
|
|
65
69
|
export class PlatformLicenseRuntime implements LicenseEntitlementResolver {
|
|
66
70
|
private readonly stateStorage: LicenseStorage;
|
|
@@ -285,12 +289,7 @@ function installAuditEvent(
|
|
|
285
289
|
id: crypto.randomUUID(),
|
|
286
290
|
action: "license.update",
|
|
287
291
|
outcome: success ? "success" : "failure",
|
|
288
|
-
actor:
|
|
289
|
-
id: context.actor.id,
|
|
290
|
-
type: "user",
|
|
291
|
-
name: context.actor.name,
|
|
292
|
-
roleCodes: context.actor.roleCodes,
|
|
293
|
-
},
|
|
292
|
+
actor: auditActor(context),
|
|
294
293
|
target: {
|
|
295
294
|
type: "runtime-license",
|
|
296
295
|
id: result.license?.claims.licenseId || candidate?.claims.licenseId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
2
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
3
3
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
4
|
+
import { MCP_API_PATH } from "@southwind-ai/shared";
|
|
4
5
|
import { createFoundationSnapshot, loadSystemModules } from "../foundation.js";
|
|
5
6
|
import { createServerRuntime } from "../runtime.js";
|
|
6
7
|
import { createSystemRepositories } from "../system/seed.js";
|
|
@@ -150,7 +151,7 @@ async function connectClient(
|
|
|
150
151
|
token: string,
|
|
151
152
|
): Promise<Client> {
|
|
152
153
|
const transport = new StreamableHTTPClientTransport(
|
|
153
|
-
new URL(
|
|
154
|
+
new URL(`http://localhost${MCP_API_PATH}`),
|
|
154
155
|
{
|
|
155
156
|
requestInit: {
|
|
156
157
|
headers: { authorization: `Bearer ${token}` },
|
|
@@ -175,7 +176,7 @@ function mcpRequest(token?: string, origin?: string): Request {
|
|
|
175
176
|
});
|
|
176
177
|
if (token) headers.set("authorization", `Bearer ${token}`);
|
|
177
178
|
if (origin) headers.set("origin", origin);
|
|
178
|
-
return new Request(
|
|
179
|
+
return new Request(`http://localhost${MCP_API_PATH}`, {
|
|
179
180
|
method: "POST",
|
|
180
181
|
headers,
|
|
181
182
|
body: JSON.stringify({
|
|
@@ -10,7 +10,10 @@ import type {
|
|
|
10
10
|
ModuleToolInput,
|
|
11
11
|
ModuleToolResult,
|
|
12
12
|
} from "@southwind-ai/server-sdk";
|
|
13
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
MCP_SERVER_INFO,
|
|
15
|
+
type FeatureFlagDefinition,
|
|
16
|
+
} from "@southwind-ai/shared";
|
|
14
17
|
import { evaluateAccessGuards } from "../access-guards.js";
|
|
15
18
|
import type { RequestGuardContext } from "../guards.js";
|
|
16
19
|
import { findFeature } from "../route-guards.js";
|
|
@@ -69,14 +72,11 @@ export class PlatformMcpAdapter {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
private createServer(context: RequestGuardContext): Server {
|
|
72
|
-
const server = new Server(
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"仅调用当前账号可见的只读业务工具;权限和数据范围由 Windy 平台逐次校验。",
|
|
78
|
-
},
|
|
79
|
-
);
|
|
75
|
+
const server = new Server(MCP_SERVER_INFO, {
|
|
76
|
+
capabilities: { tools: {} },
|
|
77
|
+
instructions:
|
|
78
|
+
"仅调用当前账号可见的只读业务工具;权限和数据范围由 Windy 平台逐次校验。",
|
|
79
|
+
});
|
|
80
80
|
|
|
81
81
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
82
82
|
tools: await this.allowedQueryTools(context),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MCP_ROUTE_PATH } from "@southwind-ai/shared";
|
|
1
2
|
import type { RouteApp } from "../system/routes.js";
|
|
2
3
|
import { PlatformMcpAdapter } from "./adapter.js";
|
|
3
4
|
|
|
@@ -15,7 +16,7 @@ export function registerMcpRoutes(
|
|
|
15
16
|
const context = rawContext as McpRouteContext;
|
|
16
17
|
return adapter.handle(context.request, context.guardContext, context.body);
|
|
17
18
|
};
|
|
18
|
-
app.get(
|
|
19
|
-
app.post(
|
|
20
|
-
app.delete(
|
|
19
|
+
app.get(MCP_ROUTE_PATH, handle);
|
|
20
|
+
app.post(MCP_ROUTE_PATH, handle);
|
|
21
|
+
app.delete(MCP_ROUTE_PATH, handle);
|
|
21
22
|
}
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
} from "./module-host.js";
|
|
18
18
|
import type { ServerPersistence } from "./persistence.js";
|
|
19
19
|
// @windy-module system.storage begin
|
|
20
|
-
import {
|
|
20
|
+
import { createPlatformUploadPolicies } from "./storage/runtime.js";
|
|
21
21
|
// @windy-module system.storage end
|
|
22
22
|
import {
|
|
23
23
|
createSystemRepositories,
|
|
@@ -51,7 +51,7 @@ export async function bootstrapServerModules(
|
|
|
51
51
|
installations: installedBusinessModules,
|
|
52
52
|
baseUploadPolicies: [
|
|
53
53
|
// @windy-module system.storage begin
|
|
54
|
-
...
|
|
54
|
+
...createPlatformUploadPolicies(platformConfig),
|
|
55
55
|
// @windy-module system.storage end
|
|
56
56
|
],
|
|
57
57
|
});
|
|
@@ -15,7 +15,11 @@ import {
|
|
|
15
15
|
} from "@southwind-ai/server-sdk";
|
|
16
16
|
import type { AuditEvent } from "@southwind-ai/shared";
|
|
17
17
|
import type { ModuleManifest } from "@southwind-ai/modules";
|
|
18
|
-
import
|
|
18
|
+
import {
|
|
19
|
+
auditActor,
|
|
20
|
+
type RequestActor,
|
|
21
|
+
type RequestGuardContext,
|
|
22
|
+
} from "../guards.js";
|
|
19
23
|
import type { createServerRuntime } from "../runtime.js";
|
|
20
24
|
import { unavailableModuleStoragePort } from "./storage-port.js";
|
|
21
25
|
|
|
@@ -130,11 +134,7 @@ function buildAuditEvent(
|
|
|
130
134
|
id: randomUUIDv7(),
|
|
131
135
|
action,
|
|
132
136
|
outcome: "success",
|
|
133
|
-
actor:
|
|
134
|
-
id: context.actor.id,
|
|
135
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
136
|
-
name: context.actor.name,
|
|
137
|
-
},
|
|
137
|
+
actor: auditActor(context),
|
|
138
138
|
target: input.target,
|
|
139
139
|
requestId: context.requestId,
|
|
140
140
|
metadata: input.metadata,
|
|
@@ -6,7 +6,7 @@ import type {
|
|
|
6
6
|
} from "@southwind-ai/server-sdk";
|
|
7
7
|
import { evaluateAccessGuards } from "../access-guards.js";
|
|
8
8
|
import { isServerScopeContext } from "../data-access/context.js";
|
|
9
|
-
import type
|
|
9
|
+
import { auditActor, type RequestGuardContext } from "../guards.js";
|
|
10
10
|
import type { createServerRuntime } from "../runtime.js";
|
|
11
11
|
import { findFeature } from "../route-guards.js";
|
|
12
12
|
import type { ModuleStorageScope } from "./storage-port.js";
|
|
@@ -73,14 +73,7 @@ function resourceFileEvent(
|
|
|
73
73
|
id: randomUUIDv7(),
|
|
74
74
|
action: policy.auditAction,
|
|
75
75
|
outcome,
|
|
76
|
-
actor:
|
|
77
|
-
id: input.guardContext.actor.id,
|
|
78
|
-
type:
|
|
79
|
-
input.guardContext.actor.type === "anonymous"
|
|
80
|
-
? ("user" as const)
|
|
81
|
-
: input.guardContext.actor.type,
|
|
82
|
-
name: input.guardContext.actor.name,
|
|
83
|
-
},
|
|
76
|
+
actor: auditActor(input.guardContext),
|
|
84
77
|
target: {
|
|
85
78
|
type: request.resourceType,
|
|
86
79
|
id: request.resourceId,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import type { AuditEvent, FeatureFlagDefinition } from "@southwind-ai/shared";
|
|
9
9
|
import { DataScopedResourceService } from "../data-access/scoped-resource.js";
|
|
10
10
|
import { departmentDescendantResolver } from "../data-access/department-tree.js";
|
|
11
|
-
import type
|
|
11
|
+
import { auditActor, type RequestGuardContext } from "../guards.js";
|
|
12
12
|
import { evaluateRouteGuards, findFeature } from "../route-guards.js";
|
|
13
13
|
import type { createServerRuntime } from "../runtime.js";
|
|
14
14
|
import type { SystemRepositories } from "../system/seed.js";
|
|
@@ -125,11 +125,7 @@ function queryAuditEvent(
|
|
|
125
125
|
id: randomUUIDv7(),
|
|
126
126
|
action: auditAction,
|
|
127
127
|
outcome: "success",
|
|
128
|
-
actor:
|
|
129
|
-
id: context.actor.id,
|
|
130
|
-
type: context.actor.type === "anonymous" ? "user" : context.actor.type,
|
|
131
|
-
name: context.actor.name,
|
|
132
|
-
},
|
|
128
|
+
actor: auditActor(context),
|
|
133
129
|
target: { type: "system.user-directory", id: "enabled-users" },
|
|
134
130
|
requestId: context.requestId,
|
|
135
131
|
metadata: {
|