1
0
Fork 0

Compare commits

...

10 commits

Author SHA1 Message Date
chaos 56f40547de
remove gitlab ci 2023-09-30 12:54:07 +01:00
chaos d5fc05612e
update flake 2023-09-27 17:23:40 +01:00
chaos 6365eb49ce
fix mkPackageOption args in nixos-module 2023-09-13 16:17:51 +01:00
chaos fe68878411
add a nix flake and update dependencies 2023-09-13 16:02:51 +01:00
Chaos c170f2a622
update npm package versions 2023-07-28 20:47:42 +00:00
Chaos 3147482acf
Add hybrid mode to KV View 2022-12-14 15:07:03 +00:00
Chaos 506fdbd1cb
add a copy totp button on kv pages with __vaultui_totp_path 2022-11-23 16:45:04 +00:00
Chaos 760e16c371
fix a number of bugs and add __vaultui_totp_path 2022-11-23 16:16:45 +00:00
ChaotiCryptidz 3898265348
add "a-z a-Z 0-9" to pwgen 2022-08-28 16:16:14 +01:00
ChaotiCryptidz 32c60a0d75
fix pwgen range width 2022-08-28 16:14:09 +01:00
31 changed files with 14441 additions and 143 deletions

3
.browserslistrc Normal file
View file

@ -0,0 +1,3 @@
> 0.25%
not dead
since 2016

2
.envrc
View file

@ -1 +1 @@
use nix
use flake

View file

@ -1,12 +0,0 @@
pages:
stage: deploy
image: docker.io/library/alpine:edge
script:
- apk add nodejs npm git
- npm install --save-dev
- npx webpack
- mv dist public
- cp _redirects public
artifacts:
paths:
- public

View file

