1
0
Fork 0

add some more pages and page links better

This commit is contained in:
ChaotiCryptidz 2022-01-06 22:57:12 +00:00
parent c62cd89771
commit ff8a2995cb
19 changed files with 337 additions and 331 deletions

View file

@ -12,9 +12,9 @@ export async function deleteSecret(
const mountInfo = await getMount(baseMount); const mountInfo = await getMount(baseMount);
if (mountInfo.options.version == "2") { if (mountInfo.options.version == "2") {
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`; secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}/${name}`;
} else { } else {
secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`; secretURL = `/v1/${baseMount}/${secretPath.join("/")}/${name}`;
} }
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
request = new Request(appendAPIURL(secretURL), { request = new Request(appendAPIURL(secretURL), {

View file

@ -10,10 +10,10 @@ export async function getSecrets(
let secretMountType = "kv-v2" let secretMountType = "kv-v2"
if (secretMountType == "kv-v2") { if (secretMountType == "kv-v2") {
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}?list=true`; secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}?list=true`;
} else { } else {
// cubbyhole and v1 are identical // 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), { const request = new Request(appendAPIURL(secretURL), {
headers: getHeaders(), headers: getHeaders(),

View file

@ -52,6 +52,9 @@ import { KeyValueView } from "./ui/pages/Secrets/KeyValue/KeyValueView";
import { KeyValueSecret } from "./ui/pages/Secrets/KeyValue/KeyValueSecret"; import { KeyValueSecret } from "./ui/pages/Secrets/KeyValue/KeyValueSecret";
import { KeyValueSecretEdit } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit"; import { KeyValueSecretEdit } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit";
import { KeyValueDelete } from "./ui/pages/Secrets/KeyValue/KeyValueDelete"; 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<void> { async function onLoad(): Promise<void> {
const Main = () => ( const Main = () => (
@ -72,19 +75,25 @@ async function onLoad(): Promise<void> {
<NewTOTPEngine path="/secrets/new_secrets_engine/totp" /> <NewTOTPEngine path="/secrets/new_secrets_engine/totp" />
<NewTransitEngine path="/secrets/new_secrets_engine/trasit" /> <NewTransitEngine path="/secrets/new_secrets_engine/trasit" />
<TOTPView path="/secrets/totp/list/:mount" state={pageState} /> <KeyValueView path="/secrets/kv/list/:baseMount/:secretPath*?" state={pageState} />
<TOTPNew path="/secrets/totp/new/:mount" state={pageState} /> <KeyValueSecret path="/secrets/kv/view/:item/:baseMount/:secretPath+" state={pageState} />
<TOTPDelete path="/secrets/totp/delete/:mount/:item" state={pageState} /> <KeyValueSecretEdit path="/secrets/kv/edit/:item/:baseMount/:secretPath+" state={pageState} />
<KeyValueDelete path="/secrets/kv/delete/:item/:baseMount/:secretPath+" state={pageState} />
<TOTPView path="/secrets/totp/list/:baseMount" state={pageState} />
<TOTPNew path="/secrets/totp/new/:baseMount" state={pageState} />
<TOTPDelete path="/secrets/totp/delete/:baseMount/:item" state={pageState} />
<TransitView path="/secrets/transit/list/:baseMount" state={pageState} />
<TransitViewSecret path="/secrets/transit/view/:baseMount/:secretItem" state={pageState} />
<NewTransitKey path="/secrets/transit/new/:baseMount" state={pageState} />
<KeyValueView path="/secrets/kv/list/:mount+" state={pageState} />
<KeyValueSecret path="/secrets/kv/view/:item/:mount+" state={pageState} />
<KeyValueSecretEdit path="/secrets/kv/edit/:item/:mount+" state={pageState} />
<KeyValueDelete path="/secrets/kv/delete/:item/:mount+" state={pageState} />
<div default><p>PAGE NOT YET IMPLEMENTED</p></div> <div default><p>PAGE NOT YET IMPLEMENTED</p></div>
</Router> </Router>
); );
render( render(
<> <>
<NavBar /> <NavBar />

View file

@ -4,19 +4,22 @@ import { deleteSecret } from "../../../../api/kv/deleteSecret";
import { Component, render } from "preact"; import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { splitKVMount } from "./splitKVMount";
export class KeyValueDelete extends Component<DefaultPageProps> { export class KeyValueDelete extends Component<DefaultPageProps> {
render() { render() {
const mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
const mountSplit = splitKVMount(mount); const secretPath = this.props.matches["secretPath"].split("/");
const baseMount = mountSplit.baseMount;
const secretPath = mountSplit.restOfMount;
const item = this.props.matches["item"]; const item = this.props.matches["item"];
return ( return (
<> <>
<SecretTitleElement type="kv" item={this.props.matches["item"]} mount={mount} suffix={i18next.t("kv_sec_edit_suffix")} /> <SecretTitleElement
type="kv"
baseMount={baseMount}
secretPath={secretPath}
item={item}
suffix={i18next.t("kv_sec_edit_suffix")}
/>
<div> <div>
<h5>{i18next.t("kv_delete_text")}</h5> <h5>{i18next.t("kv_delete_text")}</h5>
<button <button
@ -24,7 +27,7 @@ export class KeyValueDelete extends Component<DefaultPageProps> {
onClick={async () => { onClick={async () => {
await deleteSecret( await deleteSecret(
baseMount, baseMount,
secretPath.map((e) => e + "/"), secretPath,
item, item,
); );
window.history.back(); window.history.back();

View file

@ -1,25 +1,29 @@
import { Form } from "../../../elements/Form"; import { Form } from "../../../elements/Form";
import { Margin } from "../../../elements/Margin"; import { Margin } from "../../../elements/Margin";
import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { createOrUpdateSecret } from "../../../../api/kv/createOrUpdateSecret"; import { createOrUpdateSecret } from "../../../../api/kv/createOrUpdateSecret";
import { render } from "preact"; import { Component, render } from "preact";
import { setErrorText } from "../../../../pageUtils"; import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router";
import { kvViewURL } from "../../pageLinks";
export class KeyValueNewPage extends Page { export class KeyValueNewPage extends Component<DefaultPageProps> {
constructor() { render(){
super(); const baseMount = this.props.matches["baseMount"];
} const secretPath = (this.props.matches["secretPath"] || "").split("/");
async goBack(): Promise<void> { return(
await this.router.changePage("KEY_VALUE_VIEW"); <>
} <SecretTitleElement
type="kv"
async render(): Promise<void> { baseMount={baseMount}
render( secretPath={secretPath}
suffix={i18next.t("kv_sec_edit_suffix")}
/>
<div> <div>
<Form onSubmit={async (formData) => await this.newKVSecretHandleForm(formData)}> <Form onSubmit={async (formData) => await this.newKVSecretHandleForm(formData, baseMount, secretPath)}>
<Margin> <Margin>
<input <input
class="uk-input uk-form-width-medium" class="uk-input uk-form-width-medium"
@ -33,41 +37,28 @@ export class KeyValueNewPage extends Page {
{i18next.t("kv_new_create_btn")} {i18next.t("kv_new_create_btn")}
</button> </button>
</Form> </Form>
</div>, </div>
this.router.pageContentElement, </>
); );
} }
async newKVSecretHandleForm(formData: FormData): Promise<void> { async newKVSecretHandleForm(formData: FormData, baseMount: string, secretPath: string[]): Promise<void> {
const path = formData.get("path") as string; const path = formData.get("path") as string;
let keyData = {};
if (["kv-v1", "cubbyhole"].includes(this.state.secretMountType)) { // TODO: check only do this on kv v1
keyData = { key: "value" }; let keyData = { "key": "value" };
}
try { try {
await createOrUpdateSecret( await createOrUpdateSecret(
this.state.baseMount, baseMount,
this.state.secretPath, secretPath,
path, path,
keyData, keyData,
); );
await this.router.changePage("KEY_VALUE_VIEW"); route(kvViewURL(baseMount, secretPath, path))
} catch (e: unknown) { } catch (e: unknown) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);
} }
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("kv_new_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string {
return i18next.t("kv_new_title");
}
} }

View file

@ -2,17 +2,14 @@ import { CodeBlock } from "../../../elements/CodeBlock";
import { Component, JSX, render } from "preact"; import { Component, JSX, render } from "preact";
import { CopyableInputBox } from "../../../elements/CopyableInputBox"; import { CopyableInputBox } from "../../../elements/CopyableInputBox";
import { Grid, GridSizes } from "../../../elements/Grid"; import { Grid, GridSizes } from "../../../elements/Grid";
import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { getCapabilities } from "../../../../api/sys/getCapabilities"; import { getCapabilities } from "../../../../api/sys/getCapabilities";
import { getSecret } from "../../../../api/kv/getSecret"; import { getSecret } from "../../../../api/kv/getSecret";
import { sortedObjectMap } from "../../../../utils"; import { sortedObjectMap } from "../../../../utils";
import { undeleteSecret } from "../../../../api/kv/undeleteSecret";
import i18next from "i18next"; import i18next from "i18next";
import { splitKVMount } from "./splitKVMount";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { getMount } from "../../../../api/sys/getMounts";
import { route } from "preact-router"; import { route } from "preact-router";
import { kvDeleteURL, kvEditURL } from "../../pageLinks";
export type KVSecretViewProps = { export type KVSecretViewProps = {
kvData: Record<string, unknown>; kvData: Record<string, unknown>;
@ -55,10 +52,8 @@ type KeyValueSecretState = {
export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretState> { export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretState> {
async componentDidMount() { async componentDidMount() {
const mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
const mountSplit = splitKVMount(mount); const secretPath = this.props.matches["secretPath"].split("/");
const baseMount = mountSplit.baseMount;
const secretPath = mountSplit.restOfMount;
const secretItem = this.props.matches["item"]; const secretItem = this.props.matches["item"];
const caps = ( const caps = (
@ -86,22 +81,23 @@ export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretSt
render() { render() {
if (!this.state.baseMount) return; if (!this.state.baseMount) return;
// TODO: maybe add some sort of version viewing
// no idea what gonna do for that
return ( return (
<> <>
<SecretTitleElement type="kv" item={this.props.matches["item"]} mount={this.props.matches["mount"]} suffix={i18next.t("kv_sec_edit_suffix")} /> <SecretTitleElement
type="kv"
item={this.props.matches["item"]}
baseMount={this.state.baseMount}
secretPath={this.state.secretPath}
suffix={i18next.t("kv_sec_edit_suffix")}
/>
<div> <div>
<p id="buttonsBlock"> <p id="buttonsBlock">
{ {
// Delete Button
this.state.caps.includes("delete") && ( this.state.caps.includes("delete") && (
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
route("/secrets/kv/delete/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/")) route(kvDeleteURL(this.state.baseMount, this.state.secretPath, this.state.secretItem))
//await this.router.changePage("KEY_VALUE_DELETE");
}} }}
> >
{i18next.t("kv_secret_delete_btn")} {i18next.t("kv_secret_delete_btn")}
@ -112,7 +108,7 @@ export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretSt
<button <button
class="uk-button uk-button-primary" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
route("/secrets/kv/edit/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/")) route(kvEditURL(this.state.baseMount, this.state.secretPath, this.state.secretItem))
}} }}
> >
{i18next.t("kv_secret_edit_btn")} {i18next.t("kv_secret_edit_btn")}
@ -125,14 +121,5 @@ export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretSt
</> </>
); );
}
//async renderPageTitle(): Promise<void> {
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
//}
get name(): string {
return i18next.t("kv_secret_title");
} }
} }

View file

@ -8,9 +8,6 @@ import { setErrorText } from "../../../../pageUtils";
import { sortedObjectMap, verifyJSONString } from "../../../../utils"; import { sortedObjectMap, verifyJSONString } from "../../../../utils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { splitKVMount } from "./splitKVMount";
import { route } from "preact-router";
//import { highlightElement } from "prismjs";
export type KVEditProps = { export type KVEditProps = {
baseMount: string; baseMount: string;
@ -109,28 +106,21 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
export class KeyValueSecretEdit extends Component<DefaultPageProps> { export class KeyValueSecretEdit extends Component<DefaultPageProps> {
render() { render() {
const mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
const mountSplit = splitKVMount(mount); const secretPath = this.props.matches["secretPath"].split("/");
const baseMount = mountSplit.baseMount;
const restOfMount = mountSplit.restOfMount;
const item = this.props.matches["item"]; const item = this.props.matches["item"];
return ( return (
<> <>
<SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} suffix={i18next.t("kv_sec_edit_suffix")} /> <SecretTitleElement
<KVEditor baseMount={baseMount} secretPath={restOfMount} secretItem={item} /> type="kv"
baseMount={baseMount}
secretPath={secretPath}
item={this.props.matches["item"]}
suffix={i18next.t("kv_sec_edit_suffix")}
/>
<KVEditor baseMount={baseMount} secretPath={secretPath} secretItem={item} />
</> </>
); );
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("kv_sec_edit_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string {
return i18next.t("kv_sec_edit_title");
}
} }

View file

@ -8,7 +8,8 @@ import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router"; import { route } from "preact-router";
import { splitKVMount } from "./splitKVMount"; import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks";
import { getMount } from "../../../../api/sys/getMounts";
export type KVKeysListProps = DefaultPageProps & { export type KVKeysListProps = DefaultPageProps & {
@ -22,15 +23,16 @@ type KVKeysListState = {
searchQuery: string; searchQuery: string;
}; };
function SecretsList(baseMount: string, restOfMount: string[], secrets: string[]): JSX.Element[] { function SecretsList(baseMount: string, secretPath: string[], secrets: string[]): JSX.Element[] {
return secrets.map((secret) => ( return secrets.map((secret) => (
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
console.log(baseMount, secretPath, secret)
if (secret.endsWith("/")) { if (secret.endsWith("/")) {
route("/secrets/kv/list/" + baseMount + "/" + restOfMount.join("/") + "/" + secret) route(kvListURL(baseMount, [...secretPath, secret.replace("/", "")].filter((e)=>e.length > 0)))
} else { } else {
route("/secrets/kv/view/" + secret + "/" + baseMount + "/" + restOfMount.join("/")) route(kvViewURL(baseMount, secretPath, secret))
} }
}} }}
> >
@ -54,7 +56,7 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
try { try {
const keys = await getSecrets( const keys = await getSecrets(
this.props.baseMount, this.props.baseMount,
this.props.secretPath.map((e) => e + "/"), this.props.secretPath,
); );
this.setState({ this.setState({
dataLoaded: true, dataLoaded: true,
@ -118,7 +120,6 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
this.setState({ this.setState({
searchQuery: (this.searchBarRef.current as unknown as HTMLInputElement).value, searchQuery: (this.searchBarRef.current as unknown as HTMLInputElement).value,
}); });
console.log("meow");
}} }}
/> />
<br /> <br />
@ -140,60 +141,65 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
type KeyValueViewState = { type KeyValueViewState = {
pathCaps: string[]; pathCaps: string[];
mountCaps: string[]; mountCaps: string[];
mountType: string;
} }
export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> { export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> {
async componentDidMount() { async componentDidMount() {
const mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
const mountSplit = splitKVMount(mount); const secretPath = this.props.matches["secretPath"].split("/");
const baseMount = mountSplit.baseMount;
const restOfMount = mountSplit.restOfMount;
const mountsPath = "/sys/mounts/" + baseMount; const mountsPath = "/sys/mounts/" + baseMount;
const currentPath = baseMount + restOfMount.join(); const currentPath = baseMount + secretPath.join();
const caps = await getCapabilitiesPath([mountsPath, currentPath]); const caps = await getCapabilitiesPath([mountsPath, currentPath]);
const mount = await getMount(baseMount);
this.setState({ this.setState({
mountCaps: caps[mountsPath], mountCaps: caps[mountsPath],
pathCaps: caps[currentPath], pathCaps: caps[currentPath],
mountType: mount.type,
}); });
} }
render() { render() {
if (!this.state.pathCaps) return; if (!this.state.pathCaps) return;
const mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
const mountSplit = splitKVMount(mount); const secretPath = this.props.matches["secretPath"].split("/");
const baseMount = mountSplit.baseMount;
const restOfMount = mountSplit.restOfMount;
return ( return (
<> <>
<SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} /> <SecretTitleElement
type="kv"
baseMount={baseMount}
secretPath={secretPath}
item={this.props.matches["item"]}
/>
<p> <p>
{this.state.pathCaps.includes("create") && ( {this.state.pathCaps.includes("create") && (
<button <button
class="uk-button uk-button-primary" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
route("/secrets/kv/new/" + mount) route(kvNewURL(baseMount, secretPath.length > 0 ? secretPath : null))
}} }}
> >
{i18next.t("kv_view_new_btn")} {i18next.t("kv_view_new_btn")}
</button> </button>
)} )}
{restOfMount.length == 0 && this.state.mountCaps.includes("delete") && ( {secretPath.length == 0 && this.state.mountCaps.includes("delete") && (
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
route("/secrets/delete_engine/" + mount) route(delSecretsEngineURL(baseMount))
}} }}
> >
{i18next.t("kv_view_delete_btn")} {i18next.t("kv_view_delete_btn")}
</button> </button>
)} )}
</p> </p>
{//"TODO: " == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>} {this.state.mountType == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>}
} <KVKeysList baseMount={baseMount} secretPath={secretPath} state={this.props.state} />
<KVKeysList baseMount={baseMount} secretPath={restOfMount} state={this.props.state} />
</> </>
); );
} }

View file

@ -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
};
}

View file

@ -8,6 +8,7 @@ import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { PageTitle } from "../../../elements/PageTitle"; import { PageTitle } from "../../../elements/PageTitle";
import { route } from "preact-router"; import { route } from "preact-router";
import { kvListURL } from "../../pageLinks";
export class NewKVEngine extends Component { export class NewKVEngine extends Component {
render() { render() {
@ -57,7 +58,7 @@ export class NewKVEngine extends Component {
version: version, version: version,
}, },
}); });
route("/secrets/kv/list/" + name + "/") route(kvListURL(name, []));
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);

View file

@ -8,6 +8,7 @@ import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router"; import { route } from "preact-router";
import { PageTitle } from "../../../elements/PageTitle"; import { PageTitle } from "../../../elements/PageTitle";
import { totpListURL } from "../../pageLinks";
export class NewTOTPEngine extends Component { export class NewTOTPEngine extends Component {
render() { render() {
@ -43,7 +44,7 @@ export class NewTOTPEngine extends Component {
name: name, name: name,
type: "totp", type: "totp",
}); });
route("/secrets/totp/list/" + name + "/") route(totpListURL(name));
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);

View file

@ -1,29 +1,28 @@
import { route } from "preact-router"; import { route } from "preact-router";
import { JSX } from "preact/jsx-runtime"; import { JSX } from "preact/jsx-runtime";
import { Page } from "../../../types/Page"; import { kvListURL } from "../pageLinks";
type SecretTitleElementProps = { type SecretTitleElementProps = {
type: string; type: string;
mount: string; baseMount: string;
secretPath?: string[];
item?: string; item?: string;
suffix?: string; suffix?: string;
}; };
export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element { export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element {
const type = props.type; const type = props.type;
const mount = props.mount;
const item = props.item || ""; const item = props.item || "";
const suffix = props.suffix || ""; const suffix = props.suffix || "";
const baseMount = props.baseMount;
const baseMount = mount.split("/")[0]; const secretPath = props.secretPath || [];
const restOfMount = mount.split("/");
restOfMount.shift();
return ( return (
<h3 class="uk-card-title" id="pageTitle"> <h3 class="uk-card-title" id="pageTitle">
<div> <div>
<a <a
test-data="secrets-title-first-slash"
onClick={async () => { onClick={async () => {
route("/secrets"); route("/secrets");
}} }}
@ -31,22 +30,25 @@ export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element
{"/ "} {"/ "}
</a> </a>
<a href={"/secrets/" + type + "/list/" + baseMount + "/"}> <a href={"/secrets/" + type + "/list/" + baseMount + "/"} test-data="secrets-title-baseMount">
{baseMount + " "} {baseMount + "/ "}
</a> </a>
{...restOfMount.map((secretPath, index, secretPaths) => ( {...secretPath.map((secretPath, index, secretPaths) => {
<a // TODO: find where a '' is returned so dont need this
if (secretPath.length < 1) return;
return <a test-data="secrets-title-secretPath"
onClick={async () => { onClick={async () => {
if (type == "kv") { if (type == "kv") {
let secretPath = secretPaths.slice(0, index + 1); let secretPath = secretPaths.slice(0, index + 1);
route("/secrets/kv/list/" + baseMount + "/" + secretPath.join("/")) route(kvListURL(baseMount, secretPath))
} }
}} }}
> >
{secretPath + "/" + " "} {secretPath + "/" + " "}
</a> </a>
))} })}
{item.length != 0 && <span>{item}</span>} {item.length != 0 && <span>{item}</span>}
{suffix.length != 0 && <span>{suffix}</span>} {suffix.length != 0 && <span>{suffix}</span>}
</div> </div>

View file

@ -5,35 +5,34 @@ import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router"; import { route } from "preact-router";
import { totpListURL } from "../../pageLinks";
export class TOTPDelete extends Component<DefaultPageProps> { export class TOTPDelete extends Component<DefaultPageProps> {
render() { render() {
return ( return (
<>
<SecretTitleElement
type="totp"
baseMount={this.props.matches["baseMount"]}
item={this.props.matches["item"]}
suffix={i18next.t("totp_delete_suffix")}
/>
<div> <div>
<h5>{i18next.t("totp_delete_text")}</h5> <h5>{i18next.t("totp_delete_text")}</h5>
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
let mount = this.props.matches["mount"]; let baseMount = this.props.matches["baseMount"];
let item = this.props.matches["item"]; let item = this.props.matches["item"];
await deleteTOTP(mount, item); await deleteTOTP(baseMount, item);
route("/secrets/totp/list/" + mount) route(totpListURL(baseMount));
}} }}
> >
{i18next.t("kv_delete_btn")} {i18next.t("kv_delete_btn")}
</button> </button>
</div> </div>
</>
); );
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("totp_delete_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string {
return i18next.t("totp_delete_title");
}
} }

View file

@ -21,7 +21,7 @@ function removeDashSpaces(str: string): string {
return str; return str;
} }
export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean }> { export class TOTPNewForm extends Component<{ baseMount: string }, { qrMode: boolean }> {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -40,8 +40,8 @@ export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean
}; };
try { try {
await addNewTOTP(this.props.mount, parms); await addNewTOTP(this.props.baseMount, parms);
route("/secrets/totp/list/" + this.props.mount) route("/secrets/totp/list/" + this.props.baseMount)
} catch (e: unknown) { } catch (e: unknown) {
const error = e as Error; const error = e as Error;
setErrorText(`API Error: ${error.message}`); setErrorText(`API Error: ${error.message}`);
@ -121,23 +121,12 @@ export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean
export class TOTPNew extends Component<DefaultPageProps> { export class TOTPNew extends Component<DefaultPageProps> {
render() { render() {
let mount = this.props.matches["mount"]; const baseMount = this.props.matches["baseMount"];
return ( return (
<> <>
<SecretTitleElement type="totp" mount={mount} suffix={i18next.t("totp_new_suffix")} /> <SecretTitleElement type="totp" baseMount={baseMount} suffix={i18next.t("totp_new_suffix")} />
<TOTPNewForm mount={this.props.matches["mount"]} /> <TOTPNewForm baseMount={baseMount} />
</> </>
); );
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("totp_new_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string {
return i18next.t("totp_new_title");
}
} }

View file

@ -13,9 +13,10 @@ import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps"; import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router"; import { route } from "preact-router";
import { delSecretsEngineURL, totpNewURL } from "../../pageLinks";
type TOTPGridItemProps = { type TOTPGridItemProps = {
mount: string; totpKey: string; canDelete: boolean; baseMount: string; totpKey: string; canDelete: boolean;
} }
export class RefreshingTOTPGridItem extends Component< export class RefreshingTOTPGridItem extends Component<
@ -29,7 +30,7 @@ export class RefreshingTOTPGridItem extends Component<
timer: unknown; timer: unknown;
updateTOTPCode(): void { 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 }); this.setState({ totpValue: code });
}); });
} }
@ -56,7 +57,7 @@ export class RefreshingTOTPGridItem extends Component<
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
route("/secrets/totp/delete/" + this.props.mount + "/" + this.props.totpKey); route("/secrets/totp/delete/" + this.props.baseMount + "/" + this.props.totpKey);
}} }}
> >
{i18next.t("totp_view_secret_delete_btn")} {i18next.t("totp_view_secret_delete_btn")}
@ -69,15 +70,12 @@ export class RefreshingTOTPGridItem extends Component<
} }
} }
type TOTPViewProps = DefaultPageProps & {
}
type TOTPViewState = { type TOTPViewState = {
capabilities?: CapabilitiesType; capabilities?: CapabilitiesType;
totpItems: TOTPGridItemProps[]; totpItems: TOTPGridItemProps[];
} }
export class TOTPView extends Component<TOTPViewProps, TOTPViewState> { export class TOTPView extends Component<DefaultPageProps, TOTPViewState> {
constructor() { constructor() {
super(); super();
this.refresher = undefined; this.refresher = undefined;
@ -87,22 +85,22 @@ export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
refresher: number; refresher: number;
async componentDidMount() { async componentDidMount() {
var mount = this.props.matches["mount"]; var baseMount = this.props.matches["baseMount"];
const mountsPath = "/sys/mounts/" + mount; const mountsPath = "/sys/mounts/" + baseMount;
const caps = await getCapabilitiesPath([mountsPath, mount]); const caps = await getCapabilitiesPath([mountsPath, baseMount]);
let totpItems: TOTPGridItemProps[] = []; let totpItems: TOTPGridItemProps[] = [];
// TODO: tidy this up i guess // TODO: tidy this up i guess
try { try {
totpItems = await Promise.all( totpItems = await Promise.all(
Array.from(await getTOTPKeys(mount)).map(async (key) => { Array.from(await getTOTPKeys(baseMount)).map(async (key) => {
const totpCaps = await getCapsPath( const totpCaps = await getCapsPath(
removeDoubleSlash(mount + "code/" + key), removeDoubleSlash(baseMount + "/code/" + key),
); );
if (totpCaps.includes("read")) { if (totpCaps.includes("read")) {
return { return {
mount: mount, baseMount: baseMount,
totpKey: key, totpKey: key,
canDelete: totpCaps.includes("delete"), canDelete: totpCaps.includes("delete"),
}; };
@ -124,22 +122,22 @@ export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
render() { render() {
if (!this.state.capabilities) return; if (!this.state.capabilities) return;
var mount = this.props.matches["mount"]; var baseMount = this.props.matches["baseMount"];
const mountsPath = "/sys/mounts/" + mount; const mountsPath = "/sys/mounts/" + baseMount;
const mountCaps = this.state.capabilities[mountsPath]; const mountCaps = this.state.capabilities[mountsPath];
const totpCaps = this.state.capabilities[mount]; const totpCaps = this.state.capabilities[baseMount];
return ( return (
<> <>
<SecretTitleElement type="totp" mount={mount} /> <SecretTitleElement type="totp" baseMount={baseMount} />
<div> <div>
<p> <p>
{totpCaps.includes("create") && ( {totpCaps.includes("create") && (
<button <button
class="uk-button uk-button-primary" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
route("/secrets/totp/new/" + mount) route(totpNewURL(baseMount))
}} }}
> >
{i18next.t("totp_view_new_btn")} {i18next.t("totp_view_new_btn")}
@ -149,7 +147,7 @@ export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
route("/secrets/delete_engine/" + mount) route(delSecretsEngineURL(baseMount))
}} }}
> >
{i18next.t("totp_view_delete_btn")} {i18next.t("totp_view_delete_btn")}
@ -164,7 +162,7 @@ export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
} else { } else {
return this.state.totpItems.map((totpItem) => { return this.state.totpItems.map((totpItem) => {
return <RefreshingTOTPGridItem return <RefreshingTOTPGridItem
mount={totpItem.mount} baseMount={totpItem.baseMount}
totpKey={totpItem.totpKey} totpKey={totpItem.totpKey}
canDelete={totpItem.canDelete} canDelete={totpItem.canDelete}
/> />
@ -178,8 +176,4 @@ export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
); );
} }
get name(): string {
return i18next.t("totp_view_title");
}
} }

View file

@ -1,19 +1,28 @@
import { Form } from "../../../elements/Form"; import { Form } from "../../../elements/Form";
import { Margin } from "../../../elements/Margin"; import { Margin } from "../../../elements/Margin";
import { MarginInline } from "../../../elements/MarginInline"; import { MarginInline } from "../../../elements/MarginInline";
import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { newTransitKey } from "../../../../api/transit/newTransitKey"; import { newTransitKey } from "../../../../api/transit/newTransitKey";
import { render } from "preact"; import { Component } from "preact";
import { setErrorText } from "../../../../pageUtils"; import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router";
import { transitViewSecretURL } from "../../pageLinks";
export class NewTransitKeyPage extends Page { export class NewTransitKey extends Component<DefaultPageProps> {
constructor() { constructor() {
super(); super();
} }
async render(): Promise<void> { render() {
render( const baseMount = this.props.matches["baseMount"];
return (
<>
<SecretTitleElement
type="transit"
baseMount={baseMount}
suffix={i18next.t("transit_new_key_suffix")}
/>
<Form <Form
onSubmit={async (data) => { onSubmit={async (data) => {
await this.onSubmit(data); await this.onSubmit(data);
@ -54,36 +63,27 @@ export class NewTransitKeyPage extends Page {
{i18next.t("transit_new_key_create_btn")} {i18next.t("transit_new_key_create_btn")}
</button> </button>
</MarginInline> </MarginInline>
</Form>, </Form>
this.router.pageContentElement, </>
); );
} }
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
const baseMount = this.props.matches["baseMount"];
const name = data.get("name") as string; const name = data.get("name") as string;
const type = data.get("type") as string; const type = data.get("type") as string;
try { try {
await newTransitKey(this.state.baseMount, { await newTransitKey(baseMount, {
name: name, name: name,
type: type, type: type,
}); });
this.state.secretItem = name; route(transitViewSecretURL(baseMount, name))
await this.router.changePage("TRANSIT_VIEW_SECRET");
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);
} }
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("transit_new_key_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string {
return i18next.t("transit_new_key_title");
}
} }

View file

@ -1,16 +1,18 @@
import { Component, JSX, render } from "preact"; import { Component, JSX, render } from "preact";
import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { getCapabilitiesPath } from "../../../../api/sys/getCapabilities"; import { CapabilitiesType, getCapabilitiesPath } from "../../../../api/sys/getCapabilities";
import { getTransitKeys } from "../../../../api/transit/getTransitKeys"; import { getTransitKeys } from "../../../../api/transit/getTransitKeys";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { delSecretsEngineURL, transitNewSecretURL, transitViewSecretURL } from "../../pageLinks";
type TransitViewListState = { type TransitViewListState = {
contentLoaded: boolean; contentLoaded: boolean;
transitKeysList: string[]; transitKeysList: string[];
}; };
export class TransitViewListItem extends Component<{ page: Page }, TransitViewListState> { export class TransitViewListItem extends Component<{ baseMount: string }, TransitViewListState> {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -22,7 +24,7 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi
timer: unknown; timer: unknown;
getTransitKeys(): void { getTransitKeys(): void {
void getTransitKeys(this.props.page.state.baseMount) void getTransitKeys(this.props.baseMount)
.then((keys) => { .then((keys) => {
this.setState({ this.setState({
contentLoaded: true, contentLoaded: true,
@ -56,8 +58,7 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
this.props.page.state.secretItem = key; route(transitViewSecretURL(this.props.baseMount, key))
await this.props.page.router.changePage("TRANSIT_VIEW_SECRET");
}} }}
> >
{key} {key}
@ -69,31 +70,32 @@ export class TransitViewListItem extends Component<{ page: Page }, TransitViewLi
} }
} }
export class TransitViewPage extends Page { export class TransitView extends Component<DefaultPageProps, {caps: CapabilitiesType}> {
constructor() { async componentDidMount() {
super(); const baseMount = this.props.matches["baseMount"];
const mountsPath = "/sys/mounts/" + baseMount;
const caps = await getCapabilitiesPath([mountsPath, baseMount]);
this.setState({caps})
} }
async goBack(): Promise<void> { render() {
await this.router.changePage("SECRETS_HOME"); 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<void> { return (
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(
<> <>
<SecretTitleElement type="transit" baseMount={baseMount} />
<p> <p>
{transitCaps.includes("create") && ( {transitCaps.includes("create") && (
<button <button
class="uk-button uk-button-primary" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
await this.router.changePage("TRANSIT_NEW_KEY"); route(transitNewSecretURL(baseMount));
}} }}
> >
{i18next.t("transit_view_new_btn")} {i18next.t("transit_view_new_btn")}
@ -103,24 +105,15 @@ export class TransitViewPage extends Page {
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
await this.router.changePage("DELETE_SECRET_ENGINE"); route(delSecretsEngineURL(baseMount))
}} }}
> >
{i18next.t("transit_view_delete_btn")} {i18next.t("transit_view_delete_btn")}
</button> </button>
)} )}
</p> </p>
<TransitViewListItem page={this} /> <TransitViewListItem baseMount={baseMount} />
</>, </>
this.router.pageContentElement,
); );
} }
//async renderPageTitle(): Promise<void> {
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
//}
get name(): string {
return i18next.t("transit_view_title");
}
} }

View file

@ -3,22 +3,27 @@ import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { Tile } from "../../../elements/Tile"; import { Tile } from "../../../elements/Tile";
import { getTransitKey } from "../../../../api/transit/getTransitKey"; import { getTransitKey } from "../../../../api/transit/getTransitKey";
import { render } from "preact"; import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { TransitKeyType } from "../../../../api/types/transit";
export class TransitViewSecretPage extends Page { export class TransitViewSecret extends Component<DefaultPageProps, {transitKey: TransitKeyType}> {
constructor() { async componentDidMount() {
super(); const baseMount = this.props.matches["baseMount"];
const secretItem = this.props.matches["secretItem"];
const transitKey = await getTransitKey(baseMount, secretItem);
this.setState({transitKey});
} }
async goBack(): Promise<void> { render() {
await this.router.changePage("TRANSIT_VIEW"); 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<void> {
const transitKey = await getTransitKey(this.state.baseMount, this.state.secretItem);
render( return (
<Grid size={GridSizes.MATCHING_TWO_ROWS}> <Grid size={GridSizes.MATCHING_TWO_ROWS}>
{transitKey.supports_encryption && ( {transitKey.supports_encryption && (
<Tile <Tile
@ -26,7 +31,7 @@ export class TransitViewSecretPage extends Page {
description={i18next.t("transit_view_encrypt_description")} description={i18next.t("transit_view_encrypt_description")}
icon="lock" icon="lock"
iconText={i18next.t("transit_view_encrypt_icon_text")} iconText={i18next.t("transit_view_encrypt_icon_text")}
onclick={async () => await this.router.changePage("TRANSIT_ENCRYPT")} onclick={async () => {} /*await this.router.changePage("TRANSIT_ENCRYPT")*/}
/> />
)} )}
{transitKey.supports_decryption && ( {transitKey.supports_decryption && (
@ -35,7 +40,7 @@ export class TransitViewSecretPage extends Page {
description={i18next.t("transit_view_decrypt_description")} description={i18next.t("transit_view_decrypt_description")}
icon="mail" icon="mail"
iconText={i18next.t("transit_view_decrypt_icon_text")} 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 && ( {transitKey.supports_decryption && (
@ -44,19 +49,10 @@ export class TransitViewSecretPage extends Page {
description={i18next.t("transit_view_rewrap_description")} description={i18next.t("transit_view_rewrap_description")}
icon="code" icon="code"
iconText={i18next.t("transit_view_rewrap_icon_text")} iconText={i18next.t("transit_view_rewrap_icon_text")}
onclick={async () => await this.router.changePage("TRANSIT_REWRAP")} onclick={async () => {} /*await this.router.changePage("TRANSIT_REWRAP")*/}
/> />
)} )}
</Grid>, </Grid>
this.router.pageContentElement,
); );
} }
//async renderPageTitle(): Promise<void> {
// render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
//}
get name(): string {
return i18next.t("transit_view_secret_title");
}
} }

View file

@ -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}`;
}
//