Files
vantage/web/lib/api.ts
T
mrhid6 a9d602d021
Server Deploy / deploy (push) Failing after 1m9s
Agent Release / build (push) Successful in 1m52s
Agent Release / msi (push) Failing after 52s
feat: harden console sessions + complete protocol support
- single-use session tokens (atomic ConsumeSessionToken) + user-bound tunnel (actor must match session opener)
- wire VNC end-to-end (stash/consume password, connect+tunnel, frontend password field)
- passphrase-protected SSH keys: passphrase_enc on Key model, capture on upload, decrypt + pass to guacd
2026-07-17 12:19:07 +01:00

324 lines
7.9 KiB
TypeScript

export type ServerStatus = "pending" | "active" | "offline";
export type KeySource = "uploaded" | "generated";
export interface PackageUpdate {
name: string;
current_version?: string;
new_version: string;
}
export interface Server {
id: string;
server_id: string;
hostname: string;
ip_address: string;
os_info: string;
status: ServerStatus;
agent_version?: string;
last_seen: string;
created_at: string;
available_updates?: PackageUpdate[];
updates_checked_at?: string;
console_protocols?: string[];
}
export interface ConsoleConnectRequest {
server_id: string;
protocol: string;
key_id?: string;
rdp_username?: string;
rdp_password?: string;
ssh_username?: string;
}
export interface ConsoleConnectResponse {
session_id: string;
token: string;
ws_path: string;
}
export interface Key {
id: string;
key_id: string;
label: string;
public_key: string;
fingerprint: string;
source: KeySource;
generated_by_server_id?: string;
has_private_key: boolean;
has_passphrase?: boolean;
created_at: string;
assigned_count?: number;
}
export interface Assignment {
id: string;
key_id: string;
server_id: string;
assigned_at: string;
revoked_at: string | null;
}
export interface AuditEvent {
id: string;
event_type: string;
actor: string;
server_id?: string;
key_id?: string;
details: string;
created_at: string;
}
export interface AlertSettings {
enabled: boolean;
webhook_url: string;
offline_threshold_minutes: number;
}
export interface EmailSettings {
enabled: boolean;
smtp_host: string;
smtp_port: number;
username: string;
password: string;
from_addr: string;
to_addrs: string[];
use_tls: boolean;
}
export interface SecretsSettings {
read_token_set: boolean;
rotated_at?: string;
}
export interface Settings {
alerts: AlertSettings;
email: EmailSettings;
secrets: SecretsSettings;
}
export interface SecretGroupSummary {
group: string;
key_count: number;
updated_at: string;
}
export interface Secret {
group: string;
key: string;
updated_at: string;
}
export interface NewServerResponse {
server_id: string;
pre_reg_token: string;
install_command: string;
}
export interface GenerateKeyOptions {
label: string;
key_type: "ed25519" | "rsa" | "ecdsa";
key_size?: number;
passphrase?: string;
comment?: string;
}
export interface KeyWithAssignments extends Key {
assignments: (Assignment & { server: Server })[];
}
export interface ServerWithKeys extends Server {
keys: (Assignment & { key: Key })[];
}
class ApiError extends Error {
constructor(
public status: number,
message: string
) {
super(message);
this.name = "ApiError";
}
}
async function request<T>(path: string, options?: RequestInit): Promise<T> {
const res = await fetch(`/api${path}`, {
credentials: "include",
headers: {
"Content-Type": "application/json",
...options?.headers,
},
...options,
});
if (!res.ok) {
const text = await res.text().catch(() => res.statusText);
throw new ApiError(res.status, text || `HTTP ${res.status}`);
}
if (res.status === 204) {
return undefined as T;
}
return res.json();
}
export const api = {
// Servers
listServers(): Promise<Server[]> {
return request<Server[]>("/servers");
},
getServer(serverId: string): Promise<ServerWithKeys> {
return request<ServerWithKeys>(`/servers/${serverId}`);
},
createServer(): Promise<NewServerResponse> {
return request<NewServerResponse>("/servers/new", { method: "POST" });
},
deleteServer(serverId: string): Promise<void> {
return request<void>(`/servers/${serverId}`, { method: "DELETE" });
},
generateKeyForServer(serverId: string, opts: GenerateKeyOptions): Promise<{ command_id: string }> {
return request<{ command_id: string }>(`/servers/${serverId}/generate-key`, {
method: "POST",
body: JSON.stringify(opts),
});
},
getUpdateCommand(): string {
return `curl -fsSL "${window.location.origin}/update" | bash`;
},
getLatestAgentVersion(): Promise<{ version: string }> {
return request<{ version: string }>("/agent/latest-version");
},
updateAgent(serverId: string): Promise<{ message: string; version: string }> {
return request<{ message: string; version: string }>(`/servers/${serverId}/update-agent`, {
method: "POST",
});
},
applyUpdates(serverId: string): Promise<{ message: string }> {
return request<{ message: string }>(`/servers/${serverId}/apply-updates`, {
method: "POST",
});
},
// Audit
listAuditEvents(limit?: number): Promise<AuditEvent[]> {
const qs = limit ? `?limit=${limit}` : "";
return request<AuditEvent[]>(`/audit${qs}`);
},
// Settings
getSettings(): Promise<Settings> {
return request<Settings>("/settings");
},
saveSettings(settings: { alerts: AlertSettings; email: EmailSettings }): Promise<{ saved: boolean }> {
return request<{ saved: boolean }>("/settings", {
method: "PUT",
body: JSON.stringify(settings),
});
},
rotateSecretsToken(): Promise<{ token: string }> {
return request<{ token: string }>("/settings/secrets-token", { method: "POST" });
},
// Secrets
listSecretGroups(): Promise<SecretGroupSummary[]> {
return request<SecretGroupSummary[]>("/secrets");
},
createSecretGroup(group: string, values: Record<string, string>): Promise<{ group: string }> {
return request<{ group: string }>("/secrets", {
method: "POST",
body: JSON.stringify({ group, values }),
});
},
getSecretGroup(group: string): Promise<{ group: string; secrets: Secret[] }> {
return request<{ group: string; secrets: Secret[] }>(`/secrets/${encodeURIComponent(group)}`);
},
putSecrets(group: string, values: Record<string, string>): Promise<{ saved: boolean }> {
return request<{ saved: boolean }>(`/secrets/${encodeURIComponent(group)}`, {
method: "PUT",
body: JSON.stringify(values),
});
},
revealSecret(group: string, key: string): Promise<{ value: string }> {
return request<{ value: string }>(`/secrets/${encodeURIComponent(group)}/reveal`, {
method: "POST",
body: JSON.stringify({ key }),
});
},
deleteSecret(group: string, key: string): Promise<void> {
return request<void>(`/secrets/${encodeURIComponent(group)}/${encodeURIComponent(key)}`, {
method: "DELETE",
});
},
deleteSecretGroup(group: string): Promise<void> {
return request<void>(`/secrets/${encodeURIComponent(group)}`, { method: "DELETE" });
},
// Keys
listKeys(): Promise<Key[]> {
return request<Key[]>("/keys");
},
getKey(keyId: string): Promise<KeyWithAssignments> {
return request<KeyWithAssignments>(`/keys/${keyId}`);
},
uploadKey(label: string, public_key: string, private_key?: string, passphrase?: string): Promise<Key> {
return request<Key>("/keys", {
method: "POST",
body: JSON.stringify({
label,
public_key,
private_key: private_key || undefined,
passphrase: passphrase || undefined,
}),
});
},
getPrivateKey(keyId: string): Promise<{ private_key: string }> {
return request<{ private_key: string }>(`/keys/${keyId}/private-key`);
},
deleteKey(keyId: string): Promise<void> {
return request<void>(`/keys/${keyId}`, { method: "DELETE" });
},
// Assignments
assignKey(keyId: string, serverId: string): Promise<Assignment> {
return request<Assignment>(`/keys/${keyId}/assign`, {
method: "POST",
body: JSON.stringify({ server_id: serverId }),
});
},
revokeKey(keyId: string, serverId: string): Promise<void> {
return request<void>(`/keys/${keyId}/assign/${serverId}`, {
method: "DELETE",
});
},
// Console
connectConsole(body: ConsoleConnectRequest): Promise<ConsoleConnectResponse> {
return request<ConsoleConnectResponse>("/console/connect", {
method: "POST",
body: JSON.stringify(body),
});
},
};