@ -1,33 +1,11 @@
VaultUI Copyright (2022) (ChaotiCryptidz)(“Licensor”)
Copyright 2022 Chaos
Hippocratic License Version Number: 2.1.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Purpose. The purpose of this License is for the Licensor named above to permit the Licensee (as defined below) broad permission, if consistent with Human Rights Laws and Human Rights Principles (as each is defined below), to use and work with the Software (as defined below) within the full scope of Licensors copyright and patent rights, if any, in the Software, while ensuring attribution and protecting the Licensor from liability.
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Permission and Conditions. The Licensor grants permission by this license (“License”), free of charge, to the extent of Licensors rights under applicable copyright and patent law, to any person or entity (the “Licensee”) obtaining a copy of this software and associated documentation files (the “Software”), to do everything with the Software that would otherwise infringe (i) the Licensors copyright in the Software or (ii) any patent claims to the Software that the Licensor can license or becomes able to license, subject to all of the following terms and conditions:
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Acceptance. This License is automatically offered to every person and entity subject to its terms and conditions. Licensee accepts this License and agrees to its terms and conditions by taking any action with the Software that, absent this License, would infringe any intellectual property right held by Licensor.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
* Notice. Licensee must ensure that everyone who gets a copy of any part of this Software from Licensee, with or without changes, also receives the License and the above copyright notice (and if included by the Licensor, patent, trademark and attribution notice). Licensee must cause any modified versions of the Software to carry prominent notices stating that Licensee changed the Software. For clarity, although Licensee is free to create modifications of the Software and distribute only the modified portion created by Licensee with additional or different terms, the portion of the Software not modified must be distributed pursuant to this License. If anyone notifies Licensee in writing that Licensee has not complied with this Notice section, Licensee can keep this License by taking all practical steps to comply within 30 days after the notice. If Licensee does not do so, Licensees License (and all rights licensed hereunder) shall end immediately.
* Compliance with Human Rights Principles and Human Rights Laws.
1. Human Rights Principles.
(a) Licensee is advised to consult the articles of the United Nations Universal Declaration of Human Rights and the United Nations Global Compact that define recognized principles of international human rights (the “Human Rights Principles”). Licensee shall use the Software in a manner consistent with Human Rights Principles.
(b) Unless the Licensor and Licensee agree otherwise, any dispute, controversy, or claim arising out of or relating to (i) Section 1(a) regarding Human Rights Principles, including the breach of Section 1(a), termination of this License for breach of the Human Rights Principles, or invalidity of Section 1(a) or (ii) a determination of whether any Law is consistent or in conflict with Human Rights Principles pursuant to Section 2, below, shall be settled by arbitration in accordance with the Hague Rules on Business and Human Rights Arbitration (the “Rules”); provided, however, that Licensee may elect not to participate in such arbitration, in which event this License (and all rights licensed hereunder) shall end immediately. The number of arbitrators shall be one unless the Rules require otherwise.
Unless both the Licensor and Licensee agree to the contrary: (1) All documents and information concerning the arbitration shall be public and may be disclosed by any party; (2) The repository referred to under Article 43 of the Rules shall make available to the public in a timely manner all documents concerning the arbitration which are communicated to it, including all submissions of the parties, all evidence admitted into the record of the proceedings, all transcripts or other recordings of hearings and all orders, decisions and awards of the arbitral tribunal, subject only to the arbitral tribunal's powers to take such measures as may be necessary to safeguard the integrity of the arbitral process pursuant to Articles 18, 33, 41 and 42 of the Rules; and (3) Article 26(6) of the Rules shall not apply.
2. Human Rights Laws. The Software shall not be used by any person or entity for any systems, activities, or other uses that violate any Human Rights Laws. “Human Rights Laws” means any applicable laws, regulations, or rules (collectively, “Laws”) that protect human, civil, labor, privacy, political, environmental, security, economic, due process, or similar rights; provided, however, that such Laws are consistent and not in conflict with Human Rights Principles (a dispute over the consistency or a conflict between Laws and Human Rights Principles shall be determined by arbitration as stated above). Where the Human Rights Laws of more than one jurisdiction are applicable or in conflict with respect to the use of the Software, the Human Rights Laws that are most protective of the individuals or groups harmed shall apply.
3. Indemnity. Licensee shall hold harmless and indemnify Licensor (and any other contributor) against all losses, damages, liabilities, deficiencies, claims, actions, judgments, settlements, interest, awards, penalties, fines, costs, or expenses of whatever kind, including Licensors reasonable attorneys fees, arising out of or relating to Licensees use of the Software in violation of Human Rights Laws or Human Rights Principles.
* Failure to Comply. Any failure of Licensee to act according to the terms and conditions of this License is both a breach of the License and an infringement of the intellectual property rights of the Licensor (subject to exceptions under Laws, e.g., fair use). In the event of a breach or infringement, the terms and conditions of this License may be enforced by Licensor under the Laws of any jurisdiction to which Licensee is subject. Licensee also agrees that the Licensor may enforce the terms and conditions of this License against Licensee through specific performance (or similar remedy under Laws) to the extent permitted by Laws. For clarity, except in the event of a breach of this License, infringement, or as otherwise stated in this License, Licensor may not terminate this License with Licensee.
* Enforceability and Interpretation. If any term or provision of this License is determined to be invalid, illegal, or unenforceable by a court of competent jurisdiction, then such invalidity, illegality, or unenforceability shall not affect any other term or provision of this License or invalidate or render unenforceable such term or provision in any other jurisdiction; provided, however, subject to a court modification pursuant to the immediately following sentence, if any term or provision of this License pertaining to Human Rights Laws or Human Rights Principles is deemed invalid, illegal, or unenforceable against Licensee by a court of competent jurisdiction, all rights in the Software granted to Licensee shall be deemed null and void as between Licensor and Licensee. Upon a determination that any term or provision is invalid, illegal, or unenforceable, to the extent permitted by Laws, the court may modify this License to affect the original purpose that the Software be used in compliance with Human Rights Principles and Human Rights Laws as closely as possible. The language in this License shall be interpreted as to its fair meaning and not strictly for or against any party.
* Disclaimer. TO THE FULL EXTENT ALLOWED BY LAW, THIS SOFTWARE COMES “AS IS,” WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED, AND LICENSOR AND ANY OTHER CONTRIBUTOR SHALL NOT BE LIABLE TO ANYONE FOR ANY DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THIS LICENSE, UNDER ANY KIND OF LEGAL CLAIM.
This Hippocratic License is an Ethical Source license (https://ethicalsource.dev) and is offered for use by licensors and licensees at their own risk, on an “AS IS” basis, and with no warranties express or implied, to the maximum extent permitted by Laws.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -10,6 +10,7 @@ VaultUI is a user interface for [Hashicorp Vault](https://www.hashicorp.com/prod
- Managing versions of secrets in v2 mounts.
- Editing and Viewing Key/Value secrets in JSON, JSON5, Yaml, etc with default settings for both.
- Search bar for easily finding a secret on mobile.
- Can add a `__vaultui_totp_path` attribute (example: `__vaultui_totp_path: "totp/gitlab"`) to KV secrets to add a link for viewing of affiliated TOTP paths.
### TOTP
- Can view and delete TOTP codes.
- Ability to scan a QR code, input a URI or input a key for adding TOTP codes.
@ -42,9 +43,7 @@ npx webpack serve --config webpack-dev.config.js
## How to host/use
We host it via GitLab pages at [VaultUI](https://vaultui.owo.monster).
Simply copy the files to root of webserver.
Simply copy the built files to root of webserver.
You will need a redirect rule to redirect any 404's or non-200's to index.html.
Cors will need to be enabled for you to use VaultUI.
@ -82,4 +81,4 @@ For example:
Language: fr
Missing: unseal_input_btn, unseal_qr_btn
```
Would mean those two missing translations need to be added to the french translation.
Would mean those two missing translations need to be added to the french translation.

8
default.nix Normal file
View file

@ -0,0 +1,8 @@
(import (let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
in
fetchTarball {
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
sha256 = lock.nodes.flake-compat.locked.narHash;
}) {src = ./.;})
.defaultNix

78
flake.lock Normal file
View file

@ -0,0 +1,78 @@
{
"nodes": {
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1695644571,
"narHash": "sha256-asS9dCCdlt1lPq0DLwkVBbVoEKuEuz+Zi3DG7pR/RxA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "6500b4580c2a1f3d0f980d32d285739d8e156d92",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"utils": "utils"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

97
flake.nix Normal file
View file

@ -0,0 +1,97 @@
{
description = "A Web UI for Hashicorp Vault";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
utils.url = "github:numtide/flake-utils";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
};
outputs = {
self,
nixpkgs,
utils,
...
}:
{
nixosModules.vaultui = import ./nixos-module.nix;
nixosModules.default = self.nixosModules.vaultui;
overlays.vaultui = final: prev: {
vaultui = final.mkYarnPackage rec {
pname = "vaultui";
version = "latest";
src = ./.;
yarnLock = ./yarn.lock;
yarnNix = ./yarn.nix;
packageJSON = ./package.json;
doDist = false;
# required for using system libsass, or else it tries to fetch new node headers
yarnPreBuild = ''
export npm_config_nodedir=${final.pkgs.nodejs}
'';
pkgConfig = {
node-sass = {
nativeBuildInputs = with final.pkgs; [pkg-config];
buildInputs = with final.pkgs; [libsass python3];
postInstall = ''
LIBSASS_EXT=auto yarn --offline run build
rm build/config.gypi
'';
};
};
buildPhase = ''
runHook preBuild
yarn run build
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p "$out/share/vaultui"
${final.rsync}/bin/rsync --recursive deps/vaultui/dist/ "$out/share/vaultui"
runHook postInstall
'';
};
};
overlays.default = self.overlays.vaultui;
}
// utils.lib.eachSystem (utils.lib.defaultSystems) (system: let
pkgs = import nixpkgs {
inherit system;
overlays = [self.overlays.default];
};
in {
defaultPackage = self.packages."${system}".vaultui;
packages.vaultui = pkgs.vaultui;
devShell = pkgs.mkShell {
LIBSASS_EXT = "auto";
npm_config_nodedir = "${pkgs.nodejs}";
nativeBuildInputs = with pkgs; [
nodejs
nodePackages.yarn
yarn2nix
# So we don't need to manually build libsass
pkg-config
libsass
python3
];
};
});
}

47
nixos-module.nix Normal file
View file

@ -0,0 +1,47 @@
{
config,
lib,
pkgs,
...
}: let
inherit (lib.modules) mkIf;
inherit (lib.options) mkOption mkEnableOption mkPackageOption;
inherit (lib) types;
cfg = config.services.vaultui;
in {
options = {
services.vaultui = {
enable = mkEnableOption "vaultui";
package = mkPackageOption pkgs "vaultui" {};
domain = mkOption {
type = types.str;
};
path = mkOption {
type = types.str;
default = "/";
};
};
};
config = mkIf cfg.enable {
services.nginx = {
enable = true;
virtualHosts."${cfg.domain}" = {
forceSSL = true;
enableACME = true;
locations = {
"${cfg.path}" = {
root = "${cfg.package}/share/vaultui";
extraConfig = ''
try_files $uri $uri/ /index.html;
'';
};
};
};
};
};
}

View file

@ -1,55 +1,58 @@
{
"name": "vaultui",
"version": "latest",
"version": "1.0.0",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"@babel/core": "^7.18.13",
"@babel/eslint-parser": "^7.18.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.18.10",
"@babel/plugin-proposal-object-rest-spread": "^7.18.9",
"@babel/plugin-syntax-import-assertions": "^7.18.6",
"@babel/plugin-transform-runtime": "^7.18.10",
"@babel/preset-env": "^7.18.10",
"@babel/core": "^7.22.17",
"@babel/eslint-parser": "^7.22.15",
"@babel/plugin-proposal-decorators": "^7.22.15",
"@babel/plugin-syntax-import-assertions": "^7.22.5",
"@babel/plugin-transform-class-properties": "^7.22.5",
"@babel/plugin-transform-runtime": "^7.22.15",
"@babel/preset-env": "^7.22.15",
"@types/file-saver": "^2.0.5",
"@types/js-yaml": "^4.0.5",
"@types/prismjs": "^1.26.0",
"@types/uikit": "^3.14.0",
"@typescript-eslint/eslint-plugin": "^5.35.1",
"@typescript-eslint/parser": "^5.35.1",
"babel-loader": "^8.2.5",
"css-loader": "^6.7.1",
"css-minimizer-webpack-plugin": "^4.0.0",
"eslint": "^8.23.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-prettier": "^4.2.1",
"@types/uikit": "^3.14.1",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"babel-loader": "^9.1.3",
"css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1",
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-sort-imports-es6-autofix": "^0.6.0",
"git-revision-webpack-plugin": "^5.0.0",
"html-webpack-plugin": "^5.5.0",
"node-sass": "^7.0.1",
"prettier": "^2.7.1",
"html-webpack-plugin": "^5.5.3",
"node-sass": "^9.0.0",
"prettier": "^3.0.3",
"raw-loader": "^4.0.2",
"sass-loader": "^13.0.2",
"ts-loader": "^9.3.1",
"typescript": "^4.8.2",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.10.0"
"sass-loader": "^13.3.2",
"ts-loader": "^9.4.4",
"typescript": "^5.2.2",
"webpack": "^5.88.2",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"clipboard": "^2.0.11",
"codejar": "^3.6.0",
"core-js": "^3.25.0",
"date-fns": "^2.29.2",
"codejar": "^4.2.0",
"core-js": "^3.32.2",
"date-fns": "^2.30.0",
"file-saver": "^2.0.5",
"i18next": "^21.9.1",
"i18next": "^23.5.1",
"js-yaml": "^4.1.0",
"json5": "^2.2.1",
"json5": "^2.2.3",
"normalize.css": "^8.0.1",
"preact": "^10.10.6",
"preact-router": "^4.1.0",
"preact": "^10.17.1",
"preact-router": "^4.1.2",
"prismjs": "^1.29.0",
"qr-scanner": "^1.4.1",
"uikit": "^3.15.5"
"qr-scanner": "^1.4.2",
"sass": "^1.66.1",
"uikit": "^3.16.26"
}
}

View file

@ -1,5 +0,0 @@
{ pkgs ? import <nixpkgs> { } }: pkgs.mkShell {
packages = with pkgs; [
nodejs
];
}

View file

@ -480,7 +480,7 @@ export class API {
name: string,
): Promise<SecretMetadataType> {
const request = new Request(
this.appendAPIURL(`/v1/${baseMount}/metadata/${secretPath.join("")}/${name}`),
this.appendAPIURL(`/v1/${baseMount}/metadata/${secretPath.join("/")}/${name}`),
{
headers: this.getHeaders(),
},

View file

@ -93,7 +93,7 @@ export const Main = () => (
api={api}
/>
<TOTPList path="/secrets/totp/list/:baseMount" settings={settings} api={api} />
<TOTPList path="/secrets/totp/list/:baseMount/:secretItem?" settings={settings} api={api} />
<TOTPNew path="/secrets/totp/new/:baseMount" settings={settings} api={api} />
<TOTPNewGenerated path="/secrets/totp/new_generated/:baseMount" settings={settings} api={api} />
<TOTPDelete path="/secrets/totp/delete/:baseMount/:item" settings={settings} api={api} />

View file

@ -5,6 +5,7 @@
@import "uikit/src/scss/components/base.scss";
// Elements
@import "uikit/src/scss/components/mixin.scss";
@import "uikit/src/scss/components/link.scss";
@import "uikit/src/scss/components/heading.scss";
@import "uikit/src/scss/components/list.scss";

View file

@ -118,7 +118,7 @@ export class Settings {
}
get kvAlwaysCodeView(): boolean {
const value = this.storage.getItem("kvAlwaysCodeView") || false;
const value = this.storage.getItem("kvAlwaysCodeView") || "false";
return value == "true";
}
set kvAlwaysCodeView(value: boolean) {
@ -126,6 +126,15 @@ export class Settings {
this.alertChange("kvAlwaysCodeView");
}
get kvUseHybridView(): boolean {
const value = this.storage.getItem("kvUseHybridView") || "true";
return value == "true";
}
set kvUseHybridView(value: boolean) {
this.storage.setItem("kvUseHybridView", String(value));
this.alertChange("kvUseHybridView");
}
get kvEditorDefaultLanguage(): string {
return this.storage.getItem("kvEditorDefaultLanguage") || "yaml";
}

View file

@ -89,6 +89,7 @@ module.exports = {
settings_kv_editor_indent: "Editor Indent",
settings_kv_always_view_in_code_mode: "Always view in code mode",
settings_kv_hide_values: "Hide values with key (comma seporated)",
settings_kv_use_hybrid_mode: "Show Secrets in hybrid mode",
// Set Vault URL Page
set_vault_url_title: "Set Vault URL",
@ -167,6 +168,8 @@ module.exports = {
kv_secret_delete_all_btn: "Delete All Versions",
kv_secret_delete_version_btn: "Delete Version {{ version }}",
kv_secret_versions_btn: "Versions",
kv_secret_view_totp_btn: "View TOTP Entry",
kv_secret_copy_totp_btn: "Copy Current TOTP Code",
kv_secret_syntax: "Syntax",
// Key Value Secret Editor Page

View file

@ -31,6 +31,7 @@ const special = "!#$%&()*+,-./:;<=>?@[]^_{|}~";
const alphabets = {
SECURE: lowerCase + upperCase + numbers + special,
SMOL: lowerCase + numbers,
SECURE_ISH: lowerCase + upperCase + numbers,
HEX: "123456789ABCDEF",
};
@ -90,7 +91,6 @@ export class PasswordGenerator extends Component<DefaultPageProps, PasswordGener
this.updateAlphabet();
}
// createRef
render(): JSX.Element {
return (
<>
@ -101,7 +101,7 @@ export class PasswordGenerator extends Component<DefaultPageProps, PasswordGener
</Margin>
<Margin>
<input
class="uk-range"
class="uk-range uk-form-width-medium"
name="length"
type="range"
value={this.state.length}
@ -121,6 +121,7 @@ export class PasswordGenerator extends Component<DefaultPageProps, PasswordGener
}}
>
<SelectOption name="a-z a-Z 0-9 specials" value={alphabets.SECURE} />
<SelectOption name="a-z a-Z 0-9" value={alphabets.SECURE_ISH} />
<SelectOption name="a-z 0-9" value={alphabets.SMOL} />
<SelectOption name="A-F 1-9" value={alphabets.HEX} />
</Select>

View file

@ -8,6 +8,7 @@ import { SecretTitleElement } from "../SecretTitleElement";
import { TextInput } from "../../../elements/forms/TextInput";
import { combineKVPath, splitKVPath } from "./kvPathUtils";
import { delSecretsEngineURL, kvListURL, kvNewURL, kvViewURL } from "../../pageLinks";
import { route } from "preact-router";
import { sendErrorNotification } from "../../../elements/ErrorMessage";
import i18next from "i18next";
@ -76,11 +77,12 @@ export class KVKeysList extends Component<KVKeysListProps, KVKeysListState> {
if (error == DoesNotExistError) {
// getSecrets also 404's on no keys so dont go all the way back.
if (this.props.secretPath.length != 0) {
window.history.back();
this.props.secretPath.pop();
route(kvListURL(this.props.baseMount, this.props.secretPath));
return;
}
} else {
throw error;
sendErrorNotification(error.message);
}
this.setState({

View file

@ -10,6 +10,7 @@ export class KeyValueVersions extends Component<DefaultPageProps, { versions: st
async componentDidMount() {
const baseMount = this.props.matches["baseMount"];
const secretPath = splitKVPath(this.props.matches["secretPath"]);
console.log("sp", secretPath, this.props.matches["secretPath"]);
const secretItem = this.props.matches["item"];
try {

View file

@ -1,3 +1,4 @@
import { API } from "../../../../api/API";
import { Button } from "../../../elements/Button";
import { CodeBlock } from "../../../elements/CodeBlock";
import { Component, JSX, createRef } from "preact";
@ -9,10 +10,12 @@ import { InputWithTitle } from "../../../elements/InputWithTitle";
import { SecretTitleElement } from "../SecretTitleElement";
import { Select, SelectOption } from "../../../elements/forms/Select";
import { SupportedLanguages, dumpData, toPrismCode } from "../../../../utils/dataInterchange";
import { kvDeleteURL, kvEditURL, kvVersionsURL } from "../../pageLinks";
import { addClipboardNotifications } from "../../../../utils/clipboardNotifs";
import { kvDeleteURL, kvEditURL, kvVersionsURL, totpListURL } from "../../pageLinks";
import { sendErrorNotification } from "../../../elements/ErrorMessage";
import { sortedObjectMap } from "../../../../utils";
import { splitKVPath } from "./kvPathUtils";
import ClipboardJS from "clipboard";
import i18next from "i18next";
type KVSecretViewDataProps = DefaultPageProps & { data: Map<string, unknown> };
@ -49,14 +52,56 @@ export class KVSecretCodeVew extends Component<KVSecretViewDataProps, { syntax:
}
}
class CopyTOTPButton extends Component<
{ baseMount: string; totpKey: string; api: API },
{ code: string }
> {
buttonRef = createRef<HTMLButtonElement>();
timer: unknown;
updateTOTPCode(): void {
void this.props.api.getTOTPCode(this.props.baseMount, this.props.totpKey).then((code) => {
this.setState({ code });
});
}
componentDidMount(): void {
this.updateTOTPCode();
this.timer = setInterval(() => {
this.updateTOTPCode();
}, 3000);
const clipboard = new ClipboardJS(this.buttonRef.current);
addClipboardNotifications(clipboard, 600);
}
componentWillUnmount(): void {
clearInterval(this.timer as number);
}
render(): JSX.Element {
return (
<Button
buttonRef={this.buttonRef}
text={i18next.t("kv_secret_copy_totp_btn")}
color="primary"
data-clipboard-text={this.state.code}
/>
);
}
}
export class KVSecretNormalVew extends Component<KVSecretViewDataProps> {
render() {
return (
<>
<div>
{Array.from(this.props.data).map((data: [string, unknown]) => {
const key = data[0];
const value = data[1] as string;
if (key == "__vaultui_totp_path") return <></>;
return (
<Grid size={GridSizes.NORMAL}>
<CopyableInputBox text={key} copyable />
@ -68,7 +113,7 @@ export class KVSecretNormalVew extends Component<KVSecretViewDataProps> {
</Grid>
);
})}
</>
</div>
);
}
}
@ -77,6 +122,27 @@ export type KVSecretViewProps = DefaultPageProps & {
kvData: Record<string, unknown>;
};
function KVTOTPExtra(props: KVSecretViewDataProps) {
if (props.data.has("__vaultui_totp_path")) {
const value = props.data.get("__vaultui_totp_path") as string;
const baseMount = value.split("/")[0];
const totpKey = value.split("/")[1];
return (
<p>
<CopyTOTPButton api={props.api} baseMount={baseMount} totpKey={totpKey} />
<Button
text={i18next.t("kv_secret_view_totp_btn")}
color="secondary"
route={totpListURL(baseMount, totpKey)}
/>
</p>
);
} else {
return <></>;
}
}
export class KVSecretVew extends Component<KVSecretViewProps, { syntax: string }> {
render(): JSX.Element {
const secretsMap = sortedObjectMap(this.props.kvData);
@ -86,10 +152,52 @@ export class KVSecretVew extends Component<KVSecretViewProps, { syntax: string }
if (typeof value == "object") isMultiLevel = true;
}
let showableAsHybrid = false;
for (const value of secretsMap.values()) {
if (typeof value != "object") showableAsHybrid = true;
}
if (showableAsHybrid && this.props.settings.kvUseHybridView && secretsMap.size >= 1) {
let kvNormalViewMap = new Map();
let kvCodeViewMap = new Map();
for (const key of secretsMap.keys()) {
const value = secretsMap.get(key);
if (typeof value == "object") {
kvCodeViewMap = kvCodeViewMap.set(key, value);
} else {
kvNormalViewMap = kvNormalViewMap.set(key, value);
}
}
kvNormalViewMap = sortedObjectMap(
Object.fromEntries(kvNormalViewMap) as Record<string, unknown>,
);
kvCodeViewMap = sortedObjectMap(Object.fromEntries(kvCodeViewMap) as Record<string, unknown>);
return (
<div>
<KVSecretNormalVew {...this.props} data={kvNormalViewMap} />
{kvCodeViewMap.size >= 1 && <KVSecretCodeVew {...this.props} data={kvCodeViewMap} />}
<KVTOTPExtra {...this.props} data={secretsMap} />
</div>
);
}
if (isMultiLevel || this.props.settings.kvAlwaysCodeView) {
return <KVSecretCodeVew {...this.props} data={secretsMap} />;
return (
<div>
<KVSecretCodeVew {...this.props} data={secretsMap} />
<KVTOTPExtra {...this.props} data={secretsMap} />
</div>
);
} else {
return <KVSecretNormalVew {...this.props} data={secretsMap} />;
return (
<div>
<KVSecretNormalVew {...this.props} data={secretsMap} />
<KVTOTPExtra {...this.props} data={secretsMap} />
</div>
);
}
}
}

View file

@ -4,6 +4,7 @@ import { DefaultPageProps } from "../../../types/DefaultPageProps";
import { InlineButtonBox } from "../../elements/InlineButtonBox";
import { MountType } from "../../../api/types/mount";
import { PageTitle } from "../../elements/PageTitle";
import { kvListURL, totpListURL, transitListURL } from "../pageLinks";
import { sendErrorNotification } from "../../elements/ErrorMessage";
import { sortedObjectMap } from "../../../utils";
import i18next from "i18next";
@ -28,23 +29,21 @@ function MountLink(props: MountLinkProps): JSX.Element {
const baseMount = props.baseMount;
let linkText = "";
let mountPathType: string;
let link = "";
if (mount.type == "kv") {
linkText = `K/V (v${mount.options.version}) - ${baseMount}`;
mountPathType = "kv";
link = kvListURL(baseMount, []);
} else if (mount.type == "cubbyhole") {
linkText = `Cubbyhole - ${baseMount}`;
mountPathType = "kv";
link = kvListURL(baseMount, []);
} else if (mount.type == "totp") {
linkText = `TOTP - ${baseMount}`;
mountPathType = "totp";
link = totpListURL(baseMount);
} else if (mount.type == "transit") {
linkText = `Transit - ${baseMount}`;
mountPathType = "transit";
link = transitListURL(baseMount);
}
const link = "/secrets/" + mountPathType + "/list/" + baseMount;
return (
<li>
<a href={link}>{linkText}</a>

View file

@ -205,37 +205,60 @@ export class TOTPList extends Component<DefaultPageProps, TOTPListState> {
const mountCaps = this.state.capabilities[mountsPath];
const totpCaps = this.state.capabilities[baseMount];
const secretItem = this.props.matches["secretItem"] || null;
const secretItemSet = secretItem != null && typeof secretItem == "string";
return (
<>
<SecretTitleElement type="totp" baseMount={baseMount} />
<div>
<InlineButtonBox>
{totpCaps.includes("create") && (
<Button
text={i18next.t("common_new")}
color="primary"
route={totpNewURL(baseMount)}
/>
)}
{totpCaps.includes("create") && (
<Button
text={i18next.t("totp_new_generated")}
color="secondary"
route={totpNewGeneratedURL(baseMount)}
/>
)}
{mountCaps.includes("delete") && (
<Button
text={i18next.t("common_delete")}
color="danger"
route={delSecretsEngineURL(baseMount)}
/>
)}
</InlineButtonBox>
<SecretTitleElement type="totp" baseMount={baseMount} item={secretItem} />
{!secretItemSet && (
<div>
<TOTPListView {...this.props} totpItems={this.state.totpItems} />
<InlineButtonBox>
{totpCaps.includes("create") && (
<Button
text={i18next.t("common_new")}
color="primary"
route={totpNewURL(baseMount)}
/>
)}
{totpCaps.includes("create") && (
<Button
text={i18next.t("totp_new_generated")}
color="secondary"
route={totpNewGeneratedURL(baseMount)}
/>
)}
{mountCaps.includes("delete") && (
<Button
text={i18next.t("common_delete")}
color="danger"
route={delSecretsEngineURL(baseMount)}
/>
)}
</InlineButtonBox>
<div>
<TOTPListView {...this.props} totpItems={this.state.totpItems} />
</div>
</div>
</div>
)}
{secretItemSet && (
<div>
<RefreshingTOTPGridItem
{...this.props}
baseMount={this.props.matches["baseMount"]}
totpKey={secretItem}
canDelete={false}
/>
<br />
<Button
text={i18next.t("common_back")}
color="primary"
onClick={() => {
window.history.back();
}}
/>
</div>
)}
</>
);
}

View file

@ -23,6 +23,7 @@ export class SetLanguage extends Component<DefaultPageProps> {
<Margin>
<Select name="language">
{Object.getOwnPropertyNames(translations).map((languageID) => {
// @ts-ignore
const languageName = i18next.getFixedT(languageID, null)("language_name");
const languageCompletionPercent = getTranslationCompletePercentage(languageID);
const name = `${languageName} (${languageCompletionPercent})`;

View file

@ -26,6 +26,7 @@ export class LanguageSetting extends Component<DefaultPageProps> {
}}
>
{Object.getOwnPropertyNames(translations).map((languageID) => {
// @ts-ignore
const languageName = i18next.getFixedT(languageID, null)("language_name");
const languageCompletionPercent = getTranslationCompletePercentage(languageID);
const name = `${languageName} (${languageCompletionPercent})`;

View file

@ -12,6 +12,7 @@ export class VaultURLSetting extends Component<DefaultPageProps> {
return (
<InputWithTitle title={i18next.t("settings_general_vault_url")}>
<TextInput
inputRef={this.vaultURLInputRef}
value={this.props.settings.apiURL}
onChange={() => {
// TODO: check for api health to see if is valid api url.

View file

@ -1,3 +1,4 @@
import { Checkbox } from "../../../elements/forms/Checkbox";
import { Component, createRef } from "preact";
import { DefaultPageProps } from "../../../../types/DefaultPageProps";
import { InputWithTitle } from "../../../elements/InputWithTitle";
@ -10,6 +11,8 @@ import i18next from "i18next";
export class KeyValueViewSettings extends Component<DefaultPageProps> {
viewSyntaxSelectRef = createRef<HTMLSelectElement>();
viewIndentInputRef = createRef<HTMLInputElement>();
hybridModeInputRef = createRef<HTMLInputElement>();
render() {
return (
<div>
@ -47,6 +50,19 @@ export class KeyValueViewSettings extends Component<DefaultPageProps> {
}}
/>
</InputWithTitle>
{/* Always view in code mode */}
<InputWithTitle title={i18next.t("settings_kv_use_hybrid_mode")}>
<Checkbox
checkboxRef={this.hybridModeInputRef}
checked={this.props.settings.kvUseHybridView}
onChange={() => {
const value = this.hybridModeInputRef.current.checked;
this.props.settings.kvUseHybridView = value;
settingsSavedNotification();
}}
/>
</InputWithTitle>
</div>
);
}

View file

@ -67,8 +67,10 @@ export function totpNewGeneratedURL(baseMount: string): string {
return `/secrets/totp/new_generated/${baseMount}`;
}
export function totpListURL(baseMount: string): string {
return `/secrets/totp/list/${baseMount}`;
export function totpListURL(baseMount: string, secretItem: string = null): string {
let link = `/secrets/totp/list/${baseMount}`;
if (secretItem != null) link += `/${secretItem.toString()}`;
return link;
}
export function totpDeleteURL(baseMount: string, secret: string): string {
@ -81,6 +83,10 @@ export function transitNewSecretURL(baseMount: string): string {
return `/secrets/transit/new/${baseMount}`;
}
export function transitListURL(baseMount: string): string {
return `/secrets/totp/list/${baseMount}`;
}
export function transitListSecretURL(baseMount: string, secret: string): string {
return `/secrets/transit/list/${baseMount}/${secret}`;
}

View file

@ -1,2 +0,0 @@
#!/usr/bin/env sh
npx npm-check-updates -u

View file

@ -13,17 +13,12 @@ var babelOptions = {
{
"corejs": { "version": 3 },
"useBuiltIns": "usage",
"targets": {
"firefox": "78",
"chrome": "84",
"safari": "11.1"
}
}
]
],
"plugins": [
["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }],
["@babel/plugin-proposal-class-properties"],
["@babel/plugin-transform-class-properties"],
["@babel/transform-runtime"],
["@babel/plugin-syntax-import-assertions"]
]

6694
yarn.lock Normal file

File diff suppressed because it is too large Load diff

7243
yarn.nix Normal file

File diff suppressed because it is too large Load diff