@codemation/host 0.0.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 +75 -0
- package/dist/CodemationConfig-XCkSV2dj.d.ts +168 -0
- package/dist/CodemationConsumerConfigLoader-Dmm2TzAA.d.ts +61 -0
- package/dist/CodemationConsumerConfigLoader-scS_RQMy.js +334 -0
- package/dist/CodemationConsumerConfigLoader-scS_RQMy.js.map +1 -0
- package/dist/CodemationFrontendBootstrapRequest-CE6DjOWJ.js +5768 -0
- package/dist/CodemationFrontendBootstrapRequest-CE6DjOWJ.js.map +1 -0
- package/dist/CodemationPluginListMerger-BNmaoXQL.js +49 -0
- package/dist/CodemationPluginListMerger-BNmaoXQL.js.map +1 -0
- package/dist/CodemationPluginListMerger-BRYqEk0y.d.ts +793 -0
- package/dist/CodemationWhitelabelConfig-DgbjgtrR.d.ts +48 -0
- package/dist/ConsoleLogger-ClPU7jtc.js +35 -0
- package/dist/ConsoleLogger-ClPU7jtc.js.map +1 -0
- package/dist/CredentialServices-BKBGe7l3.js +1030 -0
- package/dist/CredentialServices-BKBGe7l3.js.map +1 -0
- package/dist/CredentialServices-DpDpm8mL.d.ts +291 -0
- package/dist/LogLevelPolicy-4cq9z0TI.d.ts +37 -0
- package/dist/PrismaMigrationDeployer-B1E_gYz7.js +8212 -0
- package/dist/PrismaMigrationDeployer-B1E_gYz7.js.map +1 -0
- package/dist/ServerLoggerFactory-BRHxIDS7.js +340 -0
- package/dist/ServerLoggerFactory-BRHxIDS7.js.map +1 -0
- package/dist/WorkflowViewContracts-DCLpTn25.d.ts +47 -0
- package/dist/chunk-7V6ThxGB.js +39 -0
- package/dist/client-Yh7-CQud.d.ts +21995 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.js +15 -0
- package/dist/client.js.map +1 -0
- package/dist/consumer.d.ts +5 -0
- package/dist/consumer.js +7 -0
- package/dist/credentials.d.ts +50 -0
- package/dist/credentials.js +11 -0
- package/dist/credentials.js.map +1 -0
- package/dist/decorate-B-N_5S4p.js +10 -0
- package/dist/decorateParam-BTcc3KNk.js +15 -0
- package/dist/devServerSidecar.d.ts +52 -0
- package/dist/devServerSidecar.js +131 -0
- package/dist/devServerSidecar.js.map +1 -0
- package/dist/index-Bs4F1IsC.d.ts +1044 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +14 -0
- package/dist/nextServer.d.ts +89 -0
- package/dist/nextServer.js +127 -0
- package/dist/nextServer.js.map +1 -0
- package/dist/persistenceServer-K5eqlZm3.d.ts +36 -0
- package/dist/persistenceServer-W9uRw0dJ.js +19 -0
- package/dist/persistenceServer-W9uRw0dJ.js.map +1 -0
- package/dist/persistenceServer.d.ts +6 -0
- package/dist/persistenceServer.js +6 -0
- package/dist/server-BBdsATju.d.ts +132 -0
- package/dist/server-BiHSuA13.js +175 -0
- package/dist/server-BiHSuA13.js.map +1 -0
- package/dist/server.d.ts +9 -0
- package/dist/server.js +13 -0
- package/package.json +152 -0
- package/playwright.config.ts +74 -0
- package/prisma/migrations/20260315063514_init/migration.sql +16 -0
- package/prisma/migrations/20260316090000_workflow_debugger_overlay/migration.sql +9 -0
- package/prisma/migrations/20260317120000_trigger_state_store/migration.sql +3 -0
- package/prisma/migrations/20260317153000_trigger_setup_state/migration.sql +8 -0
- package/prisma/migrations/20260318110000_credentials_v2/migration.sql +49 -0
- package/prisma/migrations/20260319110000_credential_oauth2_material/migration.sql +28 -0
- package/prisma/migrations/20260319200000_codemation_auth_tables/migration.sql +56 -0
- package/prisma/migrations/20260320140000_user_invites_account_status/migration.sql +20 -0
- package/prisma/migrations/20260325120000_workflow_activation/migration.sql +8 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +179 -0
- package/prisma.config.ts +12 -0
- package/scripts/ensure-prisma-runtime-sourcemaps.mjs +42 -0
- package/scripts/integration-database-global-setup.mjs +30 -0
- package/src/application/ApplicationRequestError.ts +12 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +5 -0
- package/src/application/auth/SessionVerifier.ts +5 -0
- package/src/application/binary/OverlayPinnedBinaryUploadService.ts +119 -0
- package/src/application/binary/RunBinaryAttachmentLookupService.ts +139 -0
- package/src/application/binary/RunStateBinaryStorageKeysCollector.ts +57 -0
- package/src/application/bus/Command.ts +3 -0
- package/src/application/bus/CommandBus.ts +5 -0
- package/src/application/bus/CommandHandler.ts +5 -0
- package/src/application/bus/DomainEvent.ts +1 -0
- package/src/application/bus/DomainEventBus.ts +5 -0
- package/src/application/bus/DomainEventHandler.ts +5 -0
- package/src/application/bus/Query.ts +3 -0
- package/src/application/bus/QueryBus.ts +5 -0
- package/src/application/bus/QueryHandler.ts +5 -0
- package/src/application/commands/AcceptUserInviteCommand.ts +10 -0
- package/src/application/commands/AcceptUserInviteCommandHandler.ts +19 -0
- package/src/application/commands/CopyRunToWorkflowDebuggerCommand.ts +14 -0
- package/src/application/commands/CopyRunToWorkflowDebuggerCommandHandler.ts +56 -0
- package/src/application/commands/CreateCredentialInstanceCommand.ts +9 -0
- package/src/application/commands/CreateCredentialInstanceCommandHandler.ts +28 -0
- package/src/application/commands/CredentialCommandHandlers.ts +10 -0
- package/src/application/commands/DeleteCredentialInstanceCommand.ts +7 -0
- package/src/application/commands/DeleteCredentialInstanceCommandHandler.ts +27 -0
- package/src/application/commands/HandleWebhookInvocationCommand.ts +12 -0
- package/src/application/commands/HandleWebhookInvocationCommandHandler.ts +42 -0
- package/src/application/commands/InviteUserCommand.ts +12 -0
- package/src/application/commands/InviteUserCommandHandler.ts +22 -0
- package/src/application/commands/RegenerateUserInviteCommand.ts +12 -0
- package/src/application/commands/RegenerateUserInviteCommandHandler.ts +24 -0
- package/src/application/commands/ReplaceMutableRunWorkflowSnapshotCommand.ts +12 -0
- package/src/application/commands/ReplaceMutableRunWorkflowSnapshotCommandHandler.ts +47 -0
- package/src/application/commands/ReplaceWorkflowDebuggerOverlayCommand.ts +14 -0
- package/src/application/commands/ReplaceWorkflowDebuggerOverlayCommandHandler.ts +35 -0
- package/src/application/commands/ReplayWorkflowNodeCommand.ts +12 -0
- package/src/application/commands/ReplayWorkflowNodeCommandHandler.ts +164 -0
- package/src/application/commands/SetPinnedNodeInputCommand.ts +13 -0
- package/src/application/commands/SetPinnedNodeInputCommandHandler.ts +50 -0
- package/src/application/commands/SetWorkflowActivationCommand.ts +10 -0
- package/src/application/commands/SetWorkflowActivationCommandHandler.ts +39 -0
- package/src/application/commands/StartWorkflowRunCommand.ts +8 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +286 -0
- package/src/application/commands/TestCredentialInstanceCommand.ts +9 -0
- package/src/application/commands/TestCredentialInstanceCommandHandler.ts +28 -0
- package/src/application/commands/UpdateCredentialInstanceCommand.ts +12 -0
- package/src/application/commands/UpdateCredentialInstanceCommandHandler.ts +28 -0
- package/src/application/commands/UpdateUserAccountStatusCommand.ts +12 -0
- package/src/application/commands/UpdateUserAccountStatusCommandHandler.ts +24 -0
- package/src/application/commands/UploadOverlayPinnedBinaryCommand.ts +16 -0
- package/src/application/commands/UploadOverlayPinnedBinaryCommandHandler.ts +31 -0
- package/src/application/commands/UpsertCredentialBindingCommand.ts +11 -0
- package/src/application/commands/UpsertCredentialBindingCommandHandler.ts +28 -0
- package/src/application/commands/UpsertLocalBootstrapUserCommand.ts +12 -0
- package/src/application/commands/UpsertLocalBootstrapUserCommandHandler.ts +24 -0
- package/src/application/commands/UserAccountCommandHandlers.ts +10 -0
- package/src/application/contracts/CredentialContractsRegistry.ts +88 -0
- package/src/application/contracts/RunContracts.ts +41 -0
- package/src/application/contracts/WorkflowDebuggerContracts.ts +12 -0
- package/src/application/contracts/WorkflowViewContracts.ts +40 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +8 -0
- package/src/application/contracts/userDirectoryContracts.types.ts +60 -0
- package/src/application/dev/BootRuntimeSnapshotHolder.ts +19 -0
- package/src/application/dev/BootRuntimeSummary.types.ts +11 -0
- package/src/application/dev/DevBootstrapSummaryAssembler.ts +84 -0
- package/src/application/dev/DevBootstrapSummaryJson.types.ts +9 -0
- package/src/application/logging/LogFilter.ts +7 -0
- package/src/application/logging/Logger.ts +10 -0
- package/src/application/mapping/DataMapper.ts +3 -0
- package/src/application/mapping/WorkflowDefinitionMapper.ts +171 -0
- package/src/application/mapping/WorkflowPolicyUiPresentationFactory.ts +39 -0
- package/src/application/queries/CredentialQueryHandlers.ts +12 -0
- package/src/application/queries/GetCredentialFieldEnvStatusQuery.ts +5 -0
- package/src/application/queries/GetCredentialFieldEnvStatusQueryHandler.ts +52 -0
- package/src/application/queries/GetCredentialInstanceQuery.ts +9 -0
- package/src/application/queries/GetCredentialInstanceQueryHandler.ts +27 -0
- package/src/application/queries/GetCredentialInstanceWithSecretsQuery.ts +9 -0
- package/src/application/queries/GetCredentialInstanceWithSecretsQueryHandler.ts +27 -0
- package/src/application/queries/GetRunBinaryAttachmentQuery.ts +11 -0
- package/src/application/queries/GetRunBinaryAttachmentQueryHandler.ts +23 -0
- package/src/application/queries/GetRunStateQuery.ts +8 -0
- package/src/application/queries/GetRunStateQueryHandler.ts +21 -0
- package/src/application/queries/GetWorkflowCredentialHealthQuery.ts +9 -0
- package/src/application/queries/GetWorkflowCredentialHealthQueryHandler.ts +27 -0
- package/src/application/queries/GetWorkflowDebuggerOverlayQuery.ts +8 -0
- package/src/application/queries/GetWorkflowDebuggerOverlayQueryHandler.ts +28 -0
- package/src/application/queries/GetWorkflowDetailQuery.ts +8 -0
- package/src/application/queries/GetWorkflowDetailQueryHandler.ts +24 -0
- package/src/application/queries/GetWorkflowOverlayBinaryAttachmentQuery.ts +11 -0
- package/src/application/queries/GetWorkflowOverlayBinaryAttachmentQueryHandler.ts +23 -0
- package/src/application/queries/GetWorkflowSummariesQuery.ts +4 -0
- package/src/application/queries/GetWorkflowSummariesQueryHandler.ts +23 -0
- package/src/application/queries/ListCredentialInstancesQuery.ts +5 -0
- package/src/application/queries/ListCredentialInstancesQueryHandler.ts +27 -0
- package/src/application/queries/ListCredentialTypesQuery.ts +5 -0
- package/src/application/queries/ListCredentialTypesQueryHandler.ts +28 -0
- package/src/application/queries/ListUserAccountsQuery.ts +5 -0
- package/src/application/queries/ListUserAccountsQueryHandler.ts +22 -0
- package/src/application/queries/ListWorkflowRunsQuery.ts +8 -0
- package/src/application/queries/ListWorkflowRunsQueryHandler.ts +21 -0
- package/src/application/queries/UserAccountQueryHandlers.ts +4 -0
- package/src/application/queries/VerifyUserInviteQuery.ts +9 -0
- package/src/application/queries/VerifyUserInviteQueryHandler.ts +21 -0
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +98 -0
- package/src/application/websocket/WorkflowRunEventWebsocketRelay.ts +36 -0
- package/src/application/websocket/WorkflowWebsocketPublisher.ts +5 -0
- package/src/application/workflows/WebhookEndpointPathValidator.ts +35 -0
- package/src/application/workflows/WorkflowDebuggerOverlayStateFactory.ts +122 -0
- package/src/applicationTokens.ts +72 -0
- package/src/bootstrap/CodemationBootstrapRequest.ts +27 -0
- package/src/bootstrap/CodemationContainerFactory.ts +310 -0
- package/src/bootstrap/CodemationContainerRegistration.ts +23 -0
- package/src/bootstrap/CodemationContainerRegistrationRegistrar.ts +42 -0
- package/src/bootstrap/CodemationFrontendBootstrapRequest.ts +16 -0
- package/src/bootstrap/CodemationWorkerBootstrapRequest.ts +19 -0
- package/src/bootstrap/PreparedCodemationRuntime.ts +37 -0
- package/src/bootstrap/PreparedCodemationRuntimeFactory.ts +308 -0
- package/src/bootstrap/boot/CliRuntimeBootService.ts +27 -0
- package/src/bootstrap/boot/FrontendRuntimeBootService.ts +86 -0
- package/src/bootstrap/boot/WorkerRuntimeBootService.ts +64 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +57 -0
- package/src/bootstrap/runtime/ResolvedImplementationSelectionFactory.ts +118 -0
- package/src/client.ts +3 -0
- package/src/codemationApplication.ts +311 -0
- package/src/consumer.ts +4 -0
- package/src/credentials.ts +24 -0
- package/src/devServerSidecar.ts +10 -0
- package/src/domain/credentials/CredentialBindingService.ts +139 -0
- package/src/domain/credentials/CredentialFieldEnvOverlayService.ts +60 -0
- package/src/domain/credentials/CredentialInstanceService.ts +391 -0
- package/src/domain/credentials/CredentialMaterialResolver.ts +55 -0
- package/src/domain/credentials/CredentialRuntimeMaterialService.ts +39 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +70 -0
- package/src/domain/credentials/CredentialServices.ts +145 -0
- package/src/domain/credentials/CredentialSessionServiceImpl.ts +119 -0
- package/src/domain/credentials/CredentialTestService.ts +73 -0
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +29 -0
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +396 -0
- package/src/domain/credentials/OAuth2ProviderRegistry.ts +75 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +246 -0
- package/src/domain/runs/WorkflowRunRepository.ts +11 -0
- package/src/domain/users/UserAccountServiceRegistry.ts +315 -0
- package/src/domain/users/userLoginMethodLabels.types.ts +29 -0
- package/src/domain/workflows/WorkflowActivationPreflight.ts +32 -0
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +77 -0
- package/src/domain/workflows/WorkflowActivationRepository.ts +9 -0
- package/src/domain/workflows/WorkflowDebuggerOverlayRepository.ts +7 -0
- package/src/domain/workflows/WorkflowDebuggerOverlayState.ts +8 -0
- package/src/domain/workflows/WorkflowDefinitionRepository.ts +11 -0
- package/src/index.ts +58 -0
- package/src/infrastructure/auth/AuthJsSessionVerifier.ts +26 -0
- package/src/infrastructure/auth/DevelopmentSessionBypassVerifier.ts +12 -0
- package/src/infrastructure/binary/BinaryBodyNodeReadableFactory.ts +22 -0
- package/src/infrastructure/binary/CountingSha256Transform.ts +26 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +86 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +44 -0
- package/src/infrastructure/credentials/FrameworkBuiltinCredentialTypesRegistrar.ts +21 -0
- package/src/infrastructure/credentials/OpenAiApiKeyCredentialHealthTester.ts +89 -0
- package/src/infrastructure/credentials/OpenAiApiKeyCredentialShapes.types.ts +15 -0
- package/src/infrastructure/credentials/OpenAiApiKeyCredentialTypeFactory.ts +47 -0
- package/src/infrastructure/di/HandlesCommandRegistry.ts +24 -0
- package/src/infrastructure/di/HandlesDomainEventRegistry.ts +24 -0
- package/src/infrastructure/di/HandlesQueryRegistry.ts +24 -0
- package/src/infrastructure/di/InMemoryCommandBus.ts +47 -0
- package/src/infrastructure/di/InMemoryDomainEventBus.ts +47 -0
- package/src/infrastructure/di/InMemoryQueryBus.ts +45 -0
- package/src/infrastructure/ids/CodemationIdFactory.ts +12 -0
- package/src/infrastructure/logging/BrowserLogger.ts +1 -0
- package/src/infrastructure/logging/BrowserLoggerFactory.ts +14 -0
- package/src/infrastructure/logging/ConsoleLogger.ts +41 -0
- package/src/infrastructure/logging/FilteringLogger.ts +38 -0
- package/src/infrastructure/logging/LogLevelPolicy.ts +148 -0
- package/src/infrastructure/logging/LogLevelPolicyFactory.ts +16 -0
- package/src/infrastructure/logging/PerformanceLogPolicy.ts +10 -0
- package/src/infrastructure/logging/PerformanceLogPolicyFactory.ts +14 -0
- package/src/infrastructure/logging/ServerLogger.ts +1 -0
- package/src/infrastructure/logging/ServerLoggerFactory.ts +28 -0
- package/src/infrastructure/persistence/CodemationPostgresPrismaClientFactory.ts +9 -0
- package/src/infrastructure/persistence/CredentialPersistenceStore.ts +139 -0
- package/src/infrastructure/persistence/DatabasePersistenceResolver.ts +91 -0
- package/src/infrastructure/persistence/InMemoryTriggerSetupStateRepository.ts +23 -0
- package/src/infrastructure/persistence/InMemoryWorkflowActivationRepository.ts +18 -0
- package/src/infrastructure/persistence/InMemoryWorkflowDebuggerOverlayRepository.ts +16 -0
- package/src/infrastructure/persistence/InMemoryWorkflowRunRepository.ts +94 -0
- package/src/infrastructure/persistence/PrismaClientFactory.ts +26 -0
- package/src/infrastructure/persistence/PrismaCredentialStore.ts +368 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +184 -0
- package/src/infrastructure/persistence/PrismaTriggerSetupStateRepository.ts +68 -0
- package/src/infrastructure/persistence/PrismaWorkflowActivationRepository.ts +36 -0
- package/src/infrastructure/persistence/PrismaWorkflowDebuggerOverlayRepository.ts +65 -0
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +243 -0
- package/src/infrastructure/persistence/RuntimeWorkflowActivationPolicy.ts +27 -0
- package/src/infrastructure/persistence/SchedulerPersistenceCompatibilityValidator.ts +20 -0
- package/src/infrastructure/persistence/WorkflowDefinitionRepositoryAdapter.ts +31 -0
- package/src/infrastructure/persistence/WorkflowRunRepository.ts +46 -0
- package/src/infrastructure/persistence/generated/prisma/client.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma/default.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma/edge.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma/index.d.ts +4766 -0
- package/src/infrastructure/persistence/generated/prisma/package.json +144 -0
- package/src/infrastructure/persistence/generated/prisma/query_compiler_fast_bg.wasm +0 -0
- package/src/infrastructure/persistence/generated/prisma/runtime/client.d.ts +3358 -0
- package/src/infrastructure/persistence/generated/prisma/runtime/index-browser.d.ts +90 -0
- package/src/infrastructure/persistence/generated/prisma/schema.prisma +35 -0
- package/src/infrastructure/persistence/generated/prisma/wasm-edge-light-loader.mjs +5 -0
- package/src/infrastructure/persistence/generated/prisma/wasm-worker-loader.mjs +5 -0
- package/src/infrastructure/persistence/generated/prisma-client/client.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/client.js +5 -0
- package/src/infrastructure/persistence/generated/prisma-client/default.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/default.js +5 -0
- package/src/infrastructure/persistence/generated/prisma-client/edge.d.ts +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/edge.js +299 -0
- package/src/infrastructure/persistence/generated/prisma-client/index-browser.js +325 -0
- package/src/infrastructure/persistence/generated/prisma-client/index.d.ts +21623 -0
- package/src/infrastructure/persistence/generated/prisma-client/index.js +299 -0
- package/src/infrastructure/persistence/generated/prisma-client/package.json +144 -0
- package/src/infrastructure/persistence/generated/prisma-client/query_compiler_fast_bg.js +2 -0
- package/src/infrastructure/persistence/generated/prisma-client/query_compiler_fast_bg.wasm +0 -0
- package/src/infrastructure/persistence/generated/prisma-client/query_compiler_fast_bg.wasm-base64.js +2 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/client.d.ts +3358 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/client.js +86 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/client.js.map +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/index-browser.d.ts +90 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/index-browser.js +6 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/index-browser.js.map +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/wasm-compiler-edge.js +76 -0
- package/src/infrastructure/persistence/generated/prisma-client/runtime/wasm-compiler-edge.js.map +1 -0
- package/src/infrastructure/persistence/generated/prisma-client/schema.prisma +179 -0
- package/src/infrastructure/persistence/generated/prisma-client/wasm-edge-light-loader.mjs +5 -0
- package/src/infrastructure/persistence/generated/prisma-client/wasm-worker-loader.mjs +5 -0
- package/src/infrastructure/runtime/LiveWorkflowRepository.ts +14 -0
- package/src/infrastructure/runtime/WorkerRuntimeScheduler.ts +35 -0
- package/src/infrastructure/server/http/ServerHttpRouteParams.ts +1 -0
- package/src/infrastructure/webhooks/RequestToWebhookItemMapper.ts +128 -0
- package/src/nextServer.ts +31 -0
- package/src/persistenceServer.ts +5 -0
- package/src/presentation/config/AppConfig.ts +25 -0
- package/src/presentation/config/CodemationAppContext.ts +19 -0
- package/src/presentation/config/CodemationApplicationFacade.ts +5 -0
- package/src/presentation/config/CodemationAuthConfig.ts +31 -0
- package/src/presentation/config/CodemationClassToken.ts +3 -0
- package/src/presentation/config/CodemationConfig.ts +86 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +179 -0
- package/src/presentation/config/CodemationLogConfig.ts +22 -0
- package/src/presentation/config/CodemationPackageManifest.ts +9 -0
- package/src/presentation/config/CodemationPlugin.ts +20 -0
- package/src/presentation/config/CodemationPluginListMerger.ts +46 -0
- package/src/presentation/config/CodemationWhitelabelConfig.ts +9 -0
- package/src/presentation/config/CodemationWorkflowDiscovery.ts +3 -0
- package/src/presentation/http/ApiPaths.ts +165 -0
- package/src/presentation/http/CodemationServerGatewayFactory.ts +120 -0
- package/src/presentation/http/HttpRequestJsonBodyReader.ts +12 -0
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +33 -0
- package/src/presentation/http/ServerHttpRouteParams.ts +1 -0
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +64 -0
- package/src/presentation/http/hono/HonoApiRouteRegistrar.ts +5 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +27 -0
- package/src/presentation/http/hono/registrars/BinaryHonoApiRouteRegistrar.ts +21 -0
- package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +34 -0
- package/src/presentation/http/hono/registrars/DevHonoApiRouteRegistrar.ts +17 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +18 -0
- package/src/presentation/http/hono/registrars/RunHonoApiRouteRegistrar.ts +31 -0
- package/src/presentation/http/hono/registrars/UserHonoApiRouteRegistrar.ts +24 -0
- package/src/presentation/http/hono/registrars/WebhookHonoApiRouteRegistrar.ts +23 -0
- package/src/presentation/http/hono/registrars/WhitelabelHonoApiRouteRegistrar.ts +18 -0
- package/src/presentation/http/hono/registrars/WorkflowHonoApiRouteRegistrar.ts +33 -0
- package/src/presentation/http/routeHandlers/BinaryHttpRouteHandlerFactory.ts +101 -0
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +129 -0
- package/src/presentation/http/routeHandlers/DevBootstrapSummaryHttpRouteHandler.ts +21 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +129 -0
- package/src/presentation/http/routeHandlers/RunHttpRouteHandler.ts +82 -0
- package/src/presentation/http/routeHandlers/UserHttpRouteHandlerFactory.ts +109 -0
- package/src/presentation/http/routeHandlers/WebhookHttpRouteHandler.ts +42 -0
- package/src/presentation/http/routeHandlers/WhitelabelLogoHttpRouteHandler.ts +96 -0
- package/src/presentation/http/routeHandlers/WorkflowHttpRouteHandler.ts +104 -0
- package/src/presentation/server/CodemationConsumerAppResolver.ts +82 -0
- package/src/presentation/server/CodemationConsumerConfigExportsResolver.ts +33 -0
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +270 -0
- package/src/presentation/server/CodemationPluginDiscovery.ts +151 -0
- package/src/presentation/server/CodemationTsyringeParamInfoReader.ts +26 -0
- package/src/presentation/server/CodemationTsyringeTypeInfoRegistrar.ts +121 -0
- package/src/presentation/server/DevelopmentRuntimeRouteGuard.ts +59 -0
- package/src/presentation/server/DiscoveredWorkflowsEmptyMessageFactory.ts +11 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +24 -0
- package/src/presentation/server/WorkflowDiscoveryPathSegmentsComputer.ts +53 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +47 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +169 -0
- package/src/server.ts +14 -0
- package/tsconfig.json +10 -0
- package/vitest.shared.ts +45 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import type { CredentialRequirement, WorkflowDefinition } from "@codemation/core";
|
|
2
|
+
import {
|
|
3
|
+
AgentConfigInspector,
|
|
4
|
+
ConnectionNodeIdFactory,
|
|
5
|
+
WorkflowExecutableNodeClassifierFactory,
|
|
6
|
+
} from "@codemation/core";
|
|
7
|
+
|
|
8
|
+
import { injectable } from "@codemation/core";
|
|
9
|
+
|
|
10
|
+
export type WorkflowCredentialSlotRef = Readonly<{
|
|
11
|
+
workflowId: string;
|
|
12
|
+
nodeId: string;
|
|
13
|
+
nodeName: string;
|
|
14
|
+
requirement: CredentialRequirement;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves credential requirements for workflow node ids, including connection-owned LLM/tool children.
|
|
19
|
+
*/
|
|
20
|
+
@injectable()
|
|
21
|
+
export class WorkflowCredentialNodeResolver {
|
|
22
|
+
/**
|
|
23
|
+
* Human-readable label for credential errors (workflow node name or agent › attachment).
|
|
24
|
+
*/
|
|
25
|
+
describeCredentialNodeDisplay(workflow: WorkflowDefinition, nodeId: string): string {
|
|
26
|
+
const direct = workflow.nodes.find((n) => n.id === nodeId);
|
|
27
|
+
if (direct) {
|
|
28
|
+
return direct.name ?? direct.config.name ?? direct.id;
|
|
29
|
+
}
|
|
30
|
+
if (ConnectionNodeIdFactory.isLanguageModelConnectionNodeId(nodeId)) {
|
|
31
|
+
const parentId = this.parseParentForLanguageModelConnectionNodeId(nodeId);
|
|
32
|
+
const parent = parentId ? workflow.nodes.find((n) => n.id === parentId) : undefined;
|
|
33
|
+
const agentLabel = parent?.name ?? parentId ?? "Agent";
|
|
34
|
+
return `${agentLabel} › Language model`;
|
|
35
|
+
}
|
|
36
|
+
if (ConnectionNodeIdFactory.isToolConnectionNodeId(nodeId)) {
|
|
37
|
+
const parsed = this.parseToolConnectionNodeId(nodeId);
|
|
38
|
+
if (!parsed) {
|
|
39
|
+
return nodeId;
|
|
40
|
+
}
|
|
41
|
+
const parent = workflow.nodes.find((n) => n.id === parsed.parentNodeId);
|
|
42
|
+
const agentLabel = parent?.name ?? parsed.parentNodeId;
|
|
43
|
+
const toolConfig =
|
|
44
|
+
parent && AgentConfigInspector.isAgentNodeConfig(parent.config)
|
|
45
|
+
? parent.config.tools?.find(
|
|
46
|
+
(tool) => ConnectionNodeIdFactory.normalizeToolName(tool.name) === parsed.normalizedToolName,
|
|
47
|
+
)
|
|
48
|
+
: undefined;
|
|
49
|
+
const toolLabel = toolConfig?.presentation?.label ?? toolConfig?.name ?? parsed.normalizedToolName;
|
|
50
|
+
return `${agentLabel} › ${toolLabel}`;
|
|
51
|
+
}
|
|
52
|
+
return nodeId;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
isCredentialNodeIdInWorkflow(workflow: WorkflowDefinition, nodeId: string): boolean {
|
|
56
|
+
if (workflow.nodes.some((n) => n.id === nodeId)) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
if (ConnectionNodeIdFactory.isLanguageModelConnectionNodeId(nodeId)) {
|
|
60
|
+
const parent = this.parseParentForLanguageModelConnectionNodeId(nodeId);
|
|
61
|
+
if (parent && workflow.nodes.some((n) => n.id === parent)) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (ConnectionNodeIdFactory.isToolConnectionNodeId(nodeId)) {
|
|
66
|
+
const parsed = this.parseToolConnectionNodeId(nodeId);
|
|
67
|
+
if (parsed && workflow.nodes.some((n) => n.id === parsed.parentNodeId)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
findRequirement(
|
|
75
|
+
workflow: WorkflowDefinition,
|
|
76
|
+
nodeId: string,
|
|
77
|
+
slotKey: string,
|
|
78
|
+
): Readonly<{ nodeName: string; requirement: CredentialRequirement }> | undefined {
|
|
79
|
+
const direct = this.findDirectRequirement(workflow, nodeId, slotKey);
|
|
80
|
+
if (direct) {
|
|
81
|
+
return direct;
|
|
82
|
+
}
|
|
83
|
+
if (ConnectionNodeIdFactory.isLanguageModelConnectionNodeId(nodeId)) {
|
|
84
|
+
const parent = this.parseParentForLanguageModelConnectionNodeId(nodeId);
|
|
85
|
+
if (parent) {
|
|
86
|
+
const fromConn = this.findLanguageModelRequirement(workflow, parent, slotKey);
|
|
87
|
+
if (fromConn) {
|
|
88
|
+
return fromConn;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (ConnectionNodeIdFactory.isToolConnectionNodeId(nodeId)) {
|
|
93
|
+
const parsed = this.parseToolConnectionNodeId(nodeId);
|
|
94
|
+
if (parsed) {
|
|
95
|
+
const fromConn = this.findToolRequirement(workflow, parsed.parentNodeId, parsed.normalizedToolName, slotKey);
|
|
96
|
+
if (fromConn) {
|
|
97
|
+
return fromConn;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
listSlots(workflow: WorkflowDefinition): ReadonlyArray<WorkflowCredentialSlotRef> {
|
|
105
|
+
const slots: WorkflowCredentialSlotRef[] = [];
|
|
106
|
+
const classifier = WorkflowExecutableNodeClassifierFactory.create(workflow);
|
|
107
|
+
const hasConnectionMetadata = (workflow.connections?.length ?? 0) > 0;
|
|
108
|
+
|
|
109
|
+
for (const node of workflow.nodes) {
|
|
110
|
+
if (classifier.isConnectionOwnedNodeId(node.id)) {
|
|
111
|
+
for (const requirement of node.config.getCredentialRequirements?.() ?? []) {
|
|
112
|
+
slots.push({
|
|
113
|
+
workflowId: workflow.id,
|
|
114
|
+
nodeId: node.id,
|
|
115
|
+
nodeName: node.name ?? node.config.name ?? node.id,
|
|
116
|
+
requirement,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (AgentConfigInspector.isAgentNodeConfig(node.config)) {
|
|
123
|
+
if (!hasConnectionMetadata) {
|
|
124
|
+
const lmNodeId = ConnectionNodeIdFactory.languageModelConnectionNodeId(node.id);
|
|
125
|
+
const lmLabel = node.config.chatModel.presentation?.label ?? node.config.chatModel.name;
|
|
126
|
+
for (const requirement of node.config.chatModel.getCredentialRequirements?.() ?? []) {
|
|
127
|
+
slots.push({
|
|
128
|
+
workflowId: workflow.id,
|
|
129
|
+
nodeId: lmNodeId,
|
|
130
|
+
nodeName: lmLabel,
|
|
131
|
+
requirement,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
for (const toolConfig of node.config.tools ?? []) {
|
|
135
|
+
const toolNodeId = ConnectionNodeIdFactory.toolConnectionNodeId(node.id, toolConfig.name);
|
|
136
|
+
const toolLabel = toolConfig.presentation?.label ?? toolConfig.name;
|
|
137
|
+
for (const requirement of toolConfig.getCredentialRequirements?.() ?? []) {
|
|
138
|
+
slots.push({
|
|
139
|
+
workflowId: workflow.id,
|
|
140
|
+
nodeId: toolNodeId,
|
|
141
|
+
nodeName: toolLabel,
|
|
142
|
+
requirement,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
for (const requirement of node.config.getCredentialRequirements?.() ?? []) {
|
|
151
|
+
slots.push({
|
|
152
|
+
workflowId: workflow.id,
|
|
153
|
+
nodeId: node.id,
|
|
154
|
+
nodeName: node.name ?? node.config.name ?? node.id,
|
|
155
|
+
requirement,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return slots;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private findDirectRequirement(
|
|
163
|
+
workflow: WorkflowDefinition,
|
|
164
|
+
nodeId: string,
|
|
165
|
+
slotKey: string,
|
|
166
|
+
): Readonly<{ nodeName: string; requirement: CredentialRequirement }> | undefined {
|
|
167
|
+
const node = workflow.nodes.find((entry) => entry.id === nodeId);
|
|
168
|
+
if (!node || AgentConfigInspector.isAgentNodeConfig(node.config)) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
const requirement = node.config.getCredentialRequirements?.()?.find((entry) => entry.slotKey === slotKey);
|
|
172
|
+
if (!requirement) {
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
return { nodeName: node.name ?? node.config.name ?? node.id, requirement };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private findLanguageModelRequirement(
|
|
179
|
+
workflow: WorkflowDefinition,
|
|
180
|
+
parentNodeId: string,
|
|
181
|
+
slotKey: string,
|
|
182
|
+
): Readonly<{ nodeName: string; requirement: CredentialRequirement }> | undefined {
|
|
183
|
+
const parent = workflow.nodes.find((entry) => entry.id === parentNodeId);
|
|
184
|
+
if (!parent || !AgentConfigInspector.isAgentNodeConfig(parent.config)) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
const requirement = parent.config.chatModel
|
|
188
|
+
.getCredentialRequirements?.()
|
|
189
|
+
?.find((entry) => entry.slotKey === slotKey);
|
|
190
|
+
if (!requirement) {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
const nodeName =
|
|
194
|
+
parent.config.chatModel.presentation?.label ?? parent.config.chatModel.name ?? parent.name ?? parent.id;
|
|
195
|
+
return { nodeName, requirement };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private findToolRequirement(
|
|
199
|
+
workflow: WorkflowDefinition,
|
|
200
|
+
parentNodeId: string,
|
|
201
|
+
normalizedToolName: string,
|
|
202
|
+
slotKey: string,
|
|
203
|
+
): Readonly<{ nodeName: string; requirement: CredentialRequirement }> | undefined {
|
|
204
|
+
const parent = workflow.nodes.find((entry) => entry.id === parentNodeId);
|
|
205
|
+
if (!parent || !AgentConfigInspector.isAgentNodeConfig(parent.config)) {
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
const toolConfig = parent.config.tools?.find(
|
|
209
|
+
(tool) => ConnectionNodeIdFactory.normalizeToolName(tool.name) === normalizedToolName,
|
|
210
|
+
);
|
|
211
|
+
if (!toolConfig) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const requirement = toolConfig.getCredentialRequirements?.()?.find((entry) => entry.slotKey === slotKey);
|
|
215
|
+
if (!requirement) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
const nodeName = toolConfig.presentation?.label ?? toolConfig.name ?? parent.name ?? parent.id;
|
|
219
|
+
return { nodeName, requirement };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private parseParentForLanguageModelConnectionNodeId(nodeId: string): string | undefined {
|
|
223
|
+
if (!ConnectionNodeIdFactory.isLanguageModelConnectionNodeId(nodeId)) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
const suffix = `${ConnectionNodeIdFactory.connectionSegment}llm`;
|
|
227
|
+
return nodeId.slice(0, -suffix.length);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private parseToolConnectionNodeId(nodeId: string): { parentNodeId: string; normalizedToolName: string } | undefined {
|
|
231
|
+
if (!ConnectionNodeIdFactory.isToolConnectionNodeId(nodeId)) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
const marker = `${ConnectionNodeIdFactory.connectionSegment}tool${ConnectionNodeIdFactory.connectionSegment}`;
|
|
235
|
+
const idx = nodeId.indexOf(marker);
|
|
236
|
+
if (idx < 0) {
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
const parentNodeId = nodeId.slice(0, idx);
|
|
240
|
+
const normalizedToolName = nodeId.slice(idx + marker.length);
|
|
241
|
+
if (!parentNodeId || !normalizedToolName) {
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
244
|
+
return { parentNodeId, normalizedToolName };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PersistedRunState, RunId, RunSummary } from "@codemation/core";
|
|
2
|
+
|
|
3
|
+
export interface WorkflowRunRepository {
|
|
4
|
+
load(runId: string): Promise<PersistedRunState | undefined>;
|
|
5
|
+
|
|
6
|
+
save(state: PersistedRunState): Promise<void>;
|
|
7
|
+
|
|
8
|
+
listRuns(args: Readonly<{ workflowId?: string; limit?: number }>): Promise<ReadonlyArray<RunSummary>>;
|
|
9
|
+
|
|
10
|
+
deleteRun(runId: RunId): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { hash } from "bcryptjs";
|
|
2
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
3
|
+
import { ApplicationRequestError } from "../../application/ApplicationRequestError";
|
|
4
|
+
import type {
|
|
5
|
+
InviteUserResponseDto,
|
|
6
|
+
UpsertLocalBootstrapUserResultDto,
|
|
7
|
+
UserAccountDto,
|
|
8
|
+
UserAccountStatus,
|
|
9
|
+
VerifyUserInviteResponseDto,
|
|
10
|
+
} from "../../application/contracts/userDirectoryContracts.types";
|
|
11
|
+
import { PrismaClient } from "../../infrastructure/persistence/generated/prisma-client/client.js";
|
|
12
|
+
import type { CodemationAuthConfig } from "../../presentation/config/CodemationAuthConfig";
|
|
13
|
+
import { labelForLinkedAuthAccount } from "./userLoginMethodLabels.types";
|
|
14
|
+
|
|
15
|
+
const INVITE_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
16
|
+
|
|
17
|
+
export class UserAccountService {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly authConfig: CodemationAuthConfig | undefined,
|
|
20
|
+
private readonly prisma: PrismaClient | undefined,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
async listUsers(): Promise<ReadonlyArray<UserAccountDto>> {
|
|
24
|
+
this.assertLocalAuth();
|
|
25
|
+
const prisma = this.requirePrisma();
|
|
26
|
+
const rows = await prisma.user.findMany({
|
|
27
|
+
orderBy: { email: "asc" },
|
|
28
|
+
include: {
|
|
29
|
+
invites: {
|
|
30
|
+
where: { revokedAt: null },
|
|
31
|
+
orderBy: { createdAt: "desc" },
|
|
32
|
+
},
|
|
33
|
+
accounts: {
|
|
34
|
+
select: { provider: true, type: true },
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return rows.map((row) => this.toDto(row));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async inviteUser(email: string, requestOrigin: string): Promise<InviteUserResponseDto> {
|
|
42
|
+
this.assertLocalAuth();
|
|
43
|
+
const prisma = this.requirePrisma();
|
|
44
|
+
const normalized = email.trim().toLowerCase();
|
|
45
|
+
if (!normalized || !normalized.includes("@")) {
|
|
46
|
+
throw new ApplicationRequestError(400, "Invalid email.");
|
|
47
|
+
}
|
|
48
|
+
const existing = await prisma.user.findUnique({ where: { email: normalized } });
|
|
49
|
+
if (existing?.accountStatus === "active") {
|
|
50
|
+
throw new ApplicationRequestError(409, "User is already active.");
|
|
51
|
+
}
|
|
52
|
+
if (existing?.accountStatus === "inactive") {
|
|
53
|
+
throw new ApplicationRequestError(409, "User is inactive.");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const rawToken = this.generateRawToken();
|
|
57
|
+
const tokenHash = this.hashToken(rawToken);
|
|
58
|
+
const expiresAt = new Date(Date.now() + INVITE_TTL_MS);
|
|
59
|
+
const now = new Date();
|
|
60
|
+
|
|
61
|
+
if (existing) {
|
|
62
|
+
await prisma.$transaction(async (tx) => {
|
|
63
|
+
await tx.userInvite.updateMany({
|
|
64
|
+
where: { userId: existing.id, revokedAt: null },
|
|
65
|
+
data: { revokedAt: now },
|
|
66
|
+
});
|
|
67
|
+
await tx.userInvite.create({
|
|
68
|
+
data: { userId: existing.id, tokenHash, expiresAt, createdAt: now },
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
const user = await this.getUserDto(existing.id);
|
|
72
|
+
return { user, inviteUrl: this.buildInviteUrl(requestOrigin, rawToken) };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const created = await prisma.$transaction(async (tx) => {
|
|
76
|
+
const u = await tx.user.create({
|
|
77
|
+
data: {
|
|
78
|
+
email: normalized,
|
|
79
|
+
name: normalized.split("@")[0] ?? normalized,
|
|
80
|
+
accountStatus: "invited",
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
await tx.userInvite.create({
|
|
84
|
+
data: { userId: u.id, tokenHash, expiresAt, createdAt: now },
|
|
85
|
+
});
|
|
86
|
+
return u;
|
|
87
|
+
});
|
|
88
|
+
const user = await this.getUserDto(created.id);
|
|
89
|
+
return { user, inviteUrl: this.buildInviteUrl(requestOrigin, rawToken) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async regenerateInvite(userId: string, requestOrigin: string): Promise<InviteUserResponseDto> {
|
|
93
|
+
this.assertLocalAuth();
|
|
94
|
+
const prisma = this.requirePrisma();
|
|
95
|
+
const row = await prisma.user.findUnique({ where: { id: userId } });
|
|
96
|
+
if (!row) {
|
|
97
|
+
throw new ApplicationRequestError(404, "Unknown user.");
|
|
98
|
+
}
|
|
99
|
+
if (row.accountStatus !== "invited") {
|
|
100
|
+
throw new ApplicationRequestError(409, "Can only regenerate invites for invited users.");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const rawToken = this.generateRawToken();
|
|
104
|
+
const tokenHash = this.hashToken(rawToken);
|
|
105
|
+
const expiresAt = new Date(Date.now() + INVITE_TTL_MS);
|
|
106
|
+
const now = new Date();
|
|
107
|
+
|
|
108
|
+
await prisma.$transaction(async (tx) => {
|
|
109
|
+
await tx.userInvite.updateMany({
|
|
110
|
+
where: { userId, revokedAt: null },
|
|
111
|
+
data: { revokedAt: now },
|
|
112
|
+
});
|
|
113
|
+
await tx.userInvite.create({
|
|
114
|
+
data: { userId, tokenHash, expiresAt, createdAt: now },
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const user = await this.getUserDto(userId);
|
|
119
|
+
return { user, inviteUrl: this.buildInviteUrl(requestOrigin, rawToken) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async verifyInviteToken(rawToken: string): Promise<VerifyUserInviteResponseDto> {
|
|
123
|
+
const prisma = this.requirePrisma();
|
|
124
|
+
const trimmed = rawToken.trim();
|
|
125
|
+
if (!trimmed) {
|
|
126
|
+
return { valid: false };
|
|
127
|
+
}
|
|
128
|
+
const tokenHash = this.hashToken(trimmed);
|
|
129
|
+
const invite = await prisma.userInvite.findFirst({
|
|
130
|
+
where: { tokenHash, revokedAt: null },
|
|
131
|
+
});
|
|
132
|
+
if (!invite || invite.expiresAt <= new Date()) {
|
|
133
|
+
return { valid: false };
|
|
134
|
+
}
|
|
135
|
+
const user = await prisma.user.findUnique({ where: { id: invite.userId } });
|
|
136
|
+
if (!user?.email || user.accountStatus !== "invited") {
|
|
137
|
+
return { valid: false };
|
|
138
|
+
}
|
|
139
|
+
return { valid: true, email: user.email };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async acceptInvite(rawToken: string, password: string): Promise<void> {
|
|
143
|
+
const prisma = this.requirePrisma();
|
|
144
|
+
const trimmed = rawToken.trim();
|
|
145
|
+
if (!trimmed) {
|
|
146
|
+
throw new ApplicationRequestError(400, "Missing invite token.");
|
|
147
|
+
}
|
|
148
|
+
if (password.length < 8) {
|
|
149
|
+
throw new ApplicationRequestError(400, "Password must be at least 8 characters.");
|
|
150
|
+
}
|
|
151
|
+
const tokenHash = this.hashToken(trimmed);
|
|
152
|
+
const invite = await prisma.userInvite.findFirst({
|
|
153
|
+
where: { tokenHash, revokedAt: null },
|
|
154
|
+
});
|
|
155
|
+
if (!invite || invite.expiresAt <= new Date()) {
|
|
156
|
+
throw new ApplicationRequestError(400, "Invite is invalid or has expired.");
|
|
157
|
+
}
|
|
158
|
+
const user = await prisma.user.findUnique({ where: { id: invite.userId } });
|
|
159
|
+
if (!user || user.accountStatus !== "invited") {
|
|
160
|
+
throw new ApplicationRequestError(400, "Invite cannot be used for this account.");
|
|
161
|
+
}
|
|
162
|
+
const passwordHash = await hash(password, 12);
|
|
163
|
+
const now = new Date();
|
|
164
|
+
await prisma.$transaction(async (tx) => {
|
|
165
|
+
await tx.user.update({
|
|
166
|
+
where: { id: user.id },
|
|
167
|
+
data: { passwordHash, accountStatus: "active" },
|
|
168
|
+
});
|
|
169
|
+
await tx.userInvite.updateMany({
|
|
170
|
+
where: { userId: user.id, revokedAt: null },
|
|
171
|
+
data: { revokedAt: now },
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async updateAccountStatus(userId: string, status: UserAccountStatus): Promise<UserAccountDto> {
|
|
177
|
+
this.assertLocalAuth();
|
|
178
|
+
const prisma = this.requirePrisma();
|
|
179
|
+
if (status === "invited") {
|
|
180
|
+
throw new ApplicationRequestError(400, "Cannot set status to invited via this endpoint.");
|
|
181
|
+
}
|
|
182
|
+
const row = await prisma.user.findUnique({ where: { id: userId } });
|
|
183
|
+
if (!row) {
|
|
184
|
+
throw new ApplicationRequestError(404, "Unknown user.");
|
|
185
|
+
}
|
|
186
|
+
await prisma.user.update({
|
|
187
|
+
where: { id: userId },
|
|
188
|
+
data: { accountStatus: status },
|
|
189
|
+
});
|
|
190
|
+
return await this.getUserDto(userId);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Bootstrap path for `codemation user create`: create or update an active local user with a password hash.
|
|
195
|
+
* Not used for invite-based onboarding (see {@link inviteUser} / {@link acceptInvite}).
|
|
196
|
+
*/
|
|
197
|
+
async upsertBootstrapLocalUser(email: string, password: string): Promise<UpsertLocalBootstrapUserResultDto> {
|
|
198
|
+
this.assertLocalAuth();
|
|
199
|
+
const prisma = this.requirePrisma();
|
|
200
|
+
const normalized = email.trim().toLowerCase();
|
|
201
|
+
if (!normalized || !normalized.includes("@")) {
|
|
202
|
+
throw new ApplicationRequestError(400, "Invalid email.");
|
|
203
|
+
}
|
|
204
|
+
if (password.length < 8) {
|
|
205
|
+
throw new ApplicationRequestError(400, "Password must be at least 8 characters.");
|
|
206
|
+
}
|
|
207
|
+
const passwordHash = await hash(password, 12);
|
|
208
|
+
const existing = await prisma.user.findUnique({ where: { email: normalized } });
|
|
209
|
+
if (existing) {
|
|
210
|
+
await prisma.user.update({
|
|
211
|
+
where: { email: normalized },
|
|
212
|
+
data: { passwordHash, accountStatus: "active" },
|
|
213
|
+
});
|
|
214
|
+
return { outcome: "updated" };
|
|
215
|
+
}
|
|
216
|
+
await prisma.user.create({
|
|
217
|
+
data: {
|
|
218
|
+
email: normalized,
|
|
219
|
+
passwordHash,
|
|
220
|
+
name: normalized.split("@")[0] ?? normalized,
|
|
221
|
+
accountStatus: "active",
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
return { outcome: "created" };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
private buildInviteUrl(origin: string, rawToken: string): string {
|
|
228
|
+
const base = origin.replace(/\/$/, "");
|
|
229
|
+
return `${base}/invite/${encodeURIComponent(rawToken)}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
private hashToken(raw: string): string {
|
|
233
|
+
return createHash("sha256").update(raw, "utf8").digest("hex");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
private generateRawToken(): string {
|
|
237
|
+
return randomBytes(32).toString("base64url");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
private assertLocalAuth(): void {
|
|
241
|
+
if (this.authConfig?.kind !== "local") {
|
|
242
|
+
throw new ApplicationRequestError(403, "User management requires local authentication.");
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private requirePrisma(): PrismaClient {
|
|
247
|
+
if (!this.prisma) {
|
|
248
|
+
throw new ApplicationRequestError(503, "User management requires a database.");
|
|
249
|
+
}
|
|
250
|
+
return this.prisma;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
private async getUserDto(userId: string): Promise<UserAccountDto> {
|
|
254
|
+
const prisma = this.requirePrisma();
|
|
255
|
+
const row = await prisma.user.findUniqueOrThrow({
|
|
256
|
+
where: { id: userId },
|
|
257
|
+
include: {
|
|
258
|
+
invites: {
|
|
259
|
+
where: { revokedAt: null },
|
|
260
|
+
orderBy: { createdAt: "desc" },
|
|
261
|
+
take: 1,
|
|
262
|
+
},
|
|
263
|
+
accounts: {
|
|
264
|
+
select: { provider: true, type: true },
|
|
265
|
+
},
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
return this.toDto(row);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private toDto(row: {
|
|
272
|
+
id: string;
|
|
273
|
+
email: string | null;
|
|
274
|
+
accountStatus: string;
|
|
275
|
+
passwordHash: string | null;
|
|
276
|
+
accounts: ReadonlyArray<{ provider: string; type: string }>;
|
|
277
|
+
invites: ReadonlyArray<{ expiresAt: Date }>;
|
|
278
|
+
}): UserAccountDto {
|
|
279
|
+
const now = new Date();
|
|
280
|
+
const open = row.invites[0];
|
|
281
|
+
const inviteExpiresAt = open && open.expiresAt > now ? open.expiresAt.toISOString() : null;
|
|
282
|
+
return {
|
|
283
|
+
id: row.id,
|
|
284
|
+
email: row.email ?? "",
|
|
285
|
+
status: row.accountStatus as UserAccountStatus,
|
|
286
|
+
inviteExpiresAt,
|
|
287
|
+
loginMethods: UserAccountService.buildLoginMethods(row),
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private static buildLoginMethods(row: {
|
|
292
|
+
passwordHash: string | null;
|
|
293
|
+
accounts: ReadonlyArray<{ provider: string; type: string }>;
|
|
294
|
+
}): ReadonlyArray<string> {
|
|
295
|
+
const labels: string[] = [];
|
|
296
|
+
const seen = new Set<string>();
|
|
297
|
+
if (row.passwordHash && row.passwordHash.length > 0) {
|
|
298
|
+
labels.push("Password");
|
|
299
|
+
seen.add("Password");
|
|
300
|
+
}
|
|
301
|
+
for (const account of row.accounts) {
|
|
302
|
+
if (account.provider === "credentials") {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const label = labelForLinkedAuthAccount(account.provider, account.type);
|
|
306
|
+
if (!seen.has(label)) {
|
|
307
|
+
seen.add(label);
|
|
308
|
+
labels.push(label);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
const password = labels.filter((l) => l === "Password");
|
|
312
|
+
const rest = labels.filter((l) => l !== "Password").sort((a, b) => a.localeCompare(b));
|
|
313
|
+
return [...password, ...rest];
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Human-readable labels for Auth.js Account.provider / type (users list, admin UI).
|
|
3
|
+
*/
|
|
4
|
+
const KNOWN_PROVIDER_LABELS: Readonly<Record<string, string>> = {
|
|
5
|
+
google: "Google",
|
|
6
|
+
github: "GitHub",
|
|
7
|
+
"microsoft-entra-id": "Microsoft Entra ID",
|
|
8
|
+
"azure-ad": "Microsoft Entra ID",
|
|
9
|
+
email: "Email link",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function titleCaseProviderId(provider: string): string {
|
|
13
|
+
return provider
|
|
14
|
+
.split(/[-_.]+/)
|
|
15
|
+
.filter(Boolean)
|
|
16
|
+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())
|
|
17
|
+
.join(" ");
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function labelForLinkedAuthAccount(provider: string, accountType: string): string {
|
|
21
|
+
const known = KNOWN_PROVIDER_LABELS[provider];
|
|
22
|
+
if (known) {
|
|
23
|
+
return known;
|
|
24
|
+
}
|
|
25
|
+
if (accountType === "oidc") {
|
|
26
|
+
return `SSO (${titleCaseProviderId(provider)})`;
|
|
27
|
+
}
|
|
28
|
+
return titleCaseProviderId(provider);
|
|
29
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ApplicationRequestError } from "../../application/ApplicationRequestError";
|
|
2
|
+
import { CoreTokens, inject, injectable, type WorkflowRepository } from "@codemation/core";
|
|
3
|
+
import { CredentialBindingService } from "../credentials/CredentialServices";
|
|
4
|
+
import { WorkflowActivationPreflightRules } from "./WorkflowActivationPreflightRules";
|
|
5
|
+
|
|
6
|
+
@injectable()
|
|
7
|
+
export class WorkflowActivationPreflight {
|
|
8
|
+
constructor(
|
|
9
|
+
@inject(CoreTokens.WorkflowRepository)
|
|
10
|
+
private readonly workflowRepository: WorkflowRepository,
|
|
11
|
+
@inject(CredentialBindingService)
|
|
12
|
+
private readonly credentialBindingService: CredentialBindingService,
|
|
13
|
+
@inject(WorkflowActivationPreflightRules)
|
|
14
|
+
private readonly rules: WorkflowActivationPreflightRules,
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async assertCanActivate(workflowId: string): Promise<void> {
|
|
18
|
+
const decodedId = decodeURIComponent(workflowId);
|
|
19
|
+
const workflow = this.workflowRepository.get(decodedId);
|
|
20
|
+
if (!workflow) {
|
|
21
|
+
throw new ApplicationRequestError(404, `Unknown workflowId: ${decodedId}`);
|
|
22
|
+
}
|
|
23
|
+
const health = await this.credentialBindingService.listWorkflowHealth(decodedId);
|
|
24
|
+
const errors = [
|
|
25
|
+
...this.rules.collectNonManualTriggerErrors(workflow),
|
|
26
|
+
...this.rules.collectRequiredCredentialErrors(health),
|
|
27
|
+
];
|
|
28
|
+
if (errors.length > 0) {
|
|
29
|
+
throw new ApplicationRequestError(400, "Workflow cannot be activated.", errors);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|