create-windy 0.2.6 → 0.2.8
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 +14 -3
- package/dist/cli.js +191 -87
- package/package.json +1 -1
- package/template/.windy-template.json +2 -2
- package/template/README.md +8 -0
- package/template/apps/server/Dockerfile +2 -0
- package/template/apps/server/package.json +10 -8
- package/template/apps/server/src/access-guards.ts +1 -1
- package/template/apps/server/src/admin/routes.test.ts +10 -0
- package/template/apps/server/src/admin/routes.ts +4 -2
- package/template/apps/server/src/agent/routes.test.ts +1 -1
- package/template/apps/server/src/agent/routes.ts +2 -2
- package/template/apps/server/src/application-services.ts +3 -0
- package/template/apps/server/src/audit/drizzle-log-store.ts +2 -2
- package/template/apps/server/src/audit/policy.ts +1 -1
- package/template/apps/server/src/audit/recovery-queue.test.ts +1 -1
- package/template/apps/server/src/audit/recovery-queue.ts +1 -1
- package/template/apps/server/src/audit/redaction.ts +1 -1
- package/template/apps/server/src/audit/reliable-writer.test.ts +1 -1
- package/template/apps/server/src/audit/reliable-writer.ts +1 -1
- package/template/apps/server/src/audit/search-summary.ts +1 -1
- package/template/apps/server/src/auth/authentication-provider.test.ts +1 -1
- package/template/apps/server/src/auth/credential-service.ts +1 -1
- package/template/apps/server/src/auth/drizzle-repository.ts +2 -2
- package/template/apps/server/src/auth/drizzle-session-operations.ts +1 -1
- package/template/apps/server/src/auth/own-session-routes.test.ts +1 -1
- package/template/apps/server/src/auth/password.ts +2 -4
- package/template/apps/server/src/auth/repository.ts +1 -1
- package/template/apps/server/src/auth/routes.test.ts +1 -1
- package/template/apps/server/src/auth/service.ts +1 -1
- package/template/apps/server/src/bulk-data/artifact-store.ts +1 -1
- package/template/apps/server/src/bulk-data/dictionary-handler.ts +1 -1
- package/template/apps/server/src/bulk-data/drizzle-repository.ts +3 -3
- package/template/apps/server/src/bulk-data/repository.ts +1 -1
- package/template/apps/server/src/bulk-data/routes.test.ts +1 -1
- package/template/apps/server/src/bulk-data/routes.ts +1 -1
- package/template/apps/server/src/bulk-data/service.test.ts +1 -1
- package/template/apps/server/src/bulk-data/service.ts +1 -1
- package/template/apps/server/src/bulk-data/types.ts +1 -1
- package/template/apps/server/src/configuration/bootstrap.ts +2 -2
- package/template/apps/server/src/configuration/definition.ts +2 -2
- package/template/apps/server/src/configuration/drizzle-repository.ts +2 -2
- package/template/apps/server/src/configuration/repository.ts +1 -1
- package/template/apps/server/src/configuration/routes.ts +1 -1
- package/template/apps/server/src/configuration/service.ts +1 -1
- package/template/apps/server/src/data-access/context.ts +2 -2
- package/template/apps/server/src/data-access/query-specification.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-repository.ts +1 -1
- package/template/apps/server/src/data-access/scoped-resource.test.ts +1 -1
- package/template/apps/server/src/data-access/scoped-resource.ts +1 -1
- package/template/apps/server/src/data-governance/export/approval-port.ts +1 -1
- package/template/apps/server/src/data-governance/export/drizzle-repository.ts +1 -1
- package/template/apps/server/src/data-governance/export/job-runtime.test.ts +2 -2
- package/template/apps/server/src/data-governance/export/job-runtime.ts +2 -3
- package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +2 -2
- package/template/apps/server/src/data-governance/export/postgres-api.integration.test.ts +1 -1
- package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +3 -3
- package/template/apps/server/src/data-governance/export/routes.ts +1 -1
- package/template/apps/server/src/data-governance/export/scope-snapshot.ts +1 -1
- package/template/apps/server/src/data-governance/export/service.ts +2 -2
- package/template/apps/server/src/data-governance/export/source.ts +1 -1
- package/template/apps/server/src/data-governance/export/types.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-access.test.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-access.ts +1 -1
- package/template/apps/server/src/data-governance/plaintext-postgres.integration.test.ts +1 -1
- package/template/apps/server/src/data-governance/user-plaintext-routes.ts +1 -1
- package/template/apps/server/src/data-governance/user-projection.ts +2 -2
- package/template/apps/server/src/example-modules.ts +25 -1
- package/template/apps/server/src/feature/manifest-feature-repository.test.ts +1 -1
- package/template/apps/server/src/feature/manifest-feature-repository.ts +1 -1
- package/template/apps/server/src/feature/routes.ts +3 -6
- package/template/apps/server/src/foundation-access.test.ts +1 -1
- package/template/apps/server/src/foundation-access.ts +1 -1
- package/template/apps/server/src/foundation.ts +21 -7
- package/template/apps/server/src/guards.test.ts +1 -1
- package/template/apps/server/src/guards.ts +3 -3
- package/template/apps/server/src/index.ts +27 -3
- package/template/{packages/modules → apps/server}/src/installed-business-modules.ts +2 -2
- package/template/apps/server/src/jobs/artifact-service.test.ts +1 -1
- package/template/apps/server/src/jobs/artifact-service.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-artifact.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-claim.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-execution-repository.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-lease.ts +2 -2
- package/template/apps/server/src/jobs/drizzle-mappers.ts +3 -3
- package/template/apps/server/src/jobs/drizzle-recovery-repository.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/drizzle-repository.ts +3 -3
- package/template/apps/server/src/jobs/execution-repository.ts +1 -1
- package/template/apps/server/src/jobs/execution.test.ts +2 -2
- package/template/apps/server/src/jobs/in-memory-claim.ts +1 -1
- package/template/apps/server/src/jobs/in-memory-execution.ts +1 -1
- package/template/apps/server/src/jobs/in-memory-lease.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.test.ts +1 -1
- package/template/apps/server/src/jobs/legacy-adapter.ts +1 -1
- package/template/apps/server/src/jobs/legacy-service-runtime.ts +4 -2
- package/template/apps/server/src/jobs/repository.ts +2 -2
- package/template/apps/server/src/jobs/service.ts +2 -2
- package/template/apps/server/src/jobs/worker.ts +3 -2
- package/template/apps/server/src/license/legacy-license-resolver.ts +1 -1
- package/template/apps/server/src/license/license-resolver.test.ts +1 -1
- package/template/apps/server/src/license/license-resolver.ts +3 -3
- package/template/apps/server/src/license/routes-offline-code.test.ts +2 -2
- package/template/apps/server/src/license/routes.ts +1 -1
- package/template/apps/server/src/license/runtime-audit-reliability.test.ts +2 -2
- package/template/apps/server/src/license/runtime-reminder.test.ts +2 -2
- package/template/apps/server/src/license/runtime-service.test.ts +2 -2
- package/template/apps/server/src/license/runtime-service.ts +2 -2
- package/template/apps/server/src/module-composition.test.ts +1 -1
- package/template/apps/server/src/module-composition.ts +1 -1
- package/template/apps/server/src/module-host.test.ts +145 -0
- package/template/apps/server/src/module-host.ts +110 -0
- package/template/apps/server/src/notification/drizzle-repository.ts +2 -2
- package/template/apps/server/src/notification/repository.ts +1 -1
- package/template/apps/server/src/notification/routes.ts +1 -1
- package/template/apps/server/src/pagination.ts +1 -1
- package/template/apps/server/src/persistence.integration.test.ts +1 -1
- package/template/apps/server/src/persistence.ts +1 -1
- package/template/apps/server/src/profile/routes.ts +1 -1
- package/template/apps/server/src/route-guards.test.ts +1 -1
- package/template/apps/server/src/route-guards.ts +1 -1
- package/template/apps/server/src/runtime-feature.ts +125 -0
- package/template/apps/server/src/runtime-license.ts +1 -1
- package/template/apps/server/src/runtime.test.ts +48 -1
- package/template/apps/server/src/runtime.ts +17 -64
- package/template/apps/server/src/scheduler/audit.ts +1 -1
- package/template/apps/server/src/scheduler/definitions.test.ts +1 -1
- package/template/apps/server/src/scheduler/definitions.ts +2 -2
- package/template/apps/server/src/scheduler/drizzle-durable-links.ts +1 -1
- package/template/apps/server/src/scheduler/drizzle-mappers.ts +1 -1
- package/template/apps/server/src/scheduler/drizzle-repository.ts +1 -1
- package/template/apps/server/src/scheduler/execution-recorder.ts +1 -1
- package/template/apps/server/src/scheduler/history.ts +1 -1
- package/template/apps/server/src/scheduler/overview.ts +1 -1
- package/template/apps/server/src/scheduler/recovery-service.test.ts +1 -1
- package/template/apps/server/src/scheduler/recovery-service.ts +1 -1
- package/template/apps/server/src/scheduler/repository.ts +1 -1
- package/template/apps/server/src/scheduler/routes.ts +1 -1
- package/template/apps/server/src/scheduler/service-idle.test.ts +1 -1
- package/template/apps/server/src/scheduler/service.test.ts +1 -1
- package/template/apps/server/src/scheduler/service.ts +1 -1
- package/template/apps/server/src/scheduler/types.ts +1 -1
- package/template/apps/server/src/search/contracts.ts +2 -2
- package/template/apps/server/src/search/host.ts +1 -1
- package/template/apps/server/src/search/registry.ts +1 -1
- package/template/apps/server/src/search/routes.test.ts +1 -1
- package/template/apps/server/src/search/routes.ts +1 -1
- package/template/apps/server/src/search/service.ts +2 -2
- package/template/apps/server/src/search/test-support.ts +2 -2
- package/template/apps/server/src/session/routes.ts +1 -1
- package/template/apps/server/src/settings/drizzle-repository.ts +2 -2
- package/template/apps/server/src/settings/repository.ts +1 -1
- package/template/apps/server/src/settings/routes.test.ts +1 -1
- package/template/apps/server/src/settings/routes.ts +1 -1
- package/template/apps/server/src/settings/runtime.ts +1 -1
- package/template/apps/server/src/storage/blob-reference-registry.test.ts +1 -1
- package/template/apps/server/src/storage/blob-reference-registry.ts +1 -1
- package/template/apps/server/src/storage/drizzle-upload-repository.integration.test.ts +1 -1
- package/template/apps/server/src/storage/drizzle-upload-repository.ts +2 -2
- package/template/apps/server/src/storage/route-support.ts +1 -1
- package/template/apps/server/src/storage/routes.test.ts +1 -1
- package/template/apps/server/src/storage/routes.ts +2 -2
- package/template/apps/server/src/storage/runtime.ts +6 -4
- package/template/apps/server/src/system/audit-routes.ts +1 -1
- package/template/apps/server/src/system/department-routes.ts +1 -1
- package/template/apps/server/src/system/drizzle-repository.ts +1 -1
- package/template/apps/server/src/system/drizzle-scoped-repository.ts +1 -1
- package/template/apps/server/src/system/drizzle-system.ts +1 -1
- package/template/apps/server/src/system/entities.ts +1 -1
- package/template/apps/server/src/system/identity-route-config.ts +1 -1
- package/template/apps/server/src/system/interface-catalog.test.ts +3 -3
- package/template/apps/server/src/system/interface-catalog.ts +2 -2
- package/template/apps/server/src/system/menu-registry.ts +1 -1
- package/template/apps/server/src/system/menu-routes.ts +1 -1
- package/template/apps/server/src/system/operations.test.ts +1 -1
- package/template/apps/server/src/system/operations.ts +1 -1
- package/template/apps/server/src/system/redis-health.ts +1 -1
- package/template/apps/server/src/system/repository.ts +1 -1
- package/template/apps/server/src/system/resource-route-support.ts +1 -1
- package/template/apps/server/src/system/route-helpers.ts +1 -1
- package/template/apps/server/src/system/route-types.ts +1 -1
- package/template/apps/server/src/system/routes.test.ts +1 -1
- package/template/apps/server/src/system/routes.ts +1 -1
- package/template/apps/server/src/system/user-projection.ts +1 -1
- package/template/apps/server/src/work-order/data-scope.integration.test.ts +1 -1
- package/template/apps/server/src/work-order/drizzle-repository.integration.test.ts +1 -1
- package/template/apps/server/src/work-order/drizzle-repository.ts +2 -2
- package/template/apps/server/src/work-order/foundation-modules.test.ts +15 -5
- package/template/apps/server/src/work-order/repository.ts +1 -1
- package/template/apps/server/src/work-order/routes.test.ts +2 -2
- package/template/apps/server/src/work-order/routes.ts +2 -2
- package/template/apps/server/src/work-order/search-api.test.ts +3 -3
- package/template/apps/server/src/work-order/search-provider.test.ts +3 -3
- package/template/apps/server/src/work-order/service.ts +1 -1
- package/template/apps/server/src/work-order/task.test.ts +1 -1
- package/template/apps/server/src/work-order/task.ts +2 -2
- package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +2 -2
- package/template/apps/server/src/workflow/drizzle-repository.ts +3 -3
- package/template/apps/server/src/workflow/effects.test.ts +1 -1
- package/template/apps/server/src/workflow/effects.ts +1 -1
- package/template/apps/server/src/workflow/repository.ts +2 -2
- package/template/apps/server/src/workflow/routes.ts +3 -3
- package/template/apps/server/src/workflow/service.ts +1 -1
- package/template/apps/server/src/workflow/timeout-runtime.test.ts +1 -1
- package/template/apps/server/src/workflow/timeout-runtime.ts +2 -2
- package/template/apps/web/Dockerfile +2 -0
- package/template/apps/web/package.json +4 -4
- package/template/apps/web/runtime-server.ts +1 -1
- package/template/apps/web/src/app/layouts/AppLayout.vue +6 -0
- package/template/apps/web/src/components/auth/PasswordChangeForm.vue +52 -7
- package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +43 -0
- package/template/apps/web/src/components/common/DataPagination.vue +1 -1
- package/template/apps/web/src/components/license/LicenseActivationCodeForm.vue +1 -1
- package/template/apps/web/src/components/license/LicenseFlow.webtest.ts +2 -2
- package/template/apps/web/src/composables/useModuleConfigurations.ts +1 -1
- package/template/apps/web/src/composables/useWatermarkSettings.ts +1 -0
- package/template/apps/web/src/composables/watermark-settings.ts +6 -0
- package/template/apps/web/src/composables/watermark-settings.webtest.ts +27 -1
- package/template/apps/web/src/layout/AppSidebar.vue +12 -3
- package/template/apps/web/src/layout/AppSidebar.webtest.ts +11 -0
- package/template/apps/web/src/layout/GlobalWatermark.vue +25 -8
- package/template/apps/web/src/layout/GlobalWatermark.webtest.ts +109 -0
- package/template/apps/web/src/layout/NavigationSearchDialog.vue +1 -1
- package/template/apps/web/src/layout/NotificationMenu.vue +1 -1
- package/template/apps/web/src/layout/navigation.ts +5 -1
- package/template/apps/web/src/layout/navigation.webtest.ts +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigRollbackDialog.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.webtest.ts +1 -1
- package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +1 -1
- package/template/apps/web/src/pages/dashboard/dashboard-presenter.webtest.ts +1 -1
- package/template/apps/web/src/pages/settings/WatermarkSettingsSection.vue +21 -0
- package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +2 -2
- package/template/apps/web/src/pages/system/components/AuditActionCell.vue +7 -2
- package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +7 -2
- package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerOperationsPanel.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerOperationsPanel.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerRunTable.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerRunTable.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerStaleRecoveryForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/SchedulerStaleRecoveryForm.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +1 -1
- package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +10 -3
- package/template/apps/web/src/pages/system/components/role-policy/RolePermissionTree.vue +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/RolePermissionTree.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/RolePolicyForm.vue +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/permission-tree.ts +1 -1
- package/template/apps/web/src/pages/system/components/role-policy/permission-tree.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/composables/useSchedulerOperations.ts +2 -2
- package/template/apps/web/src/pages/system/composables/useSchedulerOperations.webtest.ts +1 -1
- package/template/apps/web/src/pages/system/composables/useSystemResource.ts +1 -1
- package/template/apps/web/src/router/manifest-routes.ts +1 -1
- package/template/apps/web/src/router/manifest-routes.webtest.ts +1 -1
- package/template/apps/web/src/services/access-api.ts +10 -1
- package/template/apps/web/src/services/configuration-api.ts +1 -1
- package/template/apps/web/src/services/license-api.ts +1 -1
- package/template/apps/web/src/services/notification-api.ts +2 -2
- package/template/apps/web/src/services/operations-api.ts +1 -1
- package/template/apps/web/src/services/platform-settings-api.ts +2 -2
- package/template/apps/web/src/services/profile-api.ts +2 -2
- package/template/docs/architecture/durable-jobs.md +5 -0
- package/template/docs/architecture/module-manifest-composition.md +14 -7
- package/template/docs/architecture/object-storage.md +1 -1
- package/template/docs/development/crud-generator.md +4 -1
- package/template/docs/platform/audit-reliability.md +1 -1
- package/template/package.json +2 -1
- package/template/packages/config/package.json +15 -4
- package/template/packages/config/src/platform.ts +1 -1
- package/template/packages/crud-generator/package.json +1 -1
- package/template/packages/crud-generator/src/generator.test.ts +16 -0
- package/template/packages/crud-generator/src/templates/domain.ts +1 -1
- package/template/packages/crud-generator/src/templates/manifest.ts +2 -2
- package/template/packages/crud-generator/src/templates/project.ts +3 -3
- package/template/packages/crud-generator/src/templates/repository.ts +1 -1
- package/template/packages/crud-generator/src/templates/routes.ts +2 -2
- package/template/packages/crud-generator/src/validation.ts +22 -1
- package/template/packages/database/package.json +15 -5
- package/template/packages/database/src/bundle-validation.ts +218 -0
- package/template/packages/database/src/dialects.ts +1 -1
- package/template/packages/database/src/runner.test.ts +160 -13
- package/template/packages/database/src/runner.ts +95 -77
- package/template/packages/database/src/schema/governed-exports.ts +1 -1
- package/template/packages/database/src/schema/identity.ts +1 -1
- package/template/packages/database/src/schema/license-v1.ts +1 -1
- package/template/packages/database/src/schema/notifications.ts +1 -1
- package/template/packages/database/src/schema/platform-settings.ts +1 -1
- package/template/packages/database/src/schema/workflow.ts +1 -1
- package/template/packages/database/src/sql-store.ts +1 -1
- package/template/packages/database/src/store.ts +1 -1
- package/template/packages/example-work-order/index.ts +1 -0
- package/template/packages/example-work-order/package.json +4 -3
- package/template/packages/example-work-order/src/contract.ts +1 -1
- package/template/packages/example-work-order/src/manifest.ts +8 -3
- package/template/packages/example-work-order/src/migration-bundle.test.ts +43 -0
- package/template/packages/example-work-order/src/migration-bundle.ts +64 -0
- package/template/packages/jobs/index.ts +2 -0
- package/template/packages/jobs/package.json +29 -0
- package/template/packages/jobs/src/handlers.test.ts +72 -0
- package/template/{apps/server/src/jobs/handler-registry.ts → packages/jobs/src/handlers.ts} +4 -4
- package/template/{apps/server/src/jobs → packages/jobs/src}/types.ts +7 -1
- package/template/packages/{license-sdk → jobs}/tsconfig.json +0 -1
- package/template/packages/modules/index.ts +1 -1
- package/template/packages/modules/package.json +15 -5
- package/template/packages/modules/src/catalog-validation.ts +123 -0
- package/template/packages/modules/src/composition.test.ts +75 -2
- package/template/packages/modules/src/composition.ts +4 -2
- package/template/packages/modules/src/manifest.ts +7 -12
- package/template/packages/modules/src/menu-composition.ts +1 -1
- package/template/packages/modules/src/system-api-binding.ts +1 -1
- package/template/packages/modules/src/system-api-data-governance.ts +1 -1
- package/template/packages/modules/src/system-api-permissions.ts +1 -1
- package/template/packages/modules/src/system-data-governance.ts +1 -1
- package/template/packages/modules/src/system-features.ts +1 -1
- package/template/packages/modules/src/system-governed-export-bindings.ts +1 -1
- package/template/packages/modules/src/system-module-catalog.ts +1 -1
- package/template/packages/server-sdk/index.ts +5 -0
- package/template/packages/server-sdk/package.json +25 -0
- package/template/packages/server-sdk/src/module-host.ts +29 -0
- package/template/packages/server-sdk/tsconfig.json +16 -0
- package/template/packages/shared/index.ts +3 -0
- package/template/packages/shared/package.json +14 -3
- package/template/packages/shared/src/audit.ts +22 -1
- package/template/packages/shared/src/license-catalog.test.ts +17 -0
- package/template/packages/shared/src/license-catalog.ts +33 -0
- package/template/packages/shared/src/migration.ts +39 -0
- package/template/packages/shared/src/password.ts +4 -0
- package/template/packages/storage/package.json +14 -3
- package/template/packages/license-sdk/index.ts +0 -13
- package/template/packages/license-sdk/package.json +0 -18
- package/template/packages/license-sdk/src/bounded-json-body.test.ts +0 -65
- package/template/packages/license-sdk/src/bounded-json-body.ts +0 -135
- package/template/packages/license-sdk/src/canonical-json.test.ts +0 -28
- package/template/packages/license-sdk/src/canonical-json.ts +0 -104
- package/template/packages/license-sdk/src/client.test.ts +0 -82
- package/template/packages/license-sdk/src/client.ts +0 -86
- package/template/packages/license-sdk/src/device.ts +0 -46
- package/template/packages/license-sdk/src/file-storage.test.ts +0 -80
- package/template/packages/license-sdk/src/file-storage.ts +0 -77
- package/template/packages/license-sdk/src/installation.test.ts +0 -241
- package/template/packages/license-sdk/src/installation.ts +0 -150
- package/template/packages/license-sdk/src/license-parser.ts +0 -56
- package/template/packages/license-sdk/src/offline-code-base32.ts +0 -57
- package/template/packages/license-sdk/src/offline-code-decompression.ts +0 -55
- package/template/packages/license-sdk/src/offline-code-limits.ts +0 -10
- package/template/packages/license-sdk/src/offline-code-wire.ts +0 -72
- package/template/packages/license-sdk/src/offline-code.test.ts +0 -172
- package/template/packages/license-sdk/src/offline-code.ts +0 -319
- package/template/packages/license-sdk/src/payload.test.ts +0 -72
- package/template/packages/license-sdk/src/payload.ts +0 -201
- package/template/packages/license-sdk/src/storage.ts +0 -64
- package/template/packages/license-sdk/src/test-fixtures.ts +0 -59
- package/template/packages/license-sdk/src/v1-test-vector.ts +0 -49
- package/template/packages/license-sdk/src/validation.test.ts +0 -62
- package/template/packages/license-sdk/src/validation.ts +0 -143
- package/template/packages/license-sdk/src/verification.test.ts +0 -97
- package/template/packages/license-sdk/src/verification.ts +0 -199
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { zlibSync } from "fflate";
|
|
3
|
-
import type { SignedLicenseV1 } from "@windy/shared";
|
|
4
|
-
import { canonicalizeJson } from "./canonical-json.js";
|
|
5
|
-
import { LicenseInstaller } from "./installation.js";
|
|
6
|
-
import {
|
|
7
|
-
decodeOfflineActivationCode,
|
|
8
|
-
encodeOfflineActivationCode,
|
|
9
|
-
OfflineActivationCodeError,
|
|
10
|
-
offlineActivationCodeErrorMessage,
|
|
11
|
-
splitOfflineActivationCode,
|
|
12
|
-
} from "./offline-code.js";
|
|
13
|
-
import { MemoryAtomicLicenseStore } from "./storage.js";
|
|
14
|
-
import { createSignedLicenseV1 } from "./test-fixtures.js";
|
|
15
|
-
import { LICENSE_V1_TEST_VECTOR } from "./v1-test-vector.js";
|
|
16
|
-
import { encodeBase32, segmentChecksum } from "./offline-code-base32.js";
|
|
17
|
-
|
|
18
|
-
const FIXED_CODE =
|
|
19
|
-
"W1-00002BKRVA5NDJP99H7CTATEHPY-MQMPK9ES4QRMBSKAK952APFSHGP-5TKN1DS14JRJEB5Z50A2H4JWBAJ-9FS77MR1ABGDAG0HCXEG60RGAYR-AKV7XXXBMVPPTC4H6QXPYWZNVFB-DZ8B73QD830858QW5JG4517A08A-0PTMQHFVKW7SXHVJ7GZJY958JD1-4CKJHW8DT8R34CSJG9MG9S6WRR6-KJ8SK5XA1D8G55C0N0RXED9M17V-0RK1C024CYG60W352R99C811TGT-MW955JRJYE9N0M43SYBTGSSXV1N-JTZXJFF38PB9VKXVQC025A99CV9-PMKKY85Q4MZK9A07C58NCZKEF8M-TF6WTKT9SQ8081JC5G0PD6PX9AM-6BBD667KDYH64M0ZSMDW5Y47BTT-6GBJVKXKQDYRHDM9WNJS4R68Y4E-5VFNY9XMXJQAYGYQ2D5FJ86XVBK-7ZBW4RY5QPAVYVNCEJE5RE4AKEW-80AHEBNCBG5X";
|
|
20
|
-
|
|
21
|
-
describe("W1 分组离线激活码", () => {
|
|
22
|
-
test("固定 V1 向量确定性编码并还原同一 SignedLicenseV1", () => {
|
|
23
|
-
const license = fixedLicense();
|
|
24
|
-
expect(encodeOfflineActivationCode(license)).toBe(FIXED_CODE);
|
|
25
|
-
const decoded = decodeOfflineActivationCode(FIXED_CODE);
|
|
26
|
-
expect(decoded.license).toEqual(license);
|
|
27
|
-
expect(decoded.canonicalLicense).toBe(canonicalizeJson(license));
|
|
28
|
-
expect(splitOfflineActivationCode(FIXED_CODE)).toHaveLength(20);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
test("忽略大小写、空白、换行和连字符并安全归一化易混字符", () => {
|
|
32
|
-
const handwritten = FIXED_CODE.toLowerCase()
|
|
33
|
-
.replace(/^w1-/, "wI\n")
|
|
34
|
-
.replace(/0/g, "o")
|
|
35
|
-
.replace(/-/g, " \n");
|
|
36
|
-
expect(decodeOfflineActivationCode(handwritten).license).toEqual(
|
|
37
|
-
fixedLicense(),
|
|
38
|
-
);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
test("单字符错误定位到具体分组,不自动修复", () => {
|
|
42
|
-
const damaged = replaceCharacter(FIXED_CODE, 3, "1");
|
|
43
|
-
expectCodeError(damaged, "group-checksum", 1);
|
|
44
|
-
try {
|
|
45
|
-
decodeOfflineActivationCode(damaged);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
expect(offlineActivationCodeErrorMessage(error)).toContain("第 1 段");
|
|
48
|
-
}
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("拒绝校验字符错误、截断和未知版本", () => {
|
|
52
|
-
expectCodeError(replaceCharacter(FIXED_CODE, 27, "1"), "group-checksum", 1);
|
|
53
|
-
expect(() => decodeOfflineActivationCode(FIXED_CODE.slice(0, -1))).toThrow(
|
|
54
|
-
OfflineActivationCodeError,
|
|
55
|
-
);
|
|
56
|
-
expectCodeError(FIXED_CODE.replace(/^W1/, "W2"), "unsupported-version");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
test("在规范化和 Base32 解码前拒绝超长打印码", () => {
|
|
60
|
-
expectCodeError(`W1-${"A".repeat(32 * 1024)}`, "input-too-long");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
test("增量解压在压缩炸弹超过声明长度时立即拒绝", () => {
|
|
64
|
-
const compressed = zlibSync(new Uint8Array(2 * 1024 * 1024));
|
|
65
|
-
const bomb = codeFromCompressedPayload(compressed, 1024);
|
|
66
|
-
expectCodeError(bomb, "decompression-limit");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
test("Base32 解码后拒绝超过压缩数据上限的信封", () => {
|
|
70
|
-
const oversizedCompressed = new Uint8Array(16 * 1024 + 1);
|
|
71
|
-
const code = codeFromCompressedPayload(oversizedCompressed, 1024);
|
|
72
|
-
expectCodeError(code, "decompression-limit");
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("编码层不认可篡改,验签失败和错误设备码都不覆盖 current", async () => {
|
|
76
|
-
const current = createSignedLicenseV1({
|
|
77
|
-
...LICENSE_V1_TEST_VECTOR.claims,
|
|
78
|
-
licenseId: "current",
|
|
79
|
-
machineCode: "machine_1",
|
|
80
|
-
issuedAt: "2026-07-01T00:00:00.000Z",
|
|
81
|
-
});
|
|
82
|
-
const candidate = createSignedLicenseV1({
|
|
83
|
-
...LICENSE_V1_TEST_VECTOR.claims,
|
|
84
|
-
licenseId: "candidate",
|
|
85
|
-
machineCode: "machine_2",
|
|
86
|
-
issuedAt: "2026-07-20T00:00:00.000Z",
|
|
87
|
-
});
|
|
88
|
-
const original = canonicalizeJson(current.license);
|
|
89
|
-
const store = new MemoryAtomicLicenseStore(original);
|
|
90
|
-
const decoded = decodeOfflineActivationCode(
|
|
91
|
-
encodeOfflineActivationCode(candidate.license),
|
|
92
|
-
);
|
|
93
|
-
const result = await new LicenseInstaller({
|
|
94
|
-
store,
|
|
95
|
-
productCode: "windy-platform",
|
|
96
|
-
machineCode: "machine_1",
|
|
97
|
-
publicKeys: { key_v1: candidate.publicKeyPem },
|
|
98
|
-
now: new Date("2026-08-01T00:00:00.000Z"),
|
|
99
|
-
}).install(decoded.canonicalLicense);
|
|
100
|
-
|
|
101
|
-
expect(result.status).toBe("rejected");
|
|
102
|
-
expect(result.failures).toContain("machine-mismatch");
|
|
103
|
-
expect(await store.readCurrent()).toBe(original);
|
|
104
|
-
|
|
105
|
-
const tampered = {
|
|
106
|
-
...candidate.license,
|
|
107
|
-
claims: {
|
|
108
|
-
...candidate.license.claims,
|
|
109
|
-
customer: { ...candidate.license.claims.customer, name: "篡改客户" },
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
const decodedTampered = decodeOfflineActivationCode(
|
|
113
|
-
encodeOfflineActivationCode(tampered),
|
|
114
|
-
);
|
|
115
|
-
const tamperedResult = await new LicenseInstaller({
|
|
116
|
-
store,
|
|
117
|
-
productCode: "windy-platform",
|
|
118
|
-
machineCode: "machine_2",
|
|
119
|
-
publicKeys: { key_v1: candidate.publicKeyPem },
|
|
120
|
-
now: new Date("2026-08-01T00:00:00.000Z"),
|
|
121
|
-
}).install(decodedTampered.canonicalLicense);
|
|
122
|
-
expect(tamperedResult.failures).toContain("invalid-signature");
|
|
123
|
-
expect(await store.readCurrent()).toBe(original);
|
|
124
|
-
});
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
function fixedLicense(): SignedLicenseV1 {
|
|
128
|
-
return {
|
|
129
|
-
formatVersion: 1,
|
|
130
|
-
algorithm: "ED25519",
|
|
131
|
-
publicKeyId: LICENSE_V1_TEST_VECTOR.keyId,
|
|
132
|
-
claims: LICENSE_V1_TEST_VECTOR.claims,
|
|
133
|
-
signature: LICENSE_V1_TEST_VECTOR.signature,
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function replaceCharacter(value: string, index: number, next: string): string {
|
|
138
|
-
const compact = value.replaceAll("-", "");
|
|
139
|
-
const replaced = compact.slice(0, index) + next + compact.slice(index + 1);
|
|
140
|
-
return replaced;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function expectCodeError(
|
|
144
|
-
value: string,
|
|
145
|
-
code: OfflineActivationCodeError["code"],
|
|
146
|
-
groupIndex?: number,
|
|
147
|
-
): void {
|
|
148
|
-
try {
|
|
149
|
-
decodeOfflineActivationCode(value);
|
|
150
|
-
throw new Error("预期离线激活码被拒绝");
|
|
151
|
-
} catch (error) {
|
|
152
|
-
expect(error).toBeInstanceOf(OfflineActivationCodeError);
|
|
153
|
-
expect((error as OfflineActivationCodeError).code).toBe(code);
|
|
154
|
-
expect((error as OfflineActivationCodeError).groupIndex).toBe(groupIndex);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function codeFromCompressedPayload(
|
|
159
|
-
compressed: Uint8Array,
|
|
160
|
-
expectedLength: number,
|
|
161
|
-
): string {
|
|
162
|
-
const envelope = new Uint8Array(4 + compressed.length + 64 + 4);
|
|
163
|
-
new DataView(envelope.buffer).setUint32(0, expectedLength);
|
|
164
|
-
envelope.set(compressed, 4);
|
|
165
|
-
const encoded = encodeBase32(envelope);
|
|
166
|
-
const groups: string[] = [];
|
|
167
|
-
for (let offset = 0; offset < encoded.length; offset += 25) {
|
|
168
|
-
const data = encoded.slice(offset, offset + 25);
|
|
169
|
-
groups.push(data + segmentChecksum(data, groups.length));
|
|
170
|
-
}
|
|
171
|
-
return `W1-${groups.join("-")}`;
|
|
172
|
-
}
|
|
@@ -1,319 +0,0 @@
|
|
|
1
|
-
import { strFromU8, strToU8, zlibSync } from "fflate";
|
|
2
|
-
import type { SignedLicense } from "@windy/shared";
|
|
3
|
-
import { canonicalizeJson } from "./canonical-json.js";
|
|
4
|
-
import { parseSignedLicense } from "./license-parser.js";
|
|
5
|
-
import {
|
|
6
|
-
decodeBase32,
|
|
7
|
-
encodeBase32,
|
|
8
|
-
isBase32Character,
|
|
9
|
-
segmentChecksum,
|
|
10
|
-
} from "./offline-code-base32.js";
|
|
11
|
-
import {
|
|
12
|
-
decodeEd25519Signature,
|
|
13
|
-
ED25519_SIGNATURE_BYTES,
|
|
14
|
-
encodeEd25519Signature,
|
|
15
|
-
fromCompactTuple,
|
|
16
|
-
toCompactTuple,
|
|
17
|
-
} from "./offline-code-wire.js";
|
|
18
|
-
import { OFFLINE_ACTIVATION_CODE_LIMITS } from "./offline-code-limits.js";
|
|
19
|
-
import {
|
|
20
|
-
decompressOfflineCode,
|
|
21
|
-
OfflineCodeDecompressionLimitError,
|
|
22
|
-
} from "./offline-code-decompression.js";
|
|
23
|
-
|
|
24
|
-
const PREFIX = "W1";
|
|
25
|
-
const DATA_CHARACTERS_PER_SEGMENT = 25;
|
|
26
|
-
const CHECKSUM_CHARACTERS = 2;
|
|
27
|
-
const MAX_CANONICAL_BYTES = OFFLINE_ACTIVATION_CODE_LIMITS.decompressedBytes;
|
|
28
|
-
|
|
29
|
-
export type OfflineActivationCodeErrorCode =
|
|
30
|
-
| "empty"
|
|
31
|
-
| "invalid-prefix"
|
|
32
|
-
| "unsupported-version"
|
|
33
|
-
| "invalid-character"
|
|
34
|
-
| "invalid-length"
|
|
35
|
-
| "input-too-long"
|
|
36
|
-
| "decompression-limit"
|
|
37
|
-
| "group-checksum"
|
|
38
|
-
| "payload-checksum"
|
|
39
|
-
| "damaged-payload"
|
|
40
|
-
| "invalid-license";
|
|
41
|
-
|
|
42
|
-
export class OfflineActivationCodeError extends Error {
|
|
43
|
-
constructor(
|
|
44
|
-
readonly code: OfflineActivationCodeErrorCode,
|
|
45
|
-
message: string,
|
|
46
|
-
readonly groupIndex?: number,
|
|
47
|
-
) {
|
|
48
|
-
super(message);
|
|
49
|
-
this.name = "OfflineActivationCodeError";
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface DecodedOfflineActivationCode {
|
|
54
|
-
license: SignedLicense;
|
|
55
|
-
canonicalLicense: string;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function encodeOfflineActivationCode(license: SignedLicense): string {
|
|
59
|
-
const canonicalLicense = canonicalizeJson(license);
|
|
60
|
-
const validated = parseSignedLicense(canonicalLicense);
|
|
61
|
-
if (!validated) {
|
|
62
|
-
throw new OfflineActivationCodeError("invalid-license", "License 格式无效");
|
|
63
|
-
}
|
|
64
|
-
const raw = strToU8(JSON.stringify(toCompactTuple(validated)));
|
|
65
|
-
if (raw.byteLength > MAX_CANONICAL_BYTES) {
|
|
66
|
-
throw new OfflineActivationCodeError("invalid-license", "License 内容过长");
|
|
67
|
-
}
|
|
68
|
-
const compressed = zlibSync(raw, { level: 9 });
|
|
69
|
-
if (compressed.byteLength > OFFLINE_ACTIVATION_CODE_LIMITS.compressedBytes) {
|
|
70
|
-
throw new OfflineActivationCodeError(
|
|
71
|
-
"invalid-license",
|
|
72
|
-
"License 压缩内容过长",
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
let signature: Uint8Array;
|
|
76
|
-
try {
|
|
77
|
-
signature = decodeEd25519Signature(validated.signature);
|
|
78
|
-
} catch (error) {
|
|
79
|
-
throw new OfflineActivationCodeError(
|
|
80
|
-
"invalid-license",
|
|
81
|
-
error instanceof Error ? error.message : "License ED25519 签名无效",
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
const envelope = new Uint8Array(
|
|
85
|
-
4 + compressed.byteLength + ED25519_SIGNATURE_BYTES + 4,
|
|
86
|
-
);
|
|
87
|
-
writeUint32(envelope, 0, raw.byteLength);
|
|
88
|
-
envelope.set(compressed, 4);
|
|
89
|
-
envelope.set(signature, 4 + compressed.byteLength);
|
|
90
|
-
writeUint32(
|
|
91
|
-
envelope,
|
|
92
|
-
envelope.byteLength - 4,
|
|
93
|
-
crc32(strToU8(canonicalLicense)),
|
|
94
|
-
);
|
|
95
|
-
return formatGroups(encodeBase32(envelope));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function decodeOfflineActivationCode(
|
|
99
|
-
input: string,
|
|
100
|
-
): DecodedOfflineActivationCode {
|
|
101
|
-
const normalized = normalizeInput(input);
|
|
102
|
-
const encoded = verifyAndJoinGroups(normalized.slice(PREFIX.length));
|
|
103
|
-
let envelope: Uint8Array;
|
|
104
|
-
try {
|
|
105
|
-
envelope = decodeBase32(encoded);
|
|
106
|
-
} catch {
|
|
107
|
-
throw new OfflineActivationCodeError(
|
|
108
|
-
"damaged-payload",
|
|
109
|
-
"激活码末尾数据不完整或已损坏",
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
if (envelope.byteLength < 10) {
|
|
113
|
-
throw new OfflineActivationCodeError(
|
|
114
|
-
"invalid-length",
|
|
115
|
-
"激活码数据长度不足",
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
const expectedLength = readUint32(envelope, 0);
|
|
119
|
-
if (expectedLength === 0 || expectedLength > MAX_CANONICAL_BYTES) {
|
|
120
|
-
throw new OfflineActivationCodeError(
|
|
121
|
-
"damaged-payload",
|
|
122
|
-
"激活码声明的内容长度无效",
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
let raw: Uint8Array;
|
|
126
|
-
try {
|
|
127
|
-
raw = decompressOfflineCode(
|
|
128
|
-
envelope.subarray(4, -(ED25519_SIGNATURE_BYTES + 4)),
|
|
129
|
-
expectedLength,
|
|
130
|
-
);
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (error instanceof OfflineCodeDecompressionLimitError) {
|
|
133
|
-
throw new OfflineActivationCodeError(
|
|
134
|
-
"decompression-limit",
|
|
135
|
-
"离线激活码内容超过安全上限",
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
throw new OfflineActivationCodeError(
|
|
139
|
-
"damaged-payload",
|
|
140
|
-
"激活码压缩内容已损坏",
|
|
141
|
-
);
|
|
142
|
-
}
|
|
143
|
-
if (raw.byteLength !== expectedLength) {
|
|
144
|
-
throw new OfflineActivationCodeError(
|
|
145
|
-
"damaged-payload",
|
|
146
|
-
"激活码内容长度校验失败",
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
let compact: unknown;
|
|
150
|
-
try {
|
|
151
|
-
compact = JSON.parse(strFromU8(raw));
|
|
152
|
-
} catch {
|
|
153
|
-
throw new OfflineActivationCodeError(
|
|
154
|
-
"damaged-payload",
|
|
155
|
-
"激活码紧凑内容已损坏",
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
const signature = encodeEd25519Signature(
|
|
159
|
-
envelope.subarray(-(ED25519_SIGNATURE_BYTES + 4), -4),
|
|
160
|
-
);
|
|
161
|
-
let canonicalLicense: string;
|
|
162
|
-
try {
|
|
163
|
-
canonicalLicense = canonicalizeJson(fromCompactTuple(compact, signature));
|
|
164
|
-
} catch (error) {
|
|
165
|
-
throw new OfflineActivationCodeError(
|
|
166
|
-
"damaged-payload",
|
|
167
|
-
error instanceof Error ? error.message : "激活码紧凑字段无效",
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
if (
|
|
171
|
-
crc32(strToU8(canonicalLicense)) !==
|
|
172
|
-
readUint32(envelope, envelope.byteLength - 4)
|
|
173
|
-
) {
|
|
174
|
-
throw new OfflineActivationCodeError(
|
|
175
|
-
"payload-checksum",
|
|
176
|
-
"激活码整体校验失败",
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
const license = parseSignedLicense(canonicalLicense);
|
|
180
|
-
if (!license || canonicalizeJson(license) !== canonicalLicense) {
|
|
181
|
-
throw new OfflineActivationCodeError(
|
|
182
|
-
"invalid-license",
|
|
183
|
-
"激活码不是规范的 License",
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
return { license, canonicalLicense };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
export function splitOfflineActivationCode(code: string): string[] {
|
|
190
|
-
const normalized = normalizeInput(code);
|
|
191
|
-
const body = normalized.slice(PREFIX.length);
|
|
192
|
-
return [PREFIX, ...chunkGroups(body)];
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function offlineActivationCodeErrorMessage(error: unknown): string {
|
|
196
|
-
if (!(error instanceof OfflineActivationCodeError))
|
|
197
|
-
return "离线激活码无法识别";
|
|
198
|
-
if (error.code === "group-checksum" && error.groupIndex) {
|
|
199
|
-
return `第 ${error.groupIndex} 段输入有误,请对照签发页面重新输入`;
|
|
200
|
-
}
|
|
201
|
-
return error.message;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
function normalizeInput(input: string): string {
|
|
205
|
-
if (input.length > OFFLINE_ACTIVATION_CODE_LIMITS.printedCharacters) {
|
|
206
|
-
throw new OfflineActivationCodeError("input-too-long", "离线激活码过长");
|
|
207
|
-
}
|
|
208
|
-
const compact = input.toUpperCase().replace(/[\s-]/g, "");
|
|
209
|
-
if (compact.length > OFFLINE_ACTIVATION_CODE_LIMITS.normalizedCharacters) {
|
|
210
|
-
throw new OfflineActivationCodeError("input-too-long", "离线激活码过长");
|
|
211
|
-
}
|
|
212
|
-
if (!compact)
|
|
213
|
-
throw new OfflineActivationCodeError("empty", "请输入离线激活码");
|
|
214
|
-
const normalized = compact.replace(/O/g, "0").replace(/[IL]/g, "1");
|
|
215
|
-
if (!normalized.startsWith("W")) {
|
|
216
|
-
throw new OfflineActivationCodeError(
|
|
217
|
-
"invalid-prefix",
|
|
218
|
-
"激活码必须以 W1 开头",
|
|
219
|
-
);
|
|
220
|
-
}
|
|
221
|
-
if (!normalized.startsWith(PREFIX)) {
|
|
222
|
-
throw new OfflineActivationCodeError(
|
|
223
|
-
"unsupported-version",
|
|
224
|
-
"不支持该激活码版本",
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
for (const character of normalized.slice(PREFIX.length)) {
|
|
228
|
-
if (!isBase32Character(character)) {
|
|
229
|
-
throw new OfflineActivationCodeError(
|
|
230
|
-
"invalid-character",
|
|
231
|
-
`激活码包含无法识别的字符:${character}`,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
return normalized;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function formatGroups(encoded: string): string {
|
|
239
|
-
const segments: string[] = [];
|
|
240
|
-
for (
|
|
241
|
-
let offset = 0;
|
|
242
|
-
offset < encoded.length;
|
|
243
|
-
offset += DATA_CHARACTERS_PER_SEGMENT
|
|
244
|
-
) {
|
|
245
|
-
const data = encoded.slice(offset, offset + DATA_CHARACTERS_PER_SEGMENT);
|
|
246
|
-
segments.push(data + segmentChecksum(data, segments.length));
|
|
247
|
-
}
|
|
248
|
-
return `${PREFIX}-${segments.join("-")}`;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function verifyAndJoinGroups(body: string): string {
|
|
252
|
-
if (body.length < 2) {
|
|
253
|
-
throw new OfflineActivationCodeError(
|
|
254
|
-
"invalid-length",
|
|
255
|
-
"激活码缺少数据分组",
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
const segments = chunkGroups(body);
|
|
259
|
-
return segments
|
|
260
|
-
.map((segment, index) => {
|
|
261
|
-
if (
|
|
262
|
-
segment.length <= CHECKSUM_CHARACTERS ||
|
|
263
|
-
segment.length > DATA_CHARACTERS_PER_SEGMENT + CHECKSUM_CHARACTERS
|
|
264
|
-
) {
|
|
265
|
-
throw new OfflineActivationCodeError(
|
|
266
|
-
"invalid-length",
|
|
267
|
-
"激活码分段长度无效",
|
|
268
|
-
index + 1,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
const data = segment.slice(0, -CHECKSUM_CHARACTERS);
|
|
272
|
-
if (
|
|
273
|
-
segment.slice(-CHECKSUM_CHARACTERS) !== segmentChecksum(data, index)
|
|
274
|
-
) {
|
|
275
|
-
throw new OfflineActivationCodeError(
|
|
276
|
-
"group-checksum",
|
|
277
|
-
`第 ${index + 1} 段校验失败`,
|
|
278
|
-
index + 1,
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
return data;
|
|
282
|
-
})
|
|
283
|
-
.join("");
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
function chunkGroups(body: string): string[] {
|
|
287
|
-
const size = DATA_CHARACTERS_PER_SEGMENT + CHECKSUM_CHARACTERS;
|
|
288
|
-
const groups: string[] = [];
|
|
289
|
-
for (let offset = 0; offset < body.length; offset += size) {
|
|
290
|
-
groups.push(body.slice(offset, offset + size));
|
|
291
|
-
}
|
|
292
|
-
return groups;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function crc32(bytes: Uint8Array): number {
|
|
296
|
-
let crc = 0xffffffff;
|
|
297
|
-
for (const byte of bytes) {
|
|
298
|
-
crc ^= byte;
|
|
299
|
-
for (let bit = 0; bit < 8; bit += 1) {
|
|
300
|
-
crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return (crc ^ 0xffffffff) >>> 0;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function readUint32(bytes: Uint8Array, offset: number): number {
|
|
307
|
-
return new DataView(
|
|
308
|
-
bytes.buffer,
|
|
309
|
-
bytes.byteOffset,
|
|
310
|
-
bytes.byteLength,
|
|
311
|
-
).getUint32(offset);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function writeUint32(bytes: Uint8Array, offset: number, value: number): void {
|
|
315
|
-
new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).setUint32(
|
|
316
|
-
offset,
|
|
317
|
-
value,
|
|
318
|
-
);
|
|
319
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { licenseVersionKey, type LicenseV1Claims } from "@windy/shared";
|
|
3
|
-
import {
|
|
4
|
-
createLicenseSigningPayload,
|
|
5
|
-
getLicensePayloadHandler,
|
|
6
|
-
LicensePayloadError,
|
|
7
|
-
parseLicenseSigningPayload,
|
|
8
|
-
} from "./payload.js";
|
|
9
|
-
|
|
10
|
-
const claims: LicenseV1Claims = {
|
|
11
|
-
licenseId: "license_1",
|
|
12
|
-
productCode: "windy-platform",
|
|
13
|
-
customer: { id: "customer_1", code: "C001", name: "示例客户" },
|
|
14
|
-
project: { id: "project_1", code: "P001", name: "生产项目" },
|
|
15
|
-
contract: { id: "contract_1", contractNumber: "HT-001", version: "1" },
|
|
16
|
-
machineCode: "machine_1",
|
|
17
|
-
issuedAt: "2026-07-10T00:00:00.000Z",
|
|
18
|
-
expiresAt: "2027-07-10T00:00:00.000Z",
|
|
19
|
-
licenseVersion: {
|
|
20
|
-
key: licenseVersionKey("standard"),
|
|
21
|
-
name: "普通付费版",
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
describe("License V1 payload handler", () => {
|
|
26
|
-
test("字段构造顺序不同仍产生相同 canonical payload", () => {
|
|
27
|
-
const reversed = Object.fromEntries(
|
|
28
|
-
Object.entries(claims).reverse(),
|
|
29
|
-
) as unknown as LicenseV1Claims;
|
|
30
|
-
const base = signedPayload(claims);
|
|
31
|
-
const reordered = signedPayload(reversed);
|
|
32
|
-
|
|
33
|
-
expect(reordered).toBe(base);
|
|
34
|
-
expect(parseLicenseSigningPayload(1, base).claims).toEqual(claims);
|
|
35
|
-
expect(base.startsWith('{"algorithm":"ED25519","claims":')).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
test("拒绝未知版本、legacy 缺省版本和非 canonical payload", () => {
|
|
39
|
-
expect(() => getLicensePayloadHandler(2)).toThrow(LicensePayloadError);
|
|
40
|
-
expect(() => getLicensePayloadHandler(undefined)).toThrow(
|
|
41
|
-
"不支持的 License 格式版本",
|
|
42
|
-
);
|
|
43
|
-
const pretty = JSON.stringify(
|
|
44
|
-
{
|
|
45
|
-
formatVersion: 1,
|
|
46
|
-
algorithm: "ED25519",
|
|
47
|
-
publicKeyId: "key_1",
|
|
48
|
-
claims,
|
|
49
|
-
},
|
|
50
|
-
null,
|
|
51
|
-
2,
|
|
52
|
-
);
|
|
53
|
-
expect(() => parseLicenseSigningPayload(1, pretty)).toThrow(
|
|
54
|
-
"必须使用 RFC 8785 canonical JSON",
|
|
55
|
-
);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
test("V1 明确拒绝 legacy 容量字段", () => {
|
|
59
|
-
expect(() =>
|
|
60
|
-
signedPayload({ ...claims, maxUsers: 10 } as unknown as LicenseV1Claims),
|
|
61
|
-
).toThrow("未知:maxUsers");
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
function signedPayload(value: LicenseV1Claims): string {
|
|
66
|
-
return createLicenseSigningPayload({
|
|
67
|
-
formatVersion: 1,
|
|
68
|
-
algorithm: "ED25519",
|
|
69
|
-
publicKeyId: "key_1",
|
|
70
|
-
claims: value,
|
|
71
|
-
});
|
|
72
|
-
}
|