1
0
Fork 0

initial work on moving to a real page router

This commit is contained in:
ChaotiCryptidz 2022-01-06 18:53:38 +00:00
parent da163c931a
commit c62cd89771
48 changed files with 1201 additions and 1209 deletions

View file

@ -17,6 +17,7 @@
"codejar": "^3.5.0", "codejar": "^3.5.0",
"core-js": "^3.20.2", "core-js": "^3.20.2",
"css-loader": "^6.5.1", "css-loader": "^6.5.1",
"css-minimizer-webpack-plugin": "^3.3.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"eslint": "^8.6.0", "eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
@ -41,7 +42,9 @@
"uikit": "^3.9.4", "uikit": "^3.9.4",
"webpack": "^5.65.0", "webpack": "^5.65.0",
"webpack-cli": "^4.9.1", "webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2", "webpack-dev-server": "^4.7.2"
"css-minimizer-webpack-plugin": "^3.3.1" },
"dependencies": {
"preact-router": "^4.0.0"
} }
} }

View file

@ -1,121 +1,121 @@
import { PageType } from "./pagerouter/PageType"; //import { PageType } from "./pagerouter/PageType";
//
import { AccessHomePage } from "./ui/pages/Access/AccessHome"; //import { AccessHomePage } from "./ui/pages/Access/AccessHome";
import { AuthHomePage } from "./ui/pages/Access/Auth/AuthHome"; //import { AuthHomePage } from "./ui/pages/Access/Auth/AuthHome";
import { AuthViewConfigPage } from "./ui/pages/Access/Auth/AuthViewConfig"; //import { AuthViewConfigPage } from "./ui/pages/Access/Auth/AuthViewConfig";
import { DeleteSecretsEnginePage } from "./ui/pages/Secrets/DeleteSecretsEngine"; //import { DeleteSecretsEnginePage } from "./ui/pages/Secrets/DeleteSecretsEngine";
import { HomePage } from "./ui/pages/Home"; //import { HomePage } from "./ui/pages/Home";
import { KeyValueDeletePage } from "./ui/pages/Secrets/KeyValue/KeyValueDelete"; //import { KeyValueDeletePage } from "./ui/pages/Secrets/KeyValue/KeyValueDelete";
import { KeyValueNewPage } from "./ui/pages/Secrets/KeyValue/KeyValueNew"; //import { KeyValueNewPage } from "./ui/pages/Secrets/KeyValue/KeyValueNew";
import { KeyValueSecretEditPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit"; //import { KeyValueSecretEditPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecretsEdit";
import { KeyValueSecretPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecret"; //import { KeyValueSecretPage } from "./ui/pages/Secrets/KeyValue/KeyValueSecret";
import { KeyValueVersionsPage } from "./ui/pages/Secrets/KeyValue/KeyValueVersions"; //import { KeyValueVersionsPage } from "./ui/pages/Secrets/KeyValue/KeyValueVersions";
import { KeyValueViewPage } from "./ui/pages/Secrets/KeyValue/KeyValueView"; //import { KeyValueViewPage } from "./ui/pages/Secrets/KeyValue/KeyValueView";
import { LoginPage } from "./ui/pages/Login"; //import { LoginPage } from "./ui/pages/Login";
import { MePage } from "./ui/pages/Me"; //import { MePage } from "./ui/pages/Me";
import { NewKVEnginePage } from "./ui/pages/Secrets/NewEngines/NewKVEngine"; //import { NewKVEnginePage } from "./ui/pages/Secrets/NewEngines/NewKVEngine";
import { NewSecretsEnginePage } from "./ui/pages/Secrets/NewSecretsEngine"; //import { NewSecretsEnginePage } from "./ui/pages/Secrets/NewSecretsEngine";
import { NewTOTPEnginePage } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine"; //import { NewTOTPEnginePage } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine";
import { NewTransitEnginePage } from "./ui/pages/Secrets/NewEngines/NewTransitEngine"; //import { NewTransitEnginePage } from "./ui/pages/Secrets/NewEngines/NewTransitEngine";
import { NewTransitKeyPage } from "./ui/pages/Secrets/Transit/NewTransitKey"; //import { NewTransitKeyPage } from "./ui/pages/Secrets/Transit/NewTransitKey";
import { Page } from "./types/Page"; //import { Page } from "./types/Page";
import { PoliciesHomePage } from "./ui/pages/Policies/PoliciesHome"; //import { PoliciesHomePage } from "./ui/pages/Policies/PoliciesHome";
import { PolicyDeletePage } from "./ui/pages/Policies/PolicyDelete"; //import { PolicyDeletePage } from "./ui/pages/Policies/PolicyDelete";
import { PolicyEditPage } from "./ui/pages/Policies/PolicyEdit"; //import { PolicyEditPage } from "./ui/pages/Policies/PolicyEdit";
import { PolicyNewPage } from "./ui/pages/Policies/PolicyNew"; //import { PolicyNewPage } from "./ui/pages/Policies/PolicyNew";
import { PolicyViewPage } from "./ui/pages/Policies/PolicyView"; //import { PolicyViewPage } from "./ui/pages/Policies/PolicyView";
import { PwGenPage } from "./ui/pages/PwGen"; //import { PwGenPage } from "./ui/pages/PwGen";
import { SecretsHomePage } from "./ui/pages/Secrets/SecretsHome"; //import { SecretsHomePage } from "./ui/pages/Secrets/SecretsHome";
import { SetLanguagePage } from "./ui/pages/SetLanguage"; //import { SetLanguagePage } from "./ui/pages/SetLanguage";
import { SetVaultURLPage } from "./ui/pages/SetVaultURL"; //import { SetVaultURLPage } from "./ui/pages/SetVaultURL";
import { TOTPDeletePage } from "./ui/pages/Secrets/TOTP/TOTPDelete"; //import { TOTPDeletePage } from "./ui/pages/Secrets/TOTP/TOTPDelete";
import { TOTPNewPage } from "./ui/pages/Secrets/TOTP/TOTPNew"; //import { TOTPNewPage } from "./ui/pages/Secrets/TOTP/TOTPNew";
import { TOTPViewPage } from "./ui/pages/Secrets/TOTP/TOTPView"; //import { TOTPViewPage } from "./ui/pages/Secrets/TOTP/TOTPView";
import { TransitDecryptPage } from "./ui/pages/Secrets/Transit/TransitDecrypt"; //import { TransitDecryptPage } from "./ui/pages/Secrets/Transit/TransitDecrypt";
import { TransitEncryptPage } from "./ui/pages/Secrets/Transit/TransitEncrypt"; //import { TransitEncryptPage } from "./ui/pages/Secrets/Transit/TransitEncrypt";
import { TransitRewrapPage } from "./ui/pages/Secrets/Transit/TransitRewrap"; //import { TransitRewrapPage } from "./ui/pages/Secrets/Transit/TransitRewrap";
import { TransitViewPage } from "./ui/pages/Secrets/Transit/TransitView"; //import { TransitViewPage } from "./ui/pages/Secrets/Transit/TransitView";
import { TransitViewSecretPage } from "./ui/pages/Secrets/Transit/TransitViewSecret"; //import { TransitViewSecretPage } from "./ui/pages/Secrets/Transit/TransitViewSecret";
import { UnsealPage } from "./ui/pages/Unseal"; //import { UnsealPage } from "./ui/pages/Unseal";
import { UserPassUserDeletePage } from "./ui/pages/Access/Auth/userpass/UserPassUserDelete"; //import { UserPassUserDeletePage } from "./ui/pages/Access/Auth/userpass/UserPassUserDelete";
import { UserPassUserEditPage } from "./ui/pages/Access/Auth/userpass/UserPassUserEdit"; //import { UserPassUserEditPage } from "./ui/pages/Access/Auth/userpass/UserPassUserEdit";
import { UserPassUserNewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserNew"; //import { UserPassUserNewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserNew";
import { UserPassUserViewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserView"; //import { UserPassUserViewPage } from "./ui/pages/Access/Auth/userpass/UserPassUserView";
import { UserPassUsersListPage } from "./ui/pages/Access/Auth/userpass/UserPassUsersList"; //import { UserPassUsersListPage } from "./ui/pages/Access/Auth/userpass/UserPassUsersList";
import { getObjectKeys } from "./utils"; //import { getObjectKeys } from "./utils";
//
type pagesList = { //type pagesList = {
[key: string]: Page; // [key: string]: Page;
}; //};
//
export const allPages: pagesList = { //export const allPages: pagesList = {
HOME: new HomePage(), // HOME: new HomePage(),
LOGIN: new LoginPage(), // LOGIN: new LoginPage(),
SET_VAULT_URL: new SetVaultURLPage(), // SET_VAULT_URL: new SetVaultURLPage(),
UNSEAL: new UnsealPage(), // UNSEAL: new UnsealPage(),
SET_LANGUAGE: new SetLanguagePage(), // SET_LANGUAGE: new SetLanguagePage(),
ME: new MePage(), // ME: new MePage(),
PW_GEN: new PwGenPage(), // PW_GEN: new PwGenPage(),
//
POLICIES_HOME: new PoliciesHomePage(), // POLICIES_HOME: new PoliciesHomePage(),
POLICY_VIEW: new PolicyViewPage(), // POLICY_VIEW: new PolicyViewPage(),
POLICY_NEW: new PolicyNewPage(), // POLICY_NEW: new PolicyNewPage(),
POLICY_EDIT: new PolicyEditPage(), // POLICY_EDIT: new PolicyEditPage(),
POLICY_DELETE: new PolicyDeletePage(), // POLICY_DELETE: new PolicyDeletePage(),
//
ACCESS_HOME: new AccessHomePage(), // ACCESS_HOME: new AccessHomePage(),
//
AUTH_HOME: new AuthHomePage(), // AUTH_HOME: new AuthHomePage(),
AUTH_VIEW_CONFIG: new AuthViewConfigPage(), // AUTH_VIEW_CONFIG: new AuthViewConfigPage(),
//
USERPASS_USERS_LIST: new UserPassUsersListPage(), // USERPASS_USERS_LIST: new UserPassUsersListPage(),
USERPASS_USER_VIEW: new UserPassUserViewPage(), // USERPASS_USER_VIEW: new UserPassUserViewPage(),
USERPASS_USER_EDIT: new UserPassUserEditPage(), // USERPASS_USER_EDIT: new UserPassUserEditPage(),
USERPASS_USER_NEW: new UserPassUserNewPage(), // USERPASS_USER_NEW: new UserPassUserNewPage(),
USERPASS_USER_DELETE: new UserPassUserDeletePage(), // USERPASS_USER_DELETE: new UserPassUserDeletePage(),
//
SECRETS_HOME: new SecretsHomePage(), // SECRETS_HOME: new SecretsHomePage(),
//
TOTP_VIEW: new TOTPViewPage(), // TOTP_VIEW: new TOTPViewPage(),
TOTP_NEW: new TOTPNewPage(), // TOTP_NEW: new TOTPNewPage(),
TOTP_DELETE: new TOTPDeletePage(), // TOTP_DELETE: new TOTPDeletePage(),
//
TRANSIT_VIEW: new TransitViewPage(), // TRANSIT_VIEW: new TransitViewPage(),
TRANSIT_NEW_KEY: new NewTransitKeyPage(), // TRANSIT_NEW_KEY: new NewTransitKeyPage(),
TRANSIT_VIEW_SECRET: new TransitViewSecretPage(), // TRANSIT_VIEW_SECRET: new TransitViewSecretPage(),
TRANSIT_ENCRYPT: new TransitEncryptPage(), // TRANSIT_ENCRYPT: new TransitEncryptPage(),
TRANSIT_DECRYPT: new TransitDecryptPage(), // TRANSIT_DECRYPT: new TransitDecryptPage(),
TRANSIT_REWRAP: new TransitRewrapPage(), // TRANSIT_REWRAP: new TransitRewrapPage(),
//
KEY_VALUE_VIEW: new KeyValueViewPage(), // KEY_VALUE_VIEW: new KeyValueViewPage(),
KEY_VALUE_SECRET: new KeyValueSecretPage(), // KEY_VALUE_SECRET: new KeyValueSecretPage(),
KEY_VALUE_VERSIONS: new KeyValueVersionsPage(), // KEY_VALUE_VERSIONS: new KeyValueVersionsPage(),
KEY_VALUE_NEW_SECRET: new KeyValueNewPage(), // KEY_VALUE_NEW_SECRET: new KeyValueNewPage(),
KEY_VALUE_DELETE: new KeyValueDeletePage(), // KEY_VALUE_DELETE: new KeyValueDeletePage(),
KEY_VALUE_SECRET_EDIT: new KeyValueSecretEditPage(), // KEY_VALUE_SECRET_EDIT: new KeyValueSecretEditPage(),
//
DELETE_SECRET_ENGINE: new DeleteSecretsEnginePage(), // DELETE_SECRET_ENGINE: new DeleteSecretsEnginePage(),
//
NEW_SECRETS_ENGINE: new NewSecretsEnginePage(), // NEW_SECRETS_ENGINE: new NewSecretsEnginePage(),
NEW_KV_ENGINE: new NewKVEnginePage(), // NEW_KV_ENGINE: new NewKVEnginePage(),
NEW_TOTP_ENGINE: new NewTOTPEnginePage(), // NEW_TOTP_ENGINE: new NewTOTPEnginePage(),
NEW_TRANSIT_ENGINE: new NewTransitEnginePage(), // NEW_TRANSIT_ENGINE: new NewTransitEnginePage(),
}; //};
//
// This should implement all o PageListType //// This should implement all o PageListType
class PageList { //class PageList {
constructor(pages: pagesList) { // constructor(pages: pagesList) {
this.pages = pages; // this.pages = pages;
} // }
//
private pages: pagesList; // private pages: pagesList;
//
async getPageIDs(): Promise<string[]> { // async getPageIDs(): Promise<string[]> {
return getObjectKeys(this.pages); // return getObjectKeys(this.pages);
} // }
async getPage(pageID: string): Promise<PageType> { // async getPage(pageID: string): Promise<PageType> {
return this.pages[pageID]; // return this.pages[pageID];
} // }
} //}
//
export const pageList = new PageList(allPages); //export const pageList = new PageList(allPages);

View file

@ -1,9 +1,9 @@
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils"; import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
import { removeDoubleSlash } from "../../utils"; import { removeDoubleSlash } from "../../utils";
import { getMount } from "../sys/getMounts";
export async function createOrUpdateSecret( export async function createOrUpdateSecret(
baseMount: string, baseMount: string,
secretMountType: string,
secretPath: string[], secretPath: string[],
name: string, name: string,
data: Record<string, unknown>, data: Record<string, unknown>,
@ -11,7 +11,8 @@ export async function createOrUpdateSecret(
let secretURL = ""; let secretURL = "";
let APIData = {}; let APIData = {};
if (secretMountType == "kv-v2") { const mountInfo = await getMount(baseMount);
if (mountInfo.options.version == "2") {
secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`; secretURL = `/v1/${baseMount}/data/${secretPath.join("/")}/${name}`;
APIData = { data: data }; APIData = { data: data };
} else { } else {

View file

@ -1,40 +1,26 @@
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils"; import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
import { removeDoubleSlash } from "../../utils"; import { removeDoubleSlash } from "../../utils";
import { getMount } from "../sys/getMounts";
export async function deleteSecret( export async function deleteSecret(
baseMount: string, baseMount: string,
secretMountType: string,
secretPath: string[], secretPath: string[],
name: string, name: string,
version: string | null = null,
): Promise<void> { ): Promise<void> {
let secretURL = ""; let secretURL = "";
let request; let request;
if (secretMountType == "kv-v2" && version != null) { const mountInfo = await getMount(baseMount);
secretURL = `/v1/${baseMount}/delete/${secretPath.join("")}/${name}`; if (mountInfo.options.version == "2") {
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, ""); secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`;
request = new Request(appendAPIURL(secretURL), {
method: "POST",
headers: {
...getHeaders(),
"Content-Type": "application/json",
},
body: version != null ? JSON.stringify({ versions: [version] }) : "{}",
});
} else { } else {
if (secretMountType == "kv-v2") { secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`;
secretURL = `/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`;
} else {
secretURL = `/v1/${baseMount}/${secretPath.join("")}/${name}`;
}
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
request = new Request(appendAPIURL(secretURL), {
method: "DELETE",
headers: getHeaders(),
});
} }
secretURL = removeDoubleSlash(secretURL).replace(/\/$/, "");
request = new Request(appendAPIURL(secretURL), {
method: "DELETE",
headers: getHeaders(),
});
const resp = await fetch(request); const resp = await fetch(request);
await checkResponse(resp); await checkResponse(resp);
} }

View file

@ -1,4 +1,5 @@
import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils"; import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
import { getMount } from "../sys/getMounts";
export async function getSecretKV1( export async function getSecretKV1(
baseMount: string, baseMount: string,
@ -21,12 +22,10 @@ export async function getSecretKV2(
baseMount: string, baseMount: string,
secretPath: string[], secretPath: string[],
name: string, name: string,
version: string | null = null,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
let secretURL = ""; 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(appendAPIURL(secretURL), { const request = new Request(appendAPIURL(secretURL), {
headers: getHeaders(), headers: getHeaders(),
@ -41,13 +40,12 @@ export async function getSecretKV2(
export async function getSecret( export async function getSecret(
baseMount: string, baseMount: string,
secretMountType: string,
secretPath: string[], secretPath: string[],
name: string, name: string,
version: string | null = null,
): Promise<Record<string, unknown>> { ): Promise<Record<string, unknown>> {
if (secretMountType == "kv-v2") { const mountInfo = await getMount(baseMount);
return await getSecretKV2(baseMount, secretPath, name, version); if (mountInfo.options.version == "2") {
return await getSecretKV2(baseMount, secretPath, name);
} else { } else {
return await getSecretKV1(baseMount, secretPath, name); return await getSecretKV1(baseMount, secretPath, name);
} }

View file

@ -2,10 +2,13 @@ import { appendAPIURL, checkResponse, getHeaders } from "../apiUtils";
export async function getSecrets( export async function getSecrets(
baseMount: string, baseMount: string,
secretMountType: string,
secretPath: string[], secretPath: string[],
): Promise<string[]> { ): Promise<string[]> {
let secretURL = ""; let secretURL = "";
// TODO: FIX THIS
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 {

View file

@ -21,3 +21,15 @@ export async function getMounts(): Promise<MountsType> {
const data = (await resp.json()) as { data: { secret: MountsType } }; const data = (await resp.json()) as { data: { secret: MountsType } };
return data.data.secret; return data.data.secret;
} }
export async function getMount(mountName: string): Promise<MountType> {
const request = new Request(appendAPIURL("/v1/sys/internal/ui/mounts/" + mountName), {
headers: getHeaders(),
});
const resp = await fetch(request);
await checkResponse(resp);
const data = (await resp.json()) as { data: MountType };
return data.data;
}

View file

@ -25,59 +25,110 @@ import translations from "./translations/index.mjs";
import { PageRouter } from "./pagerouter/PageRouter"; import { PageRouter } from "./pagerouter/PageRouter";
import { formatDistance } from "./formatDistance"; import { formatDistance } from "./formatDistance";
import { getSealStatus } from "./api/sys/getSealStatus"; import { getSealStatus } from "./api/sys/getSealStatus";
import { pageList } from "./allPages"; //import { pageList } from "./allPages";
import { pageState } from "./globalPageState"; import { pageState } from "./globalPageState";
import { playground } from "./playground"; import { playground } from "./playground";
import { reloadNavBar } from "./ui/elements/NavBar"; import { NavBar } from "./ui/elements/NavBar";
import { render } from "preact"; import { render, Component } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import Router from 'preact-router';
import { Home } from "./ui/pages/Home";
import { Me } from "./ui/pages/Me";
import { Login } from "./ui/pages/Login";
import { PasswordGenerator } from "./ui/pages/PwGen";
import { SetVaultURL } from "./ui/pages/SetVaultURL";
import { Unseal } from "./ui/pages/Unseal";
import { SetLanguage } from "./ui/pages/SetLanguage";
import { Secrets } from "./ui/pages/Secrets/SecretsHome";
import { TOTPView } from "./ui/pages/Secrets/TOTP/TOTPView";
import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew";
import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete";
import { NewSecretsEngine } from "./ui/pages/Secrets/NewSecretsEngine";
import { NewKVEngine } from "./ui/pages/Secrets/NewEngines/NewKVEngine";
import { NewTOTPEngine } from "./ui/pages/Secrets/NewEngines/NewTOTPEngine";
import { NewTransitEngine } from "./ui/pages/Secrets/NewEngines/NewTransitEngine";
import { DeleteSecretsEngine } from "./ui/pages/Secrets/DeleteSecretsEngine";
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";
async function onLoad(): Promise<void> { async function onLoad(): Promise<void> {
const Main = () => (
<Router>
<Home path="/" state={pageState} />
<Me path="/me" state={pageState} />
<Login path="/login" state={pageState} />
<PasswordGenerator path="/pw_gen" />
<SetVaultURL path="/set_vault_url" state={pageState} />
<Unseal path="/unseal" state={pageState} />
<SetLanguage path="/set_language" state={pageState} />
<Secrets path="/secrets" state={pageState} />
<DeleteSecretsEngine path="/secrets/delete_engine/:mount" state={pageState} />
<NewSecretsEngine path="/secrets/new_secrets_engine" />
<NewKVEngine path="/secrets/new_secrets_engine/kv" />
<NewTOTPEngine path="/secrets/new_secrets_engine/totp" />
<NewTransitEngine path="/secrets/new_secrets_engine/trasit" />
<TOTPView path="/secrets/totp/list/:mount" state={pageState} />
<TOTPNew path="/secrets/totp/new/:mount" state={pageState} />
<TOTPDelete path="/secrets/totp/delete/:mount/:item" 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>
</Router>
);
render( render(
<> <>
<div id="navBarBox" /> <NavBar />
<div class="uk-container uk-container-medium uk-align-center"> <div class="uk-container uk-container-medium uk-align-center">
<div class="uk-card uk-card-body"> <div class="uk-card uk-card-body">
<h3 class="uk-card-title" id="pageTitle" /> <Main />
<div id="pageContent" />
</div> </div>
</div> </div>
</>, </>,
document.body, document.body,
); );
const pageRouter = new PageRouter({ //const pageRouter = new PageRouter({
pageList: pageList, // pageList: pageList,
state: pageState, // state: pageState,
pageTitleElement: document.querySelector("#pageTitle"), // pageTitleElement: document.querySelector("#pageTitle"),
pageContentElement: document.querySelector("#pageContent"), // pageContentElement: document.querySelector("#pageContent"),
resetElementContent: !true, // resetElementContent: !true,
onPageChange: async function () { // onPageChange: async function () {
pageState.currentPage = await pageRouter.getCurrentPageID(); // pageState.currentPage = await pageRouter.getCurrentPageID();
document.documentElement.dir = pageState.pageDirection; // document.documentElement.dir = pageState.pageDirection;
}, // },
}); //});
//
reloadNavBar(pageRouter); //reloadNavBar(pageRouter);
if (process.env.NODE_ENV == "development") { if (process.env.NODE_ENV == "development") {
await playground(pageRouter); // await playground(pageRouter);
} }
await pageRouter.changePage(pageState.currentPage); //await pageRouter.changePage(pageState.currentPage);
setInterval(async () => { //setInterval(async () => {
if ((await pageRouter.getCurrentPageID()) != "UNSEAL") { // if ((await pageRouter.getCurrentPageID()) != "UNSEAL") {
if (pageState.apiURL.length != 0) { // if (pageState.apiURL.length != 0) {
return; // return;
} // }
const sealStatus = await getSealStatus(); // const sealStatus = await getSealStatus();
if (sealStatus.sealed) { // if (sealStatus.sealed) {
await pageRouter.changePage("UNSEAL"); // await pageRouter.changePage("UNSEAL");
return; // return;
} // }
} // }
}, 5000); //}, 5000);
} }
document.addEventListener( document.addEventListener(

View file

@ -5,35 +5,36 @@ import { lookupSelf } from "./api/sys/lookupSelf";
import ClipboardJS from "clipboard"; import ClipboardJS from "clipboard";
import UIkit from "uikit"; import UIkit from "uikit";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
async function prePageChecksReal(router: PageRouter) { async function prePageChecksReal(state: PageState) {
const state = router.state as PageState;
if (state.language.length == 0) { if (state.language.length == 0) {
await router.changePage("SET_LANGUAGE"); route("/set_language", true);
throw new Error("Language Not Set"); throw new Error("Language Not Set");
} }
if (!state.apiURL) { if (!state.apiURL) {
await router.changePage("SET_VAULT_URL"); route("/set_vault_url", true);
throw new Error("Vault URL Not Set"); throw new Error("Vault URL Not Set");
} }
const sealStatus = await getSealStatus(); const sealStatus = await getSealStatus();
if (sealStatus.sealed) { if (sealStatus.sealed) {
await router.changePage("UNSEAL"); route("/unseal", true);
throw new Error("Vault Sealed"); throw new Error("Vault Sealed");
} }
try { try {
await lookupSelf(); await lookupSelf();
} catch (e) { } catch (e) {
await router.changePage("LOGIN"); route("/login", true);
throw e; throw e;
} }
} }
export async function prePageChecks(router: PageRouter): Promise<boolean> { export async function prePageChecks(state: PageState): Promise<boolean> {
try { try {
await prePageChecksReal(router); await prePageChecksReal(state);
} catch (e) { } catch (e) {
console.log("OHNO", e); console.log("OHNO", e);
return false; return false;

View file

@ -35,6 +35,7 @@ module.exports = {
me_copy_token_btn: "Copy Token", me_copy_token_btn: "Copy Token",
me_renew_lease_btn: "Renew Token Lease", me_renew_lease_btn: "Renew Token Lease",
me_change_language_btn: "Change Language", me_change_language_btn: "Change Language",
me_set_vault_url_btn: "Set Vault URL",
// Home Page // Home Page
home_page_title: "Home", home_page_title: "Home",

View file

@ -0,0 +1,6 @@
import { PageState } from "../state/PageState";
export type DefaultPageProps = {
state: PageState;
matches?: {[key: string]: string}
};

View file

@ -1,29 +1,24 @@
import { JSX, render } from "preact"; import { JSX, render } from "preact";
import { PageRouter } from "../../pagerouter/PageRouter"; import { PageRouter } from "../../pagerouter/PageRouter";
import i18next from "i18next"; import i18next from "i18next";
import { getCurrentUrl, route } from "preact-router";
export type NavBarProps = {
router: PageRouter;
};
export function NavBar(props: NavBarProps): JSX.Element { export function NavBar(): JSX.Element {
return ( return (
<nav class="uk-navbar uk-navbar-container"> <nav class="uk-navbar uk-navbar-container">
<div class="uk-navbar-left"> <div class="uk-navbar-left">
<ul class="uk-navbar-nav"> <ul class="uk-navbar-nav">
<li> <li>
<a <a href="/">
onClick={async () => {
await props.router.changePage("HOME");
}}
>
{i18next.t("home_btn")} {i18next.t("home_btn")}
</a> </a>
</li> </li>
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
await props.router.goBack(); window.history.back()
// maybe???
}} }}
> >
{i18next.t("back_btn")} {i18next.t("back_btn")}
@ -32,7 +27,7 @@ export function NavBar(props: NavBarProps): JSX.Element {
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
await props.router.refresh(); route(getCurrentUrl(), false);
}} }}
> >
{i18next.t("refresh_btn")} {i18next.t("refresh_btn")}
@ -45,7 +40,7 @@ export function NavBar(props: NavBarProps): JSX.Element {
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
await props.router.changePage("ME"); route("/me", true);
}} }}
> >
{i18next.t("me_btn")} {i18next.t("me_btn")}
@ -55,8 +50,4 @@ export function NavBar(props: NavBarProps): JSX.Element {
</div> </div>
</nav> </nav>
); );
} }
export function reloadNavBar(router: PageRouter): void {
render(<NavBar router={router} />, document.querySelector("#navBarBox"));
}

View file

@ -0,0 +1,9 @@
import { JSX } from "preact";
export type PageTitleProps = {
title: string;
};
export function PageTitle(props: PageTitleProps): JSX.Element {
return <h3 class="uk-card-title" id="pageTitle">{props.title}</h3>;
}

View file

@ -4,6 +4,7 @@ import { Tile } from "../../elements/Tile";
import { notImplemented, prePageChecks } from "../../../pageUtils"; import { notImplemented, prePageChecks } from "../../../pageUtils";
import { render } from "preact"; import { render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { PageState } from "../../../state/PageState";
export class AccessHomePage extends Page { export class AccessHomePage extends Page {
constructor() { constructor() {
@ -14,7 +15,7 @@ export class AccessHomePage extends Page {
} }
async render(): Promise<void> { async render(): Promise<void> {
this.router.pageContentElement.innerHTML = ""; this.router.pageContentElement.innerHTML = "";
if (!(await prePageChecks(this.router))) return; if (!(await prePageChecks(this.router.state as PageState))) return;
render( render(
<Grid size={GridSizes.MATCHING_TWO_ROWS}> <Grid size={GridSizes.MATCHING_TWO_ROWS}>

View file

@ -6,22 +6,26 @@ import { TokenInfo } from "../../api/types/token";
import { getCapabilitiesPath } from "../../api/sys/getCapabilities"; import { getCapabilitiesPath } from "../../api/sys/getCapabilities";
import { lookupSelf } from "../../api/sys/lookupSelf"; import { lookupSelf } from "../../api/sys/lookupSelf";
import { prePageChecks, setErrorText } from "../../pageUtils"; import { prePageChecks, setErrorText } from "../../pageUtils";
import { render } from "preact"; import { Component, JSX, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { PageState } from "../../state/PageState";
import { route } from "preact-router";
import { PageTitle } from "../elements/PageTitle";
export class HomePage extends Page { type HomeProps = {
constructor() { state: PageState;
super(); }
}
async render(): Promise<void> {
await this.router.setPageContent("");
if (!(await prePageChecks(this.router))) return;
this.state.baseMount = ""; type HomeState = {
this.state.secretPath = []; selfTokenInfo: TokenInfo;
this.state.secretItem = ""; authCaps: string[];
this.state.secretVersion = null; policiesCaps: string[];
}
export class Home extends Component<HomeProps, HomeState> {
async componentDidMount() {
if (!(await prePageChecks(this.props.state))) return;
let selfTokenInfo: TokenInfo; let selfTokenInfo: TokenInfo;
try { try {
selfTokenInfo = await lookupSelf(); selfTokenInfo = await lookupSelf();
@ -29,8 +33,9 @@ export class HomePage extends Page {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);
if (error.message == "permission denied") { if (error.message == "permission denied") {
this.state.token = ""; this.props.state.token = "";
await this.router.changePage("LOGIN"); route('/login', true);
return;
} }
} }
@ -38,61 +43,67 @@ export class HomePage extends Page {
const authCaps = caps["sys/auth"]; const authCaps = caps["sys/auth"];
const policiesCaps = caps["sys/policies"]; const policiesCaps = caps["sys/policies"];
render( this.setState({
<div> selfTokenInfo: selfTokenInfo,
<ul id="textList" class="uk-nav"> authCaps: authCaps,
<li> policiesCaps: policiesCaps,
<span>{i18next.t("home_vaulturl_text", { text: this.state.apiURL })}</span> })
</li> }
<li>
<a render(): JSX.Element {
onClick={async () => { return this.state.selfTokenInfo && (
await this.router.changePage("PW_GEN"); <>
}} <PageTitle title={this.name} />
> <div>
{i18next.t("home_password_generator_btn")} <ul id="textList" class="uk-nav">
</a> <li>
</li> <span>{i18next.t("home_vaulturl_text", { text: this.props.state.apiURL })}</span>
<li> </li>
<span> <li>
{i18next.t("home_your_token_expires_in", { <a href="/pw_gen">
date: new Date(selfTokenInfo.expire_time), {i18next.t("home_password_generator_btn")}
})} </a>
</span> </li>
</li> <li>
</ul> <span>
<Margin> {i18next.t("home_your_token_expires_in", {
<Grid size={GridSizes.MATCHING_TWO_ROWS}> date: new Date(this.state.selfTokenInfo.expire_time),
<Tile })}
title={i18next.t("home_secrets_title")} </span>
description={i18next.t("home_secrets_description")} </li>
icon="file-edit" </ul>
onclick={async () => { <Margin>
await this.router.changePage("SECRETS_HOME"); <Grid size={GridSizes.MATCHING_TWO_ROWS}>
}} <Tile
/> title={i18next.t("home_secrets_title")}
<Tile description={i18next.t("home_secrets_description")}
title={i18next.t("home_access_title")} icon="file-edit"
description={i18next.t("home_access_description")} onclick={async () => {
icon="users" route("/secrets");
disabled={!authCaps.includes("read")} }}
onclick={async () => { />
await this.router.changePage("ACCESS_HOME"); <Tile
}} title={i18next.t("home_access_title")}
/> description={i18next.t("home_access_description")}
<Tile icon="users"
title={i18next.t("home_policies_title")} disabled={!this.state.authCaps.includes("read")}
description={i18next.t("home_policies_description")} onclick={async () => {
icon="pencil" route("/access");
disabled={!policiesCaps.includes("read")} }}
onclick={async () => { />
await this.router.changePage("POLICIES_HOME"); <Tile
}} title={i18next.t("home_policies_title")}
/> description={i18next.t("home_policies_description")}
</Grid> icon="pencil"
</Margin> disabled={!this.state.policiesCaps.includes("read")}
</div>, onclick={async () => {
this.router.pageContentElement, route("/policies");
}}
/>
</Grid>
</Margin>
</div>
</>
); );
} }

View file

@ -7,8 +7,12 @@ import { lookupSelf } from "../../api/sys/lookupSelf";
import { setErrorText } from "../../pageUtils"; import { setErrorText } from "../../pageUtils";
import { usernameLogin } from "../../api/auth/usernameLogin"; import { usernameLogin } from "../../api/auth/usernameLogin";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
import { PageTitle } from "../elements/PageTitle";
import { DefaultPageProps } from "../../types/DefaultPageProps";
import { PageState } from "../../state/PageState";
export class TokenLoginForm extends Component<{ page: Page }, unknown> { export class TokenLoginForm extends Component<{state: PageState}, unknown> {
constructor() { constructor() {
super(); super();
} }
@ -36,13 +40,12 @@ export class TokenLoginForm extends Component<{ page: Page }, unknown> {
} }
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
const page = this.props.page;
const token = data.get("token"); const token = data.get("token");
page.state.token = token as string; this.props.state.token = token as string;
try { try {
await lookupSelf(); await lookupSelf();
await page.router.changePage("HOME"); route("/");
} catch (e: unknown) { } catch (e: unknown) {
const error = e as Error; const error = e as Error;
document.querySelector("#tokenInput").classList.add("uk-form-danger"); document.querySelector("#tokenInput").classList.add("uk-form-danger");
@ -55,7 +58,7 @@ export class TokenLoginForm extends Component<{ page: Page }, unknown> {
} }
} }
export class UsernameLoginForm extends Component<{ page: Page }, unknown> { export class UsernameLoginForm extends Component<{state: PageState}, unknown> {
constructor() { constructor() {
super(); super();
} }
@ -93,15 +96,13 @@ export class UsernameLoginForm extends Component<{ page: Page }, unknown> {
} }
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
const page = this.props.page;
try { try {
const res = await usernameLogin( const res = await usernameLogin(
data.get("username") as string, data.get("username") as string,
data.get("password") as string, data.get("password") as string,
); );
page.state.token = res; this.props.state.token = res;
await page.router.changePage("HOME"); route("/");
} catch (e: unknown) { } catch (e: unknown) {
const error = e as Error; const error = e as Error;
document.querySelector("#usernameInput").classList.add("uk-form-danger"); document.querySelector("#usernameInput").classList.add("uk-form-danger");
@ -111,32 +112,31 @@ export class UsernameLoginForm extends Component<{ page: Page }, unknown> {
} }
} }
export class LoginPage extends Page { export class Login extends Component<DefaultPageProps> {
constructor() { render(): JSX.Element {
super(); return (
} <>
async render(): Promise<void> { <PageTitle title={this.name} />
render( <div>
<div> <ul class="uk-subnav uk-subnav-pill" uk-switcher=".switcher-container">
<ul class="uk-subnav uk-subnav-pill" uk-switcher=".switcher-container"> <li>
<li> <a>{i18next.t("log_in_with_token")}</a>
<a>{i18next.t("log_in_with_token")}</a> </li>
</li> <li>
<li> <a>{i18next.t("log_in_with_username")}</a>
<a>{i18next.t("log_in_with_username")}</a> </li>
</li> </ul>
</ul> <p id="errorText" class="uk-text-danger" />
<p id="errorText" class="uk-text-danger" /> <ul class="uk-switcher uk-margin switcher-container">
<ul class="uk-switcher uk-margin switcher-container"> <li>
<li> <TokenLoginForm state={this.props.state} />
<TokenLoginForm page={this} /> </li>
</li> <li>
<li> <UsernameLoginForm state={this.props.state} />
<UsernameLoginForm page={this} /> </li>
</li> </ul>
</ul> </div>
</div>, </>
this.router.pageContentElement,
); );
} }

View file

@ -1,11 +1,13 @@
import { Component, JSX, createRef, render } from "preact"; import { Component, JSX, createRef } from "preact";
import { Page } from "../../types/Page";
import { addClipboardNotifications, prePageChecks, setErrorText } from "../../pageUtils"; import { addClipboardNotifications, prePageChecks, setErrorText } from "../../pageUtils";
import { getCapsPath } from "../../api/sys/getCapabilities"; import { getCapsPath } from "../../api/sys/getCapabilities";
import { renewSelf } from "../../api/sys/renewSelf"; import { renewSelf } from "../../api/sys/renewSelf";
import { sealVault } from "../../api/sys/sealVault"; import { sealVault } from "../../api/sys/sealVault";
import ClipboardJS from "clipboard"; import ClipboardJS from "clipboard";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../types/DefaultPageProps";
import { route } from "preact-router";
import { PageTitle } from "../elements/PageTitle";
export class CopyLink extends Component<{ text: string; data: string }, unknown> { export class CopyLink extends Component<{ text: string; data: string }, unknown> {
linkRef = createRef(); linkRef = createRef();
@ -24,14 +26,23 @@ export class CopyLink extends Component<{ text: string; data: string }, unknown>
} }
} }
export class MePage extends Page { type MeState = {
loaded: boolean;
canSealVault: boolean;
}
export class Me extends Component<DefaultPageProps, MeState> {
defaultState = { loaded: false, canSealVault: false }
constructor() { constructor() {
super(); super();
this.state = this.defaultState;
} }
async render(): Promise<void> { componentWillUnmount() {
if (!(await prePageChecks(this.router))) return; this.setState(this.defaultState);
}
async componentDidMount() {
let canSealVault = false; let canSealVault = false;
try { try {
const caps = await getCapsPath("sys/seal"); const caps = await getCapsPath("sys/seal");
@ -40,59 +51,69 @@ export class MePage extends Page {
canSealVault = false; canSealVault = false;
} }
render( this.setState({
<ul class="uk-nav"> loaded: true,
<li> canSealVault: canSealVault,
<a });
onClick={async () => { }
this.state.token = "";
await this.router.changePage("HOME"); render(): JSX.Element {
}} return this.state.loaded && (
> <>
{i18next.t("me_log_out_btn")} <PageTitle title={this.name} />
</a> <ul class="uk-nav">
</li>
<li>
<CopyLink text={i18next.t("me_copy_token_btn")} data={this.state.token} />
</li>
<li>
<a
onClick={async () => {
try {
await renewSelf();
await this.router.changePage("HOME");
} catch (e: unknown) {
const error = e as Error;
setErrorText(error.message);
}
}}
>
{i18next.t("me_renew_lease_btn")}
</a>
</li>
{canSealVault && (
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
await sealVault(); this.props.state.token = "";
await this.router.changePage("UNSEAL"); route("/");
}} }}
> >
{i18next.t("me_seal_vault_btn")} {i18next.t("me_log_out_btn")}
</a> </a>
</li> </li>
)} <li>
<li> <CopyLink text={i18next.t("me_copy_token_btn")} data={this.props.state.token} />
<a </li>
onClick={async () => { <li>
await this.router.changePage("SET_LANGUAGE"); <a
}} onClick={async () => {
> try {
{i18next.t("me_change_language_btn")} await renewSelf();
</a> route("/");
</li> } catch (e: unknown) {
</ul>, const error = e as Error;
this.router.pageContentElement, setErrorText(error.message);
}
}}
>
{i18next.t("me_renew_lease_btn")}
</a>
</li>
{this.state.canSealVault && (
<li>
<a
onClick={async () => {
await sealVault();
route("/unseal", true);
}}
>
{i18next.t("me_seal_vault_btn")}
</a>
</li>
)}
<li>
<a href="/set_language">
{i18next.t("me_change_language_btn")}
</a>
</li>
<li>
<a href="/set_vault_url">
{i18next.t("me_set_vault_url_btn")}
</a>
</li>
</ul>
</>
); );
} }

View file

@ -4,6 +4,7 @@ import { getPolicies } from "../../../api/sys/policies/getPolicies";
import { prePageChecks } from "../../../pageUtils"; import { prePageChecks } from "../../../pageUtils";
import { render } from "preact"; import { render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { PageState } from "../../../state/PageState";
export class PoliciesHomePage extends Page { export class PoliciesHomePage extends Page {
constructor() { constructor() {
@ -14,7 +15,7 @@ export class PoliciesHomePage extends Page {
} }
async render(): Promise<void> { async render(): Promise<void> {
await this.router.setPageContent(""); await this.router.setPageContent("");
if (!(await prePageChecks(this.router))) return; if (!(await prePageChecks(this.router.state as PageState))) return;
let policies = await getPolicies(); let policies = await getPolicies();
policies = policies.sort(); policies = policies.sort();

View file

@ -5,6 +5,7 @@ import { getPolicy } from "../../../api/sys/policies/getPolicy";
import { prePageChecks } from "../../../pageUtils"; import { prePageChecks } from "../../../pageUtils";
import { render } from "preact"; import { render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { PageState } from "../../../state/PageState";
export class PolicyViewPage extends Page { export class PolicyViewPage extends Page {
constructor() { constructor() {
@ -15,7 +16,7 @@ export class PolicyViewPage extends Page {
} }
async render(): Promise<void> { async render(): Promise<void> {
await this.router.setPageContent(""); await this.router.setPageContent("");
if (!(await prePageChecks(this.router))) return; if (!(await prePageChecks(this.router.state as PageState))) return;
const policy = await getPolicy(this.state.policyItem); const policy = await getPolicy(this.state.policyItem);

View file

@ -1,5 +1,6 @@
import { Component, JSX, createRef, render } from "preact"; import { Component, JSX, createRef, render } from "preact";
import { CopyableInputBox } from "../elements/CopyableInputBox"; import { CopyableInputBox } from "../elements/CopyableInputBox";
import { PageTitle } from "../elements/PageTitle";
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 { Page } from "../../types/Page";
@ -51,7 +52,7 @@ type PasswordGeneratorState = {
alphabet: string; alphabet: string;
}; };
export class PasswordGenerator extends Component<unknown, PasswordGeneratorState> { export class PasswordGenerator extends Component<{}, PasswordGeneratorState> {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -89,65 +90,54 @@ export class PasswordGenerator extends Component<unknown, PasswordGeneratorState
// createRef // createRef
render(): JSX.Element { render(): JSX.Element {
return ( return (
<Form onSubmit={() => this.onSubmit()}> <>
<Margin> <PageTitle title={i18next.t("password_generator_title")} />
<h4>{this.getPasswordLengthText(this.state.length)}</h4> <Form onSubmit={() => this.onSubmit()}>
</Margin> <Margin>
<Margin> <h4>{this.getPasswordLengthText(this.state.length)}</h4>
<input </Margin>
class="uk-range uk-width-1-2" <Margin>
name="length" <input
type="range" class="uk-range uk-width-1-2"
value={this.state.length} name="length"
max={passwordLengthMax.toString()} type="range"
min={passwordLengthMin.toString()} value={this.state.length}
ref={this.passwordLengthSlider} max={passwordLengthMax.toString()}
onInput={() => { min={passwordLengthMin.toString()}
this.updateLength(); ref={this.passwordLengthSlider}
}} onInput={() => {
/> this.updateLength();
</Margin> }}
<Margin> />
<select </Margin>
class="uk-select uk-width-1-2" <Margin>
ref={this.alphabetSelector} <select
onInput={() => { class="uk-select uk-width-1-2"
this.updateAlphabet(); ref={this.alphabetSelector}
}} onInput={() => {
> this.updateAlphabet();
<option value={alphabets.SECURE}>a-z a-Z 0-9 specials</option> }}
<option value={alphabets.SMOL}>a-z 0-9</option> >
<option value={alphabets.HEX}>A-F 1-9</option> <option value={alphabets.SECURE}>a-z a-Z 0-9 specials</option>
</select> <option value={alphabets.SMOL}>a-z 0-9</option>
</Margin> <option value={alphabets.HEX}>A-F 1-9</option>
</select>
</Margin>
<CopyableInputBox <CopyableInputBox
text={genPassword({ text={genPassword({
length: this.state.length, length: this.state.length,
alphabet: this.state.alphabet, alphabet: this.state.alphabet,
})} })}
copyable copyable
/> />
<Margin> <Margin>
<button class="uk-button uk-button-primary uk-margin-bottom" type="submit"> <button class="uk-button uk-button-primary uk-margin-bottom" type="submit">
{i18next.t("password_generator_generate_btn")} {i18next.t("password_generator_generate_btn")}
</button> </button>
</Margin> </Margin>
</Form> </Form>
</>
); );
} }
} }
export class PwGenPage extends Page {
constructor() {
super();
}
async render(): Promise<void> {
render(<PasswordGenerator />, this.router.pageContentElement);
}
get name(): string {
return i18next.t("password_generator_title");
}
}

View file

@ -2,47 +2,45 @@ import { Form } from "../../elements/Form";
import { MarginInline } from "../../elements/MarginInline"; import { MarginInline } from "../../elements/MarginInline";
import { Page } from "../../../types/Page"; import { Page } from "../../../types/Page";
import { deleteMount } from "../../../api/sys/deleteMount"; import { deleteMount } from "../../../api/sys/deleteMount";
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 { PageTitle } from "../../elements/PageTitle";
import { route } from "preact-router";
export class DeleteSecretsEnginePage extends Page { export class DeleteSecretsEngine extends Component<DefaultPageProps> {
constructor() { render() {
super();
}
async render(): Promise<void> { return (
render( <>
<Form <PageTitle title={i18next.t("delete_secrets_engine_title", { mount: this.props.matches["mount"] })}/>
onSubmit={async () => { <Form
await this.onSubmit(); onSubmit={async () => {
}} await this.onSubmit();
> }}
<p>{i18next.t("delete_secrets_engine_message")}</p> >
<p>{i18next.t("delete_secrets_engine_message")}</p>
<p class="uk-text-danger" id="errorText" /> <p class="uk-text-danger" id="errorText" />
<MarginInline> <MarginInline>
<button class="uk-button uk-button-danger" type="submit"> <button class="uk-button uk-button-danger" type="submit">
{i18next.t("delete_secrets_engine_delete_btn")} {i18next.t("delete_secrets_engine_delete_btn")}
</button> </button>
</MarginInline> </MarginInline>
</Form>, </Form>
this.router.pageContentElement, </>
); );
} }
async onSubmit(): Promise<void> { async onSubmit(): Promise<void> {
try { try {
await deleteMount(this.state.baseMount); await deleteMount(this.props.matches["mount"]);
await this.router.changePage("SECRETS_HOME"); route("/secrets");
} catch (e: unknown) { } catch (e: unknown) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);
} }
} }
get name(): string {
return i18next.t("delete_secrets_engine_title", { mount: this.state.baseMount });
}
} }

View file

@ -1,54 +1,40 @@
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { deleteSecret } from "../../../../api/kv/deleteSecret"; import { deleteSecret } from "../../../../api/kv/deleteSecret";
import { render } from "preact"; import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { splitKVMount } from "./splitKVMount";
export class KeyValueDelete extends Component<DefaultPageProps> {
render() {
const mount = this.props.matches["mount"];
const mountSplit = splitKVMount(mount);
const baseMount = mountSplit.baseMount;
const secretPath = mountSplit.restOfMount;
const item = this.props.matches["item"];
return (
<>
<SecretTitleElement type="kv" item={this.props.matches["item"]} mount={mount} suffix={i18next.t("kv_sec_edit_suffix")} />
<div>
<h5>{i18next.t("kv_delete_text")}</h5>
<button
class="uk-button uk-button-danger"
onClick={async () => {
await deleteSecret(
baseMount,
secretPath.map((e) => e + "/"),
item,
);
window.history.back();
}}
>
{i18next.t("kv_delete_btn")}
</button>
</div>
</>
export class KeyValueDeletePage extends Page {
constructor() {
super();
}
async goBack(): Promise<void> {
if (this.state.secretVersion != null) {
this.state.secretVersion = null;
await this.router.changePage("KEY_VALUE_SECRET");
} else {
this.state.secretItem = "";
await this.router.changePage("KEY_VALUE_VIEW");
}
}
async render(): Promise<void> {
render(
<div>
<h5>{i18next.t("kv_delete_text")}</h5>
<button
class="uk-button uk-button-danger"
onClick={async () => {
await deleteSecret(
this.state.baseMount,
this.state.secretMountType,
this.state.secretPath,
this.state.secretItem,
this.state.secretVersion,
);
await this.goBack();
}}
>
{i18next.t("kv_delete_btn")}
</button>
</div>,
this.router.pageContentElement,
); );
} }
async renderPageTitle(): Promise<void> {
render(
<SecretTitleElement page={this} suffix={i18next.t("kv_delete_suffix")} />,
this.router.pageTitleElement,
);
}
get name(): string {
return i18next.t("kv_delete_title");
}
} }

View file

@ -49,7 +49,6 @@ export class KeyValueNewPage extends Page {
try { try {
await createOrUpdateSecret( await createOrUpdateSecret(
this.state.baseMount, this.state.baseMount,
this.state.secretMountType,
this.state.secretPath, this.state.secretPath,
path, path,
keyData, keyData,
@ -61,12 +60,12 @@ export class KeyValueNewPage extends Page {
} }
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("kv_new_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("kv_new_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("kv_new_title"); return i18next.t("kv_new_title");

View file

@ -9,6 +9,10 @@ import { getSecret } from "../../../../api/kv/getSecret";
import { sortedObjectMap } from "../../../../utils"; import { sortedObjectMap } from "../../../../utils";
import { undeleteSecret } from "../../../../api/kv/undeleteSecret"; import { undeleteSecret } from "../../../../api/kv/undeleteSecret";
import i18next from "i18next"; import i18next from "i18next";
import { splitKVMount } from "./splitKVMount";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { getMount } from "../../../../api/sys/getMounts";
import { route } from "preact-router";
export type KVSecretViewProps = { export type KVSecretViewProps = {
kvData: Record<string, unknown>; kvData: Record<string, unknown>;
@ -41,119 +45,92 @@ export class KVSecretVew extends Component<KVSecretViewProps, unknown> {
} }
} }
export class KeyValueSecretPage extends Page { type KeyValueSecretState = {
constructor() { baseMount: string;
super(); secretPath: string[];
} secretItem: string;
async goBack(): Promise<void> { caps: string[];
if (this.state.secretVersion != null) { secretInfo: Record<string, unknown>;
this.state.secretVersion = null; }
await this.router.changePage("KEY_VALUE_VERSIONS");
} else { export class KeyValueSecret extends Component<DefaultPageProps, KeyValueSecretState> {
this.state.secretItem = ""; async componentDidMount() {
await this.router.changePage("KEY_VALUE_VIEW"); const mount = this.props.matches["mount"];
} const mountSplit = splitKVMount(mount);
} const baseMount = mountSplit.baseMount;
async render(): Promise<void> { const secretPath = mountSplit.restOfMount;
const secretItem = this.props.matches["item"];
const caps = ( const caps = (
await getCapabilities(this.state.baseMount, this.state.secretPath, this.state.secretItem) await getCapabilities(baseMount, secretPath, secretItem)
).capabilities; ).capabilities;
let 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 secretInfo = await getSecret( const secretInfo = await getSecret(
this.state.baseMount, baseMount,
this.state.secretMountType, secretPathAPI,
this.state.secretPath, secretItem,
this.state.secretItem,
this.state.secretVersion,
); );
this.setState({
baseMount,
secretPath,
secretItem,
caps,
secretInfo
})
// On kv-v2, secrets can be deleted temporarily with the ability to restore }
// Do not show any buttons when the secret is deleted. render() {
const secretIsDeleted = secretInfo == null && this.state.secretMountType == "kv-v2"; if (!this.state.baseMount) return;
render( // TODO: maybe add some sort of version viewing
<div> // no idea what gonna do for that
<p id="buttonsBlock">
{ return (
// Delete Button <>
!secretIsDeleted && caps.includes("delete") && ( <SecretTitleElement type="kv" item={this.props.matches["item"]} mount={this.props.matches["mount"]} suffix={i18next.t("kv_sec_edit_suffix")} />
<div>
<p id="buttonsBlock">
{
// Delete Button
this.state.caps.includes("delete") && (
<button
class="uk-button uk-button-danger"
onClick={async () => {
route("/secrets/kv/delete/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/"))
//await this.router.changePage("KEY_VALUE_DELETE");
}}
>
{i18next.t("kv_secret_delete_btn")}
</button>
)
}
{this.state.caps.includes("update") && (
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
await this.router.changePage("KEY_VALUE_DELETE"); route("/secrets/kv/edit/" + this.state.secretItem + "/" + this.state.baseMount + "/" + this.state.secretPath.join("/"))
}} }}
> >
{((): string => { {i18next.t("kv_secret_edit_btn")}
// Delete Secret on kv-v1
let deleteButtonText = i18next.t("kv_secret_delete_btn");
if (this.state.secretMountType == "kv-v2" && this.state.secretVersion == null) {
// Delete All
deleteButtonText = i18next.t("kv_secret_delete_all_btn");
} else if (
this.state.secretMountType == "kv-v2" &&
this.state.secretVersion != null
) {
// Delete Version X
deleteButtonText = i18next.t("kv_secret_delete_version_btn", {
version: this.state.secretVersion,
});
}
return deleteButtonText;
})()}
</button> </button>
) )}
} </p>
{!secretIsDeleted && caps.includes("update") && this.state.secretVersion == null && (
<button
class="uk-button uk-button-primary"
onClick={async () => {
await this.router.changePage("KEY_VALUE_SECRET_EDIT");
}}
>
{i18next.t("kv_secret_edit_btn")}
</button>
)}
{!secretIsDeleted && this.state.secretMountType == "kv-v2" && (
<button
class="uk-button uk-button-secondary"
onClick={async () => {
await this.router.changePage("KEY_VALUE_VERSIONS");
}}
>
{i18next.t("kv_secret_versions_btn")}
</button>
)}
</p>
{!secretIsDeleted && <KVSecretVew kvData={secretInfo} />} {<KVSecretVew kvData={this.state.secretInfo} />}
</div>
</>
{secretIsDeleted && (
<>
<p>{i18next.t("kv_secret_deleted_text")}</p>
<button
class="uk-button uk-button-primary"
onClick={async () => {
await undeleteSecret(
this.state.baseMount,
this.state.secretPath,
this.state.secretItem,
this.state.secretVersion,
);
await this.router.refresh();
}}
>
{i18next.t("kv_secret_restore_btn")}
</button>
</>
)}
</div>,
this.router.pageContentElement,
); );
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render(<SecretTitleElement page={this} />, this.router.pageTitleElement); // render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
} //}
get name(): string { get name(): string {
return i18next.t("kv_secret_title"); return i18next.t("kv_secret_title");

View file

@ -7,21 +7,26 @@ import { getSecret } from "../../../../api/kv/getSecret";
import { setErrorText } from "../../../../pageUtils"; 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 { splitKVMount } from "./splitKVMount";
import { route } from "preact-router";
//import { highlightElement } from "prismjs"; //import { highlightElement } from "prismjs";
export type KVEditProps = { export type KVEditProps = {
page: Page; baseMount: string;
secretPath: string[];
secretItem: string;
}; };
type KVEditState = type KVEditState =
| { | {
dataLoaded: false; dataLoaded: false;
} }
| { | {
dataLoaded: true; dataLoaded: true;
kvData: Record<string, unknown>; kvData: Record<string, unknown>;
code: string; code: string;
}; };
export class KVEditor extends Component<KVEditProps, KVEditState> { export class KVEditor extends Component<KVEditProps, KVEditState> {
constructor() { constructor() {
@ -41,13 +46,12 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
} }
await createOrUpdateSecret( await createOrUpdateSecret(
this.props.page.state.baseMount, this.props.baseMount,
this.props.page.state.secretMountType, this.props.secretPath.map((e) => e + "/"),
this.props.page.state.secretPath, this.props.secretItem,
this.props.page.state.secretItem,
JSON.parse(editorContent), JSON.parse(editorContent),
); );
await this.props.page.router.changePage("KEY_VALUE_SECRET"); window.history.back();
} }
onCodeUpdate(code: string): void { onCodeUpdate(code: string): void {
@ -58,10 +62,9 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
loadData(): void { loadData(): void {
void getSecret( void getSecret(
this.props.page.state.baseMount, this.props.baseMount,
this.props.page.state.secretMountType, this.props.secretPath.map((e) => e + "/"),
this.props.page.state.secretPath, this.props.secretItem,
this.props.page.state.secretItem,
).then((kvData) => { ).then((kvData) => {
this.setState({ this.setState({
dataLoaded: true, dataLoaded: true,
@ -104,24 +107,29 @@ export class KVEditor extends Component<KVEditProps, KVEditState> {
} }
} }
export class KeyValueSecretEditPage extends Page { export class KeyValueSecretEdit extends Component<DefaultPageProps> {
constructor() { render() {
super(); const mount = this.props.matches["mount"];
} const mountSplit = splitKVMount(mount);
async goBack(): Promise<void> { const baseMount = mountSplit.baseMount;
await this.router.changePage("KEY_VALUE_SECRET"); const restOfMount = mountSplit.restOfMount;
} const item = this.props.matches["item"];
async render(): Promise<void> {
render(<KVEditor page={this} />, this.router.pageContentElement);
}
async renderPageTitle(): Promise<void> { return (
render( <>
<SecretTitleElement page={this} suffix={i18next.t("kv_sec_edit_suffix")} />, <SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} suffix={i18next.t("kv_sec_edit_suffix")} />
this.router.pageTitleElement, <KVEditor baseMount={baseMount} secretPath={restOfMount} secretItem={item} />
</>
); );
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("kv_sec_edit_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string { get name(): string {
return i18next.t("kv_sec_edit_title"); return i18next.t("kv_sec_edit_title");
} }

View file

@ -1,55 +0,0 @@
import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement";
import { getSecretMetadata } from "../../../../api/kv/getSecretMetadata";
import { objectToMap } from "../../../../utils";
import { render } from "preact";
import i18next from "i18next";
export class KeyValueVersionsPage extends Page {
constructor() {
super();
}
async goBack(): Promise<void> {
if (this.state.secretVersion != null) {
this.state.secretVersion = null;
}
await this.router.changePage("KEY_VALUE_SECRET");
}
async render(): Promise<void> {
const metadata = await getSecretMetadata(
this.state.baseMount,
this.state.secretPath,
this.state.secretItem,
);
const versions = Array.from(objectToMap(metadata.versions).keys());
render(
<ul class="uk-nav uk-nav-default">
{versions.map((ver) => (
<li>
<a
onClick={async () => {
this.state.secretVersion = ver;
await this.router.changePage("KEY_VALUE_SECRET");
}}
>
{`v${ver}`}
</a>
</li>
))}
</ul>,
this.router.pageContentElement,
);
}
async renderPageTitle(): Promise<void> {
render(
<SecretTitleElement page={this} suffix={i18next.t("kv_sec_versions_suffix")} />,
this.router.pageTitleElement,
);
}
get name(): string {
return i18next.t("kv_sec_versions_title");
}
}

View file

@ -2,15 +2,17 @@ import { Component, createRef, JSX, render } from "preact";
import { DoesNotExistError } from "../../../../types/internalErrors"; import { DoesNotExistError } from "../../../../types/internalErrors";
import { Page } from "../../../../types/Page"; 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 { getSecrets } from "../../../../api/kv/getSecrets"; import { getSecrets } from "../../../../api/kv/getSecrets";
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 { splitKVMount } from "./splitKVMount";
export type KVKeysListProps = {
page: Page; export type KVKeysListProps = DefaultPageProps & {
baseMount: string; baseMount: string;
secretMountType: string;
secretPath: string[]; secretPath: string[];
}; };
@ -20,17 +22,15 @@ type KVKeysListState = {
searchQuery: string; searchQuery: string;
}; };
function SecretsList(secrets: string[], page: Page): JSX.Element[] { function SecretsList(baseMount: string, restOfMount: string[], secrets: string[]): JSX.Element[] {
return secrets.map((secret) => ( return secrets.map((secret) => (
<li> <li>
<a <a
onClick={async () => { onClick={async () => {
if (secret.endsWith("/")) { if (secret.endsWith("/")) {
page.state.pushSecretPath(secret); route("/secrets/kv/list/" + baseMount + "/" + restOfMount.join("/") + "/" + secret)
await page.router.changePage("KEY_VALUE_VIEW");
} else { } else {
page.state.secretItem = secret; route("/secrets/kv/view/" + secret + "/" + baseMount + "/" + restOfMount.join("/"))
await page.router.changePage("KEY_VALUE_SECRET");
} }
}} }}
> >
@ -51,12 +51,10 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
} }
async loadData(): Promise<void> { async loadData(): Promise<void> {
const page = this.props.page;
try { try {
const keys = await getSecrets( const keys = await getSecrets(
this.props.baseMount, this.props.baseMount,
this.props.secretMountType, this.props.secretPath.map((e) => e + "/"),
this.props.secretPath,
); );
this.setState({ this.setState({
dataLoaded: true, dataLoaded: true,
@ -68,12 +66,13 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
if (error == DoesNotExistError) { if (error == DoesNotExistError) {
// getSecrets also 404's on no keys so dont go all the way back. // getSecrets also 404's on no keys so dont go all the way back.
if (this.props.secretPath.length != 0) { if (this.props.secretPath.length != 0) {
await page.goBack(); window.history.back();
return; return;
} }
} else { } else {
setErrorText(error.message); setErrorText(error.message);
} }
this.setState({ this.setState({
dataLoaded: true, dataLoaded: true,
keys: null, keys: null,
@ -84,7 +83,6 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
componentDidUpdate(prevProps: KVKeysListProps): void { componentDidUpdate(prevProps: KVKeysListProps): void {
if ( if (
prevProps.baseMount !== this.props.baseMount || prevProps.baseMount !== this.props.baseMount ||
prevProps.secretMountType !== this.props.secretMountType ||
prevProps.secretPath !== this.props.secretPath prevProps.secretPath !== this.props.secretPath
) { ) {
this.setState({ this.setState({
@ -131,7 +129,7 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
if (this.state.searchQuery.length > 0) { if (this.state.searchQuery.length > 0) {
secrets = secrets.filter((secret) => secret.includes(this.state.searchQuery)); secrets = secrets.filter((secret) => secret.includes(this.state.searchQuery));
} }
return SecretsList(secrets, this.props.page); return SecretsList(this.props.baseMount, this.props.secretPath, secrets);
})()} })()}
</ul> </ul>
</> </>
@ -139,65 +137,66 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
} }
} }
export class KeyValueViewPage extends Page { type KeyValueViewState = {
constructor() { pathCaps: string[];
super(); mountCaps: string[];
} }
async goBack(): Promise<void> {
if (this.state.secretPath.length != 0) {
this.state.popSecretPath();
await this.router.changePage("KEY_VALUE_VIEW");
} else {
await this.router.changePage("SECRETS_HOME");
}
}
async render(): Promise<void> {
const mountsPath = "/sys/mounts/" + this.state.baseMount;
const currentPath = this.state.baseMount + this.state.secretPath.join("/");
const caps = await getCapabilitiesPath([mountsPath, currentPath]);
const mountCaps = caps[mountsPath];
const pathCaps = caps[currentPath];
render( export class KeyValueView extends Component<DefaultPageProps, KeyValueViewState> {
async componentDidMount() {
const mount = this.props.matches["mount"];
const mountSplit = splitKVMount(mount);
const baseMount = mountSplit.baseMount;
const restOfMount = mountSplit.restOfMount;
const mountsPath = "/sys/mounts/" + baseMount;
const currentPath = baseMount + restOfMount.join();
const caps = await getCapabilitiesPath([mountsPath, currentPath]);
this.setState({
mountCaps: caps[mountsPath],
pathCaps: caps[currentPath],
});
}
render() {
if (!this.state.pathCaps) return;
const mount = this.props.matches["mount"];
const mountSplit = splitKVMount(mount);
const baseMount = mountSplit.baseMount;
const restOfMount = mountSplit.restOfMount;
return (
<> <>
<SecretTitleElement type="kv" mount={mount} item={this.props.matches["item"]} />
<p> <p>
{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 () => {
await this.router.changePage("KEY_VALUE_NEW_SECRET"); route("/secrets/kv/new/" + mount)
}} }}
> >
{i18next.t("kv_view_new_btn")} {i18next.t("kv_view_new_btn")}
</button> </button>
)} )}
{this.state.secretPath.length == 0 && mountCaps.includes("delete") && ( {restOfMount.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 () => {
await this.router.changePage("DELETE_SECRET_ENGINE"); route("/secrets/delete_engine/" + mount)
}} }}
> >
{i18next.t("kv_view_delete_btn")} {i18next.t("kv_view_delete_btn")}
</button> </button>
)} )}
</p> </p>
{this.state.secretMountType == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>} {//"TODO: " == "cubbyhole" && <p>{i18next.t("kv_view_cubbyhole_text")}</p>}
<KVKeysList }
page={this} <KVKeysList baseMount={baseMount} secretPath={restOfMount} state={this.props.state} />
baseMount={this.state.baseMount} </>
secretMountType={this.state.secretMountType}
secretPath={this.state.secretPath}
/>
</>,
this.router.pageContentElement,
); );
} }
async renderPageTitle(): Promise<void> {
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
}
get name(): string { get name(): string {
return i18next.t("kv_view_title"); return i18next.t("kv_view_title");
} }

View file

@ -0,0 +1,10 @@
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

@ -3,47 +3,45 @@ import { Margin } from "../../../elements/Margin";
import { MarginInline } from "../../../elements/MarginInline"; import { MarginInline } from "../../../elements/MarginInline";
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { newMount } from "../../../../api/sys/newMount"; import { newMount } from "../../../../api/sys/newMount";
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 { PageTitle } from "../../../elements/PageTitle";
import { route } from "preact-router";
export class NewKVEnginePage extends Page { export class NewKVEngine extends Component {
constructor() { render() {
super(); return (
} <>
async goBack(): Promise<void> { <PageTitle title={i18next.t("new_kv_engine_title")} />
await this.router.changePage("SECRETS_HOME"); <Form onSubmit={(data) => this.submit(data)}>
} <Margin>
async render(): Promise<void> { <input
render( class="uk-input uk-form-width-medium"
<Form onSubmit={(data) => this.submit(data)}> name="name"
<Margin> type="text"
<input placeholder={i18next.t("new_kv_engine_name_input")}
class="uk-input uk-form-width-medium" required
name="name" />
type="text" </Margin>
placeholder={i18next.t("new_kv_engine_name_input")} <Margin>
required <select class="uk-input uk-form-width-medium" name="version">
/> <option label={i18next.t("new_kv_engine_version_2")} value="2">
</Margin> {i18next.t("new_kv_engine_version_2")}
<Margin> </option>
<select class="uk-input uk-form-width-medium" name="version"> <option label={i18next.t("new_kv_engine_version_1")} value="1">
<option label={i18next.t("new_kv_engine_version_2")} value="2"> {i18next.t("new_kv_engine_version_1")}
{i18next.t("new_kv_engine_version_2")} </option>
</option> </select>
<option label={i18next.t("new_kv_engine_version_1")} value="1"> </Margin>
{i18next.t("new_kv_engine_version_1")} <p class="uk-text-danger" id="errorText" />
</option> <MarginInline>
</select> <button class="uk-button uk-button-primary" type="submit">
</Margin> {i18next.t("new_kv_engine_create_btn")}
<p class="uk-text-danger" id="errorText" /> </button>
<MarginInline> </MarginInline>
<button class="uk-button uk-button-primary" type="submit"> </Form>
{i18next.t("new_kv_engine_create_btn")} </>
</button>
</MarginInline>
</Form>,
this.router.pageContentElement,
); );
} }
@ -59,16 +57,10 @@ export class NewKVEnginePage extends Page {
version: version, version: version,
}, },
}); });
this.state.secretMountType = "kv-v" + version; route("/secrets/kv/list/" + name + "/")
this.state.baseMount = name + "/";
await this.router.changePage("KEY_VALUE_VIEW");
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);
} }
} }
get name(): string {
return i18next.t("new_kv_engine_title");
}
} }

View file

@ -3,37 +3,35 @@ import { Margin } from "../../../elements/Margin";
import { MarginInline } from "../../../elements/MarginInline"; import { MarginInline } from "../../../elements/MarginInline";
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { newMount } from "../../../../api/sys/newMount"; import { newMount } from "../../../../api/sys/newMount";
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 { route } from "preact-router";
import { PageTitle } from "../../../elements/PageTitle";
export class NewTOTPEnginePage extends Page { export class NewTOTPEngine extends Component {
constructor() { render() {
super(); return (
} <>
async goBack(): Promise<void> { <PageTitle title={i18next.t("new_totp_engine_title")} />
await this.router.changePage("SECRETS_HOME"); <Form onSubmit={(data) => this.submit(data)}>
} <Margin>
async render(): Promise<void> { <input
render( class="uk-input uk-form-width-medium"
<Form onSubmit={(data) => this.submit(data)}> name="name"
<Margin> type="text"
<input placeholder={i18next.t("new_totp_engine_name_input")}
class="uk-input uk-form-width-medium" required
name="name" />
type="text" </Margin>
placeholder={i18next.t("new_totp_engine_name_input")} <p class="uk-text-danger" id="errorText" />
required <MarginInline>
/> <button class="uk-button uk-button-primary" type="submit">
</Margin> {i18next.t("new_totp_engine_create_btn")}
<p class="uk-text-danger" id="errorText" /> </button>
<MarginInline> </MarginInline>
<button class="uk-button uk-button-primary" type="submit"> </Form>,
{i18next.t("new_totp_engine_create_btn")} </>
</button>
</MarginInline>
</Form>,
this.router.pageContentElement,
); );
} }
@ -45,9 +43,7 @@ export class NewTOTPEnginePage extends Page {
name: name, name: name,
type: "totp", type: "totp",
}); });
this.state.secretMountType = "totp"; route("/secrets/totp/list/" + name + "/")
this.state.baseMount = name + "/";
await this.router.changePage("TOTP_VIEW");
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);

View file

@ -3,37 +3,35 @@ import { Margin } from "../../../elements/Margin";
import { MarginInline } from "../../../elements/MarginInline"; import { MarginInline } from "../../../elements/MarginInline";
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { newMount } from "../../../../api/sys/newMount"; import { newMount } from "../../../../api/sys/newMount";
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 { PageTitle } from "../../../elements/PageTitle";
import { route } from "preact-router";
export class NewTransitEnginePage extends Page { export class NewTransitEngine extends Component {
constructor() { render() {
super(); return (
} <>
async goBack(): Promise<void> { <PageTitle title={i18next.t("new_totp_engine_title")} />
await this.router.changePage("SECRETS_HOME"); <Form onSubmit={(data) => this.submit(data)}>
} <Margin>
async render(): Promise<void> { <input
render( class="uk-input uk-form-width-medium"
<Form onSubmit={(data) => this.submit(data)}> name="name"
<Margin> type="text"
<input placeholder={i18next.t("new_transit_engine_name_input")}
class="uk-input uk-form-width-medium" required
name="name" />
type="text" </Margin>
placeholder={i18next.t("new_transit_engine_name_input")} <p class="uk-text-danger" id="errorText" />
required <MarginInline>
/> <button class="uk-button uk-button-primary" type="submit">
</Margin> {i18next.t("new_transit_engine_create_btn")}
<p class="uk-text-danger" id="errorText" /> </button>
<MarginInline> </MarginInline>
<button class="uk-button uk-button-primary" type="submit"> </Form>,
{i18next.t("new_transit_engine_create_btn")} </>
</button>
</MarginInline>
</Form>,
this.router.pageContentElement,
); );
} }
@ -45,9 +43,7 @@ export class NewTransitEnginePage extends Page {
name: name, name: name,
type: "transit", type: "transit",
}); });
this.state.secretMountType = "transit"; route("/secrets/transit/list/" + name + "/")
this.state.baseMount = name + "/";
await this.router.changePage("TRANSIT_VIEW");
} catch (e) { } catch (e) {
const error = e as Error; const error = e as Error;
setErrorText(error.message); setErrorText(error.message);

View file

@ -1,44 +1,40 @@
import { Grid, GridSizes } from "../../elements/Grid"; import { Grid, GridSizes } from "../../elements/Grid";
import { Page } from "../../../types/Page"; import { Page } from "../../../types/Page";
import { Tile } from "../../elements/Tile"; import { Tile } from "../../elements/Tile";
import { render } from "preact"; import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
import { PageTitle } from "../../elements/PageTitle";
export class NewSecretsEnginePage extends Page { export class NewSecretsEngine extends Component {
constructor() { render() {
super(); return (
} <>
<PageTitle title={i18next.t("new_secrets_engine_title")} />
async render(): Promise<void> {
render(
<Grid size={GridSizes.MATCHING_TWO_ROWS}> <Grid size={GridSizes.MATCHING_TWO_ROWS}>
<Tile <Tile
title={i18next.t("new_secrets_engine_kv_title")} title={i18next.t("new_secrets_engine_kv_title")}
description={i18next.t("new_secrets_engine_kv_description")} description={i18next.t("new_secrets_engine_kv_description")}
onclick={async () => { onclick={async () => {
await this.router.changePage("NEW_KV_ENGINE"); route("/secrets/new_secrets_engine/kv")
}} }}
/> />
<Tile <Tile
title={i18next.t("new_secrets_engine_totp_title")} title={i18next.t("new_secrets_engine_totp_title")}
description={i18next.t("new_secrets_engine_totp_description")} description={i18next.t("new_secrets_engine_totp_description")}
onclick={async () => { onclick={async () => {
await this.router.changePage("NEW_TOTP_ENGINE"); route("/secrets/new_secrets_engine/totp")
}} }}
/> />
<Tile <Tile
title={i18next.t("new_secrets_engine_transit_title")} title={i18next.t("new_secrets_engine_transit_title")}
description={i18next.t("new_secrets_engine_transit_description")} description={i18next.t("new_secrets_engine_transit_description")}
onclick={async () => { onclick={async () => {
await this.router.changePage("NEW_TRANSIT_ENGINE"); route("/secrets/new_secrets_engine/transit")
}} }}
/> />
</Grid>, </Grid>
this.router.pageContentElement, </>
); );
} }
get name(): string {
return i18next.t("new_secrets_engine_title");
}
} }

View file

@ -1,69 +1,55 @@
import { route } from "preact-router";
import { JSX } from "preact/jsx-runtime"; import { JSX } from "preact/jsx-runtime";
import { Page } from "../../../types/Page"; import { Page } from "../../../types/Page";
import { CopyStateLinkButton } from "../../elements/CopyStateLinkButton";
function currentTitleSecretText(page: Page): string {
let secretItemText = page.state.secretItem;
if (page.state.secretVersion !== null) secretItemText += ` (v${page.state.secretVersion})`;
return secretItemText;
}
type SecretTitleElementProps = { type SecretTitleElementProps = {
page: Page; type: string;
mount: string;
item?: string;
suffix?: string; suffix?: string;
}; };
export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element { export function SecretTitleElement(props: SecretTitleElementProps): JSX.Element {
const page = props.page; const type = props.type;
const mount = props.mount;
const item = props.item || "";
const suffix = props.suffix || ""; const suffix = props.suffix || "";
const baseMount = mount.split("/")[0];
const restOfMount = mount.split("/");
restOfMount.shift();
return ( return (
<div> <h3 class="uk-card-title" id="pageTitle">
<a
onClick={async () => {
await page.router.changePage("SECRETS_HOME");
}}
>
{"/ "}
</a>
<a <div>
onClick={async () => {
page.state.secretPath = [];
page.state.secretItem = "";
page.state.secretVersion = null;
if (
page.state.secretMountType.startsWith("kv") ||
page.state.secretMountType == "cubbyhole"
) {
await page.router.changePage("KEY_VALUE_VIEW");
} else if (page.state.secretMountType == "totp") {
await page.router.changePage("TOTP_VIEW");
} else if (page.state.secretMountType == "transit") {
await page.router.changePage("TRANSIT_VIEW");
}
}}
>
{page.state.baseMount + " "}
</a>
{...page.state.secretPath.map((secretPath, index, secretPaths) => (
<a <a
onClick={async () => { onClick={async () => {
page.state.secretVersion = null; route("/secrets");
if (page.state.secretMountType.startsWith("kv")) {
page.state.secretPath = secretPaths.slice(0, index + 1);
await page.router.changePage("KEY_VALUE_VIEW");
}
}} }}
> >
{secretPath + " "} {"/ "}
</a> </a>
))}
{page.state.secretItem.length != 0 && <span>{currentTitleSecretText(page)}</span>} <a href={"/secrets/" + type + "/list/" + baseMount + "/"}>
{suffix.length != 0 && <span>{suffix}</span>} {baseMount + " "}
<CopyStateLinkButton state={page.state} /> </a>
</div>
{...restOfMount.map((secretPath, index, secretPaths) => (
<a
onClick={async () => {
if (type == "kv") {
let secretPath = secretPaths.slice(0, index + 1);
route("/secrets/kv/list/" + baseMount + "/" + secretPath.join("/"))
}
}}
>
{secretPath + "/" + " "}
</a>
))}
{item.length != 0 && <span>{item}</span>}
{suffix.length != 0 && <span>{suffix}</span>}
</div>
</h3>
); );
} }

View file

@ -1,16 +1,14 @@
import { JSX, render } from "preact"; import { Component, JSX, render } from "preact";
import { MountType, getMounts } from "../../../api/sys/getMounts"; import { MountType, getMounts } from "../../../api/sys/getMounts";
import { Page } from "../../../types/Page"; import { Page } from "../../../types/Page";
import { getCapsPath } from "../../../api/sys/getCapabilities"; import { getCapsPath } from "../../../api/sys/getCapabilities";
import { prePageChecks } from "../../../pageUtils"; import { prePageChecks } from "../../../pageUtils";
import { sortedObjectMap } from "../../../utils"; import { sortedObjectMap } from "../../../utils";
import i18next from "i18next"; import i18next from "i18next";
import { PageState } from "../../../state/PageState";
export type MountLinkProps = { import { DefaultPageProps } from "../../../types/DefaultPageProps";
page: Page; import { route } from "preact-router";
mount: MountType; import { PageTitle } from "../../elements/PageTitle";
baseMount: string;
};
const supportedMountTypes = ["kv", "totp", "transit", "cubbyhole"]; const supportedMountTypes = ["kv", "totp", "transit", "cubbyhole"];
@ -22,97 +20,96 @@ export function isSupportedMount(mount: MountType): boolean {
return true; return true;
} }
export type MountLinkProps = {
state: PageState;
mount: MountType;
baseMount: string;
};
function MountLink(props: MountLinkProps): JSX.Element { function MountLink(props: MountLinkProps): JSX.Element {
const mount = props.mount; const mount = props.mount;
const baseMount = props.baseMount; const baseMount = props.baseMount;
const page = props.page;
const secretMountType = mount.type == "kv" ? "kv-v" + String(mount.options.version) : mount.type;
let linkText = ""; let linkText = "";
let linkPage: string; let mountPathType: string;
if (mount.type == "kv") { if (mount.type == "kv") {
linkText = `K/V (v${mount.options.version}) - ${baseMount}`; linkText = `K/V (v${mount.options.version}) - ${baseMount}`;
linkPage = "KEY_VALUE_VIEW"; mountPathType = "kv";
} else if (mount.type == "totp") { } else if (mount.type == "totp") {
linkText = `TOTP - ${baseMount}`; linkText = `TOTP - ${baseMount}`;
linkPage = "TOTP_VIEW"; mountPathType = "totp";
} else if (mount.type == "transit") { } else if (mount.type == "transit") {
linkText = `Transit - ${baseMount}`; linkText = `Transit - ${baseMount}`;
linkPage = "TRANSIT_VIEW"; mountPathType = "transit";
} else if (mount.type == "cubbyhole") { } else if (mount.type == "cubbyhole") {
linkText = `Cubbyhole - ${baseMount}`; linkText = `Cubbyhole - ${baseMount}`;
linkPage = "KEY_VALUE_VIEW"; mountPathType = "kv";
} }
let link = "/secrets/" + mountPathType + "/list/" + baseMount;
return ( return (
<li> <li>
<a <a href={link}>
onClick={async () => {
page.state.baseMount = baseMount;
page.state.secretMountType = secretMountType;
await page.router.changePage(linkPage);
}}
>
{linkText} {linkText}
</a> </a>
</li> </li>
); );
} }
export class SecretsHomePage extends Page { type SecretsState = {
constructor() { mountsMap: Map<String, MountType>;
super(); capabilities: string[];
} }
async goBack(): Promise<void> {
await this.router.changePage("HOME");
}
async render(): Promise<void> {
if (!(await prePageChecks(this.router))) return;
this.state.baseMount = ""; export class Secrets extends Component<DefaultPageProps, SecretsState> {
this.state.secretPath = []; async componentDidMount() {
this.state.secretItem = ""; if (!(await prePageChecks(this.props.state))) return;
this.state.secretVersion = null;
const mountsCapabilities = await getCapsPath("/sys/mounts"); const mountsCapabilities = await getCapsPath("/sys/mounts");
const mounts = await getMounts(); const mounts = await getMounts();
// sort it by secretPath so it's in alphabetical order consistantly. // sort it by secretPath so it's in alphabetical order consistantly.
const mountsMap = sortedObjectMap(mounts); const mountsMap = sortedObjectMap(mounts);
this.setState({
capabilities: mountsCapabilities,
mountsMap: mountsMap as Map<String, MountType>
})
}
render(
<div> render() {
return this.state.mountsMap && (
<>
<PageTitle title={i18next.t("secrets_home_page_title")} />
<div> <div>
<p> <div>
{mountsCapabilities.includes("sudo") && mountsCapabilities.includes("create") && ( <p>
<button {this.state.capabilities.includes("sudo") && this.state.capabilities.includes("create") && (
class="uk-button uk-button-primary" <button
onClick={async () => { class="uk-button uk-button-primary"
await this.router.changePage("NEW_SECRETS_ENGINE"); onClick={() => {
}} route("/secrets/new_secrets_engine")
> }}
{i18next.t("secrets_home_new_secrets_engine_button")} >
</button> {i18next.t("secrets_home_new_secrets_engine_button")}
)} </button>
</p> )}
</p>
</div>
<div class="uk-margin-top">
<ul class="uk-nav uk-nav-default">
{Array.from(this.state.mountsMap).map((args: [string, MountType]) => {
const baseMount = args[0];
const mount = args[1];
console.log(baseMount, mount)
if (isSupportedMount(mount)) {
return <MountLink state={this.props.state} mount={mount} baseMount={baseMount} />;
}
})}
</ul>
</div>
</div> </div>
<div class="uk-margin-top"> </>
<ul class="uk-nav uk-nav-default">
{Array.from(mountsMap as Map<string, MountType>).map((args: [string, MountType]) => {
const baseMount = args[0];
const mount = args[1];
if (isSupportedMount(mount)) {
return <MountLink page={this} mount={mount} baseMount={baseMount} />;
}
})}
</ul>
</div>
</div>,
this.router.pageContentElement,
); );
} }
get name(): string {
return i18next.t("secrets_home_page_title");
}
} }

View file

@ -1,46 +1,37 @@
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { deleteTOTP } from "../../../../api/totp/deleteTOTP"; import { deleteTOTP } from "../../../../api/totp/deleteTOTP";
import { render } from "preact"; import { Component, render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { route } from "preact-router";
export class TOTPDeletePage extends Page { export class TOTPDelete extends Component<DefaultPageProps> {
constructor() { render() {
super(); return (
}
async cleanup(): Promise<void> {
this.state.secretItem = "";
}
async goBack(): Promise<void> {
this.state.secretItem = "";
await this.router.changePage("TOTP_VIEW");
}
async render(): Promise<void> {
render(
<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 () => {
await deleteTOTP(this.state.baseMount, this.state.secretItem); let mount = this.props.matches["mount"];
await this.goBack(); let item = this.props.matches["item"];
await deleteTOTP(mount, item);
route("/secrets/totp/list/" + mount)
}} }}
> >
{i18next.t("kv_delete_btn")} {i18next.t("kv_delete_btn")}
</button> </button>
</div>, </div>
this.router.pageContentElement,
); );
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("totp_delete_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("totp_delete_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("totp_delete_title"); return i18next.t("totp_delete_title");

View file

@ -8,6 +8,8 @@ import { SecretTitleElement } from "../SecretTitleElement";
import { addNewTOTP } from "../../../../api/totp/addNewTOTP"; import { addNewTOTP } from "../../../../api/totp/addNewTOTP";
import { setErrorText } from "../../../../pageUtils"; import { setErrorText } from "../../../../pageUtils";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
function replaceAll(str: string, replace: string, replaceWith: string): string { function replaceAll(str: string, replace: string, replaceWith: string): string {
return str.replace(new RegExp(replace, "g"), replaceWith); return str.replace(new RegExp(replace, "g"), replaceWith);
@ -19,7 +21,7 @@ function removeDashSpaces(str: string): string {
return str; return str;
} }
export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }> { export class TOTPNewForm extends Component<{ mount: string }, { qrMode: boolean }> {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -30,7 +32,6 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
uriInputRef = createRef<HTMLInputElement>(); uriInputRef = createRef<HTMLInputElement>();
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
const page = this.props.page;
const parms = { const parms = {
url: data.get("uri") as string, url: data.get("uri") as string,
key: removeDashSpaces(data.get("key") as string).toUpperCase(), key: removeDashSpaces(data.get("key") as string).toUpperCase(),
@ -39,8 +40,8 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
}; };
try { try {
await addNewTOTP(page.state.baseMount, parms); await addNewTOTP(this.props.mount, parms);
await page.router.changePage("TOTP_VIEW"); route("/secrets/totp/list/" + this.props.mount)
} 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}`);
@ -118,24 +119,24 @@ export class TOTPNewForm extends Component<{ page: Page }, { qrMode: boolean }>
} }
} }
export class TOTPNewPage extends Page { export class TOTPNew extends Component<DefaultPageProps> {
constructor() { render() {
super(); let mount = this.props.matches["mount"];
} return (
async goBack(): Promise<void> { <>
await this.router.changePage("TOTP_VIEW"); <SecretTitleElement type="totp" mount={mount} suffix={i18next.t("totp_new_suffix")} />
} <TOTPNewForm mount={this.props.matches["mount"]} />
async render(): Promise<void> { </>
render(<TOTPNewForm page={this} />, this.router.pageContentElement);
}
async renderPageTitle(): Promise<void> {
render(
<SecretTitleElement page={this} suffix={i18next.t("totp_new_suffix")} />,
this.router.pageTitleElement,
); );
} }
//async renderPageTitle(): Promise<void> {
// render(
// <SecretTitleElement page={this} suffix={i18next.t("totp_new_suffix")} />,
// this.router.pageTitleElement,
// );
//}
get name(): string { get name(): string {
return i18next.t("totp_new_title"); return i18next.t("totp_new_title");
} }

View file

@ -5,15 +5,21 @@ import { Grid, GridSizes } from "../../../elements/Grid";
import { MarginInline } from "../../../elements/MarginInline"; import { MarginInline } from "../../../elements/MarginInline";
import { Page } from "../../../../types/Page"; import { Page } from "../../../../types/Page";
import { SecretTitleElement } from "../SecretTitleElement"; import { SecretTitleElement } from "../SecretTitleElement";
import { getCapabilitiesPath, getCapsPath } from "../../../../api/sys/getCapabilities"; import { CapabilitiesType, getCapabilitiesPath, getCapsPath } from "../../../../api/sys/getCapabilities";
import { getTOTPCode } from "../../../../api/totp/getTOTPCode"; import { getTOTPCode } from "../../../../api/totp/getTOTPCode";
import { getTOTPKeys } from "../../../../api/totp/getTOTPKeys"; import { getTOTPKeys } from "../../../../api/totp/getTOTPKeys";
import { removeDoubleSlash } from "../../../../utils"; import { removeDoubleSlash } from "../../../../utils";
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";
type TOTPGridItemProps = {
mount: string; totpKey: string; canDelete: boolean;
}
export class RefreshingTOTPGridItem extends Component< export class RefreshingTOTPGridItem extends Component<
{ baseMount: string; totpKey: string; page: Page; canDelete: boolean }, TOTPGridItemProps,
{ totpValue: string } { totpValue: string }
> { > {
constructor() { constructor() {
@ -23,7 +29,7 @@ export class RefreshingTOTPGridItem extends Component<
timer: unknown; timer: unknown;
updateTOTPCode(): void { updateTOTPCode(): void {
void getTOTPCode(this.props.baseMount, this.props.totpKey).then((code) => { void getTOTPCode(this.props.mount, this.props.totpKey).then((code) => {
this.setState({ totpValue: code }); this.setState({ totpValue: code });
}); });
} }
@ -50,9 +56,7 @@ export class RefreshingTOTPGridItem extends Component<
<button <button
class="uk-button uk-button-danger" class="uk-button uk-button-danger"
onClick={async () => { onClick={async () => {
const page = this.props.page; route("/secrets/totp/delete/" + this.props.mount + "/" + this.props.totpKey);
page.state.secretItem = this.props.totpKey;
await page.router.changePage("TOTP_DELETE");
}} }}
> >
{i18next.t("totp_view_secret_delete_btn")} {i18next.t("totp_view_secret_delete_btn")}
@ -65,34 +69,77 @@ export class RefreshingTOTPGridItem extends Component<
} }
} }
export class TOTPViewPage extends Page { type TOTPViewProps = DefaultPageProps & {
}
type TOTPViewState = {
capabilities?: CapabilitiesType;
totpItems: TOTPGridItemProps[];
}
export class TOTPView extends Component<TOTPViewProps, TOTPViewState> {
constructor() { constructor() {
super(); super();
this.refresher = undefined; this.refresher = undefined;
this.state = { capabilities: null, totpItems: [] };
} }
refresher: number; refresher: number;
async goBack(): Promise<void> { async componentDidMount() {
await this.router.changePage("SECRETS_HOME"); var mount = this.props.matches["mount"];
const mountsPath = "/sys/mounts/" + mount;
const caps = await getCapabilitiesPath([mountsPath, mount]);
let totpItems: TOTPGridItemProps[] = [];
// TODO: tidy this up i guess
try {
totpItems = await Promise.all(
Array.from(await getTOTPKeys(mount)).map(async (key) => {
const totpCaps = await getCapsPath(
removeDoubleSlash(mount + "code/" + key),
);
if (totpCaps.includes("read")) {
return {
mount: mount,
totpKey: key,
canDelete: totpCaps.includes("delete"),
};
}
}),
);
} catch (e: unknown) {
const error = e as Error;
if (error != DoesNotExistError) {
setErrorText(error.message);
}
}
this.setState({
capabilities: caps,
totpItems: totpItems
});
} }
async render(): Promise<void> { render() {
this.state.secretItem = ""; if (!this.state.capabilities) return;
var mount = this.props.matches["mount"];
const mountsPath = "/sys/mounts/" + this.state.baseMount; const mountsPath = "/sys/mounts/" + mount;
const caps = await getCapabilitiesPath([mountsPath, this.state.baseMount]); const mountCaps = this.state.capabilities[mountsPath];
const mountCaps = caps[mountsPath]; const totpCaps = this.state.capabilities[mount];
const totpCaps = caps[this.state.baseMount];
render( return (
<>
<SecretTitleElement type="totp" mount={mount} />
<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 () => {
await this.router.changePage("TOTP_NEW"); route("/secrets/totp/new/" + mount)
}} }}
> >
{i18next.t("totp_view_new_btn")} {i18next.t("totp_view_new_btn")}
@ -102,7 +149,7 @@ export class TOTPViewPage 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("/secrets/delete_engine/" + mount)
}} }}
> >
{i18next.t("totp_view_delete_btn")} {i18next.t("totp_view_delete_btn")}
@ -111,44 +158,25 @@ export class TOTPViewPage extends Page {
</p> </p>
<div id="totpList"> <div id="totpList">
{ {
await (async () => { (() => {
try { if (this.state.totpItems.length == 0) {
const elem = await Promise.all( return <p>{i18next.t("totp_view_empty")}</p>;
Array.from(await getTOTPKeys(this.state.baseMount)).map(async (key) => { } else {
const caps = await getCapsPath( return this.state.totpItems.map((totpItem) => {
removeDoubleSlash(this.state.baseMount + "code/" + key), return <RefreshingTOTPGridItem
); mount={totpItem.mount}
if (caps.includes("read")) { totpKey={totpItem.totpKey}
return ( canDelete={totpItem.canDelete}
<RefreshingTOTPGridItem />
baseMount={this.state.baseMount} })
totpKey={key}
page={this}
canDelete={caps.includes("delete")}
/>
);
}
}),
);
return elem;
} catch (e: unknown) {
const error = e as Error;
if (error == DoesNotExistError) {
return <p>{i18next.t("totp_view_empty")}</p>;
} else {
setErrorText(error.message);
}
} }
})() })()
} }
</div> </div>
</div>, </div>
this.router.pageContentElement, </>
);
}
async renderPageTitle(): Promise<void> { );
render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
} }
get name(): string { get name(): string {

View file

@ -76,12 +76,12 @@ export class NewTransitKeyPage extends Page {
} }
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("transit_new_key_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("transit_new_key_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("transit_new_key_title"); return i18next.t("transit_new_key_title");

View file

@ -82,12 +82,12 @@ export class TransitDecryptPage extends Page {
} }
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("transit_decrypt_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("transit_decrypt_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("transit_decrypt_title"); return i18next.t("transit_decrypt_title");

View file

@ -79,12 +79,12 @@ export class TransitEncryptPage extends Page {
} }
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("transit_encrypt_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("transit_encrypt_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("transit_encrypt_title"); return i18next.t("transit_encrypt_title");

View file

@ -94,12 +94,12 @@ export class TransitRewrapPage extends Page {
} }
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render( // render(
<SecretTitleElement page={this} suffix={i18next.t("transit_rewrap_suffix")} />, // <SecretTitleElement page={this} suffix={i18next.t("transit_rewrap_suffix")} />,
this.router.pageTitleElement, // this.router.pageTitleElement,
); // );
} //}
get name(): string { get name(): string {
return i18next.t("transit_rewrap_title"); return i18next.t("transit_rewrap_title");

View file

@ -116,9 +116,9 @@ export class TransitViewPage extends Page {
); );
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render(<SecretTitleElement page={this} />, this.router.pageTitleElement); // render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
} //}
get name(): string { get name(): string {
return i18next.t("transit_view_title"); return i18next.t("transit_view_title");

View file

@ -52,9 +52,9 @@ export class TransitViewSecretPage extends Page {
); );
} }
async renderPageTitle(): Promise<void> { //async renderPageTitle(): Promise<void> {
render(<SecretTitleElement page={this} />, this.router.pageTitleElement); // render(<SecretTitleElement page={this} />, this.router.pageTitleElement);
} //}
get name(): string { get name(): string {
return i18next.t("transit_view_secret_title"); return i18next.t("transit_view_secret_title");

View file

@ -5,51 +5,52 @@ import translations from "../../translations/index.mjs";
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 { Component } from "preact";
import { reloadNavBar } from "../elements/NavBar";
import { render } from "preact";
import i18next from "i18next"; import i18next from "i18next";
import { route } from "preact-router";
import { PageTitle } from "../elements/PageTitle";
import { DefaultPageProps } from "../../types/DefaultPageProps.js";
const languageIDs = Object.getOwnPropertyNames(translations); const languageIDs = Object.getOwnPropertyNames(translations);
export class SetLanguagePage extends Page { export class SetLanguage extends Component<DefaultPageProps> {
constructor() { constructor() {
super(); super();
} }
async render(): Promise<void> { render() {
render( return (
<Form onSubmit={(data) => this.onSubmit(data)}> <>
<Margin> <PageTitle title={i18next.t("set_language_title")} />
<select class="uk-select uk-form-width-large" name="language"> <Form onSubmit={(data) => this.onSubmit(data)}>
{languageIDs.map((languageID) => ( <Margin>
<option value={languageID}> <select class="uk-select uk-form-width-large" name="language">
{i18next.getFixedT(languageID, null)("language_name")} {languageIDs.map((languageID) => (
</option> <option value={languageID}>
))} {i18next.getFixedT(languageID, null)("language_name")}
</select> </option>
</Margin> ))}
<p class="uk-text-danger" id="errorText" /> </select>
<MarginInline> </Margin>
<button class="uk-button uk-button-primary" type="submit"> <p class="uk-text-danger" id="errorText" />
{i18next.t("set_language_btn")} <MarginInline>
</button> <button class="uk-button uk-button-primary" type="submit">
</MarginInline> {i18next.t("set_language_btn")}
</Form>, </button>
this.router.pageContentElement, </MarginInline>
</Form>
</>
); );
} }
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
const language = data.get("language") as string; const language = data.get("language") as string;
this.state.language = language; this.props.state.language = language;
const t = await i18next.changeLanguage(language); const t = await i18next.changeLanguage(language);
this.state.pageDirection = t("language_direction"); this.props.state.pageDirection = t("language_direction");
reloadNavBar(this.router); // TODO: make navbar somethingy
await this.router.changePage("HOME"); //reloadNavBar(this.router);
} route("/");
get name(): string {
return i18next.t("set_language_title");
} }
} }

View file

@ -1,14 +1,16 @@
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 { Page } from "../../types/Page";
import { render } from "preact"; import { Component, render } from "preact";
import { DefaultPageProps } from "../../types/DefaultPageProps";
import { route } from "preact-router";
import { PageTitle } from "../elements/PageTitle";
export class SetVaultURLPage extends Page { export class SetVaultURL extends Component<DefaultPageProps> {
constructor() { render() {
super(); return (
} <>
async render(): Promise<void> { <PageTitle title={this.name} />
render(
<Form onSubmit={(data) => this.onSubmit(data)}> <Form onSubmit={(data) => this.onSubmit(data)}>
<Margin> <Margin>
<input <input
@ -25,18 +27,19 @@ export class SetVaultURLPage extends Page {
Set Set
</button> </button>
</Margin> </Margin>
</Form>, </Form>
</>
this.router.pageContentElement,
); );
} }
async onSubmit(data: FormData): Promise<void> { async onSubmit(data: FormData): Promise<void> {
this.state.apiURL = data.get("vaultURL") as string; // TODO: check if vault is actually working here.
await this.router.changePage("HOME"); this.props.state.apiURL = data.get("vaultURL") as string;
route("/")
} }
get name(): string { get name(): string {
// TODO:
return "Set Vault URL"; return "Set Vault URL";
} }
} }

View file

@ -9,6 +9,9 @@ import { setErrorText } from "../../pageUtils";
import { submitUnsealKey } from "../../api/sys/submitUnsealKey"; import { submitUnsealKey } from "../../api/sys/submitUnsealKey";
import { toStr } from "../../utils"; import { toStr } from "../../utils";
import i18next from "i18next"; import i18next from "i18next";
import { DefaultPageProps } from "../../types/DefaultPageProps";
import { PageTitle } from "../elements/PageTitle";
import { route } from "preact-router";
const UnsealInputModes = { const UnsealInputModes = {
FORM_INPUT: "FORM_INPUT", FORM_INPUT: "FORM_INPUT",
@ -50,7 +53,7 @@ type UnsealPageState = {
keys_needed: number; keys_needed: number;
}; };
export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState> { export class Unseal extends Component<DefaultPageProps, UnsealPageState> {
constructor() { constructor() {
super(); super();
this.state = { this.state = {
@ -79,7 +82,7 @@ export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState
keys_needed: data.t, keys_needed: data.t,
}); });
if (!data.sealed) { if (!data.sealed) {
void this.props.page.router.changePage("HOME"); route("/");
} }
}); });
} }
@ -97,61 +100,50 @@ export class UnsealPageElement extends Component<{ page: Page }, UnsealPageState
render(): JSX.Element { render(): JSX.Element {
return ( return (
<div> <>
<progress <PageTitle title={i18next.t("unseal_vault_text")} />
class="uk-progress" <div>
value={this.state.keys_submitted} <progress
max={this.state.keys_needed} class="uk-progress"
/> value={this.state.keys_submitted}
max={this.state.keys_needed}
/>
<p id="errorText" class="uk-text-danger uk-margin-top" /> <p id="errorText" class="uk-text-danger uk-margin-top" />
<p> <p>
{i18next.t("unseal_keys_progress", { {i18next.t("unseal_keys_progress", {
progress: toStr(this.state.keys_submitted), progress: toStr(this.state.keys_submitted),
keys_needed: toStr(this.state.keys_needed), keys_needed: toStr(this.state.keys_needed),
})} })}
</p> </p>
{this.state.mode == UnsealInputModes.FORM_INPUT && ( {this.state.mode == UnsealInputModes.FORM_INPUT && (
<UnsealFormInput onSubmit={(code) => this.submitKey(code)} /> <UnsealFormInput onSubmit={(code) => this.submitKey(code)} />
)} )}
{this.state.mode == UnsealInputModes.QR_INPUT && ( {this.state.mode == UnsealInputModes.QR_INPUT && (
<QRScanner onScan={(code) => this.submitKey(code)} /> <QRScanner onScan={(code) => this.submitKey(code)} />
)} )}
<button <button
class="uk-button uk-button-primary" class="uk-button uk-button-primary"
onClick={async () => { onClick={async () => {
let newMethod: string; let newMethod: string;
if (this.state.mode == UnsealInputModes.FORM_INPUT) { if (this.state.mode == UnsealInputModes.FORM_INPUT) {
newMethod = UnsealInputModes.QR_INPUT; newMethod = UnsealInputModes.QR_INPUT;
} else { } else {
newMethod = UnsealInputModes.FORM_INPUT; newMethod = UnsealInputModes.FORM_INPUT;
} }
this.setState({ mode: newMethod }); this.setState({ mode: newMethod });
}} }}
> >
{this.state.mode == UnsealInputModes.QR_INPUT {this.state.mode == UnsealInputModes.QR_INPUT
? i18next.t("unseal_input_btn") ? i18next.t("unseal_input_btn")
: i18next.t("unseal_qr_btn")} : i18next.t("unseal_qr_btn")}
</button> </button>
</div> </div>
</>
); );
} }
} }
export class UnsealPage extends Page {
constructor() {
super();
}
async render(): Promise<void> {
render(<UnsealPageElement page={this} />, this.router.pageContentElement);
}
get name(): string {
return i18next.t("unseal_vault_text");
}
}

View file

@ -16,6 +16,7 @@ module.exports = {
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', filename: 'bundle.js',
publicPath: '/',
}, },
stats: { stats: {
colors: true, colors: true,
@ -31,6 +32,7 @@ module.exports = {
], ],
devServer: { devServer: {
open: process.env.BROWSER, open: process.env.BROWSER,
historyApiFallback: true,
}, },
resolve: { resolve: {
modules: ['node_modules'], modules: ['node_modules'],

View file

@ -41,6 +41,7 @@ module.exports = {
output: { output: {
path: path.resolve(__dirname, 'dist'), path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', filename: 'bundle.js',
publicPath: '/',
}, },
stats: { stats: {
colors: true, colors: true,
@ -56,6 +57,7 @@ module.exports = {
], ],
devServer: { devServer: {
open: process.env.BROWSER || "microsoft-edge-dev", open: process.env.BROWSER || "microsoft-edge-dev",
historyApiFallback: true,
}, },
resolve: { resolve: {
modules: ['node_modules'], modules: ['node_modules'],