[codex] clean up plugin-based secret management
Unified · Split
| @@ -571,6 +571,7 @@ · 570 unmodified lines | |||
| 571 | "version": "0.0.0", | 571 | "version": "0.0.0", |
| 572 | "dependencies": { | 572 | "dependencies": { |
| 573 | "@executor/platform-sdk": "workspace:*", | 573 | "@executor/platform-sdk": "workspace:*", |
| 574 | "@napi-rs/keyring": "^1.2.0", | ||
| 574 | "effect": "catalog:", | 575 | "effect": "catalog:", |
| 575 | }, | 576 | }, |
| 576 | "devDependencies": { | 577 | "devDependencies": { |
| @@ -1202,6 +1203,32 @@ · 625 unmodified lines | |||
| 1202 | 1203 | ||
| 1203 | "@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="], | 1204 | "@mswjs/interceptors": ["@mswjs/interceptors@0.41.3", "", { "dependencies": { "@open-draft/deferred-promise": "^2.2.0", "@open-draft/logger": "^0.3.0", "@open-draft/until": "^2.0.0", "is-node-process": "^1.2.0", "outvariant": "^1.4.3", "strict-event-emitter": "^0.5.1" } }, "sha512-cXu86tF4VQVfwz8W1SPbhoRyHJkti6mjH/XJIxp40jhO4j2k1m4KYrEykxqWPkFF3vrK4rgQppBh//AwyGSXPA=="], |
| 1204 | 1205 | ||
| 1206 | "@napi-rs/keyring": ["@napi-rs/keyring@1.2.0", "", { "optionalDependencies": { "@napi-rs/keyring-darwin-arm64": "1.2.0", "@napi-rs/keyring-darwin-x64": "1.2.0", "@napi-rs/keyring-freebsd-x64": "1.2.0", "@napi-rs/keyring-linux-arm-gnueabihf": "1.2.0", "@napi-rs/keyring-linux-arm64-gnu": "1.2.0", "@napi-rs/keyring-linux-arm64-musl": "1.2.0", "@napi-rs/keyring-linux-riscv64-gnu": "1.2.0", "@napi-rs/keyring-linux-x64-gnu": "1.2.0", "@napi-rs/keyring-linux-x64-musl": "1.2.0", "@napi-rs/keyring-win32-arm64-msvc": "1.2.0", "@napi-rs/keyring-win32-ia32-msvc": "1.2.0", "@napi-rs/keyring-win32-x64-msvc": "1.2.0" } }, "sha512-d0d4Oyxm+v980PEq1ZH2PmS6cvpMIRc17eYpiU47KgW+lzxklMu6+HOEOPmxrpnF/XQZ0+Q78I2mgMhbIIo/dg=="], | ||
| 1207 | |||
| 1208 | "@napi-rs/keyring-darwin-arm64": ["@napi-rs/keyring-darwin-arm64@1.2.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CA83rDeyONDADO25JLZsh3eHY8yTEtm/RS6ecPsY+1v+dSawzT9GywBMu2r6uOp1IEhQs/xAfxgybGAFr17lSA=="], | ||
| 1209 | |||
| 1210 | "@napi-rs/keyring-darwin-x64": ["@napi-rs/keyring-darwin-x64@1.2.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-dBHjtKRCj4ByfnfqIKIJLo3wueQNJhLRyuxtX/rR4K/XtcS7VLlRD01XXizjpre54vpmObj63w+ZpHG+mGM8uA=="], | ||
| 1211 | |||
| 1212 | "@napi-rs/keyring-freebsd-x64": ["@napi-rs/keyring-freebsd-x64@1.2.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-DPZFr11pNJSnaoh0dzSUNF+T6ORhy3CkzUT3uGixbA71cAOPJ24iG8e8QrLOkuC/StWrAku3gBnth2XMWOcR3Q=="], | ||
| 1213 | |||
| 1214 | "@napi-rs/keyring-linux-arm-gnueabihf": ["@napi-rs/keyring-linux-arm-gnueabihf@1.2.0", "", { "os": "linux", "cpu": "arm" }, "sha512-8xv6DyEMlvRdqJzp4F39RLUmmTQsLcGYYv/3eIfZNZN1O5257tHxTrFYqAsny659rJJK2EKeSa7PhrSibQqRWQ=="], | ||
| 1215 | |||
| 1216 | "@napi-rs/keyring-linux-arm64-gnu": ["@napi-rs/keyring-linux-arm64-gnu@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-Pu2V6Py+PBt7inryEecirl+t+ti8bhZphjP+W68iVaXHUxLdWmkgL9KI1VkbRHbx5k8K5Tew9OP218YfmVguIA=="], | ||
| 1217 | |||
| 1218 | "@napi-rs/keyring-linux-arm64-musl": ["@napi-rs/keyring-linux-arm64-musl@1.2.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-8TDymrpC4P1a9iDEaegT7RnrkmrJN5eNZh3Im3UEV5PPYGtrb82CRxsuFohthCWQW81O483u1bu+25+XA4nKUw=="], | ||
| 1219 | |||
| 1220 | "@napi-rs/keyring-linux-riscv64-gnu": ["@napi-rs/keyring-linux-riscv64-gnu@1.2.0", "", { "os": "linux", "cpu": "none" }, "sha512-awsB5XI1MYL7fwfjMDGmKOWvNgJEO7mM7iVEMS0fO39f0kVJnOSjlu7RHcXAF0LOx+0VfF3oxbWqJmZbvRCRHw=="], | ||
| 1221 | |||
| 1222 | "@napi-rs/keyring-linux-x64-gnu": ["@napi-rs/keyring-linux-x64-gnu@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8E+7z4tbxSJXxIBqA+vfB1CGajpCDRyTyqXkBig5NtASrv4YXcntSo96Iah2QDR5zD3dSTsmbqJudcj9rKKuHQ=="], | ||
| 1223 | |||
| 1224 | "@napi-rs/keyring-linux-x64-musl": ["@napi-rs/keyring-linux-x64-musl@1.2.0", "", { "os": "linux", "cpu": "x64" }, "sha512-8RZ8yVEnmWr/3BxKgBSzmgntI7lNEsY7xouNfOsQkuVAiCNmxzJwETspzK3PQ2FHtDxgz5vHQDEBVGMyM4hUHA=="], | ||
| 1225 | |||
| 1226 | "@napi-rs/keyring-win32-arm64-msvc": ["@napi-rs/keyring-win32-arm64-msvc@1.2.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-AoqaDZpQ6KPE19VBLpxyORcp+yWmHI9Xs9Oo0PJ4mfHma4nFSLVdhAubJCxdlNptHe5va7ghGCHj3L9Akiv4cQ=="], | ||
| 1227 | |||
| 1228 | "@napi-rs/keyring-win32-ia32-msvc": ["@napi-rs/keyring-win32-ia32-msvc@1.2.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EYL+EEI6bCsYi3LfwcQdnX3P/R76ENKNn+3PmpGheBsUFLuh0gQuP7aMVHM4rTw6UVe+L3vCLZSptq/oeacz0A=="], | ||
| 1229 | |||
| 1230 | "@napi-rs/keyring-win32-x64-msvc": ["@napi-rs/keyring-win32-x64-msvc@1.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-xFlx/TsmqmCwNU9v+AVnEJgoEAlBYgzFF5Ihz1rMpPAt4qQWWkMd4sCyM1gMJ1A/GnRqRegDiQpwaxGUHFtFbA=="], | ||
| 1231 | |||
| 1205 | "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], | 1232 | "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" } }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], |
| 1206 | 1233 | ||
| 1207 | "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], | 1234 | "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], |
| @@ -65,6 +65,10 @@ type SecretMaterialSummary = { · 64 unmodified lines | |||
| 65 | updatedAt: number; | 65 | updatedAt: number; |
| 66 | }; | 66 | }; |
| 67 | 67 | ||
| 68 | type SqliteSecretStoredData = { | ||
| 69 | value: string; | ||
| 70 | }; | ||
| 71 | |||
| 68 | const SQLITE_SECRET_STORE_KIND = "sqlite"; | 72 | const SQLITE_SECRET_STORE_KIND = "sqlite"; |
| 69 | const SQLITE_SECRET_STORE_ID = "sts_sqlite"; | 73 | const SQLITE_SECRET_STORE_ID = "sts_sqlite"; |
| 70 | 74 | ||
| @@ -92,7 +96,7 @@ const sourceArtifacts = sqliteTable("source_artifacts", { · 21 unmodified lines | |||
| 92 | 96 | ||
| 93 | const secretMaterials = sqliteTable("secret_materials", { | 97 | const secretMaterials = sqliteTable("secret_materials", { |
| 94 | id: text("id").primaryKey(), | 98 | id: text("id").primaryKey(), |
| 95 | providerId: text("provider_id").notNull(), | 99 | storeId: text("store_id").notNull(), |
| 96 | name: text("name"), | 100 | name: text("name"), |
| 97 | purpose: text("purpose").notNull(), | 101 | purpose: text("purpose").notNull(), |
| 98 | createdAt: integer("created_at").notNull(), | 102 | createdAt: integer("created_at").notNull(), |
| @@ -100,6 +104,11 @@ const secretMaterials = sqliteTable("secret_materials", { · 1 unmodified line | |||
| 100 | json: text("json").notNull(), | 104 | json: text("json").notNull(), |
| 101 | }); | 105 | }); |
| 102 | 106 | ||
| 107 | const secretMaterialStoredData = sqliteTable("secret_material_stored_data", { | ||
| 108 | secretId: text("secret_id").primaryKey(), | ||
| 109 | json: text("json").notNull(), | ||
| 110 | }); | ||
| 111 | |||
| 103 | const executions = sqliteTable("executions", { | 112 | const executions = sqliteTable("executions", { |
| 104 | id: text("id").primaryKey(), | 113 | id: text("id").primaryKey(), |
| 105 | scopeId: text("scope_id").notNull(), | 114 | scopeId: text("scope_id").notNull(), |
| @@ -213,7 +222,7 @@ const openSqliteStore = (databasePath: string) => { · 107 unmodified lines | |||
| 213 | 222 | ||
| 214 | CREATE TABLE IF NOT EXISTS secret_materials ( | 223 | CREATE TABLE IF NOT EXISTS secret_materials ( |
| 215 | id TEXT PRIMARY KEY NOT NULL, | 224 | id TEXT PRIMARY KEY NOT NULL, |
| 216 | provider_id TEXT NOT NULL, | 225 | store_id TEXT NOT NULL, |
| 217 | name TEXT, | 226 | name TEXT, |
| 218 | purpose TEXT NOT NULL, | 227 | purpose TEXT NOT NULL, |
| 219 | created_at INTEGER NOT NULL, | 228 | created_at INTEGER NOT NULL, |
| @@ -221,6 +230,11 @@ const openSqliteStore = (databasePath: string) => { · 1 unmodified line | |||
| 221 | json TEXT NOT NULL | 230 | json TEXT NOT NULL |
| 222 | ); | 231 | ); |
| 223 | 232 | ||
| 233 | CREATE TABLE IF NOT EXISTS secret_material_stored_data ( | ||
| 234 | secret_id TEXT PRIMARY KEY NOT NULL, | ||
| 235 | json TEXT NOT NULL | ||
| 236 | ); | ||
| 237 | |||
| 224 | CREATE TABLE IF NOT EXISTS executions ( | 238 | CREATE TABLE IF NOT EXISTS executions ( |
| 225 | id TEXT PRIMARY KEY NOT NULL, | 239 | id TEXT PRIMARY KEY NOT NULL, |
| 226 | scope_id TEXT NOT NULL, | 240 | scope_id TEXT NOT NULL, |
| @@ -241,6 +255,7 @@ const openSqliteStore = (databasePath: string) => { · 14 unmodified lines | |||
| 241 | json TEXT NOT NULL | 255 | json TEXT NOT NULL |
| 242 | ); | 256 | ); |
| 243 | `); | 257 | `); |
| 258 | |||
| 244 | return { | 259 | return { |
| 245 | sqlite, | 260 | sqlite, |
| 246 | db, | 261 | db, |
| @@ -292,7 +307,7 @@ const createStorageDomains = ( · 45 unmodified lines | |||
| 292 | listAll: (): readonly SecretMaterialSummary[] => | 307 | listAll: (): readonly SecretMaterialSummary[] => |
| 293 | store.db.select().from(secretMaterials).all().map((row) => ({ | 308 | store.db.select().from(secretMaterials).all().map((row) => ({ |
| 294 | id: row.id, | 309 | id: row.id, |
| 295 | storeId: row.providerId, | 310 | storeId: row.storeId, |
| 296 | name: row.name, | 311 | name: row.name, |
| 297 | purpose: row.purpose, | 312 | purpose: row.purpose, |
| 298 | createdAt: row.createdAt, | 313 | createdAt: row.createdAt, |
| @@ -301,7 +316,7 @@ const createStorageDomains = ( · 2 unmodified lines | |||
| 301 | upsert: (material: SecretMaterial) => | 316 | upsert: (material: SecretMaterial) => |
| 302 | store.db.insert(secretMaterials).values({ | 317 | store.db.insert(secretMaterials).values({ |
| 303 | id: material.id, | 318 | id: material.id, |
| 304 | providerId: material.storeId, | 319 | storeId: material.storeId, |
| 305 | name: material.name, | 320 | name: material.name, |
| 306 | purpose: material.purpose, | 321 | purpose: material.purpose, |
| 307 | createdAt: material.createdAt, | 322 | createdAt: material.createdAt, |
| @@ -310,7 +325,7 @@ const createStorageDomains = ( · 2 unmodified lines | |||
| 310 | }).onConflictDoUpdate({ | 325 | }).onConflictDoUpdate({ |
| 311 | target: secretMaterials.id, | 326 | target: secretMaterials.id, |
| 312 | set: { | 327 | set: { |
| 313 | providerId: material.storeId, | 328 | storeId: material.storeId, |
| 314 | name: material.name, | 329 | name: material.name, |
| 315 | purpose: material.purpose, | 330 | purpose: material.purpose, |
| 316 | createdAt: material.createdAt, | 331 | createdAt: material.createdAt, |
| @@ -320,7 +335,7 @@ const createStorageDomains = ( · 3 unmodified lines | |||
| 320 | }).run(), | 335 | }).run(), |
| 321 | updateById: ( | 336 | updateById: ( |
| 322 | id: SecretMaterial["id"], | 337 | id: SecretMaterial["id"], |
| 323 | update: { name?: string | null; value?: string }, | 338 | update: { name?: string | null }, |
| 324 | ) => { | 339 | ) => { |
| 325 | const row = store.db.select().from(secretMaterials).where(eq(secretMaterials.id, id)).get(); | 340 | const row = store.db.select().from(secretMaterials).where(eq(secretMaterials.id, id)).get(); |
| 326 | if (!row) return null; | 341 | if (!row) return null; |
| @@ -328,11 +343,10 @@ const createStorageDomains = ( · 1 unmodified line | |||
| 328 | const next = { | 343 | const next = { |
| 329 | ...current, | 344 | ...current, |
| 330 | name: update.name === undefined ? current.name : update.name, | 345 | name: update.name === undefined ? current.name : update.name, |
| 331 | value: update.value === undefined ? current.value : update.value, | ||
| 332 | updatedAt: Date.now(), | 346 | updatedAt: Date.now(), |
| 333 | }; | 347 | }; |
| 334 | store.db.update(secretMaterials).set({ | 348 | store.db.update(secretMaterials).set({ |
| 335 | providerId: next.storeId, | 349 | storeId: next.storeId, |
| 336 | name: next.name, | 350 | name: next.name, |
| 337 | purpose: next.purpose, | 351 | purpose: next.purpose, |
| 338 | createdAt: next.createdAt, | 352 | createdAt: next.createdAt, |
| @@ -356,6 +370,40 @@ const createStorageDomains = ( · 17 unmodified lines | |||
| 356 | store.db.delete(secretMaterials).where(eq(secretMaterials.id, id)).run(); | 370 | store.db.delete(secretMaterials).where(eq(secretMaterials.id, id)).run(); |
| 357 | return true; | 371 | return true; |
| 358 | }, | 372 | }, |
| 373 | secretMaterialStoredData: { | ||
| 374 | getBySecretId: (secretId: SecretMaterial["id"]) => { | ||
| 375 | const row = store.db.select().from(secretMaterialStoredData).where( | ||
| 376 | eq(secretMaterialStoredData.secretId, secretId), | ||
| 377 | ).get(); | ||
| 378 | return row | ||
| 379 | ? { | ||
| 380 | secretId, | ||
| 381 | data: parseJson<SqliteSecretStoredData>(row.json), | ||
| 382 | } | ||
| 383 | : null; | ||
| 384 | }, | ||
| 385 | upsert: (record: { secretId: SecretMaterial["id"]; data: unknown }) => { | ||
| 386 | store.db.insert(secretMaterialStoredData).values({ | ||
| 387 | secretId: record.secretId, | ||
| 388 | json: JSON.stringify(record.data), | ||
| 389 | }).onConflictDoUpdate({ | ||
| 390 | target: secretMaterialStoredData.secretId, | ||
| 391 | set: { | ||
| 392 | json: JSON.stringify(record.data), | ||
| 393 | }, | ||
| 394 | }).run(); | ||
| 395 | }, | ||
| 396 | removeBySecretId: (secretId: SecretMaterial["id"]) => { | ||
| 397 | const row = store.db.select({ secretId: secretMaterialStoredData.secretId }).from( | ||
| 398 | secretMaterialStoredData, | ||
| 399 | ).where(eq(secretMaterialStoredData.secretId, secretId)).get(); | ||
| 400 | if (!row) return false; | ||
| 401 | store.db.delete(secretMaterialStoredData).where( | ||
| 402 | eq(secretMaterialStoredData.secretId, secretId), | ||
| 403 | ).run(); | ||
| 404 | return true; | ||
| 405 | }, | ||
| 406 | }, | ||
| 359 | }, | 407 | }, |
| 360 | executions: { | 408 | executions: { |
| 361 | runs: { | 409 | runs: { |
| @@ -612,14 +660,18 @@ export const createSqliteExecutorBackend = ( · 250 unmodified lines | |||
| 612 | secrets: { | 660 | secrets: { |
| 613 | ...secrets, | 661 | ...secrets, |
| 614 | secretStores: secrets.secretStores, | 662 | secretStores: secrets.secretStores, |
| 663 | secretMaterialStoredData: secrets.secretMaterialStoredData, | ||
| 615 | resolve: ({ ref }) => { | 664 | resolve: ({ ref }) => { |
| 616 | const material = secrets.getById( | 665 | const material = secrets.getById( |
| 617 | ref.secretId as SecretMaterial["id"], | 666 | ref.secretId as SecretMaterial["id"], |
| 618 | ); | 667 | ); |
| 619 | if (!material || material.value === null) { | 668 | const stored = secrets.secretMaterialStoredData.getBySecretId( |
| 669 | ref.secretId as SecretMaterial["id"], | ||
| 670 | ); | ||
| 671 | if (!material || !stored) { | ||
| 620 | throw new Error(`Missing secret material ${ref.secretId}`); | 672 | throw new Error(`Missing secret material ${ref.secretId}`); |
| 621 | } | 673 | } |
| 622 | return material.value; | 674 | return (stored.data as SqliteSecretStoredData).value; |
| 623 | }, | 675 | }, |
| 624 | store: ({ purpose, value, name, storeId }) => { | 676 | store: ({ purpose, value, name, storeId }) => { |
| 625 | const now = Date.now(); | 677 | const now = Date.now(); |
| @@ -627,28 +679,44 @@ export const createSqliteExecutorBackend = ( · 1 unmodified line | |||
| 627 | const material: SecretMaterial = { | 679 | const material: SecretMaterial = { |
| 628 | id, | 680 | id, |
| 629 | storeId: (storeId ?? SQLITE_SECRET_STORE_ID) as SecretMaterial["storeId"], | 681 | storeId: (storeId ?? SQLITE_SECRET_STORE_ID) as SecretMaterial["storeId"], |
| 630 | handle: id, | ||
| 631 | name: name ?? null, | 682 | name: name ?? null, |
| 632 | purpose, | 683 | purpose, |
| 633 | value, | ||
| 634 | createdAt: now, | 684 | createdAt: now, |
| 635 | updatedAt: now, | 685 | updatedAt: now, |
| 636 | }; | 686 | }; |
| 637 | secrets.upsert(material); | 687 | secrets.upsert(material); |
| 688 | secrets.secretMaterialStoredData.upsert({ | ||
| 689 | secretId: id, | ||
| 690 | data: { | ||
| 691 | value, | ||
| 692 | } satisfies SqliteSecretStoredData, | ||
| 693 | }); | ||
| 638 | return { | 694 | return { |
| 639 | secretId: material.id, | 695 | secretId: material.id, |
| 640 | } satisfies SecretRef; | 696 | } satisfies SecretRef; |
| 641 | }, | 697 | }, |
| 642 | delete: (ref) => | 698 | delete: (ref) => { |
| 643 | secrets.removeById(ref.secretId as SecretMaterial["id"]), | 699 | secrets.secretMaterialStoredData.removeBySecretId( |
| 700 | ref.secretId as SecretMaterial["id"], | ||
| 701 | ); | ||
| 702 | return secrets.removeById(ref.secretId as SecretMaterial["id"]); | ||
| 703 | }, | ||
| 644 | update: ({ ref, name, value }) => { | 704 | update: ({ ref, name, value }) => { |
| 645 | const updated = secrets.updateById( | 705 | const updated = secrets.updateById( |
| 646 | ref.secretId as SecretMaterial["id"], | 706 | ref.secretId as SecretMaterial["id"], |
| 647 | { name, value }, | 707 | { name }, |
| 648 | ); | 708 | ); |
| 649 | if (!updated) { | 709 | if (!updated) { |
| 650 | throw new Error(`Missing secret material ${ref.secretId}`); | 710 | throw new Error(`Missing secret material ${ref.secretId}`); |
| 651 | } | 711 | } |
| 712 | if (value !== undefined) { | ||
| 713 | secrets.secretMaterialStoredData.upsert({ | ||
| 714 | secretId: ref.secretId as SecretMaterial["id"], | ||
| 715 | data: { | ||
| 716 | value, | ||
| 717 | } satisfies SqliteSecretStoredData, | ||
| 718 | }); | ||
| 719 | } | ||
| 652 | return updated; | 720 | return updated; |
| 653 | }, | 721 | }, |
| 654 | }, | 722 | }, |
| @@ -11,7 +11,6 @@ import { · 10 unmodified lines | |||
| 11 | LocalExecutorConfigSchema, | 11 | LocalExecutorConfigSchema, |
| 12 | type LocalExecutorConfig, | 12 | type LocalExecutorConfig, |
| 13 | type LocalConfigPolicy, | 13 | type LocalConfigPolicy, |
| 14 | type LocalConfigSecretProvider, | ||
| 15 | type LocalConfigSource, | 14 | type LocalConfigSource, |
| 16 | } from "@executor/platform-sdk/schema"; | 15 | } from "@executor/platform-sdk/schema"; |
| 17 | import type { LoadedLocalExecutorConfig } from "@executor/platform-sdk/runtime"; | 16 | import type { LoadedLocalExecutorConfig } from "@executor/platform-sdk/runtime"; |
| @@ -249,19 +248,6 @@ const mergePolicyMaps = ( · 231 unmodified lines | |||
| 249 | }; | 248 | }; |
| 250 | }; | 249 | }; |
| 251 | 250 | ||
| 252 | const mergeSecretProviderMaps = ( | ||
| 253 | base: Record<string, LocalConfigSecretProvider> | undefined, | ||
| 254 | extra: Record<string, LocalConfigSecretProvider> | undefined, | ||
| 255 | ): Record<string, LocalConfigSecretProvider> | undefined => { | ||
| 256 | if (!base && !extra) { | ||
| 257 | return undefined; | ||
| 258 | } | ||
| 259 | return { | ||
| 260 | ...base, | ||
| 261 | ...extra, | ||
| 262 | }; | ||
| 263 | }; | ||
| 264 | |||
| 265 | export const mergeLocalExecutorConfigs = ( | 251 | export const mergeLocalExecutorConfigs = ( |
| 266 | base: LocalExecutorConfig | null, | 252 | base: LocalExecutorConfig | null, |
| 267 | extra: LocalExecutorConfig | null, | 253 | extra: LocalExecutorConfig | null, |
| @@ -278,16 +264,6 @@ export const mergeLocalExecutorConfigs = ( · 10 unmodified lines | |||
| 278 | }, | 264 | }, |
| 279 | sources: mergeSourceMaps(base?.sources, extra?.sources), | 265 | sources: mergeSourceMaps(base?.sources, extra?.sources), |
| 280 | policies: mergePolicyMaps(base?.policies, extra?.policies), | 266 | policies: mergePolicyMaps(base?.policies, extra?.policies), |
| 281 | secrets: { | ||
| 282 | providers: mergeSecretProviderMaps( | ||
| 283 | base?.secrets?.providers, | ||
| 284 | extra?.secrets?.providers, | ||
| 285 | ), | ||
| 286 | defaults: { | ||
| 287 | ...base?.secrets?.defaults, | ||
| 288 | ...extra?.secrets?.defaults, | ||
| 289 | }, | ||
| 290 | }, | ||
| 291 | }); | 267 | }); |
| 292 | }; | 268 | }; |
| 293 | 269 | ||
| @@ -26,13 +26,21 @@ import { · 25 unmodified lines | |||
| 26 | unknownLocalErrorDetails, | 26 | unknownLocalErrorDetails, |
| 27 | } from "./errors"; | 27 | } from "./errors"; |
| 28 | 28 | ||
| 29 | const LOCAL_EXECUTOR_STATE_VERSION = 1 as const; | 29 | const LOCAL_EXECUTOR_STATE_VERSION = 2 as const; |
| 30 | const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json"; | 30 | const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json"; |
| 31 | 31 | ||
| 32 | const SecretMaterialStoredDataRecordSchema = Schema.Struct({ | ||
| 33 | secretId: Schema.String, | ||
| 34 | data: Schema.Unknown, | ||
| 35 | }); | ||
| 36 | |||
| 37 | type SecretMaterialStoredDataRecord = typeof SecretMaterialStoredDataRecordSchema.Type; | ||
| 38 | |||
| 32 | const LocalExecutorStateSnapshotSchema = Schema.Struct({ | 39 | const LocalExecutorStateSnapshotSchema = Schema.Struct({ |
| 33 | version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION), | 40 | version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION), |
| 34 | secretStores: Schema.Array(SecretStoreSchema), | 41 | secretStores: Schema.Array(SecretStoreSchema), |
| 35 | secretMaterials: Schema.Array(SecretMaterialSchema), | 42 | secretMaterials: Schema.Array(SecretMaterialSchema), |
| 43 | secretMaterialStoredData: Schema.Array(SecretMaterialStoredDataRecordSchema), | ||
| 36 | executions: Schema.Array(ExecutionSchema), | 44 | executions: Schema.Array(ExecutionSchema), |
| 37 | executionInteractions: Schema.Array(ExecutionInteractionSchema), | 45 | executionInteractions: Schema.Array(ExecutionInteractionSchema), |
| 38 | executionSteps: Schema.Array(ExecutionStepSchema), | 46 | executionSteps: Schema.Array(ExecutionStepSchema), |
| @@ -53,6 +61,7 @@ const defaultLocalExecutorStateSnapshot = (): LocalExecutorStateSnapshot => ({ · 14 unmodified lines | |||
| 53 | version: LOCAL_EXECUTOR_STATE_VERSION, | 61 | version: LOCAL_EXECUTOR_STATE_VERSION, |
| 54 | secretStores: [], | 62 | secretStores: [], |
| 55 | secretMaterials: [], | 63 | secretMaterials: [], |
| 64 | secretMaterialStoredData: [], | ||
| 56 | executions: [], | 65 | executions: [], |
| 57 | executionInteractions: [], | 66 | executionInteractions: [], |
| 58 | executionSteps: [], | 67 | executionSteps: [], |
| @@ -60,7 +69,6 @@ const defaultLocalExecutorStateSnapshot = (): LocalExecutorStateSnapshot => ({ · 1 unmodified line | |||
| 60 | 69 | ||
| 61 | const cloneValue = <T>(value: T): T => | 70 | const cloneValue = <T>(value: T): T => |
| 62 | JSON.parse(JSON.stringify(value)) as T; | 71 | JSON.parse(JSON.stringify(value)) as T; |
| 63 | |||
| 64 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | 72 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => |
| 65 | new LocalFileSystemError({ | 73 | new LocalFileSystemError({ |
| 66 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, | 74 | message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`, |
| @@ -156,18 +164,19 @@ export const writeLocalExecutorStateSnapshot = (input: { · 89 unmodified lines | |||
| 156 | }): Effect.Effect<void, LocalFileSystemError> => | 164 | }): Effect.Effect<void, LocalFileSystemError> => |
| 157 | bindNodeFileSystem(writeStateToDisk(input.context, input.state)); | 165 | bindNodeFileSystem(writeStateToDisk(input.context, input.state)); |
| 158 | 166 | ||
| 159 | const mergeById = <T extends { id: string }>( | 167 | const mergeByKey = <T>( |
| 160 | current: readonly T[], | 168 | current: readonly T[], |
| 161 | imported: readonly T[], | 169 | imported: readonly T[], |
| 170 | getKey: (value: T) => string, | ||
| 162 | ): T[] => { | 171 | ): T[] => { |
| 163 | const merged = new Map<string, T>(); | 172 | const merged = new Map<string, T>(); |
| 164 | 173 | ||
| 165 | for (const item of imported) { | 174 | for (const item of imported) { |
| 166 | merged.set(item.id, cloneValue(item)); | 175 | merged.set(getKey(item), cloneValue(item)); |
| 167 | } | 176 | } |
| 168 | 177 | ||
| 169 | for (const item of current) { | 178 | for (const item of current) { |
| 170 | merged.set(item.id, cloneValue(item)); | 179 | merged.set(getKey(item), cloneValue(item)); |
| 171 | } | 180 | } |
| 172 | 181 | ||
| 173 | return [...merged.values()]; | 182 | return [...merged.values()]; |
| @@ -178,22 +187,35 @@ export const mergeImportedLocalExecutorStateSnapshot = (input: { · 4 unmodified lines | |||
| 178 | imported: Partial<Omit<LocalExecutorStateSnapshot, "version">>; | 187 | imported: Partial<Omit<LocalExecutorStateSnapshot, "version">>; |
| 179 | }): LocalExecutorStateSnapshot => ({ | 188 | }): LocalExecutorStateSnapshot => ({ |
| 180 | version: LOCAL_EXECUTOR_STATE_VERSION, | 189 | version: LOCAL_EXECUTOR_STATE_VERSION, |
| 181 | secretStores: mergeById( | 190 | secretStores: mergeByKey( |
| 182 | input.current.secretStores, | 191 | input.current.secretStores, |
| 183 | input.imported.secretStores ?? [], | 192 | input.imported.secretStores ?? [], |
| 193 | (item) => item.id, | ||
| 184 | ), | 194 | ), |
| 185 | secretMaterials: mergeById( | 195 | secretMaterials: mergeByKey( |
| 186 | input.current.secretMaterials, | 196 | input.current.secretMaterials, |
| 187 | input.imported.secretMaterials ?? [], | 197 | input.imported.secretMaterials ?? [], |
| 198 | (item) => item.id, | ||
| 188 | ), | 199 | ), |
| 189 | executions: mergeById(input.current.executions, input.imported.executions ?? []), | 200 | secretMaterialStoredData: mergeByKey( |
| 190 | executionInteractions: mergeById( | 201 | input.current.secretMaterialStoredData, |
| 202 | input.imported.secretMaterialStoredData ?? [], | ||
| 203 | (item) => item.secretId, | ||
| 204 | ), | ||
| 205 | executions: mergeByKey( | ||
| 206 | input.current.executions, | ||
| 207 | input.imported.executions ?? [], | ||
| 208 | (item) => item.id, | ||
| 209 | ), | ||
| 210 | executionInteractions: mergeByKey( | ||
| 191 | input.current.executionInteractions, | 211 | input.current.executionInteractions, |
| 192 | input.imported.executionInteractions ?? [], | 212 | input.imported.executionInteractions ?? [], |
| 213 | (item) => item.id, | ||
| 193 | ), | 214 | ), |
| 194 | executionSteps: mergeById( | 215 | executionSteps: mergeByKey( |
| 195 | input.current.executionSteps, | 216 | input.current.executionSteps, |
| 196 | input.imported.executionSteps ?? [], | 217 | input.imported.executionSteps ?? [], |
| 218 | (item) => item.id, | ||
| 197 | ), | 219 | ), |
| 198 | }); | 220 | }); |
| 199 | 221 | ||
| @@ -390,7 +412,7 @@ export const createLocalExecutorStateStore = ( · 190 unmodified lines | |||
| 390 | 412 | ||
| 391 | updateById: ( | 413 | updateById: ( |
| 392 | id: SecretMaterial["id"], | 414 | id: SecretMaterial["id"], |
| 393 | update: { name?: string | null; value?: string }, | 415 | update: { name?: string | null }, |
| 394 | ) => | 416 | ) => |
| 395 | stateManager.mutate((state) => { | 417 | stateManager.mutate((state) => { |
| 396 | let updated: SecretMaterial | null = null; | 418 | let updated: SecretMaterial | null = null; |
| @@ -402,7 +424,6 @@ export const createLocalExecutorStateStore = ( · 5 unmodified lines | |||
| 402 | updated = { | 424 | updated = { |
| 403 | ...material, | 425 | ...material, |
| 404 | ...(update.name !== undefined ? { name: update.name } : {}), | 426 | ...(update.name !== undefined ? { name: update.name } : {}), |
| 405 | ...(update.value !== undefined ? { value: update.value } : {}), | ||
| 406 | updatedAt: Date.now(), | 427 | updatedAt: Date.now(), |
| 407 | } satisfies SecretMaterial; | 428 | } satisfies SecretMaterial; |
| 408 | return updated; | 429 | return updated; |
| @@ -432,6 +453,48 @@ export const createLocalExecutorStateStore = ( · 23 unmodified lines | |||
| 432 | } satisfies StateMutationResult<boolean>; | 453 | } satisfies StateMutationResult<boolean>; |
| 433 | }), | 454 | }), |
| 434 | }, | 455 | }, |
| 456 | secretMaterialStoredData: { | ||
| 457 | getBySecretId: (secretId: SecretMaterial["id"]) => | ||
| 458 | stateManager.read((state) => { | ||
| 459 | const record = state.secretMaterialStoredData.find( | ||
| 460 | (candidate) => candidate.secretId === secretId, | ||
| 461 | ); | ||
| 462 | return record | ||
| 463 | ? Option.some(cloneValue(record)) | ||
| 464 | : Option.none<SecretMaterialStoredDataRecord>(); | ||
| 465 | }), | ||
| 466 | |||
| 467 | upsert: (record: SecretMaterialStoredDataRecord) => | ||
| 468 | stateManager.mutate((state) => { | ||
| 469 | const nextRecords = state.secretMaterialStoredData.filter( | ||
| 470 | (candidate) => candidate.secretId !== record.secretId, | ||
| 471 | ); | ||
| 472 | nextRecords.push(cloneValue(record)); | ||
| 473 | |||
| 474 | return { | ||
| 475 | state: { | ||
| 476 | ...state, | ||
| 477 | secretMaterialStoredData: nextRecords, | ||
| 478 | }, | ||
| 479 | value: undefined, | ||
| 480 | } satisfies StateMutationResult<void>; | ||
| 481 | }), | ||
| 482 | |||
| 483 | removeBySecretId: (secretId: SecretMaterial["id"]) => | ||
| 484 | stateManager.mutate((state) => { | ||
| 485 | const nextRecords = state.secretMaterialStoredData.filter( | ||
| 486 | (candidate) => candidate.secretId !== secretId, | ||
| 487 | ); | ||
| 488 | |||
| 489 | return { | ||
| 490 | state: { | ||
| 491 | ...state, | ||
| 492 | secretMaterialStoredData: nextRecords, | ||
| 493 | }, | ||
| 494 | value: nextRecords.length !== state.secretMaterialStoredData.length, | ||
| 495 | } satisfies StateMutationResult<boolean>; | ||
| 496 | }), | ||
| 497 | }, | ||
| 435 | executions: { | 498 | executions: { |
| 436 | listByScope: (scopeId: Execution["scopeId"]) => | 499 | listByScope: (scopeId: Execution["scopeId"]) => |
| 437 | stateManager.read((state) => | 500 | stateManager.read((state) => |
| @@ -25,9 +25,9 @@ import type { · 24 unmodified lines | |||
| 25 | } from "@executor/platform-sdk/schema"; | 25 | } from "@executor/platform-sdk/schema"; |
| 26 | import { | 26 | import { |
| 27 | type ExecutorRuntime, | 27 | type ExecutorRuntime, |
| 28 | emptyExecutorPluginRegistry, | ||
| 28 | type ExecutorRuntimeOptions, | 29 | type ExecutorRuntimeOptions, |
| 29 | type ResolveSecretMaterial, | 30 | type ResolveSecretMaterial, |
| 30 | emptyExecutorPluginRegistry, | ||
| 31 | } from "@executor/platform-sdk/runtime"; | 31 | } from "@executor/platform-sdk/runtime"; |
| 32 | import * as Effect from "effect/Effect"; | 32 | import * as Effect from "effect/Effect"; |
| 33 | import { | 33 | import { |
| @@ -283,6 +283,8 @@ export const createLocalExecutorRepositoriesEffect = ( · 249 unmodified lines | |||
| 283 | }, | 283 | }, |
| 284 | secrets: { | 284 | secrets: { |
| 285 | secretStores: executorStateStorage.executorState.secretStores, | 285 | secretStores: executorStateStorage.executorState.secretStores, |
| 286 | secretMaterialStoredData: | ||
| 287 | executorStateStorage.executorState.secretMaterialStoredData, | ||
| 286 | ...executorStateStorage.executorState.secretMaterials, | 288 | ...executorStateStorage.executorState.secretMaterials, |
| 287 | resolve: resolveSecretMaterial, | 289 | resolve: resolveSecretMaterial, |
| 288 | store: createDefaultSecretMaterialStorer({ | 290 | store: createDefaultSecretMaterialStorer({ |
| @@ -255,15 +255,17 @@ export const createDefaultSecretMaterialStorer = (input: { · 254 unmodified lines | |||
| 255 | const material: SecretMaterial = { | 255 | const material: SecretMaterial = { |
| 256 | id: `sec_${randomUUID()}` as SecretMaterial["id"], | 256 | id: `sec_${randomUUID()}` as SecretMaterial["id"], |
| 257 | storeId: store.id, | 257 | storeId: store.id, |
| 258 | handle: created.handle, | ||
| 259 | name: created.name, | 258 | name: created.name, |
| 260 | purpose, | 259 | purpose, |
| 261 | value: created.value ?? null, | ||
| 262 | createdAt: now, | 260 | createdAt: now, |
| 263 | updatedAt: now, | 261 | updatedAt: now, |
| 264 | }; | 262 | }; |
| 265 | 263 | ||
| 266 | yield* input.executorState.secretMaterials.upsert(material); | 264 | yield* input.executorState.secretMaterials.upsert(material); |
| 265 | yield* input.executorState.secretMaterialStoredData.upsert({ | ||
| 266 | secretId: material.id, | ||
| 267 | data: created.secretStored, | ||
| 268 | }); | ||
| 267 | 269 | ||
| 268 | return { | 270 | return { |
| 269 | secretId: material.id, | 271 | secretId: material.id, |
| @@ -309,12 +311,16 @@ export const createDefaultSecretMaterialUpdater = (input: { · 39 unmodified lines | |||
| 309 | 311 | ||
| 310 | const nextMaterial: SecretMaterial = { | 312 | const nextMaterial: SecretMaterial = { |
| 311 | ...material, | 313 | ...material, |
| 312 | ...(updated.handle ? { handle: updated.handle } : {}), | ||
| 313 | name: updated.name, | 314 | name: updated.name, |
| 314 | value: updated.value ?? material.value, | ||
| 315 | updatedAt: Date.now(), | 315 | updatedAt: Date.now(), |
| 316 | }; | 316 | }; |
| 317 | yield* input.executorState.secretMaterials.upsert(nextMaterial); | 317 | yield* input.executorState.secretMaterials.upsert(nextMaterial); |
| 318 | if (updated.secretStored !== undefined) { | ||
| 319 | yield* input.executorState.secretMaterialStoredData.upsert({ | ||
| 320 | secretId: nextMaterial.id, | ||
| 321 | data: updated.secretStored, | ||
| 322 | }); | ||
| 323 | } | ||
| 318 | 324 | ||
| 319 | return { | 325 | return { |
| 320 | id: nextMaterial.id, | 326 | id: nextMaterial.id, |
| @@ -359,6 +365,7 @@ export const createDefaultSecretMaterialDeleter = (input: { · 38 unmodified lines | |||
| 359 | return false; | 365 | return false; |
| 360 | } | 366 | } |
| 361 | 367 | ||
| 368 | yield* input.executorState.secretMaterialStoredData.removeBySecretId(material.id); | ||
| 362 | return yield* input.executorState.secretMaterials.removeById(material.id); | 369 | return yield* input.executorState.secretMaterials.removeById(material.id); |
| 363 | }); | 370 | }); |
| 364 | }; | 371 | }; |
| @@ -442,6 +449,7 @@ export const SecretMaterialLive = (input: { · 77 unmodified lines | |||
| 442 | SecretMaterialResolverLive(input), | 449 | SecretMaterialResolverLive(input), |
| 443 | SecretMaterialStorerLive({ | 450 | SecretMaterialStorerLive({ |
| 444 | executorState: input.executorState, | 451 | executorState: input.executorState, |
| 452 | pluginRegistry: input.pluginRegistry, | ||
| 445 | resolveSecretMaterial: | 453 | resolveSecretMaterial: |
| 446 | input.resolveSecretMaterial | 454 | input.resolveSecretMaterial |
| 447 | ?? createDefaultSecretMaterialResolver({ | 455 | ?? createDefaultSecretMaterialResolver({ |
| @@ -449,11 +457,11 @@ export const SecretMaterialLive = (input: { · 1 unmodified line | |||
| 449 | pluginRegistry: input.pluginRegistry, | 457 | pluginRegistry: input.pluginRegistry, |
| 450 | keychainServiceName: input.keychainServiceName, | 458 | keychainServiceName: input.keychainServiceName, |
| 451 | }), | 459 | }), |
| 452 | pluginRegistry: input.pluginRegistry, | ||
| 453 | keychainServiceName: input.keychainServiceName, | 460 | keychainServiceName: input.keychainServiceName, |
| 454 | }), | 461 | }), |
| 455 | SecretMaterialDeleterLive({ | 462 | SecretMaterialDeleterLive({ |
| 456 | executorState: input.executorState, | 463 | executorState: input.executorState, |
| 464 | pluginRegistry: input.pluginRegistry, | ||
| 457 | resolveSecretMaterial: | 465 | resolveSecretMaterial: |
| 458 | input.resolveSecretMaterial | 466 | input.resolveSecretMaterial |
| 459 | ?? createDefaultSecretMaterialResolver({ | 467 | ?? createDefaultSecretMaterialResolver({ |
| @@ -461,11 +469,11 @@ export const SecretMaterialLive = (input: { · 1 unmodified line | |||
| 461 | pluginRegistry: input.pluginRegistry, | 469 | pluginRegistry: input.pluginRegistry, |
| 462 | keychainServiceName: input.keychainServiceName, | 470 | keychainServiceName: input.keychainServiceName, |
| 463 | }), | 471 | }), |
| 464 | pluginRegistry: input.pluginRegistry, | ||
| 465 | keychainServiceName: input.keychainServiceName, | 472 | keychainServiceName: input.keychainServiceName, |
| 466 | }), | 473 | }), |
| 467 | SecretMaterialUpdaterLive({ | 474 | SecretMaterialUpdaterLive({ |
| 468 | executorState: input.executorState, | 475 | executorState: input.executorState, |
| 476 | pluginRegistry: input.pluginRegistry, | ||
| 469 | resolveSecretMaterial: | 477 | resolveSecretMaterial: |
| 470 | input.resolveSecretMaterial | 478 | input.resolveSecretMaterial |
| 471 | ?? createDefaultSecretMaterialResolver({ | 479 | ?? createDefaultSecretMaterialResolver({ |
| @@ -473,7 +481,6 @@ export const SecretMaterialLive = (input: { · 1 unmodified line | |||
| 473 | pluginRegistry: input.pluginRegistry, | 481 | pluginRegistry: input.pluginRegistry, |
| 474 | keychainServiceName: input.keychainServiceName, | 482 | keychainServiceName: input.keychainServiceName, |
| 475 | }), | 483 | }), |
| 476 | pluginRegistry: input.pluginRegistry, | ||
| 477 | keychainServiceName: input.keychainServiceName, | 484 | keychainServiceName: input.keychainServiceName, |
| 478 | }), | 485 | }), |
| 479 | ); | 486 | ); |
| @@ -176,6 +176,13 @@ const toSecretsBackend = ( · 175 unmodified lines | |||
| 176 | toOptionEffect(input.secretStores.updateById(id, patch)), | 176 | toOptionEffect(input.secretStores.updateById(id, patch)), |
| 177 | removeById: (id) => toEffect(input.secretStores.removeById(id)), | 177 | removeById: (id) => toEffect(input.secretStores.removeById(id)), |
| 178 | }, | 178 | }, |
| 179 | secretMaterialStoredData: { | ||
| 180 | getBySecretId: (secretId) => | ||
| 181 | toOptionEffect(input.secretMaterialStoredData.getBySecretId(secretId)), | ||
| 182 | upsert: (record) => toEffect(input.secretMaterialStoredData.upsert(record)), | ||
| 183 | removeBySecretId: (secretId) => | ||
| 184 | toEffect(input.secretMaterialStoredData.removeBySecretId(secretId)), | ||
| 185 | }, | ||
| 179 | getById: (id) => toOptionEffect(input.getById(id)), | 186 | getById: (id) => toOptionEffect(input.getById(id)), |
| 180 | listAll: () => toEffect(input.listAll()), | 187 | listAll: () => toEffect(input.listAll()), |
| 181 | upsert: (material) => toEffect(input.upsert(material)), | 188 | upsert: (material) => toEffect(input.upsert(material)), |
| @@ -82,7 +82,7 @@ const getSecretStoreContributionOption = (kind: string) => · 81 unmodified lines | |||
| 82 | try { | 82 | try { |
| 83 | return Option.some(getSecretStoreContribution(pluginRegistry, kind)); | 83 | return Option.some(getSecretStoreContribution(pluginRegistry, kind)); |
| 84 | } catch { | 84 | } catch { |
| 85 | return Option.none<typeof getSecretStoreContribution extends (registry: any, kind: string) => infer T ? T : never>(); | 85 | return Option.none<any>(); |
| 86 | } | 86 | } |
| 87 | }); | 87 | }); |
| 88 | 88 | ||
| @@ -125,10 +125,9 @@ const toSecretStoreContract = ( · 36 unmodified lines | |||
| 125 | 125 | ||
| 126 | const createImportedSecretRecord = (input: { | 126 | const createImportedSecretRecord = (input: { |
| 127 | store: SecretStore; | 127 | store: SecretStore; |
| 128 | handle: string; | 128 | secretStored: unknown; |
| 129 | name: string | null; | 129 | name: string | null; |
| 130 | purpose: SecretMaterialPurpose; | 130 | purpose: SecretMaterialPurpose; |
| 131 | value?: string | null; | ||
| 132 | }) => | 131 | }) => |
| 133 | Effect.gen(function* () { | 132 | Effect.gen(function* () { |
| 134 | const executorState = yield* ExecutorStateStore; | 133 | const executorState = yield* ExecutorStateStore; |
| @@ -136,15 +135,17 @@ const createImportedSecretRecord = (input: { · 1 unmodified line | |||
| 136 | const material: SecretMaterial = { | 135 | const material: SecretMaterial = { |
| 137 | id: SecretMaterialIdSchema.make(`sec_${crypto.randomUUID()}`), | 136 | id: SecretMaterialIdSchema.make(`sec_${crypto.randomUUID()}`), |
| 138 | storeId: input.store.id, | 137 | storeId: input.store.id, |
| 139 | handle: input.handle, | ||
| 140 | name: input.name, | 138 | name: input.name, |
| 141 | purpose: input.purpose, | 139 | purpose: input.purpose, |
| 142 | value: input.value ?? null, | ||
| 143 | createdAt: now, | 140 | createdAt: now, |
| 144 | updatedAt: now, | 141 | updatedAt: now, |
| 145 | }; | 142 | }; |
| 146 | 143 | ||
| 147 | yield* executorState.secretMaterials.upsert(material); | 144 | yield* executorState.secretMaterials.upsert(material); |
| 145 | yield* executorState.secretMaterialStoredData.upsert({ | ||
| 146 | secretId: material.id, | ||
| 147 | data: input.secretStored, | ||
| 148 | }); | ||
| 148 | 149 | ||
| 149 | return { | 150 | return { |
| 150 | id: material.id, | 151 | id: material.id, |
| @@ -384,7 +385,7 @@ export const browseLocalSecretStore = (input: { · 233 unmodified lines | |||
| 384 | cause instanceof Error ? cause.message : "Failed browsing secret store.", | 385 | cause instanceof Error ? cause.message : "Failed browsing secret store.", |
| 385 | ), | 386 | ), |
| 386 | ), | 387 | ), |
| 387 | Effect.map((result) => result satisfies BrowseSecretStoreResult), | 388 | Effect.map((result) => result as BrowseSecretStoreResult), |
| 388 | ); | 389 | ); |
| 389 | }); | 390 | }); |
| 390 | 391 | ||
| @@ -437,9 +438,8 @@ export const importLocalSecretFromStore = (input: { · 46 unmodified lines | |||
| 437 | 438 | ||
| 438 | return yield* createImportedSecretRecord({ | 439 | return yield* createImportedSecretRecord({ |
| 439 | store, | 440 | store, |
| 440 | handle: created.handle, | 441 | secretStored: created.secretStored, |
| 441 | name: created.name, | 442 | name: created.name, |
| 442 | purpose: input.payload.purpose ?? "auth_material", | 443 | purpose: input.payload.purpose ?? "auth_material", |
| 443 | value: created.value, | ||
| 444 | }); | 444 | }); |
| 445 | }); | 445 | }); |
| @@ -1,4 +1,5 @@ | |||
| 1 | import * as Effect from "effect/Effect"; | 1 | import * as Effect from "effect/Effect"; |
| 2 | import * as Option from "effect/Option"; | ||
| 2 | 3 | ||
| 3 | import type { SourceCatalogSyncResult } from "@executor/source-core"; | 4 | import type { SourceCatalogSyncResult } from "@executor/source-core"; |
| 4 | import type { | 5 | import type { |
| @@ -21,6 +22,9 @@ import type { · 16 unmodified lines | |||
| 21 | } from "@executor/source-core"; | 22 | } from "@executor/source-core"; |
| 22 | import type * as Schema from "effect/Schema"; | 23 | import type * as Schema from "effect/Schema"; |
| 23 | import { runtimeEffectError } from "./runtime/effect-errors"; | 24 | import { runtimeEffectError } from "./runtime/effect-errors"; |
| 25 | import { | ||
| 26 | ExecutorStateStore, | ||
| 27 | } from "./runtime/executor-state-store"; | ||
| 24 | import { ScopeConfigStore } from "./runtime/scope/storage"; | 28 | import { ScopeConfigStore } from "./runtime/scope/storage"; |
| 25 | 29 | ||
| 26 | export type PluginCleanup = { | 30 | export type PluginCleanup = { |
| @@ -329,6 +333,7 @@ export type ExecutorSecretStorePluginDefinition< · 302 unmodified lines | |||
| 329 | TConnectInput, | 333 | TConnectInput, |
| 330 | TStoreConfig, | 334 | TStoreConfig, |
| 331 | TStored, | 335 | TStored, |
| 336 | TSecretStored, | ||
| 332 | _TUpdateInput extends { | 337 | _TUpdateInput extends { |
| 333 | storeId: string; | 338 | storeId: string; |
| 334 | config: TStoreConfig; | 339 | config: TStoreConfig; |
| @@ -369,6 +374,7 @@ export type ExecutorSecretStorePluginDefinition< · 34 unmodified lines | |||
| 369 | }) => Effect.Effect<void, Error, any>; | 374 | }) => Effect.Effect<void, Error, any>; |
| 370 | resolveSecret: (input: { | 375 | resolveSecret: (input: { |
| 371 | secret: SecretMaterial; | 376 | secret: SecretMaterial; |
| 377 | secretStored: TSecretStored; | ||
| 372 | store: SecretStore; | 378 | store: SecretStore; |
| 373 | stored: TStored | null; | 379 | stored: TStored | null; |
| 374 | context?: { | 380 | context?: { |
| @@ -382,23 +388,23 @@ export type ExecutorSecretStorePluginDefinition< · 7 unmodified lines | |||
| 382 | value: string; | 388 | value: string; |
| 383 | name?: string | null; | 389 | name?: string | null; |
| 384 | }) => Effect.Effect<{ | 390 | }) => Effect.Effect<{ |
| 385 | handle: string; | ||
| 386 | name: string | null; | 391 | name: string | null; |
| 387 | value?: string | null; | 392 | secretStored: TSecretStored; |
| 388 | }, Error, any>; | 393 | }, Error, any>; |
| 389 | updateSecret?: (input: { | 394 | updateSecret?: (input: { |
| 390 | secret: SecretMaterial; | 395 | secret: SecretMaterial; |
| 396 | secretStored: TSecretStored; | ||
| 391 | store: SecretStore; | 397 | store: SecretStore; |
| 392 | stored: TStored | null; | 398 | stored: TStored | null; |
| 393 | name?: string | null; | 399 | name?: string | null; |
| 394 | value?: string; | 400 | value?: string; |
| 395 | }) => Effect.Effect<{ | 401 | }) => Effect.Effect<{ |
| 396 | handle?: string; | ||
| 397 | name: string | null; | 402 | name: string | null; |
| 398 | value?: string | null; | 403 | secretStored?: TSecretStored; |
| 399 | }, Error, any>; | 404 | }, Error, any>; |
| 400 | deleteSecret?: (input: { | 405 | deleteSecret?: (input: { |
| 401 | secret: SecretMaterial; | 406 | secret: SecretMaterial; |
| 407 | secretStored: TSecretStored; | ||
| 402 | store: SecretStore; | 408 | store: SecretStore; |
| 403 | stored: TStored | null; | 409 | stored: TStored | null; |
| 404 | }) => Effect.Effect<boolean, Error, any>; | 410 | }) => Effect.Effect<boolean, Error, any>; |
| @@ -417,9 +423,8 @@ export type ExecutorSecretStorePluginDefinition< · 12 unmodified lines | |||
| 417 | purpose: SecretMaterialPurpose; | 423 | purpose: SecretMaterialPurpose; |
| 418 | name?: string | null; | 424 | name?: string | null; |
| 419 | }) => Effect.Effect<{ | 425 | }) => Effect.Effect<{ |
| 420 | handle: string; | ||
| 421 | name: string | null; | 426 | name: string | null; |
| 422 | value?: string | null; | 427 | secretStored: TSecretStored; |
| 423 | }, Error, any>; | 428 | }, Error, any>; |
| 424 | capabilities?: (input: { | 429 | capabilities?: (input: { |
| 425 | store: SecretStore; | 430 | store: SecretStore; |
| @@ -479,6 +484,7 @@ export type ExecutorSecretStorePluginInput< · 53 unmodified lines | |||
| 479 | TConnectInput = unknown, | 484 | TConnectInput = unknown, |
| 480 | TStoreConfig = unknown, | 485 | TStoreConfig = unknown, |
| 481 | TStored = unknown, | 486 | TStored = unknown, |
| 487 | TSecretStored = unknown, | ||
| 482 | TUpdateInput extends { | 488 | TUpdateInput extends { |
| 483 | storeId: string; | 489 | storeId: string; |
| 484 | config: TStoreConfig; | 490 | config: TStoreConfig; |
| @@ -494,6 +500,7 @@ export type ExecutorSecretStorePluginInput< · 9 unmodified lines | |||
| 494 | TConnectInput, | 500 | TConnectInput, |
| 495 | TStoreConfig, | 501 | TStoreConfig, |
| 496 | TStored, | 502 | TStored, |
| 503 | TSecretStored, | ||
| 497 | TUpdateInput | 504 | TUpdateInput |
| 498 | >; | 505 | >; |
| 499 | extendExecutor?: (input: ExecutorSdkPluginContext & { | 506 | extendExecutor?: (input: ExecutorSdkPluginContext & { |
| @@ -771,7 +778,7 @@ const createExecutorSourcePluginApi = < · 271 unmodified lines | |||
| 771 | const loadStoreOfKind = ( | 778 | const loadStoreOfKind = ( |
| 772 | storeId: SecretStore["id"], | 779 | storeId: SecretStore["id"], |
| 773 | input: { | 780 | input: { |
| 774 | definition: ExecutorSecretStorePluginDefinition<any, any, any, any, any>; | 781 | definition: ExecutorSecretStorePluginDefinition<any, any, any, any, any, any>; |
| 775 | host: ExecutorSecretStorePluginInternalHost; | 782 | host: ExecutorSecretStorePluginInternalHost; |
| 776 | }, | 783 | }, |
| 777 | ): Effect.Effect<SecretStore, Error, any> => | 784 | ): Effect.Effect<SecretStore, Error, any> => |
| @@ -787,11 +794,28 @@ const loadStoreOfKind = ( · 9 unmodified lines | |||
| 787 | return store; | 794 | return store; |
| 788 | }); | 795 | }); |
| 789 | 796 | ||
| 797 | const loadSecretMaterialStoredData = <TSecretStored>( | ||
| 798 | secretId: SecretMaterial["id"], | ||
| 799 | ): Effect.Effect<TSecretStored, Error, any> => | ||
| 800 | Effect.gen(function* () { | ||
| 801 | const executorState = yield* ExecutorStateStore; | ||
| 802 | const stored = yield* executorState.secretMaterialStoredData.getBySecretId(secretId); | ||
| 803 | if (Option.isNone(stored)) { | ||
| 804 | return yield* runtimeEffectError( | ||
| 805 | "plugins", | ||
| 806 | `Secret storage missing for ${secretId}.`, | ||
| 807 | ); | ||
| 808 | } | ||
| 809 | |||
| 810 | return stored.value.data as TSecretStored; | ||
| 811 | }); | ||
| 812 | |||
| 790 | const createExecutorSecretStorePluginApi = < | 813 | const createExecutorSecretStorePluginApi = < |
| 791 | TAddInput, | 814 | TAddInput, |
| 792 | TConnectInput, | 815 | TConnectInput, |
| 793 | TStoreConfig, | 816 | TStoreConfig, |
| 794 | TStored, | 817 | TStored, |
| 818 | TSecretStored, | ||
| 795 | _TUpdateInput extends { | 819 | _TUpdateInput extends { |
| 796 | storeId: string; | 820 | storeId: string; |
| 797 | config: TStoreConfig; | 821 | config: TStoreConfig; |
| @@ -802,6 +826,7 @@ const createExecutorSecretStorePluginApi = < · 4 unmodified lines | |||
| 802 | TConnectInput, | 826 | TConnectInput, |
| 803 | TStoreConfig, | 827 | TStoreConfig, |
| 804 | TStored, | 828 | TStored, |
| 829 | TSecretStored, | ||
| 805 | _TUpdateInput | 830 | _TUpdateInput |
| 806 | >, | 831 | >, |
| 807 | host: ExecutorSecretStorePluginInternalHost, | 832 | host: ExecutorSecretStorePluginInternalHost, |
| @@ -962,9 +987,8 @@ type ExecutorSecretStoreContribution<TInput = unknown> = { · 154 unmodified lines | |||
| 962 | value: string; | 987 | value: string; |
| 963 | name?: string | null; | 988 | name?: string | null; |
| 964 | }) => Effect.Effect<{ | 989 | }) => Effect.Effect<{ |
| 965 | handle: string; | ||
| 966 | name: string | null; | 990 | name: string | null; |
| 967 | value?: string | null; | 991 | secretStored: unknown; |
| 968 | }, Error, any>; | 992 | }, Error, any>; |
| 969 | updateSecret: (input: { | 993 | updateSecret: (input: { |
| 970 | secret: SecretMaterial; | 994 | secret: SecretMaterial; |
| @@ -972,9 +996,8 @@ type ExecutorSecretStoreContribution<TInput = unknown> = { · 1 unmodified line | |||
| 972 | name?: string | null; | 996 | name?: string | null; |
| 973 | value?: string; | 997 | value?: string; |
| 974 | }) => Effect.Effect<{ | 998 | }) => Effect.Effect<{ |
| 975 | handle?: string; | ||
| 976 | name: string | null; | 999 | name: string | null; |
| 977 | value?: string | null; | 1000 | secretStored?: unknown; |
| 978 | }, Error, any>; | 1001 | }, Error, any>; |
| 979 | deleteSecret: (input: { | 1002 | deleteSecret: (input: { |
| 980 | secret: SecretMaterial; | 1003 | secret: SecretMaterial; |
| @@ -993,9 +1016,8 @@ type ExecutorSecretStoreContribution<TInput = unknown> = { · 12 unmodified lines | |||
| 993 | purpose: SecretMaterialPurpose; | 1016 | purpose: SecretMaterialPurpose; |
| 994 | name?: string | null; | 1017 | name?: string | null; |
| 995 | }) => Effect.Effect<{ | 1018 | }) => Effect.Effect<{ |
| 996 | handle: string; | ||
| 997 | name: string | null; | 1019 | name: string | null; |
| 998 | value?: string | null; | 1020 | secretStored: unknown; |
| 999 | }, Error, any>; | 1021 | }, Error, any>; |
| 1000 | }; | 1022 | }; |
| 1001 | 1023 | ||
| @@ -1076,6 +1098,7 @@ const createExecutorSecretStoreContribution = < · 74 unmodified lines | |||
| 1076 | TConnectInput, | 1098 | TConnectInput, |
| 1077 | TStoreConfig, | 1099 | TStoreConfig, |
| 1078 | TStored, | 1100 | TStored, |
| 1101 | TSecretStored, | ||
| 1079 | TUpdateInput extends { | 1102 | TUpdateInput extends { |
| 1080 | storeId: string; | 1103 | storeId: string; |
| 1081 | config: TStoreConfig; | 1104 | config: TStoreConfig; |
| @@ -1086,6 +1109,7 @@ const createExecutorSecretStoreContribution = < · 4 unmodified lines | |||
| 1086 | TConnectInput, | 1109 | TConnectInput, |
| 1087 | TStoreConfig, | 1110 | TStoreConfig, |
| 1088 | TStored, | 1111 | TStored, |
| 1112 | TSecretStored, | ||
| 1089 | TUpdateInput | 1113 | TUpdateInput |
| 1090 | >, | 1114 | >, |
| 1091 | ): ExecutorSecretStoreContribution<TAddInput> => ({ | 1115 | ): ExecutorSecretStoreContribution<TAddInput> => ({ |
| @@ -1153,13 +1177,17 @@ const createExecutorSecretStoreContribution = < · 61 unmodified lines | |||
| 1153 | ), | 1177 | ), |
| 1154 | resolveSecret: ({ secret, store, context }) => | 1178 | resolveSecret: ({ secret, store, context }) => |
| 1155 | Effect.flatMap( | 1179 | Effect.flatMap( |
| 1156 | definition.storage.get({ | 1180 | Effect.all({ |
| 1157 | scopeId: store.scopeId, | 1181 | stored: definition.storage.get({ |
| 1158 | storeId: store.id, | 1182 | scopeId: store.scopeId, |
| 1183 | storeId: store.id, | ||
| 1184 | }), | ||
| 1185 | secretStored: loadSecretMaterialStoredData<TSecretStored>(secret.id), | ||
| 1159 | }), | 1186 | }), |
| 1160 | (stored) => | 1187 | ({ stored, secretStored }) => |
| 1161 | definition.store.resolveSecret({ | 1188 | definition.store.resolveSecret({ |
| 1162 | secret, | 1189 | secret, |
| 1190 | secretStored, | ||
| 1163 | store, | 1191 | store, |
| 1164 | stored, | 1192 | stored, |
| 1165 | context, | 1193 | context, |
| @@ -1188,13 +1216,17 @@ const createExecutorSecretStoreContribution = < · 22 unmodified lines | |||
| 1188 | updateSecret: ({ secret, store, name, value }) => | 1216 | updateSecret: ({ secret, store, name, value }) => |
| 1189 | definition.store.updateSecret | 1217 | definition.store.updateSecret |
| 1190 | ? Effect.flatMap( | 1218 | ? Effect.flatMap( |
| 1191 | definition.storage.get({ | 1219 | Effect.all({ |
| 1192 | scopeId: store.scopeId, | 1220 | stored: definition.storage.get({ |
| 1193 | storeId: store.id, | 1221 | scopeId: store.scopeId, |
| 1222 | storeId: store.id, | ||
| 1223 | }), | ||
| 1224 | secretStored: loadSecretMaterialStoredData<TSecretStored>(secret.id), | ||
| 1194 | }), | 1225 | }), |
| 1195 | (stored) => | 1226 | ({ stored, secretStored }) => |
| 1196 | definition.store.updateSecret!({ | 1227 | definition.store.updateSecret!({ |
| 1197 | secret, | 1228 | secret, |
| 1229 | secretStored, | ||
| 1198 | store, | 1230 | store, |
| 1199 | stored, | 1231 | stored, |
| 1200 | name, | 1232 | name, |
| @@ -1208,13 +1240,17 @@ const createExecutorSecretStoreContribution = < · 7 unmodified lines | |||
| 1208 | deleteSecret: ({ secret, store }) => | 1240 | deleteSecret: ({ secret, store }) => |
| 1209 | definition.store.deleteSecret | 1241 | definition.store.deleteSecret |
| 1210 | ? Effect.flatMap( | 1242 | ? Effect.flatMap( |
| 1211 | definition.storage.get({ | 1243 | Effect.all({ |
| 1212 | scopeId: store.scopeId, | 1244 | stored: definition.storage.get({ |
| 1213 | storeId: store.id, | 1245 | scopeId: store.scopeId, |
| 1246 | storeId: store.id, | ||
| 1247 | }), | ||
| 1248 | secretStored: loadSecretMaterialStoredData<TSecretStored>(secret.id), | ||
| 1214 | }), | 1249 | }), |
| 1215 | (stored) => | 1250 | ({ stored, secretStored }) => |
| 1216 | definition.store.deleteSecret!({ | 1251 | definition.store.deleteSecret!({ |
| 1217 | secret, | 1252 | secret, |
| 1253 | secretStored, | ||
| 1218 | store, | 1254 | store, |
| 1219 | stored, | 1255 | stored, |
| 1220 | }), | 1256 | }), |
| @@ -1377,6 +1413,7 @@ export const defineExecutorSecretStorePlugin = < · 156 unmodified lines | |||
| 1377 | TConnectInput, | 1413 | TConnectInput, |
| 1378 | TStoreConfig, | 1414 | TStoreConfig, |
| 1379 | TStored, | 1415 | TStored, |
| 1416 | TSecretStored, | ||
| 1380 | TUpdateInput extends { | 1417 | TUpdateInput extends { |
| 1381 | storeId: string; | 1418 | storeId: string; |
| 1382 | config: TStoreConfig; | 1419 | config: TStoreConfig; |
| @@ -1389,6 +1426,7 @@ export const defineExecutorSecretStorePlugin = < · 6 unmodified lines | |||
| 1389 | TConnectInput, | 1426 | TConnectInput, |
| 1390 | TStoreConfig, | 1427 | TStoreConfig, |
| 1391 | TStored, | 1428 | TStored, |
| 1429 | TSecretStored, | ||
| 1392 | TUpdateInput, | 1430 | TUpdateInput, |
| 1393 | TExtension | 1431 | TExtension |
| 1394 | >, | 1432 | >, |
| @@ -14,9 +14,8 @@ describe("live-execution", () => { · 13 unmodified lines | |||
| 14 | action: "accept", | 14 | action: "accept", |
| 15 | content: { | 15 | content: { |
| 16 | authKind: "bearer", | 16 | authKind: "bearer", |
| 17 | tokenRef: { | 17 | tokenSecretRef: { |
| 18 | providerId: "local", | 18 | secretId: "sec_123", |
| 19 | handle: "sec_123", | ||
| 20 | }, | 19 | }, |
| 21 | }, | 20 | }, |
| 22 | }); | 21 | }); |
| @@ -71,7 +71,10 @@ const serializeJson = (value: unknown): string | null => { · 70 unmodified lines | |||
| 71 | }; | 71 | }; |
| 72 | 72 | ||
| 73 | const SENSITIVE_INTERACTION_CONTENT_KEYS = new Set([ | 73 | const SENSITIVE_INTERACTION_CONTENT_KEYS = new Set([ |
| 74 | "tokenRef", | 74 | "tokenSecretRef", |
| 75 | "accessTokenRef", | ||
| 76 | "refreshTokenRef", | ||
| 77 | "clientSecretRef", | ||
| 75 | "tokenSecretMaterialId", | 78 | "tokenSecretMaterialId", |
| 76 | ]); | 79 | ]); |
| 77 | 80 | ||
| @@ -18,6 +18,11 @@ type SecretMaterialSummary = { · 17 unmodified lines | |||
| 18 | updatedAt: number; | 18 | updatedAt: number; |
| 19 | }; | 19 | }; |
| 20 | 20 | ||
| 21 | export type SecretMaterialStoredDataRecord = { | ||
| 22 | secretId: string; | ||
| 23 | data: unknown; | ||
| 24 | }; | ||
| 25 | |||
| 21 | type SecretStoreSummary = { | 26 | type SecretStoreSummary = { |
| 22 | id: string; | 27 | id: string; |
| 23 | scopeId: string; | 28 | scopeId: string; |
| @@ -56,7 +61,7 @@ export type ExecutorStateStoreShape = { · 32 unmodified lines | |||
| 56 | upsert: (material: SecretMaterial) => Effect.Effect<void, Error, never>; | 61 | upsert: (material: SecretMaterial) => Effect.Effect<void, Error, never>; |
| 57 | updateById: ( | 62 | updateById: ( |
| 58 | id: SecretMaterial["id"], | 63 | id: SecretMaterial["id"], |
| 59 | update: { name?: string | null; value?: string }, | 64 | update: { name?: string | null }, |
| 60 | ) => Effect.Effect< | 65 | ) => Effect.Effect< |
| 61 | import("effect/Option").Option<SecretMaterialSummary>, | 66 | import("effect/Option").Option<SecretMaterialSummary>, |
| 62 | Error, | 67 | Error, |
| @@ -64,6 +69,21 @@ export type ExecutorStateStoreShape = { · 1 unmodified line | |||
| 64 | >; | 69 | >; |
| 65 | removeById: (id: SecretMaterial["id"]) => Effect.Effect<boolean, Error, never>; | 70 | removeById: (id: SecretMaterial["id"]) => Effect.Effect<boolean, Error, never>; |
| 66 | }; | 71 | }; |
| 72 | secretMaterialStoredData: { | ||
| 73 | getBySecretId: ( | ||
| 74 | secretId: SecretMaterial["id"], | ||
| 75 | ) => Effect.Effect< | ||
| 76 | import("effect/Option").Option<SecretMaterialStoredDataRecord>, | ||
| 77 | Error, | ||
| 78 | never | ||
| 79 | >; | ||
| 80 | upsert: ( | ||
| 81 | record: SecretMaterialStoredDataRecord, | ||
| 82 | ) => Effect.Effect<void, Error, never>; | ||
| 83 | removeBySecretId: ( | ||
| 84 | secretId: SecretMaterial["id"], | ||
| 85 | ) => Effect.Effect<boolean, Error, never>; | ||
| 86 | }; | ||
| 67 | executions: { | 87 | executions: { |
| 68 | listByScope: ( | 88 | listByScope: ( |
| 69 | scopeId: Execution["scopeId"], | 89 | scopeId: Execution["scopeId"], |
| @@ -112,7 +112,6 @@ export * from "./execution/service"; · 111 unmodified lines | |||
| 112 | export * from "./source-artifacts"; | 112 | export * from "./source-artifacts"; |
| 113 | export * from "./scope-config"; | 113 | export * from "./scope-config"; |
| 114 | export * from "./scope-state"; | 114 | export * from "./scope-state"; |
| 115 | export * from "./scope/config-secrets"; | ||
| 116 | export * from "./sources/source-store/config"; | 115 | export * from "./sources/source-store/config"; |
| 117 | export * from "./sources/source-store/plugin-local-config"; | 116 | export * from "./sources/source-store/plugin-local-config"; |
| 118 | export { | 117 | export { |
| @@ -244,6 +243,7 @@ export const prewarmWorkspaceSourceCatalogToolIndex = (input: { · 125 unmodified lines | |||
| 244 | export type RuntimeSecretsStorageServices = | 243 | export type RuntimeSecretsStorageServices = |
| 245 | & { | 244 | & { |
| 246 | secretStores: ExecutorStateStoreShape["secretStores"]; | 245 | secretStores: ExecutorStateStoreShape["secretStores"]; |
| 246 | secretMaterialStoredData: ExecutorStateStoreShape["secretMaterialStoredData"]; | ||
| 247 | } | 247 | } |
| 248 | & ExecutorStateStoreShape["secretMaterials"] | 248 | & ExecutorStateStoreShape["secretMaterials"] |
| 249 | & RuntimeSecretMaterialServices; | 249 | & RuntimeSecretMaterialServices; |
| @@ -343,7 +343,14 @@ const toExecutorStateStoreShape = ( · 93 unmodified lines | |||
| 343 | input: RuntimeStorageServices, | 343 | input: RuntimeStorageServices, |
| 344 | ): ExecutorStateStoreShape => ({ | 344 | ): ExecutorStateStoreShape => ({ |
| 345 | secretStores: input.secrets.secretStores, | 345 | secretStores: input.secrets.secretStores, |
| 346 | secretMaterials: input.secrets, | 346 | secretMaterials: { |
| 347 | getById: input.secrets.getById, | ||
| 348 | listAll: input.secrets.listAll, | ||
| 349 | upsert: input.secrets.upsert, | ||
| 350 | updateById: input.secrets.updateById, | ||
| 351 | removeById: input.secrets.removeById, | ||
| 352 | }, | ||
| 353 | secretMaterialStoredData: input.secrets.secretMaterialStoredData, | ||
| 347 | executions: input.executions.runs, | 354 | executions: input.executions.runs, |
| 348 | executionInteractions: input.executions.interactions, | 355 | executionInteractions: input.executions.interactions, |
| 349 | executionSteps: input.executions.steps, | 356 | executionSteps: input.executions.steps, |
| @@ -1,9 +0,0 @@ | |||
| 1 | export const CONFIG_SECRET_PROVIDER_PREFIX = "config:"; | ||
| 2 | |||
| 3 | export const toConfigSecretProviderId = (providerAlias: string): string => | ||
| 4 | `${CONFIG_SECRET_PROVIDER_PREFIX}${providerAlias}`; | ||
| 5 | |||
| 6 | export const fromConfigSecretProviderId = (providerId: string): string | null => | ||
| 7 | providerId.startsWith(CONFIG_SECRET_PROVIDER_PREFIX) | ||
| 8 | ? providerId.slice(CONFIG_SECRET_PROVIDER_PREFIX.length) | ||
| 9 | : null; | ||
| @@ -3,7 +3,6 @@ import type { · 2 unmodified lines | |||
| 3 | Source, | 3 | Source, |
| 4 | } from "#schema"; | 4 | } from "#schema"; |
| 5 | import * as Schema from "effect/Schema"; | 5 | import * as Schema from "effect/Schema"; |
| 6 | |||
| 7 | import { | 6 | import { |
| 8 | cloneJson, | 7 | cloneJson, |
| 9 | configSourceBaseFromLocalSource, | 8 | configSourceBaseFromLocalSource, |
| @@ -8,54 +8,6 @@ export const LocalExecutorRuntimeSchema = Schema.Literal( · 7 unmodified lines | |||
| 8 | "deno", | 8 | "deno", |
| 9 | ); | 9 | ); |
| 10 | 10 | ||
| 11 | export const LocalConfigSecretProviderSourceSchema = Schema.Literal( | ||
| 12 | "env", | ||
| 13 | "file", | ||
| 14 | "exec", | ||
| 15 | "params", | ||
| 16 | ); | ||
| 17 | |||
| 18 | export const LocalConfigEnvSecretProviderSchema = Schema.Struct({ | ||
| 19 | source: Schema.Literal("env"), | ||
| 20 | }); | ||
| 21 | |||
| 22 | export const LocalConfigFileSecretProviderModeSchema = Schema.Literal( | ||
| 23 | "singleValue", | ||
| 24 | "json", | ||
| 25 | ); | ||
| 26 | |||
| 27 | export const LocalConfigFileSecretProviderSchema = Schema.Struct({ | ||
| 28 | source: Schema.Literal("file"), | ||
| 29 | path: Schema.String, | ||
| 30 | mode: Schema.optional(LocalConfigFileSecretProviderModeSchema), | ||
| 31 | }); | ||
| 32 | |||
| 33 | export const LocalConfigExecSecretProviderSchema = Schema.Struct({ | ||
| 34 | source: Schema.Literal("exec"), | ||
| 35 | command: Schema.String, | ||
| 36 | args: Schema.optional(Schema.Array(Schema.String)), | ||
| 37 | env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })), | ||
| 38 | allowSymlinkCommand: Schema.optional(Schema.Boolean), | ||
| 39 | trustedDirs: Schema.optional(Schema.Array(Schema.String)), | ||
| 40 | }); | ||
| 41 | |||
| 42 | export const LocalConfigSecretProviderSchema = Schema.Union( | ||
| 43 | LocalConfigEnvSecretProviderSchema, | ||
| 44 | LocalConfigFileSecretProviderSchema, | ||
| 45 | LocalConfigExecSecretProviderSchema, | ||
| 46 | ); | ||
| 47 | |||
| 48 | export const LocalConfigExplicitSecretRefSchema = Schema.Struct({ | ||
| 49 | source: LocalConfigSecretProviderSourceSchema, | ||
| 50 | provider: Schema.String, | ||
| 51 | id: Schema.String, | ||
| 52 | }); | ||
| 53 | |||
| 54 | export const LocalConfigSecretInputSchema = Schema.Union( | ||
| 55 | Schema.String, | ||
| 56 | LocalConfigExplicitSecretRefSchema, | ||
| 57 | ); | ||
| 58 | |||
| 59 | export const LocalConfigSourceConnectionSchema = Schema.Struct({ | 11 | export const LocalConfigSourceConnectionSchema = Schema.Struct({ |
| 60 | endpoint: Schema.optional(Schema.NullOr(Schema.String)), | 12 | endpoint: Schema.optional(Schema.NullOr(Schema.String)), |
| 61 | auth: Schema.optional(Schema.Unknown), | 13 | auth: Schema.optional(Schema.Unknown), |
| @@ -90,22 +42,6 @@ export const LocalConfigPolicySchema = Schema.Struct({ · 28 unmodified lines | |||
| 90 | priority: Schema.optional(Schema.Number), | 42 | priority: Schema.optional(Schema.Number), |
| 91 | }); | 43 | }); |
| 92 | 44 | ||
| 93 | export const LocalConfigSecretsSchema = Schema.Struct({ | ||
| 94 | providers: Schema.optional( | ||
| 95 | Schema.Record({ | ||
| 96 | key: Schema.String, | ||
| 97 | value: LocalConfigSecretProviderSchema, | ||
| 98 | }), | ||
| 99 | ), | ||
| 100 | defaults: Schema.optional( | ||
| 101 | Schema.Struct({ | ||
| 102 | env: Schema.optional(Schema.String), | ||
| 103 | file: Schema.optional(Schema.String), | ||
| 104 | exec: Schema.optional(Schema.String), | ||
| 105 | }), | ||
| 106 | ), | ||
| 107 | }); | ||
| 108 | |||
| 109 | export const LocalConfigWorkspaceSchema = Schema.Struct({ | 45 | export const LocalConfigWorkspaceSchema = Schema.Struct({ |
| 110 | name: Schema.optional(Schema.String), | 46 | name: Schema.optional(Schema.String), |
| 111 | }); | 47 | }); |
| @@ -125,17 +61,8 @@ export const LocalExecutorConfigSchema = Schema.Struct({ · 13 unmodified lines | |||
| 125 | value: LocalConfigPolicySchema, | 61 | value: LocalConfigPolicySchema, |
| 126 | }), | 62 | }), |
| 127 | ), | 63 | ), |
| 128 | secrets: Schema.optional(LocalConfigSecretsSchema), | ||
| 129 | }); | 64 | }); |
| 130 | 65 | ||
| 131 | export type LocalConfigSecretProviderSource = | ||
| 132 | typeof LocalConfigSecretProviderSourceSchema.Type; | ||
| 133 | export type LocalConfigSecretProvider = | ||
| 134 | typeof LocalConfigSecretProviderSchema.Type; | ||
| 135 | export type LocalConfigExplicitSecretRef = | ||
| 136 | typeof LocalConfigExplicitSecretRefSchema.Type; | ||
| 137 | export type LocalConfigSecretInput = typeof LocalConfigSecretInputSchema.Type; | ||
| 138 | export type LocalConfigPolicy = typeof LocalConfigPolicySchema.Type; | 66 | export type LocalConfigPolicy = typeof LocalConfigPolicySchema.Type; |
| 139 | export type LocalConfigSecrets = typeof LocalConfigSecretsSchema.Type; | ||
| 140 | export type LocalExecutorRuntime = typeof LocalExecutorRuntimeSchema.Type; | 67 | export type LocalExecutorRuntime = typeof LocalExecutorRuntimeSchema.Type; |
| 141 | export type LocalExecutorConfig = typeof LocalExecutorConfigSchema.Type; | 68 | export type LocalExecutorConfig = typeof LocalExecutorConfigSchema.Type; |
| @@ -22,8 +22,6 @@ export const SecretMaterialSchema = Schema.Struct({ · 21 unmodified lines | |||
| 22 | name: Schema.NullOr(Schema.String), | 22 | name: Schema.NullOr(Schema.String), |
| 23 | purpose: SecretMaterialPurposeSchema, | 23 | purpose: SecretMaterialPurposeSchema, |
| 24 | storeId: SecretStoreIdSchema, | 24 | storeId: SecretStoreIdSchema, |
| 25 | handle: Schema.String, | ||
| 26 | value: Schema.NullOr(Schema.String), | ||
| 27 | createdAt: TimestampMsSchema, | 25 | createdAt: TimestampMsSchema, |
| 28 | updatedAt: TimestampMsSchema, | 26 | updatedAt: TimestampMsSchema, |
| 29 | }); | 27 | }); |
| @@ -1,8 +1,7 @@ | |||
| 1 | import * as Schema from "effect/Schema"; | 1 | import * as Schema from "effect/Schema"; |
| 2 | 2 | ||
| 3 | export const SecretRefSchema = Schema.Struct({ | 3 | export const SecretRefSchema = Schema.Struct({ |
| 4 | providerId: Schema.String, | 4 | secretId: Schema.String, |
| 5 | handle: Schema.String, | ||
| 6 | }); | 5 | }); |
| 7 | 6 | ||
| 8 | export const CredentialSlotSchema = Schema.Literal("runtime", "import"); | 7 | export const CredentialSlotSchema = Schema.Literal("runtime", "import"); |
| @@ -557,7 +557,6 @@ const googleDiscoveryStoredSourceDataFromLocalConfig = (input: { · 556 unmodified lines | |||
| 557 | if (Option.isSome(current)) { | 557 | if (Option.isSome(current)) { |
| 558 | return current.value.config; | 558 | return current.value.config; |
| 559 | } | 559 | } |
| 560 | |||
| 561 | throw new Error("Unsupported Google Discovery local source config."); | 560 | throw new Error("Unsupported Google Discovery local source config."); |
| 562 | }; | 561 | }; |
| 563 | 562 | ||
| @@ -342,7 +342,6 @@ const graphqlStoredSourceDataFromLocalConfig = (input: { · 341 unmodified lines | |||
| 342 | if (Option.isSome(current)) { | 342 | if (Option.isSome(current)) { |
| 343 | return normalizeStoredSourceData(current.value.config); | 343 | return normalizeStoredSourceData(current.value.config); |
| 344 | } | 344 | } |
| 345 | |||
| 346 | throw new Error("Unsupported GraphQL local source config."); | 345 | throw new Error("Unsupported GraphQL local source config."); |
| 347 | }; | 346 | }; |
| 348 | 347 | ||
| @@ -1,5 +1,7 @@ | |||
| 1 | import { randomUUID } from "node:crypto"; | 1 | import { randomUUID } from "node:crypto"; |
| 2 | import { spawn } from "node:child_process"; | 2 | import { |
| 3 | Entry, | ||
| 4 | } from "@napi-rs/keyring"; | ||
| 3 | 5 | ||
| 4 | import * as Effect from "effect/Effect"; | 6 | import * as Effect from "effect/Effect"; |
| 5 | 7 | ||
| @@ -13,16 +15,13 @@ import { · 7 unmodified lines | |||
| 13 | export const KEYCHAIN_SECRET_STORE_KIND = "keychain"; | 15 | export const KEYCHAIN_SECRET_STORE_KIND = "keychain"; |
| 14 | export const KEYCHAIN_SECRET_STORE_ID = "sts_builtin_keychain"; | 16 | export const KEYCHAIN_SECRET_STORE_ID = "sts_builtin_keychain"; |
| 15 | 17 | ||
| 18 | type KeychainSecretStoredData = { | ||
| 19 | account: string; | ||
| 20 | }; | ||
| 21 | |||
| 16 | const DEFAULT_KEYCHAIN_SERVICE_NAME = "executor"; | 22 | const DEFAULT_KEYCHAIN_SERVICE_NAME = "executor"; |
| 17 | const KEYCHAIN_COMMAND_TIMEOUT_MS = 5_000; | ||
| 18 | const KEYCHAIN_SERVICE_NAME_ENV = "EXECUTOR_KEYCHAIN_SERVICE_NAME"; | 23 | const KEYCHAIN_SERVICE_NAME_ENV = "EXECUTOR_KEYCHAIN_SERVICE_NAME"; |
| 19 | 24 | ||
| 20 | type SpawnResult = { | ||
| 21 | exitCode: number; | ||
| 22 | stdout: string; | ||
| 23 | stderr: string; | ||
| 24 | }; | ||
| 25 | |||
| 26 | const toError = (cause: unknown): Error => | 25 | const toError = (cause: unknown): Error => |
| 27 | cause instanceof Error ? cause : new Error(String(cause)); | 26 | cause instanceof Error ? cause : new Error(String(cause)); |
| 28 | 27 | ||
| @@ -35,280 +34,99 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 6 unmodified lines | |||
| 35 | return trimmed.length > 0 ? trimmed : null; | 34 | return trimmed.length > 0 ? trimmed : null; |
| 36 | }; | 35 | }; |
| 37 | 36 | ||
| 38 | const ensureNonEmptyString = (value: string | undefined | null): string | null => { | ||
| 39 | const trimmed = value?.trim(); | ||
| 40 | return trimmed && trimmed.length > 0 ? trimmed : null; | ||
| 41 | }; | ||
| 42 | |||
| 43 | const resolveKeychainServiceName = (value: string | undefined): string => | 37 | const resolveKeychainServiceName = (value: string | undefined): string => |
| 44 | trimOrNull(value) | 38 | trimOrNull(value) |
| 45 | ?? trimOrNull(process.env[KEYCHAIN_SERVICE_NAME_ENV]) | 39 | ?? trimOrNull(process.env[KEYCHAIN_SERVICE_NAME_ENV]) |
| 46 | ?? DEFAULT_KEYCHAIN_SERVICE_NAME; | 40 | ?? DEFAULT_KEYCHAIN_SERVICE_NAME; |
| 47 | 41 | ||
| 48 | const runCommand = (input: { | 42 | const isSupportedPlatform = () => |
| 49 | command: string; | 43 | process.platform === "darwin" |
| 50 | args: ReadonlyArray<string>; | 44 | || process.platform === "linux" |
| 51 | stdin?: string; | 45 | || process.platform === "win32"; |
| 52 | operation: string; | ||
| 53 | timeoutMs?: number; | ||
| 54 | }): Effect.Effect<SpawnResult, Error, never> => | ||
| 55 | Effect.tryPromise({ | ||
| 56 | try: () => | ||
| 57 | new Promise<SpawnResult>((resolve, reject) => { | ||
| 58 | const child = spawn(input.command, [...input.args], { | ||
| 59 | stdio: "pipe", | ||
| 60 | env: process.env, | ||
| 61 | }); | ||
| 62 | |||
| 63 | let stdout = ""; | ||
| 64 | let stderr = ""; | ||
| 65 | let settled = false; | ||
| 66 | const timeout = input.timeoutMs === undefined | ||
| 67 | ? null | ||
| 68 | : setTimeout(() => { | ||
| 69 | if (settled) { | ||
| 70 | return; | ||
| 71 | } | ||
| 72 | |||
| 73 | settled = true; | ||
| 74 | child.kill("SIGKILL"); | ||
| 75 | reject( | ||
| 76 | new Error( | ||
| 77 | `${input.operation}: '${input.command}' timed out after ${input.timeoutMs}ms`, | ||
| 78 | ), | ||
| 79 | ); | ||
| 80 | }, input.timeoutMs); | ||
| 81 | |||
| 82 | child.stdout.on("data", (chunk) => { | ||
| 83 | stdout += chunk.toString("utf8"); | ||
| 84 | }); | ||
| 85 | |||
| 86 | child.stderr.on("data", (chunk) => { | ||
| 87 | stderr += chunk.toString("utf8"); | ||
| 88 | }); | ||
| 89 | 46 | ||
| 90 | child.on("error", (error) => { | 47 | const keychainDisplayName = () => |
| 91 | if (settled) { | 48 | process.platform === "darwin" |
| 92 | return; | 49 | ? "macOS Keychain" |
| 93 | } | 50 | : process.platform === "win32" |
| 94 | settled = true; | 51 | ? "Windows Credential Manager" |
| 95 | if (timeout) { | 52 | : "Desktop Keyring"; |
| 96 | clearTimeout(timeout); | ||
| 97 | } | ||
| 98 | reject(error); | ||
| 99 | }); | ||
| 100 | 53 | ||
| 101 | child.on("close", (code) => { | 54 | const createKeyringEntry = (input: { |
| 102 | if (settled) { | 55 | providerHandle: string; |
| 103 | return; | 56 | keychainServiceName: string; |
| 104 | } | 57 | }) => |
| 105 | settled = true; | 58 | Effect.try({ |
| 106 | if (timeout) { | 59 | try: () => { |
| 107 | clearTimeout(timeout); | 60 | if (!isSupportedPlatform()) { |
| 108 | } | 61 | throw runtimeEffectError( |
| 109 | resolve({ | 62 | "plugin-keychain-secret-store", |
| 110 | exitCode: code ?? 0, | 63 | `system-keyring: unsupported on platform '${process.platform}'`, |
| 111 | stdout, | 64 | ); |
| 112 | stderr, | 65 | } |
| 113 | }); | ||
| 114 | }); | ||
| 115 | |||
| 116 | if (input.stdin !== undefined) { | ||
| 117 | child.stdin.write(input.stdin); | ||
| 118 | } | ||
| 119 | 66 | ||
| 120 | child.stdin.end(); | 67 | return new Entry(input.keychainServiceName, input.providerHandle); |
| 121 | }), | 68 | }, |
| 122 | catch: toError, | 69 | catch: toError, |
| 123 | }); | 70 | }); |
| 124 | 71 | ||
| 125 | const ensureCommandSuccess = (input: { | ||
| 126 | result: SpawnResult; | ||
| 127 | operation: string; | ||
| 128 | message: string; | ||
| 129 | }): Effect.Effect<SpawnResult, Error, never> => { | ||
| 130 | if (input.result.exitCode === 0) { | ||
| 131 | return Effect.succeed(input.result); | ||
| 132 | } | ||
| 133 | |||
| 134 | const details = ensureNonEmptyString(input.result.stderr) | ||
| 135 | ?? ensureNonEmptyString(input.result.stdout) | ||
| 136 | ?? "command returned non-zero exit code"; | ||
| 137 | |||
| 138 | return Effect.fail( | ||
| 139 | runtimeEffectError( | ||
| 140 | "plugin-keychain-secret-store", | ||
| 141 | `${input.operation}: ${input.message}: ${details}`, | ||
| 142 | ), | ||
| 143 | ); | ||
| 144 | }; | ||
| 145 | |||
| 146 | const readKeychainSecretValue = (input: { | 72 | const readKeychainSecretValue = (input: { |
| 147 | providerHandle: string; | 73 | providerHandle: string; |
| 148 | keychainServiceName: string; | 74 | keychainServiceName: string; |
| 149 | }) => { | 75 | }) => |
| 150 | switch (process.platform) { | 76 | Effect.flatMap( |
| 151 | case "darwin": | 77 | createKeyringEntry(input), |
| 152 | return runCommand({ | 78 | (entry) => |
| 153 | command: "security", | 79 | Effect.try({ |
| 154 | args: [ | 80 | try: () => entry.getPassword(), |
| 155 | "find-generic-password", | 81 | catch: toError, |
| 156 | "-a", | ||
| 157 | input.providerHandle, | ||
| 158 | "-s", | ||
| 159 | input.keychainServiceName, | ||
| 160 | "-w", | ||
| 161 | ], | ||
| 162 | operation: "keychain.get", | ||
| 163 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 164 | }).pipe( | 82 | }).pipe( |
| 165 | Effect.flatMap((result) => | 83 | Effect.flatMap((value) => |
| 166 | ensureCommandSuccess({ | 84 | value !== null |
| 167 | result, | 85 | ? Effect.succeed(value) |
| 168 | operation: "keychain.get", | 86 | : Effect.fail( |
| 169 | message: "Failed loading secret from macOS keychain", | 87 | runtimeEffectError( |
| 170 | })), | 88 | "plugin-keychain-secret-store", |
| 171 | Effect.map((result) => result.stdout.trimEnd()), | 89 | `keychain.get: secret not found for service '${input.keychainServiceName}' and account '${input.providerHandle}'`, |
| 172 | ); | 90 | ), |
| 173 | case "linux": | 91 | )), |
| 174 | return runCommand({ | 92 | ), |
| 175 | command: "secret-tool", | 93 | ); |
| 176 | args: [ | ||
| 177 | "lookup", | ||
| 178 | "service", | ||
| 179 | input.keychainServiceName, | ||
| 180 | "account", | ||
| 181 | input.providerHandle, | ||
| 182 | ], | ||
| 183 | operation: "keychain.get", | ||
| 184 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 185 | }).pipe( | ||
| 186 | Effect.flatMap((result) => | ||
| 187 | ensureCommandSuccess({ | ||
| 188 | result, | ||
| 189 | operation: "keychain.get", | ||
| 190 | message: "Failed loading secret from desktop keyring", | ||
| 191 | })), | ||
| 192 | Effect.map((result) => result.stdout.trimEnd()), | ||
| 193 | ); | ||
| 194 | default: | ||
| 195 | return Effect.fail( | ||
| 196 | runtimeEffectError( | ||
| 197 | "plugin-keychain-secret-store", | ||
| 198 | `keychain.get: unsupported on platform '${process.platform}'`, | ||
| 199 | ), | ||
| 200 | ); | ||
| 201 | } | ||
| 202 | }; | ||
| 203 | 94 | ||
| 204 | const writeKeychainSecretValue = (input: { | 95 | const writeKeychainSecretValue = (input: { |
| 205 | providerHandle: string; | 96 | providerHandle: string; |
| 206 | name?: string | null; | 97 | name?: string | null; |
| 207 | value: string; | 98 | value: string; |
| 208 | keychainServiceName: string; | 99 | keychainServiceName: string; |
| 209 | }) => { | 100 | }) => |
| 210 | const secretName = trimOrNull(input.name); | 101 | Effect.flatMap( |
| 211 | 102 | createKeyringEntry(input), | |
| 212 | switch (process.platform) { | 103 | (entry) => |
| 213 | case "darwin": | 104 | Effect.try({ |
| 214 | return runCommand({ | 105 | try: () => entry.setPassword(input.value), |
| 215 | command: "security", | 106 | catch: (cause) => |
| 216 | args: [ | 107 | runtimeEffectError( |
| 217 | "add-generic-password", | 108 | "plugin-keychain-secret-store", |
| 218 | "-a", | 109 | `keychain.put: Failed storing secret in ${keychainDisplayName().toLowerCase()}: ${toError(cause).message}`, |
| 219 | input.providerHandle, | 110 | ), |
| 220 | "-s", | 111 | }).pipe(Effect.asVoid), |
| 221 | input.keychainServiceName, | 112 | ); |
| 222 | "-w", | ||
| 223 | input.value, | ||
| 224 | "-U", | ||
| 225 | ...(secretName ? ["-l", secretName] : []), | ||
| 226 | ], | ||
| 227 | operation: "keychain.put", | ||
| 228 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 229 | }).pipe( | ||
| 230 | Effect.flatMap((result) => | ||
| 231 | ensureCommandSuccess({ | ||
| 232 | result, | ||
| 233 | operation: "keychain.put", | ||
| 234 | message: "Failed storing secret in macOS keychain", | ||
| 235 | })), | ||
| 236 | Effect.asVoid, | ||
| 237 | ); | ||
| 238 | case "linux": | ||
| 239 | return runCommand({ | ||
| 240 | command: "secret-tool", | ||
| 241 | args: [ | ||
| 242 | "store", | ||
| 243 | "--label", | ||
| 244 | secretName ?? input.keychainServiceName, | ||
| 245 | "service", | ||
| 246 | input.keychainServiceName, | ||
| 247 | "account", | ||
| 248 | input.providerHandle, | ||
| 249 | ], | ||
| 250 | stdin: input.value, | ||
| 251 | operation: "keychain.put", | ||
| 252 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 253 | }).pipe( | ||
| 254 | Effect.flatMap((result) => | ||
| 255 | ensureCommandSuccess({ | ||
| 256 | result, | ||
| 257 | operation: "keychain.put", | ||
| 258 | message: "Failed storing secret in desktop keyring", | ||
| 259 | })), | ||
| 260 | Effect.asVoid, | ||
| 261 | ); | ||
| 262 | default: | ||
| 263 | return Effect.fail( | ||
| 264 | runtimeEffectError( | ||
| 265 | "plugin-keychain-secret-store", | ||
| 266 | `keychain.put: unsupported on platform '${process.platform}'`, | ||
| 267 | ), | ||
| 268 | ); | ||
| 269 | } | ||
| 270 | }; | ||
| 271 | 113 | ||
| 272 | const deleteKeychainSecretValue = (input: { | 114 | const deleteKeychainSecretValue = (input: { |
| 273 | providerHandle: string; | 115 | providerHandle: string; |
| 274 | keychainServiceName: string; | 116 | keychainServiceName: string; |
| 275 | }) => { | 117 | }) => |
| 276 | switch (process.platform) { | 118 | Effect.flatMap( |
| 277 | case "darwin": | 119 | createKeyringEntry(input), |
| 278 | return runCommand({ | 120 | (entry) => |
| 279 | command: "security", | 121 | Effect.try({ |
| 280 | args: [ | 122 | try: () => entry.deletePassword(), |
| 281 | "delete-generic-password", | 123 | catch: (cause) => |
| 282 | "-a", | 124 | runtimeEffectError( |
| 283 | input.providerHandle, | 125 | "plugin-keychain-secret-store", |
| 284 | "-s", | 126 | `keychain.delete: Failed deleting secret from ${keychainDisplayName().toLowerCase()}: ${toError(cause).message}`, |
| 285 | input.keychainServiceName, | 127 | ), |
| 286 | ], | 128 | }), |
| 287 | operation: "keychain.delete", | 129 | ); |
| 288 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 289 | }).pipe(Effect.map((result) => result.exitCode === 0)); | ||
| 290 | case "linux": | ||
| 291 | return runCommand({ | ||
| 292 | command: "secret-tool", | ||
| 293 | args: [ | ||
| 294 | "clear", | ||
| 295 | "service", | ||
| 296 | input.keychainServiceName, | ||
| 297 | "account", | ||
| 298 | input.providerHandle, | ||
| 299 | ], | ||
| 300 | operation: "keychain.delete", | ||
| 301 | timeoutMs: KEYCHAIN_COMMAND_TIMEOUT_MS, | ||
| 302 | }).pipe(Effect.map((result) => result.exitCode === 0)); | ||
| 303 | default: | ||
| 304 | return Effect.fail( | ||
| 305 | runtimeEffectError( | ||
| 306 | "plugin-keychain-secret-store", | ||
| 307 | `keychain.delete: unsupported on platform '${process.platform}'`, | ||
| 308 | ), | ||
| 309 | ); | ||
| 310 | } | ||
| 311 | }; | ||
| 312 | 130 | ||
| 313 | const builtinSecretStoreStorage = <TStored>(value: TStored) => ({ | 131 | const builtinSecretStoreStorage = <TStored>(value: TStored) => ({ |
| 314 | get: () => Effect.succeed(value), | 132 | get: () => Effect.succeed(value), |
| @@ -316,19 +134,29 @@ const builtinSecretStoreStorage = <TStored>(value: TStored) => ({ · 1 unmodified line | |||
| 316 | remove: () => Effect.void, | 134 | remove: () => Effect.void, |
| 317 | }); | 135 | }); |
| 318 | 136 | ||
| 319 | export const keychainSecretStoreSdkPlugin = defineExecutorSecretStorePlugin({ | 137 | export const keychainSecretStoreSdkPlugin = defineExecutorSecretStorePlugin< |
| 138 | typeof KEYCHAIN_SECRET_STORE_KIND, | ||
| 139 | unknown, | ||
| 140 | { name: string }, | ||
| 141 | { kind: typeof KEYCHAIN_SECRET_STORE_KIND; name: string }, | ||
| 142 | {}, | ||
| 143 | KeychainSecretStoredData, | ||
| 144 | { | ||
| 145 | storeId: string; | ||
| 146 | config: { kind: typeof KEYCHAIN_SECRET_STORE_KIND; name: string }; | ||
| 147 | } | ||
| 148 | >({ | ||
| 320 | key: KEYCHAIN_SECRET_STORE_KIND, | 149 | key: KEYCHAIN_SECRET_STORE_KIND, |
| 321 | secretStore: { | 150 | secretStore: { |
| 322 | kind: KEYCHAIN_SECRET_STORE_KIND, | 151 | kind: KEYCHAIN_SECRET_STORE_KIND, |
| 323 | displayName: | 152 | displayName: keychainDisplayName(), |
| 324 | process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring", | ||
| 325 | builtin: { | 153 | builtin: { |
| 326 | storeId: KEYCHAIN_SECRET_STORE_ID, | 154 | storeId: KEYCHAIN_SECRET_STORE_ID, |
| 327 | defaultPriority: 10, | 155 | defaultPriority: 10, |
| 328 | enabled: () => process.platform === "darwin" || process.platform === "linux", | 156 | enabled: isSupportedPlatform, |
| 329 | createStore: () => ({ | 157 | createStore: () => ({ |
| 330 | kind: KEYCHAIN_SECRET_STORE_KIND, | 158 | kind: KEYCHAIN_SECRET_STORE_KIND, |
| 331 | name: process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring", | 159 | name: keychainDisplayName(), |
| 332 | status: "connected", | 160 | status: "connected", |
| 333 | enabled: true, | 161 | enabled: true, |
| 334 | }), | 162 | }), |
| @@ -352,50 +180,50 @@ export const keychainSecretStoreSdkPlugin = defineExecutorSecretStorePlugin({ · 17 unmodified lines | |||
| 352 | kind: KEYCHAIN_SECRET_STORE_KIND, | 180 | kind: KEYCHAIN_SECRET_STORE_KIND, |
| 353 | name: store.name, | 181 | name: store.name, |
| 354 | }), | 182 | }), |
| 355 | resolveSecret: ({ secret }) => | 183 | resolveSecret: ({ secretStored }) => |
| 356 | readKeychainSecretValue({ | 184 | readKeychainSecretValue({ |
| 357 | providerHandle: secret.handle, | 185 | providerHandle: secretStored.account, |
| 358 | keychainServiceName: resolveKeychainServiceName(undefined), | 186 | keychainServiceName: resolveKeychainServiceName(undefined), |
| 359 | }), | 187 | }), |
| 360 | createSecret: ({ value, name }) => | 188 | createSecret: ({ value, name }) => |
| 361 | Effect.gen(function* () { | 189 | Effect.gen(function* () { |
| 362 | const providerHandle = randomUUID(); | 190 | const account = randomUUID(); |
| 363 | yield* writeKeychainSecretValue({ | 191 | yield* writeKeychainSecretValue({ |
| 364 | providerHandle, | 192 | providerHandle: account, |
| 365 | name, | 193 | name, |
| 366 | value, | 194 | value, |
| 367 | keychainServiceName: resolveKeychainServiceName(undefined), | 195 | keychainServiceName: resolveKeychainServiceName(undefined), |
| 368 | }); | 196 | }); |
| 369 | return { | 197 | return { |
| 370 | handle: providerHandle, | ||
| 371 | name: trimOrNull(name), | 198 | name: trimOrNull(name), |
| 372 | value: null, | 199 | secretStored: { |
| 200 | account, | ||
| 201 | } satisfies KeychainSecretStoredData, | ||
| 373 | }; | 202 | }; |
| 374 | }), | 203 | }), |
| 375 | updateSecret: ({ secret, name, value }) => | 204 | updateSecret: ({ secret, secretStored, name, value }) => |
| 376 | Effect.gen(function* () { | 205 | Effect.gen(function* () { |
| 377 | const keychainServiceName = resolveKeychainServiceName(undefined); | 206 | const keychainServiceName = resolveKeychainServiceName(undefined); |
| 378 | const nextName = trimOrNull(name ?? secret.name); | 207 | const nextName = trimOrNull(name ?? secret.name); |
| 379 | const nextValue = value | 208 | const nextValue = value |
| 380 | ?? (yield* readKeychainSecretValue({ | 209 | ?? (yield* readKeychainSecretValue({ |
| 381 | providerHandle: secret.handle, | 210 | providerHandle: secretStored.account, |
| 382 | keychainServiceName, | 211 | keychainServiceName, |
| 383 | })); | 212 | })); |
| 384 | yield* writeKeychainSecretValue({ | 213 | yield* writeKeychainSecretValue({ |
| 385 | providerHandle: secret.handle, | 214 | providerHandle: secretStored.account, |
| 386 | name: nextName, | 215 | name: nextName, |
| 387 | value: nextValue, | 216 | value: nextValue, |
| 388 | keychainServiceName, | 217 | keychainServiceName, |
| 389 | }); | 218 | }); |
| 390 | return { | 219 | return { |
| 391 | handle: secret.handle, | ||
| 392 | name: nextName, | 220 | name: nextName, |
| 393 | value: null, | 221 | secretStored, |
| 394 | }; | 222 | }; |
| 395 | }), | 223 | }), |
| 396 | deleteSecret: ({ secret }) => | 224 | deleteSecret: ({ secretStored }) => |
| 397 | deleteKeychainSecretValue({ | 225 | deleteKeychainSecretValue({ |
| 398 | providerHandle: secret.handle, | 226 | providerHandle: secretStored.account, |
| 399 | keychainServiceName: resolveKeychainServiceName(undefined), | 227 | keychainServiceName: resolveKeychainServiceName(undefined), |
| 400 | }), | 228 | }), |
| 401 | capabilities: () => ({ | 229 | capabilities: () => ({ |
| @@ -13,6 +13,7 @@ · 12 unmodified lines | |||
| 13 | }, | 13 | }, |
| 14 | "dependencies": { | 14 | "dependencies": { |
| 15 | "@executor/platform-sdk": "workspace:*", | 15 | "@executor/platform-sdk": "workspace:*", |
| 16 | "@napi-rs/keyring": "^1.2.0", | ||
| 16 | "effect": "catalog:" | 17 | "effect": "catalog:" |
| 17 | }, | 18 | }, |
| 18 | "devDependencies": { | 19 | "devDependencies": { |
| @@ -1,17 +1,16 @@ | |||
| 1 | import { randomUUID } from "node:crypto"; | ||
| 2 | |||
| 3 | import * as Effect from "effect/Effect"; | 1 | import * as Effect from "effect/Effect"; |
| 4 | 2 | ||
| 5 | import { | 3 | import { |
| 6 | defineExecutorSecretStorePlugin, | 4 | defineExecutorSecretStorePlugin, |
| 7 | } from "@executor/platform-sdk/plugins"; | 5 | } from "@executor/platform-sdk/plugins"; |
| 8 | import { | ||
| 9 | runtimeEffectError, | ||
| 10 | } from "@executor/platform-sdk/runtime"; | ||
| 11 | 6 | ||
| 12 | export const LOCAL_SECRET_STORE_KIND = "local"; | 7 | export const LOCAL_SECRET_STORE_KIND = "local"; |
| 13 | export const LOCAL_SECRET_STORE_ID = "sts_builtin_local"; | 8 | export const LOCAL_SECRET_STORE_ID = "sts_builtin_local"; |
| 14 | 9 | ||
| 10 | type LocalSecretStoredData = { | ||
| 11 | value: string; | ||
| 12 | }; | ||
| 13 | |||
| 15 | const builtinSecretStoreStorage = <TStored>(value: TStored) => ({ | 14 | const builtinSecretStoreStorage = <TStored>(value: TStored) => ({ |
| 16 | get: () => Effect.succeed(value), | 15 | get: () => Effect.succeed(value), |
| 17 | put: () => Effect.void, | 16 | put: () => Effect.void, |
| @@ -27,7 +26,18 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 9 unmodified lines | |||
| 27 | return trimmed.length > 0 ? trimmed : null; | 26 | return trimmed.length > 0 ? trimmed : null; |
| 28 | }; | 27 | }; |
| 29 | 28 | ||
| 30 | export const localSecretStoreSdkPlugin = defineExecutorSecretStorePlugin({ | 29 | export const localSecretStoreSdkPlugin = defineExecutorSecretStorePlugin< |
| 30 | typeof LOCAL_SECRET_STORE_KIND, | ||
| 31 | unknown, | ||
| 32 | { name: string }, | ||
| 33 | { kind: typeof LOCAL_SECRET_STORE_KIND; name: string }, | ||
| 34 | {}, | ||
| 35 | LocalSecretStoredData, | ||
| 36 | { | ||
| 37 | storeId: string; | ||
| 38 | config: { kind: typeof LOCAL_SECRET_STORE_KIND; name: string }; | ||
| 39 | } | ||
| 40 | >({ | ||
| 31 | key: LOCAL_SECRET_STORE_KIND, | 41 | key: LOCAL_SECRET_STORE_KIND, |
| 32 | secretStore: { | 42 | secretStore: { |
| 33 | kind: LOCAL_SECRET_STORE_KIND, | 43 | kind: LOCAL_SECRET_STORE_KIND, |
| @@ -61,29 +71,20 @@ export const localSecretStoreSdkPlugin = defineExecutorSecretStorePlugin({ · 27 unmodified lines | |||
| 61 | kind: LOCAL_SECRET_STORE_KIND, | 71 | kind: LOCAL_SECRET_STORE_KIND, |
| 62 | name: store.name, | 72 | name: store.name, |
| 63 | }), | 73 | }), |
| 64 | resolveSecret: ({ secret }) => { | 74 | resolveSecret: ({ secretStored }) => Effect.succeed(secretStored.value), |
| 65 | if (secret.value === null) { | ||
| 66 | return Effect.fail( | ||
| 67 | runtimeEffectError( | ||
| 68 | "plugin-local-secret-store", | ||
| 69 | `Local secret ${secret.id} does not have a stored value`, | ||
| 70 | ), | ||
| 71 | ); | ||
| 72 | } | ||
| 73 | |||
| 74 | return Effect.succeed(secret.value); | ||
| 75 | }, | ||
| 76 | createSecret: ({ value, name }) => | 75 | createSecret: ({ value, name }) => |
| 77 | Effect.succeed({ | 76 | Effect.succeed({ |
| 78 | handle: `local:${randomUUID()}`, | ||
| 79 | name: trimOrNull(name), | 77 | name: trimOrNull(name), |
| 80 | value, | 78 | secretStored: { |
| 79 | value, | ||
| 80 | } satisfies LocalSecretStoredData, | ||
| 81 | }), | 81 | }), |
| 82 | updateSecret: ({ secret, name, value }) => | 82 | updateSecret: ({ secret, secretStored, name, value }) => |
| 83 | Effect.succeed({ | 83 | Effect.succeed({ |
| 84 | handle: secret.handle, | ||
| 85 | name: trimOrNull(name ?? secret.name), | 84 | name: trimOrNull(name ?? secret.name), |
| 86 | ...(value !== undefined ? { value } : {}), | 85 | secretStored: { |
| 86 | value: value ?? secretStored.value, | ||
| 87 | } satisfies LocalSecretStoredData, | ||
| 87 | }), | 88 | }), |
| 88 | deleteSecret: () => Effect.succeed(true), | 89 | deleteSecret: () => Effect.succeed(true), |
| 89 | capabilities: () => ({ | 90 | capabilities: () => ({ |
| @@ -58,6 +58,10 @@ export type OnePasswordStoreStorage = { · 57 unmodified lines | |||
| 58 | }) => Effect.Effect<void, Error, never>; | 58 | }) => Effect.Effect<void, Error, never>; |
| 59 | }; | 59 | }; |
| 60 | 60 | ||
| 61 | type OnePasswordSecretStoredData = { | ||
| 62 | uri: string; | ||
| 63 | }; | ||
| 64 | |||
| 61 | const ONEPASSWORD_REQUEST_TIMEOUT_MS = 15_000; | 65 | const ONEPASSWORD_REQUEST_TIMEOUT_MS = 15_000; |
| 62 | 66 | ||
| 63 | const timedOutOnePasswordRequestError = (operation: string) => | 67 | const timedOutOnePasswordRequestError = (operation: string) => |
| @@ -373,9 +377,10 @@ const importSecretFromSelection = (input: { · 309 unmodified lines | |||
| 373 | } | 377 | } |
| 374 | 378 | ||
| 375 | return { | 379 | return { |
| 376 | handle: `op://${input.stored!.vaultId}/${item.id}/${field.id}`, | 380 | secretStored: { |
| 381 | uri: `op://${input.stored!.vaultId}/${item.id}/${field.id}`, | ||
| 382 | } satisfies OnePasswordSecretStoredData, | ||
| 377 | name: input.name?.trim() || `${item.title} · ${field.title}`, | 383 | name: input.name?.trim() || `${item.title} · ${field.title}`, |
| 378 | value: null, | ||
| 379 | }; | 384 | }; |
| 380 | }); | 385 | }); |
| 381 | 386 | ||
| @@ -385,29 +390,37 @@ const createImportedSecretRecord = (input: { · 3 unmodified lines | |||
| 385 | storage: { | 390 | storage: { |
| 386 | secrets: { | 391 | secrets: { |
| 387 | upsert: (value: SecretMaterial) => Effect.Effect<void, Error, never>; | 392 | upsert: (value: SecretMaterial) => Effect.Effect<void, Error, never>; |
| 393 | secretMaterialStoredData: { | ||
| 394 | upsert: (value: { | ||
| 395 | secretId: SecretMaterial["id"]; | ||
| 396 | data: OnePasswordSecretStoredData; | ||
| 397 | }) => Effect.Effect<void, Error, never>; | ||
| 398 | }; | ||
| 388 | }; | 399 | }; |
| 389 | }; | 400 | }; |
| 390 | }; | 401 | }; |
| 391 | }; | 402 | }; |
| 392 | store: SecretStore; | 403 | store: SecretStore; |
| 393 | handle: string; | 404 | secretStored: OnePasswordSecretStoredData; |
| 394 | name: string | null; | 405 | name: string | null; |
| 395 | purpose?: SecretMaterialPurpose; | 406 | purpose?: SecretMaterialPurpose; |
| 396 | }): Effect.Effect<OnePasswordImportSecretResult, Error, never> => | 407 | }): Effect.Effect<OnePasswordImportSecretResult, Error, any> => |
| 397 | Effect.gen(function* () { | 408 | Effect.gen(function* () { |
| 398 | const now = Date.now(); | 409 | const now = Date.now(); |
| 399 | const secret: SecretMaterial = { | 410 | const secret: SecretMaterial = { |
| 400 | id: SecretMaterialIdSchema.make(`sec_${crypto.randomUUID()}`), | 411 | id: SecretMaterialIdSchema.make(`sec_${crypto.randomUUID()}`), |
| 401 | storeId: input.store.id, | 412 | storeId: input.store.id, |
| 402 | handle: input.handle, | ||
| 403 | name: input.name, | 413 | name: input.name, |
| 404 | purpose: input.purpose ?? "auth_material", | 414 | purpose: input.purpose ?? "auth_material", |
| 405 | value: null, | ||
| 406 | createdAt: now, | 415 | createdAt: now, |
| 407 | updatedAt: now, | 416 | updatedAt: now, |
| 408 | }; | 417 | }; |
| 409 | 418 | ||
| 410 | yield* input.executor.runtime.storage.secrets.upsert(secret); | 419 | yield* input.executor.runtime.storage.secrets.upsert(secret); |
| 420 | yield* input.executor.runtime.storage.secrets.secretMaterialStoredData.upsert({ | ||
| 421 | secretId: secret.id, | ||
| 422 | data: input.secretStored, | ||
| 423 | }); | ||
| 411 | 424 | ||
| 412 | return { | 425 | return { |
| 413 | id: secret.id, | 426 | id: secret.id, |
| @@ -419,14 +432,14 @@ const createImportedSecretRecord = (input: { · 5 unmodified lines | |||
| 419 | }; | 432 | }; |
| 420 | }); | 433 | }); |
| 421 | 434 | ||
| 422 | const parseSecretReference = (handle: string): { | 435 | const parseSecretReference = (uri: string): { |
| 423 | vaultId: string; | 436 | vaultId: string; |
| 424 | itemId: string; | 437 | itemId: string; |
| 425 | fieldId: string; | 438 | fieldId: string; |
| 426 | } => { | 439 | } => { |
| 427 | const match = /^op:\/\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(handle); | 440 | const match = /^op:\/\/([^/]+)\/([^/]+)\/([^/]+)$/.exec(uri); |
| 428 | if (!match) { | 441 | if (!match) { |
| 429 | throw new Error(`Invalid 1Password secret reference: ${handle}`); | 442 | throw new Error(`Invalid 1Password secret reference: ${uri}`); |
| 430 | } | 443 | } |
| 431 | 444 | ||
| 432 | return { | 445 | return { |
| @@ -461,6 +474,7 @@ export const onePasswordSdkPlugin = (input: { · 28 unmodified lines | |||
| 461 | OnePasswordConnectInput, | 474 | OnePasswordConnectInput, |
| 462 | OnePasswordStoreConfigPayload, | 475 | OnePasswordStoreConfigPayload, |
| 463 | OnePasswordStoredStoreData, | 476 | OnePasswordStoredStoreData, |
| 477 | OnePasswordSecretStoredData, | ||
| 464 | OnePasswordUpdateStoreInput | 478 | OnePasswordUpdateStoreInput |
| 465 | >({ | 479 | >({ |
| 466 | key: ONEPASSWORD_SECRET_STORE_KIND, | 480 | key: ONEPASSWORD_SECRET_STORE_KIND, |
| @@ -502,12 +516,12 @@ export const onePasswordSdkPlugin = (input: { · 35 unmodified lines | |||
| 502 | vaultId: stored.vaultId, | 516 | vaultId: stored.vaultId, |
| 503 | auth: stored.auth, | 517 | auth: stored.auth, |
| 504 | } satisfies OnePasswordStoreConfigPayload), | 518 | } satisfies OnePasswordStoreConfigPayload), |
| 505 | resolveSecret: ({ secret, stored }) => | 519 | resolveSecret: ({ secretStored, stored }) => |
| 506 | Effect.flatMap(makeClient(stored), (client) => | 520 | Effect.flatMap(makeClient(stored), (client) => |
| 507 | Effect.tryPromise({ | 521 | Effect.tryPromise({ |
| 508 | try: () => | 522 | try: () => |
| 509 | runOnePasswordRequest("secret resolution", () => | 523 | runOnePasswordRequest("secret resolution", () => |
| 510 | client.secrets.resolve(secret.handle) | 524 | client.secrets.resolve(secretStored.uri) |
| 511 | ), | 525 | ), |
| 512 | catch: (cause) => | 526 | catch: (cause) => |
| 513 | cause instanceof Error ? cause : new Error(String(cause)), | 527 | cause instanceof Error ? cause : new Error(String(cause)), |
| @@ -533,18 +547,20 @@ export const onePasswordSdkPlugin = (input: { · 19 unmodified lines | |||
| 533 | ); | 547 | ); |
| 534 | 548 | ||
| 535 | return { | 549 | return { |
| 536 | handle: `op://${stored!.vaultId}/${item.id}/${ONEPASSWORD_SECRET_FIELD_ID}`, | 550 | secretStored: { |
| 551 | uri: `op://${stored!.vaultId}/${item.id}/${ONEPASSWORD_SECRET_FIELD_ID}`, | ||
| 552 | } satisfies OnePasswordSecretStoredData, | ||
| 537 | name: item.title, | 553 | name: item.title, |
| 538 | }; | 554 | }; |
| 539 | }, | 555 | }, |
| 540 | catch: (cause) => | 556 | catch: (cause) => |
| 541 | cause instanceof Error ? cause : new Error(String(cause)), | 557 | cause instanceof Error ? cause : new Error(String(cause)), |
| 542 | })), | 558 | })), |
| 543 | updateSecret: ({ secret, stored, name, value }) => | 559 | updateSecret: ({ secret, secretStored, stored, name, value }) => |
| 544 | Effect.flatMap(makeClient(stored), (client) => | 560 | Effect.flatMap(makeClient(stored), (client) => |
| 545 | Effect.tryPromise({ | 561 | Effect.tryPromise({ |
| 546 | try: async () => { | 562 | try: async () => { |
| 547 | const parsed = parseSecretReference(secret.handle); | 563 | const parsed = parseSecretReference(secretStored.uri); |
| 548 | const item = await runOnePasswordRequest("secret update", () => | 564 | const item = await runOnePasswordRequest("secret update", () => |
| 549 | client.items.get(parsed.vaultId, parsed.itemId) | 565 | client.items.get(parsed.vaultId, parsed.itemId) |
| 550 | ); | 566 | ); |
| @@ -559,16 +575,17 @@ export const onePasswordSdkPlugin = (input: { · 8 unmodified lines | |||
| 559 | ); | 575 | ); |
| 560 | return { | 576 | return { |
| 561 | name: updated.title, | 577 | name: updated.title, |
| 578 | secretStored, | ||
| 562 | }; | 579 | }; |
| 563 | }, | 580 | }, |
| 564 | catch: (cause) => | 581 | catch: (cause) => |
| 565 | cause instanceof Error ? cause : new Error(String(cause)), | 582 | cause instanceof Error ? cause : new Error(String(cause)), |
| 566 | })), | 583 | })), |
| 567 | deleteSecret: ({ secret, stored }) => | 584 | deleteSecret: ({ secretStored, stored }) => |
| 568 | Effect.flatMap(makeClient(stored), (client) => | 585 | Effect.flatMap(makeClient(stored), (client) => |
| 569 | Effect.tryPromise({ | 586 | Effect.tryPromise({ |
| 570 | try: async () => { | 587 | try: async () => { |
| 571 | const parsed = parseSecretReference(secret.handle); | 588 | const parsed = parseSecretReference(secretStored.uri); |
| 572 | await runOnePasswordRequest("secret deletion", () => | 589 | await runOnePasswordRequest("secret deletion", () => |
| 573 | client.items.delete(parsed.vaultId, parsed.itemId) | 590 | client.items.delete(parsed.vaultId, parsed.itemId) |
| 574 | ); | 591 | ); |
| @@ -662,7 +679,7 @@ export const onePasswordSdkPlugin = (input: { · 87 unmodified lines | |||
| 662 | return yield* createImportedSecretRecord({ | 679 | return yield* createImportedSecretRecord({ |
| 663 | executor, | 680 | executor, |
| 664 | store, | 681 | store, |
| 665 | handle: imported.handle, | 682 | secretStored: imported.secretStored, |
| 666 | name: imported.name, | 683 | name: imported.name, |
| 667 | }); | 684 | }); |
| 668 | }), | 685 | }), |
| @@ -202,7 +202,6 @@ const openApiStoredSourceDataFromLocalConfig = (input: { · 201 unmodified lines | |||
| 202 | lastSyncAt: null, | 202 | lastSyncAt: null, |
| 203 | }); | 203 | }); |
| 204 | } | 204 | } |
| 205 | |||
| 206 | throw new Error("Unsupported OpenAPI local source config."); | 205 | throw new Error("Unsupported OpenAPI local source config."); |
| 207 | }; | 206 | }; |
| 208 | 207 | ||