115 lines
3.6 KiB
TypeScript
115 lines
3.6 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({ args }) {
|
|
// citty runs a command's handler after its selected subcommand handler.
|
|
// Only a bare `trust` invocation has no positional arguments here.
|
|
if (args._.length > 0) return;
|
|
await grantTrust(await current());
|
|
},
|
|
});
|