From 7608df51b3c16a19a8b2690e1f615a126a850fd1 Mon Sep 17 00:00:00 2001 From: ChaotiCryptidz Date: Tue, 11 Jan 2022 12:45:35 +0000 Subject: [PATCH] Add vault-generated totp code support. --- src/ThemeLoader.tsx | 46 ++--- src/api/API.ts | 5 +- src/api/types/totp.ts | 18 ++ src/main.tsx | 2 +- src/pages.tsx | 2 + src/playground.tsx | 3 +- src/settings/Settings.ts | 4 +- src/translations/en.js | 13 ++ src/ui/pages/Me.tsx | 22 ++- src/ui/pages/Secrets/TOTP/TOTPList.tsx | 12 +- .../pages/Secrets/TOTP/TOTPNewGenerated.tsx | 170 ++++++++++++++++++ src/ui/pages/pageLinks.tsx | 4 + 12 files changed, 264 insertions(+), 37 deletions(-) create mode 100644 src/api/types/totp.ts create mode 100644 src/ui/pages/Secrets/TOTP/TOTPNewGenerated.tsx diff --git a/src/ThemeLoader.tsx b/src/ThemeLoader.tsx index 55ed51c..c2b1640 100644 --- a/src/ThemeLoader.tsx +++ b/src/ThemeLoader.tsx @@ -2,32 +2,32 @@ import { Component } from "preact"; import { settings } from "./globalSettings"; // @ts-ignore -import style_dark from "./scss/main-dark.scss" assert {type: "css"}; +import style_dark from "./scss/main-dark.scss" assert { type: "css" }; // @ts-ignore -import style_light from "./scss/main-light.scss" assert {type: "css"}; +import style_light from "./scss/main-light.scss" assert { type: "css" }; export const default_theme = "dark"; const themes: { [key: string]: string } = { - "dark": style_dark, - "light": style_light, + dark: style_dark as string, + light: style_light as string, +}; + +export class ThemeLoader extends Component { + componentDidMount() { + this.setCorrectStyle(settings.theme); + settings.registerListener((key: string) => { + if (key != "theme") return; + this.setCorrectStyle(settings.theme); + }); + } + + setCorrectStyle(theme: string) { + this.setState({ sheet: themes[theme] }); + } + + render() { + if (!this.state.sheet) return; + return ; + } } - -export class ThemeLoader extends Component<{}, { sheet: string }> { - componentDidMount() { - this.setCorrectStyle(settings.theme); - settings.registerListener((key: string) => { - if (key != "theme") return; - this.setCorrectStyle(settings.theme); - }) - } - - setCorrectStyle(theme: string) { - this.setState({ sheet: themes[theme] }) - } - - render() { - if (!this.state.sheet) return; - return - } -} \ No newline at end of file diff --git a/src/api/API.ts b/src/api/API.ts index b1e4413..9db35e8 100644 --- a/src/api/API.ts +++ b/src/api/API.ts @@ -12,6 +12,7 @@ import { } from "./types/transit"; import { DoesNotExistError } from "../types/internalErrors"; import { MountType, MountsType, NewMountParams } from "./types/mount"; +import { NewTOTPData, NewTOTPResp } from "./types/totp"; import { SealStatusType } from "./types/seal"; import { SecretMetadataType } from "./types/secret"; import { Settings } from "../settings/Settings"; @@ -461,7 +462,7 @@ export class API { return data.data.keys; } - async addNewTOTP(baseMount: string, parms: { name: string }): Promise { + async addNewTOTP(baseMount: string, parms: NewTOTPData): Promise { const request = new Request( this.appendAPIURL(removeDoubleSlash(`/v1/${baseMount}/keys/${parms.name}`)), { @@ -475,6 +476,8 @@ export class API { ); const resp = await fetch(request); await checkResponse(resp); + const data = (await resp.json()) as { data: NewTOTPResp }; + return data.data; } async deleteTOTP(baseMount: string, name: string): Promise { diff --git a/src/api/types/totp.ts b/src/api/types/totp.ts new file mode 100644 index 0000000..b26cd15 --- /dev/null +++ b/src/api/types/totp.ts @@ -0,0 +1,18 @@ +export type NewTOTPData = { + name: string; + generate: boolean; + exported?: boolean; + key_size?: number; + url?: string; + key?: string; + issuer?: string; + account_name?: string; + period?: number | string; + algorithm?: string; + digits?: number; +}; + +export type NewTOTPResp = { + url: string; + barcode: string; +}; diff --git a/src/main.tsx b/src/main.tsx index 76360d4..38ba144 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -25,13 +25,13 @@ import { formatDistance } from "./formatDistance"; //import { pageList } from "./allPages"; import { Main } from "./pages"; import { NavBar } from "./ui/elements/NavBar"; +import { ThemeLoader } from "./ThemeLoader"; import { api } from "./globalAPI"; import { getCurrentUrl, route } from "preact-router"; import { playground } from "./playground"; import { render } from "preact"; import { settings } from "./globalSettings"; import i18next from "i18next"; -import { ThemeLoader } from "./ThemeLoader"; async function onLoad(): Promise { document.documentElement.dir = settings.pageDirection; diff --git a/src/pages.tsx b/src/pages.tsx index db0c242..57281a8 100644 --- a/src/pages.tsx +++ b/src/pages.tsx @@ -30,6 +30,7 @@ import { SetVaultURL } from "./ui/pages/SetVaultURL"; import { TOTPDelete } from "./ui/pages/Secrets/TOTP/TOTPDelete"; import { TOTPList } from "./ui/pages/Secrets/TOTP/TOTPList"; import { TOTPNew } from "./ui/pages/Secrets/TOTP/TOTPNew"; +import { TOTPNewGenerated } from "./ui/pages/Secrets/TOTP/TOTPNewGenerated"; import { TransitDecrypt } from "./ui/pages/Secrets/Transit/TransitDecrypt"; import { TransitEncrypt } from "./ui/pages/Secrets/Transit/TransitEncrypt"; import { TransitList } from "./ui/pages/Secrets/Transit/TransitList"; @@ -81,6 +82,7 @@ export const Main = () => ( + diff --git a/src/playground.tsx b/src/playground.tsx index 7164910..3444907 100644 --- a/src/playground.tsx +++ b/src/playground.tsx @@ -1,5 +1,5 @@ -import { settings } from "./globalSettings"; import { Settings } from "./settings/Settings"; +import { settings } from "./globalSettings"; // Playground is a way to debug and test things. // Anything you put in here is gonna be run on page initial load @@ -12,7 +12,6 @@ declare global { } } - // Please empty this function before committing. export async function playground(): Promise { console.log("Welcome to Playground!"); diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 02292eb..7a46e05 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -1,5 +1,5 @@ -import { default_theme } from "../ThemeLoader"; import { StorageType } from "./storage/StorageType"; +import { default_theme } from "../ThemeLoader"; type OnChangeListener = (key: string) => void; @@ -17,7 +17,7 @@ export class Settings { } alertChange(key: string) { - for (let listener of this.listeners) { + for (const listener of this.listeners) { listener(key); } } diff --git a/src/translations/en.js b/src/translations/en.js index c906651..770c37d 100644 --- a/src/translations/en.js +++ b/src/translations/en.js @@ -13,6 +13,7 @@ module.exports = { // Common buttons / placeholders common_new: "New", common_view: "View", + common_back: "Back", common_edit: "Edit", common_create: "Create", common_delete: "Delete", @@ -176,6 +177,18 @@ module.exports = { totp_new_switch_to_qr_btn: "Switch to QR Input", totp_new_switch_back_to_manual_input_btn: "Switch back to manual input", + totp_new_generated: "New Generated", + totp_new_generated_suffix: "(new generated)", + totp_new_generated_warning: + "Make sure to save this information somewhere safe as there is no way to get it back short of Vault's raw api.", + totp_new_generated_issuer: "Issuer", + totp_new_generated_account_name: "Account Name", + totp_new_generated_algorithm: "Algorithm", + totp_new_generated_key_size: "Key Size (bytes)", + totp_new_generated_period: "Period (Refresh Time)", + totp_new_generated_digits: "Digits (Length 6/8)", + totp_new_generated_export: "Show QR & URI (Recommended)", + // TOTP Delete Page totp_delete_title: "Delete TOTP Key", totp_delete_suffix: " (delete)", diff --git a/src/ui/pages/Me.tsx b/src/ui/pages/Me.tsx index f45262b..28ffa94 100644 --- a/src/ui/pages/Me.tsx +++ b/src/ui/pages/Me.tsx @@ -1,11 +1,11 @@ import { Component, JSX, createRef } from "preact"; import { DefaultPageProps } from "../../types/DefaultPageProps"; +import { InputWithTitle } from "../elements/InputWithTitle"; import { PageTitle } from "../elements/PageTitle"; import { addClipboardNotifications, setErrorText } from "../../pageUtils"; import { route } from "preact-router"; import ClipboardJS from "clipboard"; import i18next from "i18next"; -import { InputWithTitle } from "../elements/InputWithTitle"; export class CopyLink extends Component<{ text: string; data: string }, unknown> { linkRef = createRef(); @@ -55,7 +55,7 @@ export class Me extends Component { }); } - themeSelectRef = createRef() + themeSelectRef = createRef(); render(): JSX.Element { return ( @@ -111,12 +111,20 @@ export class Me extends Component { - { + const newTheme = this.themeSelectRef.current.value; + this.props.settings.theme = newTheme; + }} + > diff --git a/src/ui/pages/Secrets/TOTP/TOTPList.tsx b/src/ui/pages/Secrets/TOTP/TOTPList.tsx index ac33b65..84f7f9a 100644 --- a/src/ui/pages/Secrets/TOTP/TOTPList.tsx +++ b/src/ui/pages/Secrets/TOTP/TOTPList.tsx @@ -6,7 +6,7 @@ import { DoesNotExistError } from "../../../../types/internalErrors"; import { Grid, GridSizes } from "../../../elements/Grid"; import { MarginInline } from "../../../elements/MarginInline"; import { SecretTitleElement } from "../SecretTitleElement"; -import { delSecretsEngineURL, totpNewURL } from "../../pageLinks"; +import { delSecretsEngineURL, totpNewGeneratedURL, totpNewURL } from "../../pageLinks"; import { removeDoubleSlash } from "../../../../utils"; import { route } from "preact-router"; import { setErrorText } from "../../../../pageUtils"; @@ -138,6 +138,16 @@ export class TOTPList extends Component { {i18next.t("common_new")} )} + {totpCaps.includes("create") && ( + + )} {mountCaps.includes("delete") && ( + + + ); + } else { + return ( + <> +

