Files
kuber/command/trust.ts
T

112 lines
3.4 KiB
TypeScript

import { defineCommand } from "citty";
import { apiRequest, KuberApiError, type ApiRequestInit } from "../lib/api";
import { ctx } from "../lib/context";
import {
readTrust,
resolveTrustIdentity,
updateTrust,
type LocalTrustRecord,
} from "../lib/trust";
import type { WorkspaceTrustResponse } from "../shared/api";
function route(project: string) {
return `/workspaces/${encodeURIComponent(project)}/trust`;
}
type TrustApiRequest = <T>(path: string, init?: ApiRequestInit) => Promise<T>;
async function current() {
const { project, cwd } = ctx();
return resolveTrustIdentity(project, cwd);
}
export async function grantTrust(
identity: LocalTrustRecord,
request: TrustApiRequest = apiRequest,
): Promise<void> {
await request(route(identity.project), {
method: "POST",
json: { fingerprint: identity.fingerprint },
});
await updateTrust((records) => [
...records.filter(
(record) =>
record.project !== identity.project ||
record.fingerprint !== identity.fingerprint,
),
identity,
]);
console.log(`Trusted this directory for namespace ${identity.project}`);
}
export async function statusTrust(
identity: LocalTrustRecord,
request: TrustApiRequest = apiRequest,
): Promise<void> {
const local = (await readTrust()).some(
(record) =>
record.project === identity.project &&
record.fingerprint === identity.fingerprint,
);
const remote = await request<WorkspaceTrustResponse>(route(identity.project));
const registered = remote.fingerprints.includes(identity.fingerprint);
console.log(`Namespace: ${identity.project}`);
console.log(`Local: ${local ? "trusted" : "untrusted"}`);
console.log(`Server: ${registered ? "registered" : "not registered"}`);
}
export async function revokeTrust(
identity: LocalTrustRecord,
request: TrustApiRequest = apiRequest,
): Promise<void> {
let remoteError: unknown;
try {
await request<void>(
`${route(identity.project)}?fingerprint=${encodeURIComponent(identity.fingerprint)}`,
{ method: "DELETE" },
);
} catch (error) {
// A missing server record is already revoked; all other failures are
// reported after the local registration is removed.
if (!(error instanceof KuberApiError && error.status === 404))
remoteError = error;
}
await updateTrust((records) =>
records.filter(
(record) =>
record.project !== identity.project ||
record.fingerprint !== identity.fingerprint,
),
);
if (remoteError) {
const detail =
remoteError instanceof Error ? remoteError.message : String(remoteError);
throw new Error(
`Removed local trust for namespace ${identity.project}, but failed to revoke the server registration: ${detail}`,
{ cause: remoteError },
);
}
console.log(`Revoked trust for namespace ${identity.project}`);
}
export const trust = defineCommand({
meta: { name: "trust", description: "Trust this directory for kuber up" },
subCommands: {
status: defineCommand({
meta: { name: "status", description: "Show local and server trust" },
async run() {
await statusTrust(await current());
},
}),
revoke: defineCommand({
meta: { name: "revoke", description: "Revoke this directory trust" },
async run() {
await revokeTrust(await current());
},
}),
},
async run() {
await grantTrust(await current());
},
});