@codemation/host 0.8.0 → 0.9.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/CHANGELOG.md +59 -0
- package/dist/{ApiPaths-Dv1dcHu_.js → ApiPaths-DCvrlIjg.js} +12 -1
- package/dist/{ApiPaths-Dv1dcHu_.js.map → ApiPaths-DCvrlIjg.js.map} +1 -1
- package/dist/{AppConfigFactory-Cx4qQvRk.js → AppConfigFactory-D4LL1aOR.js} +77 -297
- package/dist/AppConfigFactory-D4LL1aOR.js.map +1 -0
- package/dist/{AppConfigFactory-BT0y0LVC.d.ts → AppConfigFactory-DncmwCD1.d.ts} +2918 -199
- package/dist/{AppContainerFactory-DRTjG7nG.js → AppContainerFactory-CHCXP2rn.js} +1735 -474
- package/dist/AppContainerFactory-CHCXP2rn.js.map +1 -0
- package/dist/{CodemationAppContext-CGFYVcSb.d.ts → CodemationAppContext-K51b7oXe.d.ts} +3 -3
- package/dist/{CodemationAuthoring.types-DiKKogum.d.ts → CodemationAuthoring.types-BXlXIl4K.d.ts} +4 -4
- package/dist/{CodemationConfigNormalizer-48f-T66P.d.ts → CodemationConfigNormalizer-B4rDYC9h.d.ts} +3 -3
- package/dist/{CodemationConsumerConfigLoader-_PIYqwVx.d.ts → CodemationConsumerConfigLoader-Dt4jyLx6.d.ts} +2 -2
- package/dist/{CodemationPluginListMerger-DP7djJ9S.d.ts → CodemationPluginListMerger-DS6I3Xe0.d.ts} +24 -12
- package/dist/{persistenceServer-C-hH4z6l.js → CodemationPostgresPrismaClientFactory-C7156Fe-.js} +2 -2
- package/dist/CodemationPostgresPrismaClientFactory-C7156Fe-.js.map +1 -0
- package/dist/CodemationPostgresPrismaClientFactory-CTNTPnDr.d.ts +9 -0
- package/dist/{CredentialContractsRegistry-Bq2bq28t.d.ts → CredentialContractsRegistry-Dgu-rEXi.d.ts} +16 -3
- package/dist/{CredentialServices-BLloBztI.d.ts → CredentialServices-B3wPyp2y.d.ts} +4 -4
- package/dist/{CredentialServices-Dk8yypeL.js → CredentialServices-Bios0dM8.js} +10 -4
- package/dist/CredentialServices-Bios0dM8.js.map +1 -0
- package/dist/{InternalHonoApiRouteRegistrar-c7t3KnV_.d.ts → InternalHonoApiRouteRegistrar-Ce1yxpnO.d.ts} +1 -1
- package/dist/{InternalPingRegistrar-DY3kSfxP.js → InternalPingRegistrar-BavAAnvk.js} +19 -16
- package/dist/InternalPingRegistrar-BavAAnvk.js.map +1 -0
- package/dist/{ItemsInputNormalizer-_RwIfRIQ.d.ts → ItemsInputNormalizer-CFkfNMLt.d.ts} +1434 -1225
- package/dist/PrismaMigrationDeployer-DdEcXXVi.d.ts +14 -0
- package/dist/{PublicFrontendBootstrapFactory-Dv04tJ-6.d.ts → PublicFrontendBootstrapFactory-ClEjZP74.d.ts} +2 -2
- package/dist/{PublicFrontendBootstrapJsonCodec-CXG9Dxft.d.ts → PublicFrontendBootstrapJsonCodec-HNItQ7ol.d.ts} +6 -1
- package/dist/{TelemetryContracts-BtDx84Cp.d.ts → TelemetryContracts-DpZEODQM.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-6MyjCvBO.d.ts → WorkflowPolicyUiPresentationFactory-BNn2fvR_.d.ts} +2 -2
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js} +1 -1
- package/dist/{WorkflowPolicyUiPresentationFactory-Bb-ae_Zh.js.map → WorkflowPolicyUiPresentationFactory-DfvD2VHk.js.map} +1 -1
- package/dist/authoring.d.ts +4 -4
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/consumer.d.ts +5 -5
- package/dist/credentials.d.ts +5 -5
- package/dist/credentials.js +1 -1
- package/dist/devServerSidecar.d.ts +2 -2
- package/dist/dto.d.ts +5 -5
- package/dist/{index-DilAYwnH.d.ts → index-ChIfeWzk.d.ts} +71 -28
- package/dist/index.d.ts +17 -16
- package/dist/index.js +8 -8
- package/dist/infrastructure/persistence/PrismaMigrationOperations.d.ts +44 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js +302 -0
- package/dist/infrastructure/persistence/PrismaMigrationOperations.js.map +1 -0
- package/dist/mapping.d.ts +2 -2
- package/dist/mapping.js +1 -1
- package/dist/nextServer.d.ts +15 -13
- package/dist/nextServer.js +6 -6
- package/dist/pairing.d.ts +28 -9
- package/dist/pairing.js +19 -3
- package/dist/pairing.js.map +1 -0
- package/dist/{pairing.types-snfZ_OzB.d.ts → pairing.types-D9Bjn98U.d.ts} +1 -1
- package/dist/persistenceServer.d.ts +31 -7
- package/dist/persistenceServer.js +2 -2
- package/dist/{server-09PKasWR.d.ts → server-B5trn7y4.d.ts} +5 -5
- package/dist/{server-vtRCPgRJ.js → server-CNj_y0QO.js} +4 -4
- package/dist/{server-vtRCPgRJ.js.map → server-CNj_y0QO.js.map} +1 -1
- package/dist/server.d.ts +10 -10
- package/dist/server.js +8 -8
- package/package.json +11 -10
- package/playwright.config.ts +8 -2
- package/playwright.scaffolded-dev.config.ts +8 -2
- package/prisma/migrations/20260526120000_credential_material_pointer/migration.sql +18 -0
- package/prisma/migrations/20260527120000_add_human_task/migration.sql +32 -0
- package/prisma/migrations/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations/20260527130000_add_hmac_nonce/migration.sql +12 -0
- package/prisma/migrations.sqlite/20260526120000_credential_material_pointer/migration.sql +13 -0
- package/prisma/migrations.sqlite/20260527120000_add_human_task/migration.sql +30 -0
- package/prisma/migrations.sqlite/20260527130000_add_hitl_state_json/migration.sql +6 -0
- package/prisma/migrations.sqlite/20260527130000_add_hmac_nonce/migration.sql +9 -0
- package/prisma/schema.postgresql.prisma +48 -0
- package/prisma/schema.sqlite.prisma +48 -0
- package/prisma-generated/prisma-postgresql-client/edge.js +40 -6
- package/prisma-generated/prisma-postgresql-client/index-browser.js +36 -2
- package/prisma-generated/prisma-postgresql-client/index.d.ts +3179 -163
- package/prisma-generated/prisma-postgresql-client/index.js +40 -6
- package/prisma-generated/prisma-postgresql-client/package.json +1 -1
- package/prisma-generated/prisma-postgresql-client/schema.prisma +48 -0
- package/prisma-generated/prisma-sqlite-client/edge.js +40 -6
- package/prisma-generated/prisma-sqlite-client/index-browser.js +36 -2
- package/prisma-generated/prisma-sqlite-client/index.d.ts +3175 -163
- package/prisma-generated/prisma-sqlite-client/index.js +40 -6
- package/prisma-generated/prisma-sqlite-client/package.json +1 -1
- package/prisma-generated/prisma-sqlite-client/schema.prisma +48 -0
- package/src/application/contracts/CredentialContractsRegistry.ts +15 -0
- package/src/application/credentials/AppGalleryProjector.ts +69 -0
- package/src/application/hitl/DecideHumanTaskCommandHandler.ts +149 -0
- package/src/application/hitl/DecisionSchemaValidator.ts +22 -0
- package/src/application/hitl/HitlCallbackHandler.ts +96 -0
- package/src/application/mapping/WorkflowDefinitionMapper.ts +1 -3
- package/src/application/queries/CredentialQueryHandlers.ts +2 -0
- package/src/application/queries/GetCredentialAppsQuery.ts +4 -0
- package/src/application/queries/GetCredentialAppsQueryHandler.ts +27 -0
- package/src/application/telemetry/ResumeTelemetryContextForRun.ts +53 -0
- package/src/application/telemetry/TelemetryRetentionTimestampFactory.ts +9 -8
- package/src/applicationTokens.ts +11 -1
- package/src/auth/managed/ManagedCorsMiddleware.ts +20 -5
- package/src/bootstrap/AppContainerFactory.ts +100 -0
- package/src/credentials/CachingCredentialMaterialProvider.ts +96 -0
- package/src/credentials/CompositeCredentialMaterialProvider.ts +47 -0
- package/src/credentials/ControlPlaneCatalogFetcher.ts +4 -24
- package/src/credentials/ControlPlaneCredentialMaterialProvider.ts +79 -0
- package/src/credentials/CredentialOAuth2MaterialReader.ts +2 -7
- package/src/credentials/InternalCredentialsBindingRegistrar.ts +83 -0
- package/src/credentials/LocalCredentialMaterialProvider.ts +92 -0
- package/src/domain/credentials/CredentialInstanceService.ts +5 -1
- package/src/domain/credentials/CredentialTypeRegistryImpl.ts +18 -4
- package/src/domain/workflows/WorkflowActivationPreflightRules.ts +7 -4
- package/src/dto.ts +2 -0
- package/src/hitl/ControlPlaneInboxChannel.ts +102 -0
- package/src/hitl/HitlResumeTokenSigner.ts +80 -0
- package/src/hitl/HitlTimeoutJobScheduler.ts +89 -0
- package/src/hitl/HitlTimeoutWorker.ts +143 -0
- package/src/hitl/InboxChannelResolver.ts +49 -0
- package/src/hitl/LocalInboxChannel.ts +37 -0
- package/src/infrastructure/persistence/PrismaCredentialStore.ts +10 -0
- package/src/infrastructure/persistence/PrismaHmacNonceStore.ts +29 -0
- package/src/infrastructure/persistence/PrismaHumanTaskStore.ts +156 -0
- package/src/infrastructure/persistence/PrismaMigrationDeployer.ts +53 -383
- package/src/infrastructure/persistence/PrismaMigrationOperations.ts +401 -0
- package/src/infrastructure/persistence/PrismaWorkflowRunRepository.ts +39 -0
- package/src/mcp/AgentMcpIntegrationImpl.ts +5 -1
- package/src/pairing/HmacNonceStore.ts +14 -0
- package/src/pairing/HmacNonceStoreToken.ts +4 -0
- package/src/pairing/HmacRequestSigner.ts +10 -1
- package/src/pairing/InMemoryHmacNonceStore.ts +24 -0
- package/src/pairing/IncomingHmacVerifier.ts +28 -12
- package/src/pairing/InternalHmacAuthMiddleware.ts +1 -1
- package/src/pairing/index.ts +3 -0
- package/src/presentation/http/ApiPaths.ts +14 -0
- package/src/presentation/http/hono/HonoHttpAnonymousRoutePolicyRegistry.ts +4 -0
- package/src/presentation/http/hono/registrars/CredentialHonoApiRouteRegistrar.ts +1 -0
- package/src/presentation/http/hono/registrars/HitlDecideHonoApiRouteRegistrar.ts +54 -0
- package/src/presentation/http/hono/registrars/HitlInternalCallbackHonoApiRouteRegistrar.ts +33 -0
- package/src/presentation/http/hono/registrars/HitlResumeHonoApiRouteRegistrar.ts +43 -0
- package/src/presentation/http/routeHandlers/CredentialHttpRouteHandler.ts +9 -0
- package/src/presentation/http/routeHandlers/OAuth2HttpRouteHandlerFactory.ts +1 -1
- package/src/server.ts +7 -2
- package/src/workflows/InternalWorkflowTestRunRegistrar.ts +9 -0
- package/tsconfig.json +1 -0
- package/dist/AppConfigFactory-Cx4qQvRk.js.map +0 -1
- package/dist/AppContainerFactory-DRTjG7nG.js.map +0 -1
- package/dist/CredentialServices-Dk8yypeL.js.map +0 -1
- package/dist/InternalPingRegistrar-DY3kSfxP.js.map +0 -1
- package/dist/persistenceServer-B71RGvSj.d.ts +0 -30
- package/dist/persistenceServer-C-hH4z6l.js.map +0 -1
- package/src/credentials/catalogTypes.ts +0 -4
|
@@ -9,10 +9,11 @@ const PairingConfigToken = Symbol.for("codemation.pairing.PairingConfig");
|
|
|
9
9
|
//#endregion
|
|
10
10
|
//#region src/pairing/HmacRequestSigner.ts
|
|
11
11
|
let HmacRequestSigner = class HmacRequestSigner$1 {
|
|
12
|
-
constructor(config) {
|
|
12
|
+
constructor(config = null) {
|
|
13
13
|
this.config = config;
|
|
14
14
|
}
|
|
15
15
|
sign(method, urlOrPath, body) {
|
|
16
|
+
if (this.config === null) throw new Error("HmacRequestSigner.sign called without a registered PairingConfig — workspace is not in managed mode.");
|
|
16
17
|
const ts = Math.floor(Date.now() / 1e3);
|
|
17
18
|
const nonce = randomBytes(16).toString("base64");
|
|
18
19
|
const parsed = new URL(urlOrPath, "http://placeholder");
|
|
@@ -31,7 +32,7 @@ let HmacRequestSigner = class HmacRequestSigner$1 {
|
|
|
31
32
|
};
|
|
32
33
|
HmacRequestSigner = __decorate([
|
|
33
34
|
injectable(),
|
|
34
|
-
__decorateParam(0, inject(PairingConfigToken)),
|
|
35
|
+
__decorateParam(0, inject(PairingConfigToken, { isOptional: true })),
|
|
35
36
|
__decorateMetadata("design:paramtypes", [Object])
|
|
36
37
|
], HmacRequestSigner);
|
|
37
38
|
|
|
@@ -104,15 +105,20 @@ var PairingConfigFactory = class {
|
|
|
104
105
|
}
|
|
105
106
|
};
|
|
106
107
|
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/pairing/HmacNonceStoreToken.ts
|
|
110
|
+
const HmacNonceStoreToken = Symbol.for("codemation.pairing.HmacNonceStore");
|
|
111
|
+
|
|
107
112
|
//#endregion
|
|
108
113
|
//#region src/pairing/IncomingHmacVerifier.ts
|
|
109
114
|
let IncomingHmacVerifier = class IncomingHmacVerifier$1 {
|
|
110
|
-
usedNonces = /* @__PURE__ */ new Map();
|
|
111
115
|
nonceTtlSeconds = 600;
|
|
112
|
-
constructor(config) {
|
|
116
|
+
constructor(config = null, nonceStore) {
|
|
113
117
|
this.config = config;
|
|
118
|
+
this.nonceStore = nonceStore;
|
|
114
119
|
}
|
|
115
|
-
verify(method, url, body, authHeader) {
|
|
120
|
+
async verify(method, url, body, authHeader) {
|
|
121
|
+
if (this.config === null) throw new Error("IncomingHmacVerifier.verify called without a registered PairingConfig — workspace is not in managed mode.");
|
|
116
122
|
if (!this.config.pairingSecret || this.config.pairingSecret.trim().length === 0) throw new Error("IncomingHmacVerifier: pairingSecret is not configured — cannot verify HMAC requests.");
|
|
117
123
|
if (!authHeader?.startsWith("Codemation-Hmac ")) return { failure: "missing" };
|
|
118
124
|
const parts = this.parseHeader(authHeader);
|
|
@@ -135,10 +141,9 @@ let IncomingHmacVerifier = class IncomingHmacVerifier$1 {
|
|
|
135
141
|
const expectedBuf = Buffer.from(expected);
|
|
136
142
|
const actualBuf = Buffer.from(parts.sig);
|
|
137
143
|
if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) return { failure: "signature" };
|
|
138
|
-
this.pruneExpiredNonces(nowSec);
|
|
139
144
|
const nonceKey = `${parts.workspaceId}:${parts.nonce}`;
|
|
140
|
-
|
|
141
|
-
this.
|
|
145
|
+
const nonceExpiresAt = /* @__PURE__ */ new Date((nowSec + this.nonceTtlSeconds) * 1e3);
|
|
146
|
+
if (!await this.nonceStore.recordIfNew(nonceKey, nonceExpiresAt)) return { failure: "replay" };
|
|
142
147
|
return { workspaceId: parts.workspaceId };
|
|
143
148
|
}
|
|
144
149
|
parseHeader(header) {
|
|
@@ -159,14 +164,12 @@ let IncomingHmacVerifier = class IncomingHmacVerifier$1 {
|
|
|
159
164
|
sig
|
|
160
165
|
};
|
|
161
166
|
}
|
|
162
|
-
pruneExpiredNonces(nowSec) {
|
|
163
|
-
for (const [key, expiry] of this.usedNonces.entries()) if (expiry <= nowSec) this.usedNonces.delete(key);
|
|
164
|
-
}
|
|
165
167
|
};
|
|
166
168
|
IncomingHmacVerifier = __decorate([
|
|
167
169
|
injectable(),
|
|
168
|
-
__decorateParam(0, inject(PairingConfigToken)),
|
|
169
|
-
|
|
170
|
+
__decorateParam(0, inject(PairingConfigToken, { isOptional: true })),
|
|
171
|
+
__decorateParam(1, inject(HmacNonceStoreToken)),
|
|
172
|
+
__decorateMetadata("design:paramtypes", [Object, Object])
|
|
170
173
|
], IncomingHmacVerifier);
|
|
171
174
|
|
|
172
175
|
//#endregion
|
|
@@ -179,7 +182,7 @@ let InternalHmacAuthMiddleware = class InternalHmacAuthMiddleware$1 {
|
|
|
179
182
|
handle() {
|
|
180
183
|
return async (c, next) => {
|
|
181
184
|
const body = c.req.method === "GET" || c.req.method === "HEAD" ? "" : await c.req.text();
|
|
182
|
-
if ("failure" in this.verifier.verify(c.req.method, c.req.url, body, c.req.header("authorization") ?? null)) return c.json({ error: "Unauthorized" }, 401);
|
|
185
|
+
if ("failure" in await this.verifier.verify(c.req.method, c.req.url, body, c.req.header("authorization") ?? null)) return c.json({ error: "Unauthorized" }, 401);
|
|
183
186
|
c.set("body", body);
|
|
184
187
|
await next();
|
|
185
188
|
};
|
|
@@ -217,5 +220,5 @@ InternalPingRegistrar = __decorate([
|
|
|
217
220
|
], InternalPingRegistrar);
|
|
218
221
|
|
|
219
222
|
//#endregion
|
|
220
|
-
export {
|
|
221
|
-
//# sourceMappingURL=InternalPingRegistrar-
|
|
223
|
+
export { PairingConfigFactory as a, PairingConfigToken as c, HmacNonceStoreToken as i, InternalHmacAuthMiddleware as n, PairedFetch as o, IncomingHmacVerifier as r, HmacRequestSigner as s, InternalPingRegistrar as t };
|
|
224
|
+
//# sourceMappingURL=InternalPingRegistrar-BavAAnvk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InternalPingRegistrar-BavAAnvk.js","names":["HmacRequestSigner","config: PairingConfig | null","PairedFetch","signer: HmacRequestSigner","IncomingHmacVerifier","config: PairingConfig | null","nonceStore: HmacNonceStore","fields: Record<string, string>","InternalHmacAuthMiddleware","verifier: IncomingHmacVerifier","InternalPingRegistrar","hmacMiddleware: InternalHmacAuthMiddleware","pairingConfig: PairingConfig"],"sources":["../src/pairing/PairingConfigToken.ts","../src/pairing/HmacRequestSigner.ts","../src/pairing/PairedFetch.ts","../src/pairing/PairingConfigFactory.ts","../src/pairing/HmacNonceStoreToken.ts","../src/pairing/IncomingHmacVerifier.ts","../src/pairing/InternalHmacAuthMiddleware.ts","../src/pairing/InternalPingRegistrar.ts"],"sourcesContent":["import type { TypeToken } from \"@codemation/core\";\nimport type { PairingConfig } from \"./pairing.types\";\n\n// Symbol token so the DI container can inject PairingConfig.\n// Registered by PairingConfigFactory in the composition root.\nexport const PairingConfigToken = Symbol.for(\"codemation.pairing.PairingConfig\") as TypeToken<PairingConfig>;\n","import { createHmac, createHash, randomBytes } from \"node:crypto\";\nimport { inject, injectable } from \"@codemation/core\";\nimport type { PairingConfig } from \"./pairing.types\";\nimport { PairingConfigToken } from \"./PairingConfigToken\";\n\nexport interface SignedHeaders {\n readonly Authorization: string;\n}\n\n@injectable()\nexport class HmacRequestSigner {\n constructor(@inject(PairingConfigToken, { isOptional: true }) private readonly config: PairingConfig | null = null) {}\n\n sign(method: string, urlOrPath: string, body: string): SignedHeaders {\n if (this.config === null) {\n // Should never happen in managed mode (PairingConfig is registered then). In non-managed\n // mode this signer should never be called — callers like `PairedFetch` are themselves\n // only reachable via `ControlPlaneCatalogFetcher` whose poll loop checks `pairingConfig`.\n // If we land here, a CP-bound call escaped that guard.\n throw new Error(\n \"HmacRequestSigner.sign called without a registered PairingConfig — workspace is not in managed mode.\",\n );\n }\n const ts = Math.floor(Date.now() / 1000);\n const nonce = randomBytes(16).toString(\"base64\");\n\n const parsed = new URL(urlOrPath, \"http://placeholder\");\n const path = (parsed.pathname + parsed.search).toLowerCase();\n\n const bodyHash = createHash(\"sha256\").update(body, \"utf8\").digest(\"hex\");\n const baseString = [method.toUpperCase(), path, ts, nonce, bodyHash].join(\"\\n\");\n\n // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large\n const secretBytes = Buffer.from(this.config.pairingSecret, \"base64\");\n const sig = createHmac(\"sha256\", secretBytes).update(baseString, \"utf8\").digest(\"base64\");\n\n return {\n Authorization: `Codemation-Hmac v=1,workspaceId=${this.config.workspaceId},ts=${ts},nonce=${nonce},sig=${sig}`,\n };\n }\n}\n","import { inject, injectable } from \"@codemation/core\";\nimport { HmacRequestSigner } from \"./HmacRequestSigner\";\n\n/**\n * Thin fetch wrapper that automatically HMAC-signs outgoing requests\n * to the control plane using the workspace's pairing secret.\n *\n * Use this for any server-to-server request from the installation to the CP.\n */\n@injectable()\nexport class PairedFetch {\n constructor(@inject(HmacRequestSigner) private readonly signer: HmacRequestSigner) {}\n\n async get(url: string): Promise<Response> {\n const headers = this.signer.sign(\"GET\", url, \"\");\n return fetch(url, { method: \"GET\", headers: { ...headers } });\n }\n\n async post(url: string, body: unknown): Promise<Response> {\n const bodyString = JSON.stringify(body);\n const headers = this.signer.sign(\"POST\", url, bodyString);\n return fetch(url, {\n method: \"POST\",\n headers: { ...headers, \"Content-Type\": \"application/json\" },\n body: bodyString,\n });\n }\n\n async delete(url: string): Promise<Response> {\n const headers = this.signer.sign(\"DELETE\", url, \"\");\n return fetch(url, { method: \"DELETE\", headers: { ...headers } });\n }\n}\n","import type { PairingConfig } from \"./pairing.types\";\n\n/**\n * Reads pairing configuration from environment variables.\n *\n * Required env vars when pairing is enabled:\n * WORKSPACE_ID — the workspace's database ID\n * WORKSPACE_PAIRING_SECRET — base64-encoded 32-byte shared secret\n * CONTROL_PLANE_URL — base URL of the control plane API\n *\n * Returns null if any required variable is absent (pairing disabled).\n * See docs/pairing-protocol.md for full bootstrap instructions.\n */\nexport class PairingConfigFactory {\n create(env: Readonly<NodeJS.ProcessEnv>): PairingConfig | null {\n const workspaceId = env[\"WORKSPACE_ID\"];\n const pairingSecret = env[\"WORKSPACE_PAIRING_SECRET\"];\n const controlPlaneUrl = env[\"CONTROL_PLANE_URL\"];\n\n if (!workspaceId || !pairingSecret || !controlPlaneUrl) {\n return null;\n }\n\n // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is always 32 bytes; bounded by validation below.\n const decoded = Buffer.from(pairingSecret, \"base64\");\n if (decoded.length !== 32) {\n throw new Error(\n `WORKSPACE_PAIRING_SECRET must be a base64-encoded 32-byte value (got ${decoded.length} bytes). ` +\n `Generate a valid secret with: openssl rand -base64 32`,\n );\n }\n\n return { workspaceId, pairingSecret, controlPlaneUrl };\n }\n}\n","import type { TypeToken } from \"@codemation/core\";\nimport type { HmacNonceStore } from \"./HmacNonceStore\";\n\nexport const HmacNonceStoreToken = Symbol.for(\"codemation.pairing.HmacNonceStore\") as TypeToken<HmacNonceStore>;\n","import { createHmac, createHash, timingSafeEqual } from \"node:crypto\";\nimport { inject, injectable } from \"@codemation/core\";\nimport type { PairingConfig, PairingVerificationResult } from \"./pairing.types\";\nimport { PairingConfigToken } from \"./PairingConfigToken\";\nimport type { HmacNonceStore } from \"./HmacNonceStore\";\nimport { HmacNonceStoreToken } from \"./HmacNonceStoreToken\";\n\n/**\n * Verifies incoming HMAC-signed requests from the control plane.\n * Mirrors the control-plane HmacVerifier — both sides follow docs/pairing-protocol.md.\n *\n * Security (T6): The nonce store is injected and defaults to PrismaHmacNonceStore in\n * managed mode so replay protection survives process restarts within the 300-second\n * timestamp window.\n */\n@injectable()\nexport class IncomingHmacVerifier {\n private readonly nonceTtlSeconds = 600; // 10 minutes\n\n constructor(\n @inject(PairingConfigToken, { isOptional: true }) private readonly config: PairingConfig | null = null,\n @inject(HmacNonceStoreToken) private readonly nonceStore: HmacNonceStore,\n ) {}\n\n async verify(\n method: string,\n url: string,\n body: string,\n authHeader: string | null,\n ): Promise<PairingVerificationResult> {\n if (this.config === null) {\n // Same shape as HmacRequestSigner — verifier is reachable via the DI graph even in\n // non-managed mode (lazy CodemationHonoApiApp construction pulls every registered handler).\n // We accept construction without PairingConfig and throw only when verify() is actually\n // called, which shouldn't happen in non-managed mode (internal HMAC routes aren't mounted).\n throw new Error(\n \"IncomingHmacVerifier.verify called without a registered PairingConfig — workspace is not in managed mode.\",\n );\n }\n if (!this.config.pairingSecret || this.config.pairingSecret.trim().length === 0) {\n throw new Error(\"IncomingHmacVerifier: pairingSecret is not configured — cannot verify HMAC requests.\");\n }\n\n if (!authHeader?.startsWith(\"Codemation-Hmac \")) {\n return { failure: \"missing\" };\n }\n\n const parts = this.parseHeader(authHeader);\n if (!parts) return { failure: \"missing\" };\n if (parts.v !== \"1\") return { failure: \"version\" };\n\n const nowSec = Math.floor(Date.now() / 1000);\n if (Math.abs(nowSec - parts.ts) > 300) return { failure: \"expired\" };\n\n if (parts.workspaceId !== this.config.workspaceId) return { failure: \"workspace\" };\n\n const parsed = new URL(url, \"http://placeholder\");\n const path = (parsed.pathname + parsed.search).toLowerCase();\n const bodyHash = createHash(\"sha256\").update(body, \"utf8\").digest(\"hex\");\n const baseString = [method.toUpperCase(), path, parts.ts, parts.nonce, bodyHash].join(\"\\n\");\n\n // eslint-disable-next-line codemation/no-buffer-everything -- pairing secret is 32 bytes, never large\n const secretBytes = Buffer.from(this.config.pairingSecret, \"base64\");\n const expected = createHmac(\"sha256\", secretBytes).update(baseString, \"utf8\").digest(\"base64\");\n\n const expectedBuf = Buffer.from(expected);\n const actualBuf = Buffer.from(parts.sig);\n if (expectedBuf.length !== actualBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) {\n return { failure: \"signature\" };\n }\n\n const nonceKey = `${parts.workspaceId}:${parts.nonce}`;\n const nonceExpiresAt = new Date((nowSec + this.nonceTtlSeconds) * 1000);\n const isNew = await this.nonceStore.recordIfNew(nonceKey, nonceExpiresAt);\n if (!isNew) return { failure: \"replay\" };\n\n return { workspaceId: parts.workspaceId };\n }\n\n private parseHeader(header: string): {\n v: string;\n workspaceId: string;\n ts: number;\n nonce: string;\n sig: string;\n } | null {\n const payload = header.slice(\"Codemation-Hmac \".length);\n const fields: Record<string, string> = {};\n for (const part of payload.split(\",\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) return null;\n fields[part.slice(0, eq).trim()] = part.slice(eq + 1).trim();\n }\n const { v, workspaceId, ts, nonce, sig } = fields;\n if (!v || !workspaceId || !ts || !nonce || !sig) return null;\n return { v, workspaceId, ts: Number(ts), nonce, sig };\n }\n}\n","import { inject, injectable } from \"@codemation/core\";\nimport type { Context, MiddlewareHandler, Next } from \"hono\";\nimport { IncomingHmacVerifier } from \"./IncomingHmacVerifier\";\n\n/**\n * Hono middleware that verifies HMAC-signed requests on /internal/* routes.\n * Rejects with 401 on any auth failure (failure mode is never leaked).\n *\n * Downstream handlers read the consumed body from `c.get(\"body\")` when needed —\n * do NOT call `c.req.text()` again after this middleware runs.\n */\n@injectable()\nexport class InternalHmacAuthMiddleware {\n constructor(@inject(IncomingHmacVerifier) private readonly verifier: IncomingHmacVerifier) {}\n\n handle(): MiddlewareHandler {\n return async (c: Context, next: Next) => {\n const body = c.req.method === \"GET\" || c.req.method === \"HEAD\" ? \"\" : await c.req.text();\n\n const result = await this.verifier.verify(c.req.method, c.req.url, body, c.req.header(\"authorization\") ?? null);\n\n if (\"failure\" in result) {\n return c.json({ error: \"Unauthorized\" }, 401);\n }\n\n // Body stored for downstream handlers that need it (e.g., credential push).\n // Access via c.get(\"body\") — do NOT call c.req.text() again.\n // workspaceId is available from PairingConfig since installation has a single workspace.\n c.set(\"body\" as never, body);\n await next();\n };\n }\n}\n","import { inject, injectable } from \"@codemation/core\";\nimport type { Hono } from \"hono\";\nimport { InternalHmacAuthMiddleware } from \"./InternalHmacAuthMiddleware\";\nimport type { InternalHonoApiRouteRegistrar } from \"../presentation/http/hono/InternalHonoApiRouteRegistrar\";\nimport type { PairingConfig } from \"./pairing.types\";\nimport { PairingConfigToken } from \"./PairingConfigToken\";\n\n/**\n * Registers GET /internal/ping — a smoke-test endpoint for verifying workspace pairing.\n * Returns { pong: true, workspaceId } when the HMAC signature validates correctly.\n */\n@injectable()\nexport class InternalPingRegistrar implements InternalHonoApiRouteRegistrar {\n constructor(\n @inject(InternalHmacAuthMiddleware) private readonly hmacMiddleware: InternalHmacAuthMiddleware,\n @inject(PairingConfigToken) private readonly pairingConfig: PairingConfig,\n ) {}\n\n register(app: Hono): void {\n const { workspaceId } = this.pairingConfig;\n app.get(\"/internal/ping\", this.hmacMiddleware.handle(), (c) => {\n return c.json({ pong: true, workspaceId });\n });\n }\n}\n"],"mappings":"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"}
|