diff --git a/src/api/kv/deleteSecret.ts b/src/api/kv/deleteSecret.ts index 3b69fe4..540b3eb 100644 --- a/src/api/kv/deleteSecret.ts +++ b/src/api/kv/deleteSecret.ts @@ -12,9 +12,9 @@ export async function deleteSecret( const mountInfo = await getMount(baseMount); if (mountInfo.options.version == "2") { - secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`; + secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}/${name}`; } else { - secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; + secretURL = `/v1/${baseMount}/${secretPath.join("/")}/${name}`; } secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); request = new Request(appendAPIURL(secretURL), { diff --git a/src/api/kv/getSecrets.ts b/src/api/kv/getSecrets.ts index b19dd7f..c7da108 100644 --- a/src/api/kv/getSecrets.ts +++ b/src/api/kv/getSecrets.ts @@ -10,10 +10,10 @@ export async function getSecrets( let secretMountType = "kv-v2" if (secretMountType == "kv-v2") { - secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`; + secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}?list=true`; } else { // cubbyhole and v1 are identical - secretURL = `/v1/${baseMount}/${secretPath.join("")}?list=true`; + secretURL = `/v1/${baseMount}/${secretPath.join("/")}?list=true`; } const request = new Request(appendAPIURL(secretURL), { headers: getHeaders(), diff --git a/src/main.tsx b/src/main.tsx index 64b21ae..71450b6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -52,6 +52,9 @@ import { KeyValueView } from "./ui/pages/Secrets/KeyValue/KeyValueView"; import { KeyValueSecret } from "./ui/pages/Secrets/KeyValue/KeyValueSecret"; import { KeyValueSecretEdit } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit"; import { KeyValueDelete } from "./ui/pages/Secrets/KeyValue/KeyValueDelete"; +import { TransitView } from "./ui/pages/Secrets/Transit/TransitView"; +import { TransitViewSecret } from "./ui/pages/Secrets/Transit/TransitViewSecret"; +import { NewTransitKey } from "./ui/pages/Secrets/Transit/NewTransitKey"; async function onLoad(): Promise { const Main = () => ( @@ -72,19 +75,25 @@ async function onLoad(): Promise { - - - + + + + + + + + + + + + - - - -

PAGE NOT YET IMPLEMENTED

); + render( <> diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueDelete.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueDelete.tsx index decc9dd..ab03b36 100644 --- a/src/ui/pages/Secrets/KeyValue/KeyValueDelete.tsx +++ b/src/ui/pages/Secrets/KeyValue/KeyValueDelete.tsx @@ -4,19 +4,22 @@ import { deleteSecret } from "../../../../api/kv/deleteSecret"; import { Component, render } from "preact"; import i18next from "i18next"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; -import { splitKVMount } from "./splitKVMount"; export class KeyValueDelete extends Component { render() { - const mount = this.props.matches["mount"]; - const mountSplit = splitKVMount(mount); - const baseMount = mountSplit.baseMount; - const secretPath = mountSplit.restOfMount; + const baseMount = this.props.matches["baseMount"]; + const secretPath = this.props.matches["secretPath"].split("/"); const item = this.props.matches["item"]; return ( <> - +
{i18next.t("kv_delete_text")}
+ ); } - async newKVSecretHandleForm(formData: FormData): Promise { + async newKVSecretHandleForm(formData: FormData, baseMount: string, secretPath: string[]): Promise { const path = formData.get("path") as string; - let keyData = {}; - if (["kv-v1", "cubbyhole"].includes(this.state.secretMountType)) { - keyData = { key: "value" }; - } + // TODO: check only do this on kv v1 + let keyData = { "key": "value" }; try { await createOrUpdateSecret( - this.state.baseMount, - this.state.secretPath, + baseMount, + secretPath, path, keyData, ); - await this.router.changePage("KEY_VALUE_VIEW"); + route(kvViewURL(baseMount, secretPath, path)) } catch (e: unknown) { const error = e as Error; setErrorText(error.message); } } - - //async renderPageTitle(): Promise { - // render( - // , - // this.router.pageTitleElement, - // ); - //} - - get name(): string { - return i18next.t("kv_new_title"); - } } diff --git a/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx b/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx index cbe80dc..0ba152c 100644 --- a/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx +++ b/src/ui/pages/Secrets/KeyValue/KeyValueSecret.tsx @@ -2,17 +2,14 @@ import { CodeBlock } from "../../../elements/CodeBlock"; import { Component, JSX, render } from "preact"; import { CopyableInputBox } from "../../../elements/CopyableInputBox"; import { Grid, GridSizes } from "../../../elements/Grid"; -import { Page } from "../../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { getCapabilities } from "../../../../api/sys/getCapabilities"; import { getSecret } from "../../../../api/kv/getSecret"; import { sortedObjectMap } from "../../../../utils"; -import { undeleteSecret } from "../../../../api/kv/undeleteSecret"; import i18next from "i18next"; -import { splitKVMount } from "./splitKVMount"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; -import { getMount } from "../../../../api/sys/getMounts"; import { route } from "preact-router"; +import { kvDeleteURL, kvEditURL } from "../../pageLinks"; export type KVSecretViewProps = { kvData: Record; @@ -55,10 +52,8 @@ type KeyValueSecretState = { export class KeyValueSecret extends Component { async componentDidMount() { - const mount = this.props.matches["mount"]; - const mountSplit = splitKVMount(mount); - const baseMount = mountSplit.baseMount; - const secretPath = mountSplit.restOfMount; + const baseMount = this.props.matches["baseMount"]; + const secretPath = this.props.matches["secretPath"].split("/"); const secretItem = this.props.matches["item"]; const caps = ( @@ -84,24 +79,25 @@ export class KeyValueSecret extends Component - +

{ - // Delete Button this.state.caps.includes("delete") && ( )} - {restOfMount.length == 0 && this.state.mountCaps.includes("delete") && ( + {secretPath.length == 0 && this.state.mountCaps.includes("delete") && ( )}

- {//"TODO: " == "cubbyhole" &&

{i18next.t("kv_view_cubbyhole_text")}

} - } - + {this.state.mountType == "cubbyhole" &&

{i18next.t("kv_view_cubbyhole_text")}

} + ); } diff --git a/src/ui/pages/Secrets/KeyValue/splitKVMount.tsx b/src/ui/pages/Secrets/KeyValue/splitKVMount.tsx deleted file mode 100644 index 81fed36..0000000 --- a/src/ui/pages/Secrets/KeyValue/splitKVMount.tsx +++ /dev/null @@ -1,10 +0,0 @@ -export function splitKVMount(mount: string): { baseMount: string, restOfMount: string[] } { - const baseMount = mount.split("/")[0]; - const restOfMount = mount.split("/"); - restOfMount.shift(); - - return { - baseMount, - restOfMount - }; -} \ No newline at end of file diff --git a/src/ui/pages/Secrets/NewEngines/NewKVEngine.tsx b/src/ui/pages/Secrets/NewEngines/NewKVEngine.tsx index 7a5286c..638715d 100644 --- a/src/ui/pages/Secrets/NewEngines/NewKVEngine.tsx +++ b/src/ui/pages/Secrets/NewEngines/NewKVEngine.tsx @@ -8,6 +8,7 @@ import { setErrorText } from "../../../../pageUtils"; import i18next from "i18next"; import { PageTitle } from "../../../elements/PageTitle"; import { route } from "preact-router"; +import { kvListURL } from "../../pageLinks"; export class NewKVEngine extends Component { render() { @@ -57,7 +58,7 @@ export class NewKVEngine extends Component { version: version, }, }); - route("/secrets/kv/list/" + name + "/") + route(kvListURL(name, [])); } catch (e) { const error = e as Error; setErrorText(error.message); diff --git a/src/ui/pages/Secrets/NewEngines/NewTOTPEngine.tsx b/src/ui/pages/Secrets/NewEngines/NewTOTPEngine.tsx index aa55f71..758cd82 100644 --- a/src/ui/pages/Secrets/NewEngines/NewTOTPEngine.tsx +++ b/src/ui/pages/Secrets/NewEngines/NewTOTPEngine.tsx @@ -8,6 +8,7 @@ import { setErrorText } from "../../../../pageUtils"; import i18next from "i18next"; import { route } from "preact-router"; import { PageTitle } from "../../../elements/PageTitle"; +import { totpListURL } from "../../pageLinks"; export class NewTOTPEngine extends Component { render() { @@ -43,7 +44,7 @@ export class NewTOTPEngine extends Component { name: name, type: "totp", }); - route("/secrets/totp/list/" + name + "/") + route(totpListURL(name)); } catch (e) { const error = e as Error; setErrorText(error.message); diff --git a/src/ui/pages/Secrets/SecretTitleElement.tsx b/src/ui/pages/Secrets/SecretTitleElement.tsx index e27d507..8ad27e3 100644 --- a/src/ui/pages/Secrets/SecretTitleElement.tsx +++ b/src/ui/pages/Secrets/SecretTitleElement.tsx @@ -1,29 +1,28 @@ import { route } from "preact-router"; import { JSX } from "preact/jsx-runtime"; -import { Page } from "../../../types/Page"; +import { kvListURL } from "../pageLinks"; + type SecretTitleElementProps = { type: string; - mount: string; + baseMount: string; + secretPath?: string[]; item?: string; suffix?: string; }; export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element { const type = props.type; - const mount = props.mount; const item = props.item || ""; const suffix = props.suffix || ""; - - const baseMount = mount.split("/")[0]; - const restOfMount = mount.split("/"); - restOfMount.shift(); + const baseMount = props.baseMount; + const secretPath = props.secretPath || []; return (

- diff --git a/src/ui/pages/Secrets/TOTP/TOTPDelete.tsx b/src/ui/pages/Secrets/TOTP/TOTPDelete.tsx index 06dfbae..9b4c542 100644 --- a/src/ui/pages/Secrets/TOTP/TOTPDelete.tsx +++ b/src/ui/pages/Secrets/TOTP/TOTPDelete.tsx @@ -5,35 +5,34 @@ import { Component, render } from "preact"; import i18next from "i18next"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { route } from "preact-router"; +import { totpListURL } from "../../pageLinks"; export class TOTPDelete extends Component { render() { return ( -
-
{i18next.t("totp_delete_text")}
- -
+ <> + +
+
{i18next.t("totp_delete_text")}
+ +
+ + ); } - - //async renderPageTitle(): Promise { - // render( - // , - // this.router.pageTitleElement, - // ); - //} - - get name(): string { - return i18next.t("totp_delete_title"); - } } diff --git a/src/ui/pages/Secrets/TOTP/TOTPNew.tsx b/src/ui/pages/Secrets/TOTP/TOTPNew.tsx index 0ef2a69..3f77b68 100644 --- a/src/ui/pages/Secrets/TOTP/TOTPNew.tsx +++ b/src/ui/pages/Secrets/TOTP/TOTPNew.tsx @@ -21,7 +21,7 @@ function removeDashSpaces(str: string): string { return str; } -export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean }> { +export class TOTPNewForm extends Component<{ baseMount: string }, { qrMode: boolean }> { constructor() { super(); this.state = { @@ -40,8 +40,8 @@ export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean }; try { - await addNewTOTP(this.props.mount, parms); - route("/secrets/totp/list/" + this.props.mount) + await addNewTOTP(this.props.baseMount, parms); + route("/secrets/totp/list/" + this.props.baseMount) } catch (e: unknown) { const error = e as Error; setErrorText(`API Error: ${error.message}`); @@ -121,23 +121,12 @@ export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean export class TOTPNew extends Component { render() { - let mount = this.props.matches["mount"]; + const baseMount = this.props.matches["baseMount"]; return ( <> - - + + ); } - - //async renderPageTitle(): Promise { - // render( - // , - // this.router.pageTitleElement, - // ); - //} - - get name(): string { - return i18next.t("totp_new_title"); - } } diff --git a/src/ui/pages/Secrets/TOTP/TOTPView.tsx b/src/ui/pages/Secrets/TOTP/TOTPView.tsx index 49d834f..44b8c09 100644 --- a/src/ui/pages/Secrets/TOTP/TOTPView.tsx +++ b/src/ui/pages/Secrets/TOTP/TOTPView.tsx @@ -13,9 +13,10 @@ import { setErrorText } from "../../../../pageUtils"; import i18next from "i18next"; import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { route } from "preact-router"; +import { delSecretsEngineURL, totpNewURL } from "../../pageLinks"; type TOTPGridItemProps = { - mount: string; totpKey: string; canDelete: boolean; + baseMount: string; totpKey: string; canDelete: boolean; } export class RefreshingTOTPGridItem extends Component< @@ -29,7 +30,7 @@ export class RefreshingTOTPGridItem extends Component< timer: unknown; updateTOTPCode(): void { - void getTOTPCode(this.props.mount, this.props.totpKey).then((code) => { + void getTOTPCode(this.props.baseMount, this.props.totpKey).then((code) => { this.setState({ totpValue: code }); }); } @@ -56,7 +57,7 @@ export class RefreshingTOTPGridItem extends Component< - - , - this.router.pageContentElement, + render() { + const baseMount = this.props.matches["baseMount"]; + return ( + <> + +
{ + await this.onSubmit(data); + }} + > + + + + + + +

+ + + +

+ + ); } async onSubmit(data: FormData): Promise { + const baseMount = this.props.matches["baseMount"]; + const name = data.get("name") as string; const type = data.get("type") as string; try { - await newTransitKey(this.state.baseMount, { + await newTransitKey(baseMount, { name: name, type: type, }); - this.state.secretItem = name; - await this.router.changePage("TRANSIT_VIEW_SECRET"); + route(transitViewSecretURL(baseMount, name)) } catch (e) { const error = e as Error; setErrorText(error.message); } } - - //async renderPageTitle(): Promise { - // render( - // , - // this.router.pageTitleElement, - // ); - //} - - get name(): string { - return i18next.t("transit_new_key_title"); - } } diff --git a/src/ui/pages/Secrets/Transit/TransitView.tsx b/src/ui/pages/Secrets/Transit/TransitView.tsx index a4e0b2a..25f83a8 100644 --- a/src/ui/pages/Secrets/Transit/TransitView.tsx +++ b/src/ui/pages/Secrets/Transit/TransitView.tsx @@ -1,16 +1,18 @@ import { Component, JSX, render } from "preact"; -import { Page } from "../../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; -import { getCapabilitiesPath } from "../../../../api/sys/getCapabilities"; +import { CapabilitiesType, getCapabilitiesPath } from "../../../../api/sys/getCapabilities"; import { getTransitKeys } from "../../../../api/transit/getTransitKeys"; import i18next from "i18next"; +import { route } from "preact-router"; +import { DefaultPageProps } from "../../../../types/DefaultPageProps"; +import { delSecretsEngineURL, transitNewSecretURL, transitViewSecretURL } from "../../pageLinks"; type TransitViewListState = { contentLoaded: boolean; transitKeysList: string[]; }; -export class TransitViewListItem extends Component<{ page: Page }, TransitViewListState> { +export class TransitViewListItem extends Component<{ baseMount: string }, TransitViewListState> { constructor() { super(); this.state = { @@ -22,7 +24,7 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi timer: unknown; getTransitKeys(): void { - void getTransitKeys(this.props.page.state.baseMount) + void getTransitKeys(this.props.baseMount) .then((keys) => { this.setState({ contentLoaded: true, @@ -56,8 +58,7 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi
  • { - this.props.page.state.secretItem = key; - await this.props.page.router.changePage("TRANSIT_VIEW_SECRET"); + route(transitViewSecretURL(this.props.baseMount, key)) }} > {key} @@ -69,31 +70,32 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi } } -export class TransitViewPage extends Page { - constructor() { - super(); +export class TransitView extends Component { + async componentDidMount() { + const baseMount = this.props.matches["baseMount"]; + const mountsPath = "/sys/mounts/" + baseMount; + + const caps = await getCapabilitiesPath([mountsPath, baseMount]); + this.setState({caps}) } - async goBack(): Promise { - await this.router.changePage("SECRETS_HOME"); - } + render() { + if (!this.state.caps) return; + const baseMount = this.props.matches["baseMount"]; + const mountsPath = "/sys/mounts/" + baseMount; + const mountCaps = this.state.caps[mountsPath]; + const transitCaps = this.state.caps[baseMount]; - async render(): Promise { - this.state.secretItem = ""; - - const mountsPath = "/sys/mounts/" + this.state.baseMount; - const caps = await getCapabilitiesPath([mountsPath, this.state.baseMount]); - const mountCaps = caps[mountsPath]; - const transitCaps = caps[this.state.baseMount]; - - render( + return ( <> + +

    {transitCaps.includes("create") && ( )}

    - - , - this.router.pageContentElement, + + ); } - - //async renderPageTitle(): Promise { - // render(, this.router.pageTitleElement); - //} - - get name(): string { - return i18next.t("transit_view_title"); - } } diff --git a/src/ui/pages/Secrets/Transit/TransitViewSecret.tsx b/src/ui/pages/Secrets/Transit/TransitViewSecret.tsx index 266a8bc..cdcf978 100644 --- a/src/ui/pages/Secrets/Transit/TransitViewSecret.tsx +++ b/src/ui/pages/Secrets/Transit/TransitViewSecret.tsx @@ -3,22 +3,27 @@ import { Page } from "../../../../types/Page"; import { SecretTitleElement } from "../SecretTitleElement"; import { Tile } from "../../../elements/Tile"; import { getTransitKey } from "../../../../api/transit/getTransitKey"; -import { render } from "preact"; +import { Component, render } from "preact"; import i18next from "i18next"; +import { DefaultPageProps } from "../../../../types/DefaultPageProps"; +import { TransitKeyType } from "../../../../api/types/transit"; -export class TransitViewSecretPage extends Page { - constructor() { - super(); +export class TransitViewSecret extends Component { + async componentDidMount() { + const baseMount = this.props.matches["baseMount"]; + const secretItem = this.props.matches["secretItem"]; + const transitKey = await getTransitKey(baseMount, secretItem); + this.setState({transitKey}); } - async goBack(): Promise { - await this.router.changePage("TRANSIT_VIEW"); - } + render() { + if (!this.state.transitKey) return; + const baseMount = this.props.matches["baseMount"]; + const secretItem = this.props.matches["secretItem"]; + const transitKey = this.state.transitKey; + - async render(): Promise { - const transitKey = await getTransitKey(this.state.baseMount, this.state.secretItem); - - render( + return ( {transitKey.supports_encryption && ( await this.router.changePage("TRANSIT_ENCRYPT")} + onclick={async () => {} /*await this.router.changePage("TRANSIT_ENCRYPT")*/} /> )} {transitKey.supports_decryption && ( @@ -35,7 +40,7 @@ export class TransitViewSecretPage extends Page { description={i18next.t("transit_view_decrypt_description")} icon="mail" iconText={i18next.t("transit_view_decrypt_icon_text")} - onclick={async () => await this.router.changePage("TRANSIT_DECRYPT")} + onclick={async () => {} /*await this.router.changePage("TRANSIT_DECRYPT")*/} /> )} {transitKey.supports_decryption && ( @@ -44,19 +49,10 @@ export class TransitViewSecretPage extends Page { description={i18next.t("transit_view_rewrap_description")} icon="code" iconText={i18next.t("transit_view_rewrap_icon_text")} - onclick={async () => await this.router.changePage("TRANSIT_REWRAP")} + onclick={async () => {} /*await this.router.changePage("TRANSIT_REWRAP")*/} /> )} - , - this.router.pageContentElement, + ); } - - //async renderPageTitle(): Promise { - // render(, this.router.pageTitleElement); - //} - - get name(): string { - return i18next.t("transit_view_secret_title"); - } } diff --git a/src/ui/pages/pageLinks.tsx b/src/ui/pages/pageLinks.tsx new file mode 100644 index 0000000..34fb319 --- /dev/null +++ b/src/ui/pages/pageLinks.tsx @@ -0,0 +1,55 @@ +// Delete Secret Engine + +export function delSecretsEngineURL(baseMount: string): string { + return `/secrets/delete_engine/${baseMount}`; +} + +// Secrets / Key Value + +export function kvNewURL(baseMount: string, secretPath?: string[]): string { + return `/secrets/kv/new/${baseMount}` + secretPath ? `/${secretPath.join("/")}` : ""; +} + +export function kvDeleteURL(baseMount: string, secretPath: string[], secret: string): string { + return `/secrets/kv/delete/${secret}/${baseMount}/${secretPath.join("/")}`; +} + +export function kvEditURL(baseMount: string, secretPath: string[], secret: string): string { + return `/secrets/kv/edit/${secret}/${baseMount}/${secretPath.join("/")}`; +} + +export function kvViewURL(baseMount: string, secretPath: string[], secret: string): string { + return `/secrets/kv/view/${secret}/${baseMount}/${secretPath.join("/")}`; +} + +export function kvListURL(baseMount: string, secretPath: string[]): string { + console.log(baseMount, secretPath); + return `/secrets/kv/list/${baseMount}/${secretPath.join("/")}`; +} + +// Secrets / TOTP + +export function totpNewURL(baseMount: string): string { + return `/secrets/totp/new/${baseMount}`; +} + +export function totpListURL(baseMount: string): string { + return `/secrets/totp/list/${baseMount}`; +} + +export function totpDeleteURL(baseMount: string, secret: string): string { + return `/secrets/totp/delete/${baseMount}/${secret}`; +} + +// Secrets / Transit + +export function transitNewSecretURL(baseMount: string): string { + return `/secrets/transit/new/${baseMount}`; +} + +export function transitViewSecretURL(baseMount: string, secret: string): string { + return `/secrets/transit/view/${baseMount}/${secret}`; +} + + +//