@codemation/host 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +465 -0
- package/LICENSE +1 -37
- package/dist/{ApiPaths-CLTHphYZ.js → ApiPaths-Dv1dcHu_.js} +4 -4
- package/dist/ApiPaths-Dv1dcHu_.js.map +1 -0
- package/dist/{AppConfigFactory-CvpFScwB.js → AppConfigFactory-Cx4qQvRk.js} +114 -53
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +1 -0
- package/dist/{AppConfigFactory-LK76niPc.d.ts → AppConfigFactory-DnLoQ9Li.d.ts} +8527 -5549
- package/dist/{AppContainerFactory-BlLrm6_h.js → AppContainerFactory-DqKYCRNP.js} +7656 -2090
- package/dist/AppContainerFactory-DqKYCRNP.js.map +1 -0
- package/dist/{CodemationAppContext-CvWi5gey.d.ts → CodemationAppContext-CKVv9W9q.d.ts} +8 -4
- package/dist/{CodemationAuthoring.types-BuKNTDC1.d.ts → CodemationAuthoring.types-DA3G3s6d.d.ts} +25 -5
- package/dist/{CodemationAuthoring.types-DZl-sJaM.js → CodemationAuthoring.types-NGkBcmmT.js} +18 -6
- package/dist/CodemationAuthoring.types-NGkBcmmT.js.map +1 -0
- package/dist/{CodemationConfigNormalizer-CYdR0PR5.d.ts → CodemationConfigNormalizer-BAKjetJ6.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-BeAUS144.js → CodemationConsumerConfigLoader-GYpBBvqE.js} +79 -10
- package/dist/CodemationConsumerConfigLoader-GYpBBvqE.js.map +1 -0
- package/dist/{CodemationConsumerConfigLoader-C3nAj9Bj.d.ts → CodemationConsumerConfigLoader-nxOqvv46.d.ts} +17 -2
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js → CodemationPluginListMerger-D1B1IEbt.js} +1 -1
- package/dist/{CodemationPluginListMerger-B-W5Fa_X.js.map → CodemationPluginListMerger-D1B1IEbt.js.map} +1 -1
- package/dist/{CodemationPluginListMerger-D-gwVwtw.d.ts → CodemationPluginListMerger-DKLAHT2b.d.ts} +123 -16
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js +97 -0
- package/dist/CodemationTsyringeTypeInfoRegistrar-Bj6FJYFz.js.map +1 -0
- package/dist/{CodemationWhitelabelConfig-CWbcyQqn.d.ts → CodemationWhitelabelConfig-Ca2mCUeC.d.ts} +2 -2
- package/dist/{CollectionContracts.types-DdpHft0i.d.ts → CollectionContracts.types-DDyFYT_D.d.ts} +1 -1
- package/dist/{CredentialContractsRegistry-D7mcPed2.d.ts → CredentialContractsRegistry-Bq2bq28t.d.ts} +2 -2
- package/dist/{CredentialServices-DdCEP2xt.d.ts → CredentialServices-Be2I60Th.d.ts} +65 -20
- package/dist/{CredentialServices-CgxwguAv.js → CredentialServices-Dk8yypeL.js} +310 -51
- package/dist/CredentialServices-Dk8yypeL.js.map +1 -0
- package/dist/InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts +17 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js +221 -0
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +1 -0
- package/dist/{ItemsInputNormalizer-D1WppVMU.d.ts → ItemsInputNormalizer-_RwIfRIQ.d.ts} +108 -25
- package/dist/{LogLevelPolicyFactory-CampWO0l.d.ts → LogLevelPolicyFactory-ewCHLDLn.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrap-DzBgwOnG.d.ts → PublicFrontendBootstrap-Cev3qK46.d.ts} +9 -2
- package/dist/PublicFrontendBootstrapFactory-CY2FS-5g.d.ts +82 -0
- package/dist/{PublicFrontendBootstrapJsonCodec-Cl_DLRh0.d.ts → PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts} +3 -3
- package/dist/{PublicFrontendBootstrapJsonCodec-DzqvA0uo.js → PublicFrontendBootstrapJsonCodec-CegIF_ne.js} +7 -2
- package/dist/PublicFrontendBootstrapJsonCodec-CegIF_ne.js.map +1 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js +223 -0
- package/dist/ServerLoggerFactory-Ckk52S3w.js.map +1 -0
- package/dist/{TelemetryContracts-BsOD_Y17.d.ts → TelemetryContracts-BtDx84Cp.d.ts} +13 -4
- package/dist/{WorkflowPolicyUiPresentationFactory-DNE5oAI6.d.ts → WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-DhPqQ9aB.js.map → WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map} +1 -1
- package/dist/{WorkflowViewContracts-0ZgsHQdp.d.ts → WorkflowViewContracts-B7aFQcIw.d.ts} +15 -1
- package/dist/authoring.d.ts +5 -5
- package/dist/authoring.js +1 -1
- package/dist/client.d.ts +4 -4
- package/dist/client.js +2 -2
- package/dist/consumer.d.ts +6 -6
- package/dist/consumer.js +2 -2
- package/dist/credentials.d.ts +6 -6
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/devServerSidecar.js +1 -94
- package/dist/devServerSidecar.js.map +1 -1
- package/dist/dto.d.ts +6 -6
- package/dist/{index-BlGs9e9Q.d.ts → index-DilAYwnH.d.ts} +49 -3
- package/dist/index.d.ts +110 -21
- package/dist/index.js +15 -13
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +43 -88
- package/dist/nextServer.js +9 -7
- package/dist/pairing.d.ts +93 -0
- package/dist/pairing.js +5 -0
- package/dist/pairing.types-snfZ_OzB.d.ts +19 -0
- package/dist/{persistenceServer-CpNFYa_q.js → persistenceServer-C-hH4z6l.js} +2 -2
- package/dist/{persistenceServer-CpNFYa_q.js.map → persistenceServer-C-hH4z6l.js.map} +1 -1
- package/dist/persistenceServer-CeTHtC6E.d.ts +30 -0
- package/dist/persistenceServer.d.ts +8 -8
- package/dist/persistenceServer.js +3 -3
- package/dist/{server-CQWdkT7t.d.ts → server-C4bS62rg.d.ts} +21 -6
- package/dist/{server-BK43OKxW.js → server-Y7kxwtCK.js} +7 -6
- package/dist/{server-BK43OKxW.js.map → server-Y7kxwtCK.js.map} +1 -1
- package/dist/server.d.ts +14 -14
- package/dist/server.js +13 -11
- package/package.json +29 -42
- package/prisma/migrations/20260507120000_execution_instance_child_run_id/migration.sql +5 -0
- package/prisma/migrations/20260519000000_workflow_audit_log/migration.sql +23 -0
- package/prisma/migrations/20260519100000_storage_growth_fixes/migration.sql +61 -0
- package/prisma/migrations.sqlite/20260507120000_execution_instance_child_run_id/migration.sql +5 -0
- package/prisma/migrations.sqlite/20260519000000_workflow_audit_log/migration.sql +21 -0
- package/prisma/migrations.sqlite/20260519100000_storage_growth_fixes/migration.sql +29 -0
- package/prisma/schema.postgresql.prisma +56 -17
- package/prisma/schema.sqlite.prisma +56 -17
- package/prisma-generated/prisma-postgresql-client/edge.js +35 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +31 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +8971 -5718
- package/prisma-generated/prisma-postgresql-client/index.js +35 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +39 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +35 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +31 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +8963 -5715
- package/prisma-generated/prisma-sqlite-client/index.js +35 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +39 -0
- package/scripts/check-collections.mjs +18 -0
- package/scripts/generate-prisma-clients.mjs +20 -11
- package/src/application/WorkflowAuditLogPruneScheduler.ts +96 -0
- package/src/application/auth/AuthenticatedPrincipal.ts +4 -0
- package/src/application/commands/StartWorkflowRunCommandHandler.ts +4 -0
- package/src/application/contracts/WorkflowViewContracts.ts +11 -0
- package/src/application/contracts/WorkflowWebsocketMessage.ts +3 -1
- package/src/application/mapping/WorkflowDefinitionMapper.ts +44 -1
- package/src/application/runs/WorkflowRunRetentionPruneScheduler.ts +7 -1
- package/src/application/telemetry/OtelExecutionTelemetry.types.ts +5 -0
- package/src/application/telemetry/OtelExecutionTelemetryFactory.ts +4 -0
- package/src/application/telemetry/StoredTelemetrySpanScope.ts +6 -2
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +27 -17
- package/src/application/telemetry/TelemetrySpanPublisher.ts +11 -0
- package/src/application/websocket/TelemetrySpanWebsocketRelay.ts +31 -0
- package/src/applicationTokens.ts +20 -1
- package/src/audit/IAuditEmitter.ts +32 -0
- package/src/audit/PrismaWorkflowAuditLogRepository.ts +34 -0
- package/src/audit/WorkflowAuditLogWriter.ts +125 -0
- package/src/auth/managed/ManagedAuthConfig.ts +29 -0
- package/src/auth/managed/ManagedAuthMiddleware.ts +52 -0
- package/src/auth/managed/ManagedCorsMiddleware.ts +43 -0
- package/src/auth/managed/ManagedModeBootGuard.ts +27 -0
- package/src/auth/managed/index.ts +5 -0
- package/src/bootstrap/AppContainerFactory.ts +277 -29
- package/src/bootstrap/AppContainerLifecycle.ts +31 -0
- package/src/bootstrap/perf/BootTimer.ts +168 -0
- package/src/bootstrap/runtime/AppConfigFactory.ts +21 -65
- package/src/bootstrap/runtime/FrontendRuntime.ts +4 -1
- package/src/bootstrap/runtime/WorkerRuntime.ts +2 -1
- package/src/credentials/BrokerClient.ts +49 -0
- package/src/credentials/BrokerRefreshError.ts +12 -0
- package/src/credentials/BrokerRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +261 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +136 -0
- package/src/credentials/InternalCredentialsListRegistrar.ts +48 -0
- package/src/credentials/InternalCredentialsPushRegistrar.ts +125 -0
- package/src/credentials/LocalOAuthFlowExecutor.ts +316 -0
- package/src/credentials/ManagedOAuthFlowExecutor.ts +94 -0
- package/src/credentials/ManagedOAuthRefreshInvalidGrantError.ts +13 -0
- package/src/credentials/catalogTypes.ts +4 -0
- package/src/credentials/refresh/CredentialDisconnectedError.ts +11 -0
- package/src/domain/credentials/CredentialBindingService.ts +54 -2
- package/src/domain/credentials/CredentialKeyRotatedError.ts +22 -0
- package/src/domain/credentials/CredentialSecretCipher.ts +68 -6
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +117 -10
- package/src/domain/credentials/OAuth2RedirectUriResolver.ts +79 -0
- package/src/domain/credentials/WorkflowCredentialNodeResolver.ts +14 -5
- package/src/domain/telemetry/TelemetryContracts.ts +7 -1
- package/src/domain/workflows/WorkflowActivationPreflight.ts +24 -1
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +40 -1
- package/src/index.ts +6 -0
- package/src/infrastructure/binary/LocalFilesystemBinaryStorageRegistry.ts +29 -1
- package/src/infrastructure/binary/S3BinaryStorage.ts +169 -0
- package/src/infrastructure/binary/S3BinaryStorageConfig.ts +17 -0
- package/src/infrastructure/config/CodemationPluginRegistrar.ts +3 -1
- package/src/infrastructure/persistence/CodemationDatabaseUrlParser.ts +41 -0
- package/src/infrastructure/persistence/InMemoryTelemetryArtifactStore.ts +8 -3
- package/src/infrastructure/persistence/InMemoryWorkflowRunRepository.ts +1 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +21 -13
- package/src/infrastructure/persistence/PrismaTelemetryArtifactStore.ts +43 -8
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +33 -3
- package/src/infrastructure/persistence/PrismaWorkflowSnapshotRepository.ts +48 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +344 -0
- package/src/mcp/McpClientFactory.ts +29 -0
- package/src/mcp/McpConnectionPool.ts +184 -0
- package/src/mcp/McpConnectionPool.types.ts +12 -0
- package/src/mcp/McpServerCatalog.ts +104 -0
- package/src/mcp/index.ts +5 -0
- package/src/pairing/HmacRequestSigner.ts +32 -0
- package/src/pairing/IncomingHmacVerifier.ts +82 -0
- package/src/pairing/InternalHmacAuthMiddleware.ts +33 -0
- package/src/pairing/InternalPingRegistrar.ts +25 -0
- package/src/pairing/PairedFetch.ts +33 -0
- package/src/pairing/PairingConfigFactory.ts +35 -0
- package/src/pairing/PairingConfigToken.ts +6 -0
- package/src/pairing/index.ts +14 -0
- package/src/pairing/pairing.types.ts +18 -0
- package/src/pairing.ts +17 -0
- package/src/persistenceServer.ts +1 -0
- package/src/presentation/config/AppConfig.ts +7 -1
- package/src/presentation/config/CodemationAuthConfig.ts +1 -1
- package/src/presentation/config/CodemationAuthoring.types.ts +54 -5
- package/src/presentation/config/CodemationConfig.ts +3 -0
- package/src/presentation/config/CodemationConfigNormalizer.ts +39 -1
- package/src/presentation/config/CodemationPlugin.ts +2 -1
- package/src/presentation/frontend/CodemationFrontendAuthSnapshot.ts +5 -0
- package/src/presentation/frontend/CodemationFrontendAuthSnapshotFactory.ts +7 -1
- package/src/presentation/frontend/PublicFrontendBootstrap.ts +2 -0
- package/src/presentation/frontend/PublicFrontendBootstrapFactory.ts +5 -1
- package/src/presentation/frontend/PublicFrontendBootstrapJsonCodec.ts +4 -1
- package/src/presentation/http/ApiPaths.ts +4 -4
- package/src/presentation/http/ServerHttpErrorResponseFactory.ts +39 -2
- package/src/presentation/http/hono/CodemationHonoApiAppFactory.ts +33 -8
- package/src/presentation/http/hono/InternalHonoApiRouteRegistrar.ts +12 -0
- package/src/presentation/http/hono/registrars/ManagedMeHonoApiRouteRegistrar.ts +35 -0
- package/src/presentation/http/hono/registrars/OAuth2HonoApiRouteRegistrar.ts +2 -2
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +28 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +98 -41
- package/src/presentation/server/CodemationConsumerConfigLoader.ts +54 -7
- package/src/presentation/server/CodemationPluginDiscovery.ts +5 -0
- package/src/presentation/server/WorkflowDefinitionExportsResolver.ts +18 -0
- package/src/presentation/server/WorkflowModulePathFinder.ts +12 -1
- package/src/presentation/websocket/ManagedWebsocketAuthenticator.ts +50 -0
- package/src/presentation/websocket/WebsocketAuthenticator.types.ts +12 -0
- package/src/presentation/websocket/WorkflowWebsocketServer.ts +24 -3
- package/src/process/ExecaProcessRunner.ts +41 -0
- package/src/process/ProcessRunner.types.ts +39 -0
- package/src/server.ts +2 -0
- package/src/workflows/InternalWorkflowActivationRegistrar.ts +42 -0
- package/src/workflows/InternalWorkflowDetailRegistrar.ts +33 -0
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +91 -0
- package/src/workflows/InternalWorkflowsListRegistrar.ts +28 -0
- package/src/workflows/discovery/WorkflowDirectoryDiscoverer.ts +79 -0
- package/tsconfig.json +2 -0
- package/vitest.shared.ts +5 -0
- package/dist/ApiPaths-CLTHphYZ.js.map +0 -1
- package/dist/AppConfigFactory-CvpFScwB.js.map +0 -1
- package/dist/AppContainerFactory-BlLrm6_h.js.map +0 -1
- package/dist/CodemationAuthoring.types-DZl-sJaM.js.map +0 -1
- package/dist/CodemationConsumerConfigLoader-BeAUS144.js.map +0 -1
- package/dist/CredentialServices-CgxwguAv.js.map +0 -1
- package/dist/PublicFrontendBootstrapFactory-BMWqNM9a.d.ts +0 -45
- package/dist/PublicFrontendBootstrapJsonCodec-DzqvA0uo.js.map +0 -1
- package/dist/ServerLoggerFactory-BKSIh9Xv.js +0 -98
- package/dist/ServerLoggerFactory-BKSIh9Xv.js.map +0 -1
- package/dist/persistenceServer-CIVt3UOX.d.ts +0 -9
- package/src/domain/credentials/OAuth2ConnectServiceFactory.ts +0 -411
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCredentialType, DefinedCollection, DefinedNode } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, DefinedCollection, DefinedNode, McpServerDeclaration } from "@codemation/core";
|
|
2
2
|
import type { CodemationAppContext } from "./CodemationAppContext";
|
|
3
3
|
import type {
|
|
4
4
|
CodemationAppDefinition,
|
|
@@ -12,14 +12,20 @@ import type { CodemationWhitelabelConfig } from "./CodemationWhitelabelConfig";
|
|
|
12
12
|
export interface FriendlyCodemationDatabaseConfig {
|
|
13
13
|
readonly kind: "postgresql" | "sqlite";
|
|
14
14
|
readonly url?: string;
|
|
15
|
+
/** Name of an environment variable whose value is the PostgreSQL connection URL. Co-exclusive with `url`. */
|
|
16
|
+
readonly urlEnv?: string;
|
|
15
17
|
readonly filePath?: string;
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
export interface FriendlyCodemationExecutionConfig {
|
|
19
21
|
readonly mode?: "inline" | "queue";
|
|
22
|
+
/** Name of an environment variable whose value is "inline" or "queue". Co-exclusive with `mode`. */
|
|
23
|
+
readonly modeEnv?: string;
|
|
20
24
|
readonly queuePrefix?: string;
|
|
21
25
|
readonly workerQueues?: ReadonlyArray<string>;
|
|
22
26
|
readonly redisUrl?: string;
|
|
27
|
+
/** Name of an environment variable whose value is the Redis connection URL. Co-exclusive with `redisUrl`. */
|
|
28
|
+
readonly redisUrlEnv?: string;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
export interface DefineCodemationAppOptions extends Omit<
|
|
@@ -36,6 +42,13 @@ export interface DefineCodemationAppOptions extends Omit<
|
|
|
36
42
|
readonly credentials?: ReadonlyArray<AnyCredentialType>;
|
|
37
43
|
readonly register?: (context: CodemationAppContext) => void;
|
|
38
44
|
readonly whitelabel?: CodemationWhitelabelConfig;
|
|
45
|
+
/**
|
|
46
|
+
* Path (relative to the consumer project root) to a directory from which workflows are auto-discovered.
|
|
47
|
+
* All `*.ts` / `*.tsx` files (excluding `*.test.*` and `*.d.ts`) are imported and any exported
|
|
48
|
+
* `WorkflowDefinition` values are registered. Co-exclusive with providing this directory in
|
|
49
|
+
* `workflowDiscovery.directories`.
|
|
50
|
+
*/
|
|
51
|
+
readonly workflowsDir?: string;
|
|
39
52
|
}
|
|
40
53
|
|
|
41
54
|
export interface DefinePluginOptions {
|
|
@@ -44,6 +57,7 @@ export interface DefinePluginOptions {
|
|
|
44
57
|
readonly nodes?: ReadonlyArray<DefinedNode<string, Record<string, unknown>, unknown, unknown>>;
|
|
45
58
|
readonly collections?: ReadonlyArray<DefinedCollection>;
|
|
46
59
|
readonly credentials?: ReadonlyArray<AnyCredentialType>;
|
|
60
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
47
61
|
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
48
62
|
readonly sandbox?: CodemationConfig;
|
|
49
63
|
}
|
|
@@ -53,13 +67,15 @@ class CodemationAuthoringConfigFactory {
|
|
|
53
67
|
const appDefinition = this.createAppDefinition(options);
|
|
54
68
|
const credentialTypes = [...(options.credentialTypes ?? []), ...(options.credentials ?? [])];
|
|
55
69
|
const register = this.composeAppRegister(options.register, options.nodes, options.collections);
|
|
56
|
-
const { workflows,
|
|
70
|
+
const { workflows, plugins, runtime, log, mcpServers } = options;
|
|
71
|
+
const workflowDiscovery = this.mergeWorkflowDiscovery(options.workflowDiscovery, options.workflowsDir);
|
|
57
72
|
return {
|
|
58
73
|
workflows,
|
|
59
74
|
workflowDiscovery,
|
|
60
75
|
plugins,
|
|
61
76
|
runtime,
|
|
62
77
|
log,
|
|
78
|
+
mcpServers,
|
|
63
79
|
app: appDefinition,
|
|
64
80
|
credentialTypes,
|
|
65
81
|
register,
|
|
@@ -70,6 +86,7 @@ class CodemationAuthoringConfigFactory {
|
|
|
70
86
|
return {
|
|
71
87
|
pluginPackageId: options.pluginPackageId,
|
|
72
88
|
sandbox: options.sandbox,
|
|
89
|
+
mcpServers: options.mcpServers,
|
|
73
90
|
async register(context: CodemationPluginContext): Promise<void> {
|
|
74
91
|
for (const nodeDefinition of options.nodes ?? []) {
|
|
75
92
|
nodeDefinition.register(context);
|
|
@@ -112,9 +129,15 @@ class CodemationAuthoringConfigFactory {
|
|
|
112
129
|
sqliteFilePath: database.filePath,
|
|
113
130
|
};
|
|
114
131
|
}
|
|
132
|
+
if (database.url !== undefined && database.urlEnv !== undefined) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
"defineCodemationApp: database.url and database.urlEnv are mutually exclusive — provide one or the other.",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
const url = database.urlEnv !== undefined ? process.env[database.urlEnv] : database.url;
|
|
115
138
|
return {
|
|
116
139
|
kind: "postgresql",
|
|
117
|
-
url
|
|
140
|
+
url,
|
|
118
141
|
};
|
|
119
142
|
}
|
|
120
143
|
|
|
@@ -124,11 +147,37 @@ class CodemationAuthoringConfigFactory {
|
|
|
124
147
|
if (!execution) {
|
|
125
148
|
return undefined;
|
|
126
149
|
}
|
|
150
|
+
if (execution.mode !== undefined && execution.modeEnv !== undefined) {
|
|
151
|
+
throw new Error(
|
|
152
|
+
"defineCodemationApp: execution.mode and execution.modeEnv are mutually exclusive — provide one or the other.",
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
if (execution.redisUrl !== undefined && execution.redisUrlEnv !== undefined) {
|
|
156
|
+
throw new Error(
|
|
157
|
+
"defineCodemationApp: execution.redisUrl and execution.redisUrlEnv are mutually exclusive — provide one or the other.",
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
const rawMode = execution.modeEnv !== undefined ? process.env[execution.modeEnv] : execution.mode;
|
|
161
|
+
const mode = rawMode === "inline" || rawMode === "queue" ? rawMode : undefined;
|
|
162
|
+
const redisUrl = execution.redisUrlEnv !== undefined ? process.env[execution.redisUrlEnv] : execution.redisUrl;
|
|
127
163
|
return {
|
|
128
|
-
kind:
|
|
164
|
+
kind: mode,
|
|
129
165
|
queuePrefix: execution.queuePrefix,
|
|
130
166
|
workerQueues: execution.workerQueues,
|
|
131
|
-
redisUrl
|
|
167
|
+
redisUrl,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private static mergeWorkflowDiscovery(
|
|
172
|
+
existing: CodemationConfig["workflowDiscovery"],
|
|
173
|
+
workflowsDir: string | undefined,
|
|
174
|
+
): CodemationConfig["workflowDiscovery"] {
|
|
175
|
+
if (!workflowsDir) {
|
|
176
|
+
return existing;
|
|
177
|
+
}
|
|
178
|
+
const existingDirectories = existing?.directories ?? [];
|
|
179
|
+
return {
|
|
180
|
+
directories: [...existingDirectories, workflowsDir],
|
|
132
181
|
};
|
|
133
182
|
}
|
|
134
183
|
|
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
CollectionDefinition,
|
|
4
4
|
DefinedCollection,
|
|
5
5
|
EngineExecutionLimitsPolicyConfig,
|
|
6
|
+
McpServerDeclaration,
|
|
6
7
|
WorkflowDefinition,
|
|
7
8
|
} from "@codemation/core";
|
|
8
9
|
import type { CodemationAuthConfig } from "./CodemationAuthConfig";
|
|
@@ -80,6 +81,8 @@ export interface CodemationConfig {
|
|
|
80
81
|
readonly plugins?: ReadonlyArray<CodemationPlugin>;
|
|
81
82
|
/** Consumer-defined `CredentialType` entries (see `@codemation/core`), applied when the host loads config. */
|
|
82
83
|
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
84
|
+
/** MCP server declarations from this config file (merged with plugin and control-plane sources at startup). */
|
|
85
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
83
86
|
/** Declared collections available at runtime via `ctx.collections`. Accepts either raw `CollectionDefinition` or `DefinedCollection` (the result of `defineCollection(...)`). */
|
|
84
87
|
readonly collections?: ReadonlyArray<CollectionDefinition | DefinedCollection>;
|
|
85
88
|
/** Optional shell whitelabel (product name, logo path). */
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
} from "@codemation/core";
|
|
9
9
|
import type { CodemationContainerRegistration } from "../../bootstrap/CodemationContainerRegistration";
|
|
10
10
|
import type { CodemationAppContext } from "./CodemationAppContext";
|
|
11
|
+
import type { CodemationAuthConfig } from "./CodemationAuthConfig";
|
|
11
12
|
import type { CodemationClassToken } from "./CodemationClassToken";
|
|
12
13
|
import type {
|
|
13
14
|
CodemationApplicationRuntimeConfig,
|
|
@@ -25,6 +26,9 @@ export type NormalizedCodemationConfig = Omit<CodemationConfig, "collections"> &
|
|
|
25
26
|
|
|
26
27
|
export class CodemationConfigNormalizer {
|
|
27
28
|
normalize(config: CodemationConfig): NormalizedCodemationConfig {
|
|
29
|
+
const auth = config.app?.auth ?? config.auth;
|
|
30
|
+
this.assertAuthConfig(auth);
|
|
31
|
+
this.assertManagedModeConstraints(config, auth);
|
|
28
32
|
const collected = this.collectRegistration(config);
|
|
29
33
|
const normalizedRuntime = this.normalizeRuntimeConfig(config);
|
|
30
34
|
const normalizedWorkflowDiscoveryDirectories = [
|
|
@@ -34,7 +38,7 @@ export class CodemationConfigNormalizer {
|
|
|
34
38
|
|
|
35
39
|
return {
|
|
36
40
|
...config,
|
|
37
|
-
auth
|
|
41
|
+
auth,
|
|
38
42
|
containerRegistrations: collected.containerRegistrations,
|
|
39
43
|
credentialTypes: [...(config.credentialTypes ?? []), ...collected.credentialTypes],
|
|
40
44
|
collections: [...this.unwrapCollections(config.collections), ...collected.collections],
|
|
@@ -49,6 +53,23 @@ export class CodemationConfigNormalizer {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Enforces managed-mode invariants beyond what `assertAuthConfig` covers:
|
|
58
|
+
* managed-mode workspaces are always Postgres and always require at least one workflow source.
|
|
59
|
+
*/
|
|
60
|
+
private assertManagedModeConstraints(config: CodemationConfig, auth: CodemationAuthConfig | undefined): void {
|
|
61
|
+
if (auth?.kind !== "managed") {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const hasWorkflows = (config.workflows?.length ?? 0) > 0;
|
|
65
|
+
const hasWorkflowDiscovery = (config.workflowDiscovery?.directories?.length ?? 0) > 0;
|
|
66
|
+
if (!hasWorkflows && !hasWorkflowDiscovery) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'Managed-mode workspaces require at least one workflow source. Provide "workflows" or "workflowsDir" (which maps to workflowDiscovery.directories) in defineCodemationApp.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
52
73
|
private collectRegistration(config: CodemationConfig): Readonly<{
|
|
53
74
|
containerRegistrations: ReadonlyArray<CodemationContainerRegistration<unknown>>;
|
|
54
75
|
credentialTypes: ReadonlyArray<AnyCredentialType>;
|
|
@@ -187,6 +208,23 @@ export class CodemationConfigNormalizer {
|
|
|
187
208
|
};
|
|
188
209
|
}
|
|
189
210
|
|
|
211
|
+
private assertAuthConfig(authConfig: CodemationConfig["auth"]): void {
|
|
212
|
+
if (authConfig?.kind !== "managed") {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (authConfig.oauth && authConfig.oauth.length > 0) {
|
|
216
|
+
throw new Error('auth.kind "managed" cannot be combined with oauth providers. Remove the oauth config.');
|
|
217
|
+
}
|
|
218
|
+
if (authConfig.oidc && authConfig.oidc.length > 0) {
|
|
219
|
+
throw new Error('auth.kind "managed" cannot be combined with oidc providers. Remove the oidc config.');
|
|
220
|
+
}
|
|
221
|
+
if (authConfig.allowUnauthenticatedInDevelopment === true) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
'auth.kind "managed" cannot be combined with allowUnauthenticatedInDevelopment. Remove that flag.',
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
190
228
|
private mergeWorkflows(
|
|
191
229
|
configuredWorkflows: ReadonlyArray<WorkflowDefinition>,
|
|
192
230
|
registeredWorkflows: ReadonlyArray<WorkflowDefinition>,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AnyCredentialType, Container } from "@codemation/core";
|
|
1
|
+
import type { AnyCredentialType, Container, McpServerDeclaration } from "@codemation/core";
|
|
2
2
|
import type { LoggerFactory } from "../../application/logging/Logger";
|
|
3
3
|
import type { AppConfig } from "./AppConfig";
|
|
4
4
|
import type { CodemationRegistrationContextBase } from "./CodemationAppContext";
|
|
@@ -13,6 +13,7 @@ export interface CodemationPluginContext extends CodemationRegistrationContextBa
|
|
|
13
13
|
export interface CodemationPluginConfig {
|
|
14
14
|
readonly pluginPackageId?: string;
|
|
15
15
|
readonly credentialTypes?: ReadonlyArray<AnyCredentialType>;
|
|
16
|
+
readonly mcpServers?: ReadonlyArray<McpServerDeclaration>;
|
|
16
17
|
readonly register?: (context: CodemationPluginContext) => void | Promise<void>;
|
|
17
18
|
readonly sandbox?: CodemationConfig;
|
|
18
19
|
}
|
|
@@ -11,4 +11,9 @@ export type CodemationFrontendAuthSnapshot = Readonly<{
|
|
|
11
11
|
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
12
12
|
secret: string | null;
|
|
13
13
|
uiAuthEnabled: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Present when auth.kind === "managed". Carries the CP-web origin for CORS
|
|
16
|
+
* awareness. No UI login flow is rendered in managed mode.
|
|
17
|
+
*/
|
|
18
|
+
cpWebOrigin?: string;
|
|
14
19
|
}>;
|
|
@@ -29,13 +29,19 @@ export class CodemationFrontendAuthSnapshotFactory {
|
|
|
29
29
|
uiAuthEnabled: boolean;
|
|
30
30
|
}>,
|
|
31
31
|
): CodemationFrontendAuthSnapshot {
|
|
32
|
-
|
|
32
|
+
const snapshot = {
|
|
33
33
|
config: args.authConfig,
|
|
34
34
|
credentialsEnabled: args.authConfig?.kind === "local",
|
|
35
35
|
oauthProviders: this.createOauthProviders(args.authConfig),
|
|
36
36
|
secret: this.resolveAuthSecret(args.env),
|
|
37
37
|
uiAuthEnabled: args.uiAuthEnabled,
|
|
38
38
|
};
|
|
39
|
+
const cpWebOrigin =
|
|
40
|
+
args.authConfig?.kind === "managed" ? args.env["CP_WEB_ORIGIN"]?.trim() || undefined : undefined;
|
|
41
|
+
if (cpWebOrigin) {
|
|
42
|
+
return { ...snapshot, cpWebOrigin };
|
|
43
|
+
}
|
|
44
|
+
return snapshot;
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
private resolveUiAuthEnabled(authConfig: CodemationAuthConfig | undefined, env: NodeJS.ProcessEnv): boolean {
|
|
@@ -9,4 +9,6 @@ export type PublicFrontendBootstrap = Readonly<{
|
|
|
9
9
|
oauthProviders: ReadonlyArray<CodemationFrontendAuthProviderSnapshot>;
|
|
10
10
|
productName: string;
|
|
11
11
|
uiAuthEnabled: boolean;
|
|
12
|
+
/** Present in managed mode — the CP web origin. No UI login is rendered when this is set. */
|
|
13
|
+
cpWebOrigin?: string;
|
|
12
14
|
}>;
|
|
@@ -12,12 +12,16 @@ export class PublicFrontendBootstrapFactory {
|
|
|
12
12
|
|
|
13
13
|
create(): PublicFrontendBootstrap {
|
|
14
14
|
const frontendAppConfig = this.frontendAppConfigFactory.create();
|
|
15
|
-
|
|
15
|
+
const bootstrap: PublicFrontendBootstrap = {
|
|
16
16
|
credentialsEnabled: frontendAppConfig.auth.credentialsEnabled,
|
|
17
17
|
logoUrl: frontendAppConfig.logoUrl,
|
|
18
18
|
oauthProviders: frontendAppConfig.auth.oauthProviders,
|
|
19
19
|
productName: frontendAppConfig.productName,
|
|
20
20
|
uiAuthEnabled: frontendAppConfig.auth.uiAuthEnabled,
|
|
21
21
|
};
|
|
22
|
+
if (frontendAppConfig.auth.cpWebOrigin) {
|
|
23
|
+
return { ...bootstrap, cpWebOrigin: frontendAppConfig.auth.cpWebOrigin };
|
|
24
|
+
}
|
|
25
|
+
return bootstrap;
|
|
22
26
|
}
|
|
23
27
|
}
|
|
@@ -15,7 +15,7 @@ export class PublicFrontendBootstrapJsonCodec {
|
|
|
15
15
|
if (!parsed || typeof parsed !== "object") {
|
|
16
16
|
return null;
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
const base: PublicFrontendBootstrap = {
|
|
19
19
|
credentialsEnabled: parsed.credentialsEnabled === true,
|
|
20
20
|
logoUrl: typeof parsed.logoUrl === "string" && parsed.logoUrl.trim().length > 0 ? parsed.logoUrl : null,
|
|
21
21
|
oauthProviders: this.resolveOauthProviders(parsed.oauthProviders),
|
|
@@ -25,6 +25,9 @@ export class PublicFrontendBootstrapJsonCodec {
|
|
|
25
25
|
: "Codemation",
|
|
26
26
|
uiAuthEnabled: parsed.uiAuthEnabled !== false,
|
|
27
27
|
};
|
|
28
|
+
const cpWebOrigin =
|
|
29
|
+
typeof parsed.cpWebOrigin === "string" && parsed.cpWebOrigin.trim().length > 0 ? parsed.cpWebOrigin : undefined;
|
|
30
|
+
return cpWebOrigin ? { ...base, cpWebOrigin } : base;
|
|
28
31
|
} catch {
|
|
29
32
|
return null;
|
|
30
33
|
}
|
|
@@ -145,10 +145,6 @@ export class ApiPaths {
|
|
|
145
145
|
return `${this.apiBasePath}/credential-bindings`;
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
static oauth2Auth(instanceId: string): string {
|
|
149
|
-
return `${this.oauth2BasePath}/auth?instanceId=${encodeURIComponent(instanceId)}`;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
148
|
static oauth2RedirectUri(): string {
|
|
153
149
|
return `${this.oauth2BasePath}/redirect-uri`;
|
|
154
150
|
}
|
|
@@ -157,6 +153,10 @@ export class ApiPaths {
|
|
|
157
153
|
return `${this.oauth2BasePath}/disconnect?instanceId=${encodeURIComponent(instanceId)}`;
|
|
158
154
|
}
|
|
159
155
|
|
|
156
|
+
static credentialOAuthStart(): string {
|
|
157
|
+
return `${this.credentialsBasePath}/oauth/start`;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
160
|
static workflowWebsocket(): string {
|
|
161
161
|
return `${this.workflowsBasePath}/ws`;
|
|
162
162
|
}
|
|
@@ -1,13 +1,50 @@
|
|
|
1
1
|
import { ApplicationRequestError } from "../../application/ApplicationRequestError";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Shape of the JSON body returned on an unhandled 500. The canvas (and any other client)
|
|
5
|
+
* reads `message` + optional `stack` + optional `cause` to surface a copy/pastable error
|
|
6
|
+
* dialog. Generic "Internal server error" with no detail makes operator triage impossible
|
|
7
|
+
* — this contract preserves the diagnostic information the CLI logs anyway.
|
|
8
|
+
*/
|
|
9
|
+
export type ServerHttpUnhandledErrorPayload = Readonly<{
|
|
10
|
+
error: "Internal server error";
|
|
11
|
+
message: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
stack?: string;
|
|
14
|
+
cause?: string;
|
|
15
|
+
}>;
|
|
16
|
+
|
|
3
17
|
export class ServerHttpErrorResponseFactory {
|
|
4
18
|
static fromUnknown(error: unknown): Response {
|
|
5
19
|
if (error instanceof ApplicationRequestError) {
|
|
6
20
|
return Response.json(error.payload, { status: error.status });
|
|
7
21
|
}
|
|
8
22
|
this.logUnexpectedError(error);
|
|
9
|
-
|
|
10
|
-
|
|
23
|
+
return Response.json(this.toUnhandledPayload(error), { status: 500 });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
private static toUnhandledPayload(error: unknown): ServerHttpUnhandledErrorPayload {
|
|
27
|
+
if (error instanceof Error) {
|
|
28
|
+
return {
|
|
29
|
+
error: "Internal server error",
|
|
30
|
+
message: error.message || `${error.name}: <no message>`,
|
|
31
|
+
name: error.name,
|
|
32
|
+
stack: error.stack,
|
|
33
|
+
cause: this.formatCauseValue(error),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
return { error: "Internal server error", message: String(error) };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static formatCauseValue(error: Error): string | undefined {
|
|
40
|
+
if (!("cause" in error) || !error.cause) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const cause = error.cause;
|
|
44
|
+
if (cause instanceof Error) {
|
|
45
|
+
return cause.stack ?? `${cause.name}: ${cause.message}`;
|
|
46
|
+
}
|
|
47
|
+
return String(cause);
|
|
11
48
|
}
|
|
12
49
|
|
|
13
50
|
private static logUnexpectedError(error: unknown): void {
|
|
@@ -5,7 +5,9 @@ import { ApplicationTokens } from "../../../applicationTokens";
|
|
|
5
5
|
import { BinaryHttpRouteHandler } from "../routeHandlers/BinaryHttpRouteHandlerFactory";
|
|
6
6
|
import { ServerHttpErrorResponseFactory } from "../ServerHttpErrorResponseFactory";
|
|
7
7
|
import type { HonoApiRouteRegistrar } from "./HonoApiRouteRegistrar";
|
|
8
|
+
import type { InternalHonoApiRouteRegistrar } from "./InternalHonoApiRouteRegistrar";
|
|
8
9
|
import { HonoHttpAnonymousRoutePolicy } from "./HonoHttpAnonymousRoutePolicyRegistry";
|
|
10
|
+
import { ManagedCorsMiddleware } from "../../../auth/managed/ManagedCorsMiddleware";
|
|
9
11
|
|
|
10
12
|
@injectable()
|
|
11
13
|
export class CodemationHonoApiApp {
|
|
@@ -18,10 +20,21 @@ export class CodemationHonoApiApp {
|
|
|
18
20
|
registrars: ReadonlyArray<HonoApiRouteRegistrar>,
|
|
19
21
|
@inject(BinaryHttpRouteHandler)
|
|
20
22
|
binaryHttpRouteHandler: BinaryHttpRouteHandler,
|
|
23
|
+
@injectAll(ApplicationTokens.InternalHonoApiRouteRegistrar, { isOptional: true })
|
|
24
|
+
internalRegistrars: ReadonlyArray<InternalHonoApiRouteRegistrar>,
|
|
25
|
+
@injectAll(ApplicationTokens.ManagedCorsMiddleware, { isOptional: true })
|
|
26
|
+
corsMiddlewareList: ReadonlyArray<ManagedCorsMiddleware>,
|
|
21
27
|
) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
// Root app — composes /api/* (auth-gated) and /internal/* (HMAC-gated) sub-apps.
|
|
29
|
+
const root = new Hono();
|
|
30
|
+
const corsMiddleware = corsMiddlewareList[0] ?? null;
|
|
31
|
+
if (corsMiddleware) {
|
|
32
|
+
root.use("*", corsMiddleware.handle());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const api = new Hono().basePath("/api");
|
|
36
|
+
api.onError((error, _c) => ServerHttpErrorResponseFactory.fromUnknown(error));
|
|
37
|
+
api.use("*", async (c, next) => {
|
|
25
38
|
if (HonoHttpAnonymousRoutePolicy.isAnonymousRoute(c.req.raw)) {
|
|
26
39
|
await next();
|
|
27
40
|
return;
|
|
@@ -33,25 +46,37 @@ export class CodemationHonoApiApp {
|
|
|
33
46
|
await next();
|
|
34
47
|
});
|
|
35
48
|
for (const registrar of registrars) {
|
|
36
|
-
registrar.register(
|
|
49
|
+
registrar.register(api);
|
|
37
50
|
}
|
|
38
|
-
|
|
51
|
+
api.get("/workflows/:workflowId/debugger-overlay/binary/:binaryId/content", (c) =>
|
|
39
52
|
binaryHttpRouteHandler.getWorkflowOverlayBinaryContent(c.req.raw, {
|
|
40
53
|
workflowId: c.req.param("workflowId"),
|
|
41
54
|
binaryId: c.req.param("binaryId"),
|
|
42
55
|
}),
|
|
43
56
|
);
|
|
44
|
-
|
|
57
|
+
api.post("/workflows/:workflowId/debugger-overlay/binary/upload", (c) =>
|
|
45
58
|
binaryHttpRouteHandler.postWorkflowDebuggerOverlayBinaryUpload(c.req.raw, {
|
|
46
59
|
workflowId: c.req.param("workflowId"),
|
|
47
60
|
}),
|
|
48
61
|
);
|
|
49
|
-
|
|
62
|
+
api.notFound((c) => {
|
|
50
63
|
const method = c.req.method.toUpperCase();
|
|
51
64
|
const url = new URL(c.req.url);
|
|
52
65
|
return c.json({ error: `Unknown API route: ${method} ${url.pathname}` }, 404);
|
|
53
66
|
});
|
|
54
|
-
|
|
67
|
+
|
|
68
|
+
root.route("/", api);
|
|
69
|
+
|
|
70
|
+
// /internal/* routes — only mounted when pairing is configured.
|
|
71
|
+
if (internalRegistrars.length > 0) {
|
|
72
|
+
const internal = new Hono();
|
|
73
|
+
for (const registrar of internalRegistrars) {
|
|
74
|
+
registrar.register(internal);
|
|
75
|
+
}
|
|
76
|
+
root.route("/", internal);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.app = root;
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
getHono(): Hono {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Hono } from "hono";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Registrar interface for routes mounted on the installation's internal Hono app
|
|
5
|
+
* (no `/api` prefix). All routes registered here are accessible at `/internal/<path>`
|
|
6
|
+
* and are protected by HMAC auth middleware.
|
|
7
|
+
*
|
|
8
|
+
* See docs/pairing-protocol.md for the wire format and auth requirements.
|
|
9
|
+
*/
|
|
10
|
+
export interface InternalHonoApiRouteRegistrar {
|
|
11
|
+
register(app: Hono): void;
|
|
12
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { inject, injectable } from "@codemation/core";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import type { HonoApiRouteRegistrar } from "../HonoApiRouteRegistrar";
|
|
4
|
+
import type { SessionVerifier } from "../../../../application/auth/SessionVerifier";
|
|
5
|
+
import { ApplicationTokens } from "../../../../applicationTokens";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Exposes `GET /api/me` in managed-auth mode.
|
|
9
|
+
*
|
|
10
|
+
* Reads the JWT principal by re-verifying the Bearer token, and returns
|
|
11
|
+
* `{ userId, workspaceId }`. No DB lookup needed — the JWT is the source of truth.
|
|
12
|
+
*
|
|
13
|
+
* Only registered when `auth.kind === "managed"`.
|
|
14
|
+
*/
|
|
15
|
+
@injectable()
|
|
16
|
+
export class ManagedMeHonoApiRouteRegistrar implements HonoApiRouteRegistrar {
|
|
17
|
+
constructor(
|
|
18
|
+
@inject(ApplicationTokens.SessionVerifier)
|
|
19
|
+
private readonly sessionVerifier: SessionVerifier,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
register(app: Hono): void {
|
|
23
|
+
app.get("/me", async (c) => {
|
|
24
|
+
try {
|
|
25
|
+
const principal = await this.sessionVerifier.verify(c.req.raw);
|
|
26
|
+
if (!principal) {
|
|
27
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
28
|
+
}
|
|
29
|
+
return c.json({ userId: principal.id, workspaceId: principal.workspaceId ?? null });
|
|
30
|
+
} catch {
|
|
31
|
+
return c.json({ error: "Unauthorized" }, 401);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -8,8 +8,8 @@ export class OAuth2HonoApiRouteRegistrar implements HonoApiRouteRegistrar {
|
|
|
8
8
|
constructor(@inject(OAuth2HttpRouteHandler) private readonly handler: OAuth2HttpRouteHandler) {}
|
|
9
9
|
|
|
10
10
|
register(app: Hono): void {
|
|
11
|
-
app.
|
|
12
|
-
app.get("/oauth2/callback", (c) => this.handler.
|
|
11
|
+
app.post("/credentials/oauth/start", (c) => this.handler.postOAuthStart(c.req.raw));
|
|
12
|
+
app.get("/oauth2/callback", (c) => this.handler.getOAuthCallback(c.req.raw));
|
|
13
13
|
app.get("/oauth2/redirect-uri", (c) => this.handler.getRedirectUri(c.req.raw));
|
|
14
14
|
app.post("/oauth2/disconnect", (c) => this.handler.postDisconnect(c.req.raw));
|
|
15
15
|
}
|
|
@@ -2,6 +2,7 @@ import { inject, injectable } from "@codemation/core";
|
|
|
2
2
|
import { HttpRequestJsonBodyReader } from "../HttpRequestJsonBodyReader";
|
|
3
3
|
import type { CommandBus } from "../../../application/bus/CommandBus";
|
|
4
4
|
import type { QueryBus } from "../../../application/bus/QueryBus";
|
|
5
|
+
import type { SessionVerifier } from "../../../application/auth/SessionVerifier";
|
|
5
6
|
import {
|
|
6
7
|
CreateCredentialInstanceCommand,
|
|
7
8
|
DeleteCredentialInstanceCommand,
|
|
@@ -23,6 +24,8 @@ import {
|
|
|
23
24
|
ListCredentialTypesQuery,
|
|
24
25
|
} from "../../../application/queries/CredentialQueryHandlers";
|
|
25
26
|
import { ApplicationTokens } from "../../../applicationTokens";
|
|
27
|
+
import type { PairingConfig } from "../../../pairing/pairing.types";
|
|
28
|
+
import { PairingConfigToken } from "../../../pairing/PairingConfigToken";
|
|
26
29
|
import { ServerHttpErrorResponseFactory } from "../ServerHttpErrorResponseFactory";
|
|
27
30
|
import type { ServerHttpRouteParams } from "../ServerHttpRouteParams";
|
|
28
31
|
|
|
@@ -33,6 +36,10 @@ export class CredentialHttpRouteHandler {
|
|
|
33
36
|
private readonly queryBus: QueryBus,
|
|
34
37
|
@inject(ApplicationTokens.CommandBus)
|
|
35
38
|
private readonly commandBus: CommandBus,
|
|
39
|
+
@inject(ApplicationTokens.SessionVerifier)
|
|
40
|
+
private readonly sessionVerifier: SessionVerifier,
|
|
41
|
+
@inject(PairingConfigToken, { isOptional: true })
|
|
42
|
+
private readonly pairingConfig: PairingConfig | null,
|
|
36
43
|
) {}
|
|
37
44
|
|
|
38
45
|
async getCredentialTypes(): Promise<Response> {
|
|
@@ -62,6 +69,27 @@ export class CredentialHttpRouteHandler {
|
|
|
62
69
|
async getCredentialInstance(request: Request, params: ServerHttpRouteParams): Promise<Response> {
|
|
63
70
|
try {
|
|
64
71
|
const withSecrets = new URL(request.url).searchParams.get("withSecrets") === "1";
|
|
72
|
+
|
|
73
|
+
if (withSecrets) {
|
|
74
|
+
// Ownership check: fail-closed.
|
|
75
|
+
// - If the session verifier returns null (unauthenticated), reject.
|
|
76
|
+
// - In managed-JWT mode the principal's workspaceId must match the
|
|
77
|
+
// installation's own workspaceId (from PairingConfig).
|
|
78
|
+
// - In local-auth mode (pairingConfig absent) a valid non-null principal
|
|
79
|
+
// is sufficient — no cross-workspace check is possible or needed.
|
|
80
|
+
const principal = await this.sessionVerifier.verify(request);
|
|
81
|
+
if (!principal) {
|
|
82
|
+
return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
principal.source === "managed-jwt" &&
|
|
86
|
+
this.pairingConfig !== null &&
|
|
87
|
+
principal.workspaceId !== this.pairingConfig.workspaceId
|
|
88
|
+
) {
|
|
89
|
+
return Response.json({ error: "Forbidden" }, { status: 403 });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
65
93
|
const instance = withSecrets
|
|
66
94
|
? await this.queryBus.execute(new GetCredentialInstanceWithSecretsQuery(params.instanceId!))
|
|
67
95
|
: await this.queryBus.execute(new GetCredentialInstanceQuery(params.instanceId!));
|