Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
130 lines
4.7 KiB
JavaScript
130 lines
4.7 KiB
JavaScript
"use strict";
|
|
|
|
const { PluginRpcError, RPC_ERRORS } = require("./rpcRouter.cjs");
|
|
const { MAX_SECRET_BYTES, assertSecretRef } = require("./secretStore.cjs");
|
|
|
|
function assertCredentialRef(credential) {
|
|
if (credential?.kind === "secret") {
|
|
return { kind: "secret", ...assertSecretRef(credential) };
|
|
}
|
|
if (
|
|
!credential
|
|
|| typeof credential !== "object"
|
|
|| Array.isArray(credential)
|
|
|| credential.kind !== "credential"
|
|
|| typeof credential.id !== "string"
|
|
|| credential.id.length < 16
|
|
|| credential.id.length > 256
|
|
) throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Credential reference is invalid");
|
|
return { kind: "credential", id: credential.id };
|
|
}
|
|
|
|
function assertLeaseParams(params) {
|
|
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
|
throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Credential lease parameters are invalid");
|
|
}
|
|
const credential = assertCredentialRef(params.secret);
|
|
if (typeof params.operationId !== "string" || params.operationId.length < 1 || params.operationId.length > 128) {
|
|
throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Credential lease operation ID is invalid");
|
|
}
|
|
if (typeof params.purpose !== "string" || params.purpose.length < 1 || params.purpose.length > 256) {
|
|
throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Credential lease purpose is invalid");
|
|
}
|
|
if (
|
|
params.ttlMs !== undefined
|
|
&& (!Number.isSafeInteger(params.ttlMs) || params.ttlMs < 1 || params.ttlMs > 60_000)
|
|
) throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Credential lease lifetime is invalid");
|
|
return {
|
|
secret: credential,
|
|
operationId: params.operationId,
|
|
purpose: params.purpose,
|
|
...(params.ttlMs === undefined ? {} : { ttlMs: params.ttlMs }),
|
|
};
|
|
}
|
|
|
|
class PluginCredentialBroker {
|
|
constructor(options) {
|
|
this.secretStore = options.secretStore;
|
|
this.leaseStore = options.leaseStore;
|
|
this.credentialResolver = options.credentialResolver ?? null;
|
|
if (this.credentialResolver && (
|
|
typeof this.credentialResolver.assertReference !== "function"
|
|
|| typeof this.credentialResolver.resolve !== "function"
|
|
)) throw new TypeError("Credential resolver must validate and resolve credential references");
|
|
}
|
|
|
|
describeAuthorization(params) {
|
|
const validated = assertLeaseParams(params);
|
|
if (validated.secret.kind === "credential") {
|
|
return {
|
|
permission: "vault.credentials",
|
|
resources: [`credential:${validated.secret.id}`],
|
|
reason: `Use Netcatty credential for ${validated.purpose}`,
|
|
operationId: validated.operationId,
|
|
};
|
|
}
|
|
return {
|
|
permission: "secrets",
|
|
resources: [`secret:${validated.secret.key}`],
|
|
reason: `Use plugin secret for ${validated.purpose}`,
|
|
operationId: validated.operationId,
|
|
};
|
|
}
|
|
|
|
async createLease(params, context) {
|
|
const validated = assertLeaseParams(params);
|
|
await context.assertActive();
|
|
let resolveSecret;
|
|
if (validated.secret.kind === "credential") {
|
|
if (!this.credentialResolver) {
|
|
throw new PluginRpcError(RPC_ERRORS.unavailable, "Netcatty credential access is unavailable");
|
|
}
|
|
await this.credentialResolver.assertReference(validated.secret, {
|
|
pluginId: context.pluginId,
|
|
runtimeId: context.runtimeId,
|
|
operationId: validated.operationId,
|
|
purpose: validated.purpose,
|
|
signal: context.signal,
|
|
});
|
|
await context.assertActive();
|
|
resolveSecret = async (consumeContext) => {
|
|
const value = await this.credentialResolver.resolve(
|
|
validated.secret,
|
|
Object.freeze({ ...consumeContext, purpose: validated.purpose }),
|
|
);
|
|
if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > MAX_SECRET_BYTES) {
|
|
throw new PluginRpcError(RPC_ERRORS.dataLoss, "Resolved credential is invalid or too large");
|
|
}
|
|
return value;
|
|
};
|
|
} else {
|
|
this.secretStore.getRecordByReference(context.pluginId, validated.secret);
|
|
}
|
|
return this.leaseStore.issue({
|
|
pluginId: context.pluginId,
|
|
runtimeId: context.runtimeId,
|
|
credential: validated.secret,
|
|
operationId: validated.operationId,
|
|
purpose: validated.purpose,
|
|
ttlMs: validated.ttlMs,
|
|
signal: context.signal,
|
|
resolveSecret,
|
|
});
|
|
}
|
|
|
|
async consumeLease(context, lease, operationId) {
|
|
await context.assertActive();
|
|
const value = await this.leaseStore.consume({
|
|
pluginId: context.pluginId,
|
|
runtimeId: context.runtimeId,
|
|
lease,
|
|
operationId,
|
|
signal: context.signal,
|
|
});
|
|
await context.assertActive();
|
|
return value;
|
|
}
|
|
}
|
|
|
|
module.exports = { PluginCredentialBroker, assertCredentialRef, assertLeaseParams };
|