From 6c6a92b094b08a33d66194c070bcad5b1f4670fe Mon Sep 17 00:00:00 2001 From: Kitteh Date: Thu, 15 Apr 2021 15:09:43 +0100 Subject: [PATCH] Start work on transit pages. --- src/api.js | 490 +++++++++++++++++---------------- src/main.js | 4 + src/pages/Home.js | 5 +- src/pages/KeyValueView.js | 2 +- src/pages/TransitView.js | 65 +++++ src/pages/TransitViewSecret.js | 56 ++++ src/pages/index.js | 4 +- 7 files changed, 386 insertions(+), 240 deletions(-) create mode 100644 src/pages/TransitView.js create mode 100644 src/pages/TransitViewSecret.js diff --git a/src/api.js b/src/api.js index 2d90592..2e5d7bd 100644 --- a/src/api.js +++ b/src/api.js @@ -4,301 +4,317 @@ import { getAPIURL, getToken, removeDoubleSlash } from "./utils.js"; export const DoesNotExistError = new Error("Does not exist."); export async function lookupSelf() { - const request = new Request(getAPIURL() + "/v1/auth/token/lookup-self", { - headers: { - "X-Vault-Token": getToken(), - } - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - if ("data" in data) { - return data.data; - } else if ("errors" in data) { - throw new Error(data.errors[0]); - } - }); + const request = new Request(getAPIURL() + "/v1/auth/token/lookup-self", { + headers: { + "X-Vault-Token": getToken(), + } + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + if ("data" in data) { + return data.data; + } else if ("errors" in data) { + throw new Error(data.errors[0]); + } + }); } export async function renewSelf() { - const request = new Request(getAPIURL() + "/v1/auth/token/renew-self", { - method: 'POST', - headers: { - "X-Vault-Token": getToken(), - 'Content-Type': 'application/json' - }, - body: JSON.stringify({}) - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - if ("errors" in data) { - throw new Error(data.errors[0]); - } - }); + const request = new Request(getAPIURL() + "/v1/auth/token/renew-self", { + method: 'POST', + headers: { + "X-Vault-Token": getToken(), + 'Content-Type': 'application/json' + }, + body: JSON.stringify({}) + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + if ("errors" in data) { + throw new Error(data.errors[0]); + } + }); } export async function usernameLogin(username, password) { - const request = new Request(getAPIURL() + `/v1/auth/userpass/login/${username}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ "username": username, "password": password }) - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - if ("auth" in data) { - return data.auth.client_token; - } else if ("errors" in data) { - throw new Error(data.errors[0]); - } - }); + const request = new Request(getAPIURL() + `/v1/auth/userpass/login/${username}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ "username": username, "password": password }) + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + if ("auth" in data) { + return data.auth.client_token; + } else if ("errors" in data) { + throw new Error(data.errors[0]); + } + }); } export async function getMounts() { - const request = new Request(getAPIURL() + "/v1/sys/internal/ui/mounts", { - headers: { - "X-Vault-Token": getToken(), - } - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data.data.secret; - }); + const request = new Request(getAPIURL() + "/v1/sys/internal/ui/mounts", { + headers: { + "X-Vault-Token": getToken(), + } + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data.data.secret; + }); } export async function getSealStatus() { - const request = new Request(getAPIURL() + "/v1/sys/seal-status"); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data; - }); + const request = new Request(getAPIURL() + "/v1/sys/seal-status"); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data; + }); } export async function submitUnsealKey(key) { - const request = new Request(getAPIURL() + "/v1/sys/unseal", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - "key": key - }) - }); - let response = await fetch(request); - if (!response.ok) { - let json = await response.json(); - throw new Error(json.errors[0]); - } + const request = new Request(getAPIURL() + "/v1/sys/unseal", { + method: "POST", + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + "key": key + }) + }); + let response = await fetch(request); + if (!response.ok) { + let json = await response.json(); + throw new Error(json.errors[0]); + } } export async function getCapabilities(baseMount, secretPath, name) { - const request = new Request(getAPIURL() + "/v1/sys/capabilities-self", { - method: "POST", - headers: { - 'Content-Type': 'application/json', - "X-Vault-Token": getToken(), - }, - body: JSON.stringify( - { - "paths": [removeDoubleSlash(baseMount + secretPath.join("/") + "/" + name)] - } - ) - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data.capabilities; - }); + const request = new Request(getAPIURL() + "/v1/sys/capabilities-self", { + method: "POST", + headers: { + 'Content-Type': 'application/json', + "X-Vault-Token": getToken(), + }, + body: JSON.stringify( + { + "paths": [removeDoubleSlash(baseMount + secretPath.join("/") + "/" + name)] + } + ) + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data.capabilities; + }); } export async function getSecrets(baseMount, secretPath) { - let secretURL = ""; - if (pageState.currentMountType == "kv-v2") { - secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`; - } else { - // cubbyhole and v1 are identical - secretURL = `/v1/${baseMount}/${secretPath.join("")}?list=true`; + let secretURL = ""; + if (pageState.currentMountType == "kv-v2") { + secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`; + } else { + // cubbyhole and v1 are identical + secretURL = `/v1/${baseMount}/${secretPath.join("")}?list=true`; + } + const request = new Request(getAPIURL() + secretURL, { + headers: { + "X-Vault-Token": getToken(), } - const request = new Request(getAPIURL() + secretURL, { - headers: { - "X-Vault-Token": getToken(), - } - }); - return fetch(request).then(response => { - if (response.status == 404) { - throw DoesNotExistError; - } - return response.json(); - }).then(data => { - return data.data.keys; - }); + }); + return fetch(request).then(response => { + if (response.status == 404) { + throw DoesNotExistError; + } + return response.json(); + }).then(data => { + return data.data.keys; + }); } export async function getSecretMetadata(baseMount, secretPath, name) { - const request = new Request(getAPIURL() + `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`, { - headers: { - "X-Vault-Token": getToken(), - } - }); + const request = new Request(getAPIURL() + `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`, { + headers: { + "X-Vault-Token": getToken(), + } + }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data.data; - }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data.data; + }); } export async function undeleteSecret(baseMount, secretPath, name, version) { - let secretURL = `/v1/${baseMount}/undelete/${secretPath.join("")}/${name}`; - secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); - let request = new Request(getAPIURL() + secretURL, { - method: "POST", - headers: { - 'X-Vault-Token': getToken(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ "versions": [version] }) - }); - let response = await fetch(request); - if (!response.ok) { - let json = await response.json(); - throw new Error(json.errors[0]); - } + let secretURL = `/v1/${baseMount}/undelete/${secretPath.join("")}/${name}`; + secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); + let request = new Request(getAPIURL() + secretURL, { + method: "POST", + headers: { + 'X-Vault-Token': getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ "versions": [version] }) + }); + let response = await fetch(request); + if (!response.ok) { + let json = await response.json(); + throw new Error(json.errors[0]); + } } export async function getSecret(baseMount, secretPath, name, version = "0") { - let secretURL = ""; - if (pageState.currentMountType == "kv-v2") { - secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`; - if (version != 0) secretURL += `?version=${version}`; - } else { - secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; + let secretURL = ""; + if (pageState.currentMountType == "kv-v2") { + secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`; + if (version != 0) secretURL += `?version=${version}`; + } else { + secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; + } + const request = new Request(getAPIURL() + secretURL, { + headers: { + "X-Vault-Token": getToken(), } - const request = new Request(getAPIURL() + secretURL, { - headers: { - "X-Vault-Token": getToken(), - } - }); + }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return pageState.currentMountType == "kv-v2" ? data.data.data : data.data; - }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return pageState.currentMountType == "kv-v2" ? data.data.data : data.data; + }); } export async function deleteSecret(baseMount, secretPath, name, version) { - let secretURL = ""; + let secretURL = ""; - let request; + let request; - if (pageState.currentMountType == "kv-v2" && version != "0") { - secretURL = `/v1/${baseMount}/delete/${secretPath.join("")}/${name}`; - secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); - request = new Request(getAPIURL() + secretURL, { - method: "POST", - headers: { - 'X-Vault-Token': getToken(), - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ "versions": [version] }) - }); + if (pageState.currentMountType == "kv-v2" && version != "0") { + secretURL = `/v1/${baseMount}/delete/${secretPath.join("")}/${name}`; + secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); + request = new Request(getAPIURL() + secretURL, { + method: "POST", + headers: { + 'X-Vault-Token': getToken(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ "versions": [version] }) + }); + } else { + if (pageState.currentMountType == "kv-v2") { + secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`; } else { - if (pageState.currentMountType == "kv-v2") { - secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`; - } else { - secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; - } - secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); - request = new Request(getAPIURL() + secretURL, { - method: "DELETE", - headers: { - 'X-Vault-Token': getToken() - }, - }); + secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; } + secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); + request = new Request(getAPIURL() + secretURL, { + method: "DELETE", + headers: { + 'X-Vault-Token': getToken() + }, + }); + } - let response = await fetch(request); - if (!response.ok) { - let json = await response.json(); - throw new Error(json.errors[0]); - } + let response = await fetch(request); + if (!response.ok) { + let json = await response.json(); + throw new Error(json.errors[0]); + } } export async function createOrUpdateSecret(baseMount, secretPath, name, data) { - let secretURL = ""; - let APIData = {}; + let secretURL = ""; + let APIData = {}; - if (pageState.currentMountType == "kv-v2") { - secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`; - APIData = { "data": data }; - } else { - secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; - APIData = data; - } + if (pageState.currentMountType == "kv-v2") { + 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(getAPIURL() + secretURL, { - method: "POST", - headers: { - 'Content-Type': 'application/json', - 'X-Vault-Token': getToken() - }, - body: JSON.stringify(APIData, null, 0) - }); - let response = await fetch(request); - if (!response.ok) { - let json = await response.json(); - throw new Error(json.errors[0]); + secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); + const request = new Request(getAPIURL() + secretURL, { + method: "POST", + headers: { + 'Content-Type': 'application/json', + 'X-Vault-Token': getToken() + }, + body: JSON.stringify(APIData, null, 0) + }); + let response = await fetch(request); + if (!response.ok) { + let json = await response.json(); + throw new Error(json.errors[0]); + } +} + +export async function getTransitKeys(baseMount) { + const request = new Request(getAPIURL() + `/v1/${baseMount}/keys?list=true`, { + headers: { + "X-Vault-Token": getToken(), } + }); + return fetch(request).then(response => { + if (response.status == 404) { + throw DoesNotExistError; + } + return response.json(); + }).then(data => { + return data.data.keys; + }); } export async function getTOTPKeys(baseMount) { - const request = new Request(getAPIURL() + `/v1/${baseMount}/keys?list=true`, { - headers: { - "X-Vault-Token": getToken(), - } - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data.data.keys; - }); + const request = new Request(getAPIURL() + `/v1/${baseMount}/keys?list=true`, { + headers: { + "X-Vault-Token": getToken(), + } + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data.data.keys; + }); } export async function getTOTPCode(baseMount, name) { - const request = new Request(getAPIURL() + `/v1/${baseMount}/code/${name}`, { - headers: { - "X-Vault-Token": getToken(), - } - }); - return fetch(request).then(response => { - return response.json(); - }).then(data => { - return data.data.code; - }); + const request = new Request(getAPIURL() + `/v1/${baseMount}/code/${name}`, { + headers: { + "X-Vault-Token": getToken(), + } + }); + return fetch(request).then(response => { + return response.json(); + }).then(data => { + return data.data.code; + }); } export async function addNewTOTP(baseMount, parms) { - const request = new Request(getAPIURL() + removeDoubleSlash(`/v1/${baseMount}/keys/${parms.name}`), { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Vault-Token': getToken() - }, - body: JSON.stringify(parms) - }); - let response = await fetch(request); - if (!response.ok) { - let json = await response.json(); - throw new Error(json.errors[0]); - } + const request = new Request(getAPIURL() + removeDoubleSlash(`/v1/${baseMount}/keys/${parms.name}`), { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Vault-Token': getToken() + }, + body: JSON.stringify(parms) + }); + let response = await fetch(request); + if (!response.ok) { + let json = await response.json(); + throw new Error(json.errors[0]); + } } diff --git a/src/main.js b/src/main.js index b4e41d0..b6585ef 100644 --- a/src/main.js +++ b/src/main.js @@ -23,6 +23,8 @@ import { LoginPage, SetVaultURLPage, UnsealPage, + TransitViewPage, + TransitViewSecretPage, KeyValueViewPage, KeyValueSecretsPage, KeyValueVersionsPage, @@ -41,6 +43,8 @@ const pages = { LOGIN: new LoginPage(), SET_VAULT_URL: new SetVaultURLPage(), UNSEAL: new UnsealPage(), + TRANSIT_VIEW: new TransitViewPage(), + TRANSIT_VIEW_SECRET: new TransitViewSecretPage(), KEY_VALUE_VIEW: new KeyValueViewPage(), KEY_VALUE_SECRETS: new KeyValueSecretsPage(), KEY_VALUE_VERSIONS: new KeyValueVersionsPage(), diff --git a/src/pages/Home.js b/src/pages/Home.js index 4dd7140..89b20ae 100644 --- a/src/pages/Home.js +++ b/src/pages/Home.js @@ -122,7 +122,7 @@ export class HomePage extends Page { if (typeof mount != 'object') return; if (mount == null) return; if (!("type" in mount)) return; - if (!(["kv", "totp"].includes(mount.type))) return; + if (!(["kv", "totp", "transit"].includes(mount.type))) return; let mountType = mount.type == "kv" ? "kv-v" + String(mount.options.version) : mount.type; @@ -134,6 +134,9 @@ export class HomePage extends Page { } else if (mount.type == "totp") { linkText = `TOTP - ${baseMount}`; linkPage = pages.TOTP; + } else if (mount.type == "transit"){ + linkText = `Transit - ${baseMount}`; + linkPage = pages.TRANSIT_VIEW; } navList.appendChild(makeElement({ diff --git a/src/pages/KeyValueView.js b/src/pages/KeyValueView.js index 2fc50dc..01285cb 100644 --- a/src/pages/KeyValueView.js +++ b/src/pages/KeyValueView.js @@ -71,7 +71,7 @@ export class KeyValueViewPage extends Page { } else { setErrorText(e.message); } - }; + } } get name() { diff --git a/src/pages/TransitView.js b/src/pages/TransitView.js new file mode 100644 index 0000000..acf0864 --- /dev/null +++ b/src/pages/TransitView.js @@ -0,0 +1,65 @@ +import { Page } from "../types/Page.js"; +import { DoesNotExistError, getTransitKeys } from "../api.js"; +import { setErrorText, setTitleElement } from "../pageUtils.js"; +import { makeElement } from "../htmlUtils.js"; + +export class TransitViewPage extends Page { + constructor() { + super(); + } + goBack() { + changePage(pages.HOME); + } + async render() { + pageState.currentSecret = ""; + + setTitleElement(pageState); + + let newButton = makeElement({ + tag: "button", + text: "New", + class: ["uk-button", "uk-button-primary", "uk-margin-bottom"], + onclick: () => { + changePage(pages.TRANSIT_NEW_KEY); + } + }); + pageContent.appendChild(newButton); + + try { + let res = await getTransitKeys(pageState.currentBaseMount); + + pageContent.appendChild(makeElement({ + tag: "ul", + class: ["uk-nav", "uk-nav-default"], + children: [ + ...res.map(function (secret) { + return makeElement({ + tag: "li", + children: makeElement({ + tag: "a", + text: secret, + onclick: _ => { + pageState.currentSecret = secret; + changePage(pages.TRANSIT_VIEW_SECRET); + } + }) + }); + }) + ] + })); + } catch (e) { + if (e == DoesNotExistError) { + pageContent.appendChild(makeElement({ + tag: "p", + text: "You seem to have no transit keys here, would you like to create one?" + })); + } else { + setErrorText(e.message); + } + } + } + + get name() { + return "Transit View"; + } +} diff --git a/src/pages/TransitViewSecret.js b/src/pages/TransitViewSecret.js new file mode 100644 index 0000000..07ea9a6 --- /dev/null +++ b/src/pages/TransitViewSecret.js @@ -0,0 +1,56 @@ +import { Page } from "../types/Page.js"; +import { setPageContent, setTitleElement } from "../pageUtils.js"; +import { makeElement } from "../htmlUtils.js"; + +export class TransitViewSecretPage extends Page { + constructor() { + super(); + } + + makeTile(title, description, onclick) { + return makeElement({ + tag: "div", + class: ["uk-tile", "uk-tile-default", "uk-tile-primary", "uk-padding-small"], + children: makeElement({ + tag: "a", + class: "uk-link-heading", + children: [ + makeElement({ + tag: "p", + class: "uk-h4", + text: title + }), + makeElement({ + tag: "span", + class: "uk-text-muted", + text: description + }) + ] + }) + + }); + } + + async render() { + setTitleElement(pageState); + setPageContent(makeElement({ + tag: "div", + class: ["uk-child-width-1-2", "uk-grid-collapse", "uk-grid-small"], + attributes: { "uk-grid": "" }, + children: [ + makeElement({ + tag: "div", + children: [ + this.makeTile("Encrypt", "Encrypt some plaintext or base64 encoded binary."), + this.makeTile("Decrypt", "Decrypt some cyphertext."), + ] + }), + ] + + })); + } + + get name() { + return "Password Generator"; + } +} \ No newline at end of file diff --git a/src/pages/index.js b/src/pages/index.js index 66749aa..9d757ab 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -10,4 +10,6 @@ export { KeyValueVersionsPage } from "./KeyValueVersions.js"; export { KeyValueNewPage } from "./KeyValueNew.js"; export { KeyValueDeletePage } from "./KeyValueDelete.js"; export { KeyValueSecretsEditPage } from "./KeyValueSecretsEdit.js"; -export { PwGenPage } from "./PwGen.js"; \ No newline at end of file +export { PwGenPage } from "./PwGen.js"; +export { TransitViewPage } from "./TransitView.js"; +export { TransitViewSecretPage } from "./TransitViewSecret.js"; \ No newline at end of file