{i18next.t("totp_new_generated_warning")}

+ + + + + ); + } + } + + async onSubmit(data: FormData): Promise { + const isExported = data.get("exported") == "yes" ? true : false; + + const parms = { + generate: true, + name: data.get("name") as string, + issuer: data.get("issuer") as string, + account_name: data.get("account_name") as string, + exported: isExported, + key_size: parseInt(data.get("key_size") as string), + period: data.get("period") as string, + algorithm: data.get("algorithm") as string, + digits: parseInt(data.get("digits") as string), + }; + + console.log(parms); + try { + const ret = await this.props.api.addNewTOTP(this.props.baseMount, parms); + if (!isExported) { + route("/secrets/totp/list/" + this.props.baseMount); + } else { + this.setState({ exportedData: ret }); + } + } catch (e: unknown) { + const error = e as Error; + setErrorText(`API Error: ${error.message}`); + } + } +} + +export class TOTPNewGenerated extends Component { + render() { + const baseMount = this.props.matches["baseMount"]; + return ( + <> + + + + ); + } +} diff --git a/src/ui/pages/pageLinks.tsx b/src/ui/pages/pageLinks.tsx index e800b96..b9cdd0b 100644 --- a/src/ui/pages/pageLinks.tsx +++ b/src/ui/pages/pageLinks.tsx @@ -33,6 +33,10 @@ export function totpNewURL(baseMount: string): string { return `/secrets/totp/new/${baseMount}`; } +export function totpNewGeneratedURL(baseMount: string): string { + return `/secrets/totp/new_generated/${baseMount}`; +} + export function totpListURL(baseMount: string): string { return `/secrets/totp/list/${baseMount}`; }