import { AuthListAPIType, AuthListType } from "./types/auth"; import { BaseAPIResponse } from "./types/api"; import { CapabilitiesType } from "./types/capabilities"; import { DecryptionPayload, DecryptionResult, EncryptionPayload, EncryptionResult, RewrapPayload, RewrapResult, TransitKeyType, } from "./types/transit"; import { DoesNotExistError } from "../types/internalErrors"; import { MountType, MountsType, NewMountParams } from "./types/mount"; import { NewTOTPData, NewTOTPResp } from "./types/totp"; import { SealStatusType } from "./types/seal"; import { SecretMetadataType } from "./types/secret"; import { Settings } from "../settings/Settings"; import { TokenInfo } from "./types/token"; import { UserType, UserTypeAPIResp } from "./types/user"; import { getObjectKeys, removeDoubleSlash } from "../utils"; async function checkResponse(resp: Response): Promise<void> { if (resp.ok) return; if (resp.status == 404) throw DoesNotExistError; let json: BaseAPIResponse; try { json = (await resp.json()) as BaseAPIResponse; } catch { // Do Nothing } if (json?.errors?.length >= 1) { throw new Error(json.errors[0]); } } export class API { private settings: Settings; constructor(settings: Settings) { this.settings = settings; } getHeaders(): Record<string, string> { return { "X-Vault-Token": this.settings.token, }; } appendAPIURL(url: string): string { return this.settings.apiURL + url; } // List all supported auth methods async listAuth(): Promise<AuthListType> { const request = new Request(this.appendAPIURL(`/v1/sys/auth`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as AuthListAPIType; return data.data; } // Tries to login with username and password, returns token async usernameLogin(username: string, password: string): Promise<string> { const request = new Request(this.appendAPIURL(`/v1/auth/userpass/login/${username}`), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ username: username, password: password }), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { auth: { client_token: string }; }; return data.auth.client_token; } async createOrUpdateUserPassUser( path: string, username: string, data: Partial<UserType>, ): Promise<void> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/auth/${path}/users/${username}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(data, null, 0), }, ); const resp = await fetch(request); await checkResponse(resp); } async deleteUserPassUser(path: string, username: string): Promise<void> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/auth/${path}/users/${username}`)), { method: "DELETE", headers: this.getHeaders(), }, ); const resp = await fetch(request); await checkResponse(resp); } async getUserPassUser(path: string, username: string): Promise<UserType> { const request = new Request(this.appendAPIURL(`/v1/auth/${path}/users/${username}`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as UserTypeAPIResp; return data.data; } async listUserPassUsers(path: string): Promise<string[]> { const request = new Request(this.appendAPIURL(`/v1/auth/${path}/users?list=true`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { keys: string[] } }; return data.data.keys; } async getMount(mountName: string): Promise<MountType> { const request = new Request(this.appendAPIURL("/v1/sys/internal/ui/mounts/" + mountName), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: MountType }; return data.data; } async getMounts(): Promise<MountsType> { const request = new Request(this.appendAPIURL("/v1/sys/internal/ui/mounts"), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { secret: MountsType } }; return data.data.secret; } async newMount(parms: NewMountParams): Promise<void> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/sys/mounts/${parms.name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(parms), }, ); const resp = await fetch(request); await checkResponse(resp); } async deleteMount(mountPath: string): Promise<void> { const request = new Request(this.appendAPIURL("/v1/sys/mounts/" + mountPath), { method: "DELETE", headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); } async getCapabilitiesPath(path: string | string[]): Promise<CapabilitiesType> { if (!Array.isArray(path)) { path = [path]; } const request = new Request(this.appendAPIURL("/v1/sys/capabilities-self"), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify({ paths: path, }), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { capabilities: string[] }; return data; } async getCapsPath(path: string | string[]): Promise<string[]> { return (await this.getCapabilitiesPath(path)).capabilities; } async getCapabilities( baseMount: string, secretPath: string[], name: string, ): Promise<CapabilitiesType> { return await this.getCapabilitiesPath( removeDoubleSlash(baseMount + secretPath.join("/") + "/" + name), ); } async sealVault(): Promise<void> { const request = new Request(this.appendAPIURL("/v1/sys/seal"), { method: "PUT", headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); } async submitUnsealKey(key: string): Promise<void> { const request = new Request(this.appendAPIURL("/v1/sys/unseal"), { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ key: key, }), }); const resp = await fetch(request); await checkResponse(resp); } async getSealStatus(): Promise<SealStatusType> { const request = new Request(this.appendAPIURL("/v1/sys/seal-status")); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as SealStatusType; return data; } async lookupSelf(): Promise<TokenInfo> { const request = new Request(this.appendAPIURL("/v1/auth/token/lookup-self"), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: TokenInfo }; return data.data; } async renewSelf(): Promise<void> { const request = new Request(this.appendAPIURL("/v1/auth/token/renew-self"), { method: "POST", headers: { ...this.getHeaders(), "Content-Type": "application/json", }, body: JSON.stringify({}), }); const resp = await fetch(request); await checkResponse(resp); } async createOrUpdatePolicy(name: string, policy_data: string): Promise<void> { const request = new Request(this.appendAPIURL("/v1/sys/policies/acl/" + name), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify({ policy: policy_data }, null, 0), }); const resp = await fetch(request); await checkResponse(resp); } async deletePolicy(name: string): Promise<void> { const request = new Request(this.appendAPIURL("/v1/sys/policies/acl/" + name), { method: "DELETE", headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); } async getPolicies(): Promise<string[]> { const request = new Request(this.appendAPIURL("/v1/sys/policies/acl?list=true"), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { keys: string[] } }; return data.data.keys; } async getPolicy(name: string): Promise<string> { const request = new Request(this.appendAPIURL("/v1/sys/policies/acl/" + name), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { policy: string } }; return data.data.policy; } async createOrUpdateSecret( baseMount: string, secretPath: string[], name: string, data: Record<string, unknown>, ): Promise<void> { let secretURL = ""; let APIData = {}; const mountInfo = await this.getMount(baseMount); if (mountInfo.options.version == "2") { secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`; APIData = { data: data }; } else { secretURL = `/v1/${baseMount}/${secretPath.join("/")}/${name}`; APIData = data; } secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); const request = new Request(this.appendAPIURL(secretURL), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(APIData, null, 0), }); const resp = await fetch(request); await checkResponse(resp); } async deleteSecret( baseMount: string, secretPath: string[], name: string, version = "null", ): Promise<void> { let secretURL = ""; let request: Request; const mountInfo = await this.getMount(baseMount); const mountVersion = mountInfo.options.version; if (mountVersion == "2" && version != "null") { secretURL = `/v1/${baseMount}/delete/${secretPath.join("/")}/${name}`; secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); request = new Request(this.appendAPIURL(secretURL), { method: "POST", headers: { ...this.getHeaders(), "Content-Type": "application/json", }, body: JSON.stringify({ versions: [version] }), }); } else { if (mountVersion == "2") { secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}/${name}`; } else { secretURL = `/v1/${baseMount}/${secretPath.join("/")}/${name}`; } secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); request = new Request(this.appendAPIURL(secretURL), { method: "DELETE", headers: this.getHeaders(), }); } const resp = await fetch(request); await checkResponse(resp); } async undeleteSecret( baseMount: string, secretPath: string[], name: string, version = "null", ): Promise<void> { let secretURL = `/v1/${baseMount}/undelete/${secretPath.join("/")}/${name}`; secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); if (version == "null") { const meta = await this.getSecretMetadata(baseMount, secretPath, name); const versions = getObjectKeys(meta.versions); version = String(versions[versions.length - 1]); } const request = new Request(this.appendAPIURL(secretURL), { method: "POST", headers: { ...this.getHeaders(), "Content-Type": "application/json", }, body: JSON.stringify({ versions: [version] }), }); const resp = await fetch(request); await checkResponse(resp); } async getSecretKV1( baseMount: string, secretPath: string[], name: string, ): Promise<Record<string, unknown>> { const request = new Request( this.appendAPIURL(`/v1/${baseMount}/${secretPath.join("/")}/${name}`), { headers: this.getHeaders(), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as unknown; return (data as { data: Record<string, unknown> }).data; } async getSecretKV2( baseMount: string, secretPath: string[], name: string, version = "null", ): Promise<Record<string, unknown>> { let secretURL = ""; secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`; if (version != "null") secretURL += `?version=${version}`; const request = new Request(this.appendAPIURL(secretURL), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as unknown; return (data as { data: { data: Record<string, unknown> } }).data.data; } async getSecret( baseMount: string, secretPath: string[], name: string, version = "null", ): Promise<Record<string, unknown>> { const mountInfo = await this.getMount(baseMount); if (mountInfo.options.version == "2") { return await this.getSecretKV2(baseMount, secretPath, name, version); } else { return await this.getSecretKV1(baseMount, secretPath, name); } } async getSecretMetadata( baseMount: string, secretPath: string[], name: string, ): Promise<SecretMetadataType> { const request = new Request( this.appendAPIURL(`/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`), { headers: this.getHeaders(), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: SecretMetadataType }; return data.data; } async getSecrets(baseMount: string, secretPath: string[]): Promise<string[]> { let secretURL = ""; const mountInfo = await this.getMount(baseMount); if (mountInfo.options.version == "2") { secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}?list=true`; } else { secretURL = `/v1/${baseMount}/${secretPath.join("/")}?list=true`; } const request = new Request(this.appendAPIURL(secretURL), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { keys: string[] } }; return data.data.keys; } async addNewTOTP(baseMount: string, parms: NewTOTPData): Promise<NewTOTPResp> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/keys/${parms.name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(parms), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: NewTOTPResp }; return data.data; } async deleteTOTP(baseMount: string, name: string): Promise<void> { const request = new Request(this.appendAPIURL(`/v1/${baseMount}/keys/${name}`), { method: "DELETE", headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); } async getTOTPCode(baseMount: string, name: string): Promise<string> { const request = new Request(this.appendAPIURL(`/v1/${baseMount}/code/${name}`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { code: string } }; return data.data.code; } async getTOTPKeys(baseMount: string): Promise<string[]> { const request = new Request(this.appendAPIURL(`/v1/${baseMount}/keys?list=true`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { keys: string[] } }; return data.data.keys; } async getTransitKey(baseMount: string, name: string): Promise<TransitKeyType> { const request = new Request(this.appendAPIURL(`/v1/${baseMount}/keys/${name}`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: TransitKeyType }; return data.data; } async getTransitKeys(baseMount: string): Promise<string[]> { const request = new Request(this.appendAPIURL(`/v1/${baseMount}/keys?list=true`), { headers: this.getHeaders(), }); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data: { keys: string[] } }; return data.data.keys; } async newTransitKey(baseMount: string, parms: { name: string; type: string }): Promise<void> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/keys/${parms.name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(parms), }, ); const resp = await fetch(request); await checkResponse(resp); } async transitDecrypt( baseMount: string, name: string, payload: DecryptionPayload, ): Promise<DecryptionResult> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/decrypt/${name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(payload), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data?: DecryptionResult; }; return data.data; } async transitEncrypt( baseMount: string, name: string, payload: EncryptionPayload, ): Promise<EncryptionResult> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/encrypt/${name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(payload), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data?: EncryptionResult; }; return data.data; } async transitRewrap( baseMount: string, name: string, payload: RewrapPayload, ): Promise<RewrapResult> { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/rewrap/${name}`)), { method: "POST", headers: { "Content-Type": "application/json", ...this.getHeaders(), }, body: JSON.stringify(payload), }, ); const resp = await fetch(request); await checkResponse(resp); const data = (await resp.json()) as { data?: RewrapResult; }; return data.data; } }