Add KV secret version viewing again
This commit is contained in:
parent
bfaa43ce3a
commit
a8add31ff1
|
@ -18,7 +18,7 @@ import { SecretMetadataType } from "./types/secret";
|
|||
import { Settings } from "../settings/Settings";
|
||||
import { TokenInfo } from "./types/token";
|
||||
import { UserType, UserTypeAPIResp } from "./types/user";
|
||||
import { removeDoubleSlash } from "../utils";
|
||||
import { getObjectKeys, removeDoubleSlash } from "../utils";
|
||||
|
||||
async function checkResponse(resp: Response): Promise<void> {
|
||||
if (resp.ok) return;
|
||||
|
@ -352,19 +352,67 @@ export class API {
|
|||
await checkResponse(resp);
|
||||
}
|
||||
|
||||
async deleteSecret(baseMount: string, secretPath: string[], name: string): Promise<void> {
|
||||
async deleteSecret(
|
||||
baseMount: string,
|
||||
secretPath: string[],
|
||||
name: string,
|
||||
version: string = "null",
|
||||
): Promise<void> {
|
||||
let secretURL = "";
|
||||
|
||||
let request;
|
||||
|
||||
const mountInfo = await this.getMount(baseMount);
|
||||
if (mountInfo.options.version == "2") {
|
||||
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("/")}/${name}`;
|
||||
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 {
|
||||
secretURL = `/v1/${baseMount}/${secretPath.join("/")}/${name}`;
|
||||
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: string = "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: "DELETE",
|
||||
headers: this.getHeaders(),
|
||||
method: "POST",
|
||||
headers: {
|
||||
...this.getHeaders(),
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ versions: [version] }),
|
||||
});
|
||||
const resp = await fetch(request);
|
||||
await checkResponse(resp);
|
||||
|
@ -376,7 +424,7 @@ export class API {
|
|||
name: string,
|
||||
): Promise<Record<string, unknown>> {
|
||||
const request = new Request(
|
||||
this.appendAPIURL(`/v1/${baseMount}/${secretPath.join("")}/${name}`),
|
||||
this.appendAPIURL(`/v1/${baseMount}/${secretPath.join("/")}/${name}`),
|
||||
{
|
||||
headers: this.getHeaders(),
|
||||
},
|
||||
|
@ -394,10 +442,12 @@ export class API {
|
|||
baseMount: string,
|
||||
secretPath: string[],
|
||||
name: string,
|
||||
version: string = "null",
|
||||
): Promise<Record<string, unknown>> {
|
||||
let secretURL = "";
|
||||
|
||||
secretURL = `/v1/${baseMount}/data/${secretPath.join("")}/${name}`;
|
||||
secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`;
|
||||
if (version != "null") secretURL += `?version=${version}`;
|
||||
|
||||
const request = new Request(this.appendAPIURL(secretURL), {
|
||||
headers: this.getHeaders(),
|
||||
|
@ -414,10 +464,11 @@ export class API {
|
|||
baseMount: string,
|
||||
secretPath: string[],
|
||||
name: string,
|
||||
version: string = "null",
|
||||
): Promise<Record<string, unknown>> {
|
||||
const mountInfo = await this.getMount(baseMount);
|
||||
if (mountInfo.options.version == "2") {
|
||||
return await this.getSecretKV2(baseMount, secretPath, name);
|
||||
return await this.getSecretKV2(baseMount, secretPath, name, version);
|
||||
} else {
|
||||
return await this.getSecretKV1(baseMount, secretPath, name);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import { UserPassUserEdit } from "./ui/pages/Access/Auth/userpass/UserPassUserEd
|
|||
import { UserPassUserNew } from "./ui/pages/Access/Auth/userpass/UserPassUserNew";
|
||||
import { UserPassUserView } from "./ui/pages/Access/Auth/userpass/UserPassUserView";
|
||||
import { UserPassUsersList } from "./ui/pages/Access/Auth/userpass/UserPassUsersList";
|
||||
import { KeyValueVersions } from "./ui/pages/Secrets/KeyValue/KeyValueVersions";
|
||||
|
||||
export const Main = () => (
|
||||
<Router>
|
||||
|
@ -65,7 +66,12 @@ export const Main = () => (
|
|||
<KeyValueNew path="/secrets/kv/new/:baseMount/:secretPath*?" settings={settings} api={api} />
|
||||
<KeyValueList path="/secrets/kv/list/:baseMount/:secretPath*?" settings={settings} api={api} />
|
||||
<KeyValueView
|
||||
path="/secrets/kv/view/:item/:baseMount/:secretPath*?"
|
||||
path="/secrets/kv/view/:item/:version/:baseMount/:secretPath*?"
|
||||
settings={settings}
|
||||
api={api}
|
||||
/>
|
||||
<KeyValueVersions
|
||||
path="/secrets/kv/versions/:item/:baseMount/:secretPath*?"
|
||||
settings={settings}
|
||||
api={api}
|
||||
/>
|
||||
|
@ -75,7 +81,7 @@ export const Main = () => (
|
|||
api={api}
|
||||
/>
|
||||
<KeyValueDelete
|
||||
path="/secrets/kv/delete/:item/:baseMount/:secretPath*?"
|
||||
path="/secrets/kv/delete/:item/:version/:baseMount/:secretPath*?"
|
||||
settings={settings}
|
||||
api={api}
|
||||
/>
|
||||
|
@ -83,7 +89,7 @@ export const Main = () => (
|
|||
<TOTPList path="/secrets/totp/list/:baseMount" settings={settings} api={api} />
|
||||
<TOTPNew path="/secrets/totp/new/:baseMount" settings={settings} api={api} />
|
||||
<TOTPNewGenerated path="/secrets/totp/new_generated/:baseMount" settings={settings} api={api} />
|
||||
<TOTPDelete path="/secrets/totp/delete/:baseMount/:item" settings={settings} api={api} />
|
||||
<TOTPDelete path="/secrets/totp/delete/:version/:baseMount/:item" settings={settings} api={api} />
|
||||
|
||||
<TransitNew path="/secrets/transit/new/:baseMount" settings={settings} api={api} />
|
||||
<TransitList path="/secrets/transit/list/:baseMount" settings={settings} api={api} />
|
||||
|
|
|
@ -2,12 +2,15 @@ import { Component } from "preact";
|
|||
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import i18next from "i18next";
|
||||
import { route } from "preact-router";
|
||||
import { kvListURL, kvViewURL } from "../../pageLinks";
|
||||
|
||||
export class KeyValueDelete extends Component<DefaultPageProps> {
|
||||
render() {
|
||||
const baseMount = this.props.matches["baseMount"];
|
||||
const secretPath = this.props.matches["secretPath"].split("/");
|
||||
const item = this.props.matches["item"];
|
||||
const version = this.props.matches["version"];
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -23,8 +26,13 @@ export class KeyValueDelete extends Component<DefaultPageProps> {
|
|||
<button
|
||||
class="uk-button uk-button-danger"
|
||||
onClick={async () => {
|
||||
await this.props.api.deleteSecret(baseMount, secretPath, item);
|
||||
window.history.back();
|
||||
await this.props.api.deleteSecret(baseMount, secretPath, item, version);
|
||||
if (version == "null") {
|
||||
route(kvListURL(baseMount, secretPath))
|
||||
} else {
|
||||
route(kvViewURL(baseMount, secretPath, item, "null"))
|
||||
|
||||
}
|
||||
}}
|
||||
>
|
||||
{i18next.t("common_delete")}
|
||||
|
|
55
src/ui/pages/Secrets/KeyValue/KeyValueVersions.tsx
Normal file
55
src/ui/pages/Secrets/KeyValue/KeyValueVersions.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Component } from "preact";
|
||||
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { kvViewURL } from "../../pageLinks";
|
||||
import { route } from "preact-router";
|
||||
import { objectToMap } from "../../../../utils";
|
||||
|
||||
export class KeyValueVersions extends Component<DefaultPageProps, { versions: string[] }> {
|
||||
async componentDidMount() {
|
||||
const baseMount = this.props.matches["baseMount"];
|
||||
const secretPath = this.props.matches["secretPath"].split("/");
|
||||
const secretItem = this.props.matches["item"];
|
||||
|
||||
const metadata = await this.props.api.getSecretMetadata(
|
||||
baseMount,
|
||||
secretPath,
|
||||
secretItem,
|
||||
);
|
||||
|
||||
const versions = Array.from(objectToMap(metadata.versions).keys());
|
||||
|
||||
this.setState({ versions });
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!this.state.versions) return;
|
||||
const baseMount = this.props.matches["baseMount"];
|
||||
const secretPath = this.props.matches["secretPath"].split("/");
|
||||
const secretItem = this.props.matches["item"];
|
||||
|
||||
return (
|
||||
<>
|
||||
<SecretTitleElement
|
||||
type="kv"
|
||||
baseMount={baseMount}
|
||||
secretPath={secretPath}
|
||||
item={this.props.matches["item"]}
|
||||
/>
|
||||
<ul class="uk-nav uk-nav-default">
|
||||
{this.state.versions.map((ver) => (
|
||||
<li>
|
||||
<a
|
||||
onClick={async () => {
|
||||
route(kvViewURL(baseMount, secretPath, secretItem, ver));
|
||||
}}
|
||||
>
|
||||
{`v${ver}`}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,10 +4,11 @@ import { CopyableInputBox } from "../../../elements/CopyableInputBox";
|
|||
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
|
||||
import { Grid, GridSizes } from "../../../elements/Grid";
|
||||
import { SecretTitleElement } from "../SecretTitleElement";
|
||||
import { kvDeleteURL, kvEditURL } from "../../pageLinks";
|
||||
import { kvDeleteURL, kvEditURL, kvVersionsURL } from "../../pageLinks";
|
||||
import { route } from "preact-router";
|
||||
import { sortedObjectMap } from "../../../../utils";
|
||||
import i18next from "i18next";
|
||||
import { DoesNotExistError } from "../../../../types/internalErrors";
|
||||
|
||||
export type KVSecretViewProps = {
|
||||
kvData: Record<string, unknown>;
|
||||
|
@ -46,6 +47,9 @@ type KeyValueViewState = {
|
|||
secretItem: string;
|
||||
caps: string[];
|
||||
secretInfo: Record<string, unknown>;
|
||||
kvVersion: string;
|
||||
isDeleted: boolean;
|
||||
secretVersion: string;
|
||||
};
|
||||
|
||||
export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> {
|
||||
|
@ -53,27 +57,43 @@ export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState>
|
|||
const baseMount = this.props.matches["baseMount"];
|
||||
const secretPath = this.props.matches["secretPath"].split("/");
|
||||
const secretItem = this.props.matches["item"];
|
||||
const secretVersion = this.props.matches["version"];
|
||||
|
||||
const caps = (await this.props.api.getCapabilities(baseMount, secretPath, secretItem))
|
||||
.capabilities;
|
||||
|
||||
const secretPathAPI = secretPath.map((e) => e + "/");
|
||||
// TODO: this is a big hacky, fix when redo how api arguments work
|
||||
secretPathAPI[secretPathAPI.length - 1] = String(secretPathAPI[secretPathAPI.length - 1])
|
||||
.replace("/", "")
|
||||
.toString();
|
||||
const mountInfo = await this.props.api.getMount(baseMount);
|
||||
let kvVersion = mountInfo.options.version;
|
||||
|
||||
let secretInfo: Record<string, unknown>;
|
||||
|
||||
if (kvVersion == "2") {
|
||||
try {
|
||||
secretInfo = await this.props.api.getSecretKV2(baseMount, secretPath, secretItem, secretVersion);
|
||||
} catch (e) {
|
||||
if (e == DoesNotExistError) {
|
||||
secretInfo = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
secretInfo = await this.props.api.getSecretKV1(baseMount, secretPath, secretItem);
|
||||
}
|
||||
|
||||
let isDeleted = (secretInfo == null) && (kvVersion == "2");
|
||||
|
||||
const secretInfo = await this.props.api.getSecret(baseMount, secretPathAPI, secretItem);
|
||||
this.setState({
|
||||
baseMount,
|
||||
secretPath,
|
||||
secretItem,
|
||||
caps,
|
||||
secretInfo,
|
||||
kvVersion,
|
||||
isDeleted,
|
||||
secretVersion,
|
||||
});
|
||||
}
|
||||
render() {
|
||||
if (!this.state.baseMount) return;
|
||||
if (!this.state.secretInfo) return;
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -85,19 +105,43 @@ export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState>
|
|||
/>
|
||||
<div>
|
||||
<p id="buttonsBlock">
|
||||
{this.state.caps.includes("delete") && (
|
||||
<button
|
||||
class="uk-button uk-button-danger"
|
||||
onClick={async () => {
|
||||
route(
|
||||
kvDeleteURL(this.state.baseMount, this.state.secretPath, this.state.secretItem),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{i18next.t("kv_secret_delete_btn")}
|
||||
</button>
|
||||
)}
|
||||
{this.state.caps.includes("update") && (
|
||||
{
|
||||
// Delete Button
|
||||
!this.state.isDeleted && this.state.caps.includes("delete") && (
|
||||
<button
|
||||
class="uk-button uk-button-danger"
|
||||
onClick={() => {
|
||||
route(
|
||||
kvDeleteURL(
|
||||
this.state.baseMount,
|
||||
this.state.secretPath,
|
||||
this.state.secretItem,
|
||||
this.state.secretVersion,
|
||||
),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{((): string => {
|
||||
// Delete Secret on kv-v1
|
||||
let deleteButtonText = i18next.t("kv_secret_delete_btn");
|
||||
if (this.state.kvVersion == "2" && this.state.secretVersion == "null") {
|
||||
// Delete All
|
||||
deleteButtonText = i18next.t("kv_secret_delete_all_btn");
|
||||
} else if (
|
||||
this.state.kvVersion == "2" &&
|
||||
this.state.secretVersion != "null"
|
||||
) {
|
||||
// Delete Version X
|
||||
deleteButtonText = i18next.t("kv_secret_delete_version_btn", {
|
||||
version: this.state.secretVersion,
|
||||
});
|
||||
}
|
||||
return deleteButtonText;
|
||||
})()}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
{this.state.secretVersion == "null" && this.state.caps.includes("update") && (
|
||||
<button
|
||||
class="uk-button uk-button-primary"
|
||||
onClick={async () => {
|
||||
|
@ -109,9 +153,43 @@ export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState>
|
|||
{i18next.t("common_edit")}
|
||||
</button>
|
||||
)}
|
||||
{!this.state.isDeleted && this.state.kvVersion == "2" && (
|
||||
<button
|
||||
class="uk-button uk-button-secondary"
|
||||
onClick={async () => {
|
||||
route(
|
||||
kvVersionsURL(this.state.baseMount, this.state.secretPath, this.state.secretItem),
|
||||
);
|
||||
}}
|
||||
>
|
||||
{i18next.t("kv_secret_versions_btn")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
</p>
|
||||
|
||||
{<KVSecretVew kvData={this.state.secretInfo} />}
|
||||
{!this.state.isDeleted && <KVSecretVew kvData={this.state.secretInfo} />}
|
||||
|
||||
{this.state.isDeleted && (
|
||||
<>
|
||||
<p>{i18next.t("kv_secret_deleted_text")}</p>
|
||||
<button
|
||||
class="uk-button uk-button-primary"
|
||||
onClick={async () => {
|
||||
await this.props.api.undeleteSecret(
|
||||
this.state.baseMount,
|
||||
this.state.secretPath,
|
||||
this.state.secretItem,
|
||||
this.state.secretVersion,
|
||||
);
|
||||
this.setState({});
|
||||
await this.componentDidMount();
|
||||
}}
|
||||
>
|
||||
{i18next.t("kv_secret_restore_btn")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -10,16 +10,20 @@ 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 kvDeleteURL(baseMount: string, secretPath: string[], secret: string, version: string = "null"): string {
|
||||
return `/secrets/kv/delete/${secret}/${version}/${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 kvVersionsURL(baseMount: string, secretPath: string[], secret: string): string {
|
||||
return `/secrets/kv/versions/${secret}/${baseMount}/${secretPath.join("/")}`;
|
||||
}
|
||||
|
||||
export function kvViewURL(baseMount: string, secretPath: string[], secret: string, version: string = "null"): string {
|
||||
return `/secrets/kv/view/${secret}/${version}/${baseMount}/${secretPath.join("/")}`;
|
||||
}
|
||||
|
||||
export function kvListURL(baseMount: string, secretPath: string[]): string {
|
||||
|
|
Loading…
Reference in a new issue