2026-03-06 16:28:01 +02:00

398 lines
10 KiB
TypeScript

import type {
NbDnsNameserverGroup,
NbEvent,
NbGroup,
NbNetwork,
NbNetworkResource,
NbNetworkRouter,
NbPeer,
NbPolicy,
NbPostureCheck,
NbRoute,
NbSetupKey,
NbUser,
} from "./types.ts";
/** Narrowed fetch signature used for dependency injection. */
export type FetchFn = (
input: string | URL | Request,
init?: RequestInit,
) => Promise<Response>;
/** Thrown when the NetBird API returns a non-2xx status. */
export class NetbirdApiError extends Error {
constructor(
public readonly status: number,
public readonly method: string,
public readonly path: string,
public readonly body: unknown,
) {
super(`NetBird API error: ${method} ${path} returned ${status}`);
this.name = "NetbirdApiError";
}
}
/**
* Thin HTTP client for the NetBird Management API.
*
* Accepts an injectable fetch function so callers (and tests) can swap
* the transport without touching the client logic.
*/
export class NetbirdClient {
constructor(
private readonly baseUrl: string,
private readonly token: string,
private readonly fetchFn: FetchFn = fetch,
) {}
// ---------------------------------------------------------------------------
// Internal
// ---------------------------------------------------------------------------
private async request<T>(
method: string,
path: string,
body?: unknown,
): Promise<T> {
const url = `${this.baseUrl}${path}`;
const headers: Record<string, string> = {
"Authorization": `Token ${this.token}`,
"Accept": "application/json",
};
if (body !== undefined) {
headers["Content-Type"] = "application/json";
}
const resp = await this.fetchFn(url, {
method,
headers,
body: body !== undefined ? JSON.stringify(body) : undefined,
});
if (!resp.ok) {
const text = await resp.text();
let errorBody: unknown;
try {
errorBody = JSON.parse(text);
} catch {
errorBody = text;
}
throw new NetbirdApiError(resp.status, method, path, errorBody);
}
// 204 No Content — nothing to parse
if (resp.status === 204) {
return undefined as T;
}
return (await resp.json()) as T;
}
// ---------------------------------------------------------------------------
// Groups
// ---------------------------------------------------------------------------
listGroups(): Promise<NbGroup[]> {
return this.request("GET", "/groups");
}
createGroup(data: { name: string; peers?: string[] }): Promise<NbGroup> {
return this.request("POST", "/groups", data);
}
updateGroup(
id: string,
data: { name?: string; peers?: string[] },
): Promise<NbGroup> {
return this.request("PUT", `/groups/${id}`, data);
}
deleteGroup(id: string): Promise<void> {
return this.request("DELETE", `/groups/${id}`);
}
// ---------------------------------------------------------------------------
// Setup Keys
// ---------------------------------------------------------------------------
listSetupKeys(): Promise<NbSetupKey[]> {
return this.request("GET", "/setup-keys");
}
createSetupKey(data: {
name: string;
type: "one-off" | "reusable";
expires_in: number;
auto_groups?: string[];
usage_limit?: number;
}): Promise<NbSetupKey> {
return this.request("POST", "/setup-keys", data);
}
deleteSetupKey(id: number | string): Promise<void> {
return this.request("DELETE", `/setup-keys/${id}`);
}
// ---------------------------------------------------------------------------
// Peers
// ---------------------------------------------------------------------------
listPeers(): Promise<NbPeer[]> {
return this.request("GET", "/peers");
}
updatePeer(
id: string,
data: {
name?: string;
ssh_enabled?: boolean;
login_expiration_enabled?: boolean;
inactivity_expiration_enabled?: boolean;
},
): Promise<NbPeer> {
return this.request("PUT", `/peers/${id}`, data);
}
deletePeer(id: string): Promise<void> {
return this.request("DELETE", `/peers/${id}`);
}
// ---------------------------------------------------------------------------
// Policies
// ---------------------------------------------------------------------------
listPolicies(): Promise<NbPolicy[]> {
return this.request("GET", "/policies");
}
createPolicy(data: Omit<NbPolicy, "id">): Promise<NbPolicy> {
return this.request("POST", "/policies", data);
}
updatePolicy(id: string, data: Omit<NbPolicy, "id">): Promise<NbPolicy> {
return this.request("PUT", `/policies/${id}`, data);
}
deletePolicy(id: string): Promise<void> {
return this.request("DELETE", `/policies/${id}`);
}
// ---------------------------------------------------------------------------
// Routes
// ---------------------------------------------------------------------------
listRoutes(): Promise<NbRoute[]> {
return this.request("GET", "/routes");
}
createRoute(data: Omit<NbRoute, "id">): Promise<NbRoute> {
return this.request("POST", "/routes", data);
}
updateRoute(id: string, data: Omit<NbRoute, "id">): Promise<NbRoute> {
return this.request("PUT", `/routes/${id}`, data);
}
deleteRoute(id: string): Promise<void> {
return this.request("DELETE", `/routes/${id}`);
}
// ---------------------------------------------------------------------------
// DNS Nameserver Groups
// ---------------------------------------------------------------------------
listDnsNameserverGroups(): Promise<NbDnsNameserverGroup[]> {
return this.request("GET", "/dns/nameservers");
}
createDnsNameserverGroup(
data: Omit<NbDnsNameserverGroup, "id">,
): Promise<NbDnsNameserverGroup> {
return this.request("POST", "/dns/nameservers", data);
}
updateDnsNameserverGroup(
id: string,
data: Omit<NbDnsNameserverGroup, "id">,
): Promise<NbDnsNameserverGroup> {
return this.request("PUT", `/dns/nameservers/${id}`, data);
}
deleteDnsNameserverGroup(id: string): Promise<void> {
return this.request("DELETE", `/dns/nameservers/${id}`);
}
// ---------------------------------------------------------------------------
// Events
// ---------------------------------------------------------------------------
listEvents(): Promise<NbEvent[]> {
return this.request("GET", "/events/audit");
}
// ---------------------------------------------------------------------------
// Posture Checks
// ---------------------------------------------------------------------------
listPostureChecks(): Promise<NbPostureCheck[]> {
return this.request("GET", "/posture-checks");
}
createPostureCheck(
data: Omit<NbPostureCheck, "id">,
): Promise<NbPostureCheck> {
return this.request("POST", "/posture-checks", data);
}
updatePostureCheck(
id: string,
data: Omit<NbPostureCheck, "id">,
): Promise<NbPostureCheck> {
return this.request("PUT", `/posture-checks/${id}`, data);
}
deletePostureCheck(id: string): Promise<void> {
return this.request("DELETE", `/posture-checks/${id}`);
}
// ---------------------------------------------------------------------------
// Networks
// ---------------------------------------------------------------------------
listNetworks(): Promise<NbNetwork[]> {
return this.request("GET", "/networks");
}
createNetwork(
data: { name: string; description?: string },
): Promise<NbNetwork> {
return this.request("POST", "/networks", data);
}
updateNetwork(
id: string,
data: { name: string; description?: string },
): Promise<NbNetwork> {
return this.request("PUT", `/networks/${id}`, data);
}
deleteNetwork(id: string): Promise<void> {
return this.request("DELETE", `/networks/${id}`);
}
// ---------------------------------------------------------------------------
// Network Resources
// ---------------------------------------------------------------------------
listNetworkResources(networkId: string): Promise<NbNetworkResource[]> {
return this.request("GET", `/networks/${networkId}/resources`);
}
createNetworkResource(
networkId: string,
data: {
name: string;
description?: string;
address: string;
enabled: boolean;
groups: string[];
},
): Promise<NbNetworkResource> {
return this.request("POST", `/networks/${networkId}/resources`, data);
}
updateNetworkResource(
networkId: string,
resourceId: string,
data: {
name: string;
description?: string;
address: string;
enabled: boolean;
groups: string[];
},
): Promise<NbNetworkResource> {
return this.request(
"PUT",
`/networks/${networkId}/resources/${resourceId}`,
data,
);
}
deleteNetworkResource(
networkId: string,
resourceId: string,
): Promise<void> {
return this.request(
"DELETE",
`/networks/${networkId}/resources/${resourceId}`,
);
}
// ---------------------------------------------------------------------------
// Network Routers
// ---------------------------------------------------------------------------
listNetworkRouters(networkId: string): Promise<NbNetworkRouter[]> {
return this.request("GET", `/networks/${networkId}/routers`);
}
createNetworkRouter(
networkId: string,
data: Omit<NbNetworkRouter, "id">,
): Promise<NbNetworkRouter> {
return this.request("POST", `/networks/${networkId}/routers`, data);
}
updateNetworkRouter(
networkId: string,
routerId: string,
data: Omit<NbNetworkRouter, "id">,
): Promise<NbNetworkRouter> {
return this.request(
"PUT",
`/networks/${networkId}/routers/${routerId}`,
data,
);
}
deleteNetworkRouter(
networkId: string,
routerId: string,
): Promise<void> {
return this.request(
"DELETE",
`/networks/${networkId}/routers/${routerId}`,
);
}
// ---------------------------------------------------------------------------
// Users
// ---------------------------------------------------------------------------
listUsers(): Promise<NbUser[]> {
return this.request("GET", "/users");
}
createUser(data: {
email: string;
name?: string;
role: string;
auto_groups: string[];
is_service_user: boolean;
}): Promise<NbUser> {
return this.request("POST", "/users", data);
}
updateUser(
id: string,
data: { name?: string; role?: string; auto_groups?: string[] },
): Promise<NbUser> {
return this.request("PUT", `/users/${id}`, data);
}
deleteUser(id: string): Promise<void> {
return this.request("DELETE", `/users/${id}`);
}
}