From 67bbbd7f65f14febfe26c9fd414585473ef1a743 Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Sat, 10 Jul 2021 21:31:18 +0200 Subject: [PATCH] allow renaming and replacing files in the vault - fixes #4110 --- app/src/global.scss | 3 +- tabby-core/src/api/index.ts | 2 +- tabby-core/src/services/vault.service.ts | 29 ++++++++++- tabby-local/src/tabContextMenu.ts | 2 +- .../components/vaultSettingsTab.component.pug | 31 +++++++++++- .../components/vaultSettingsTab.component.ts | 50 ++++++++++++++++++- 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/app/src/global.scss b/app/src/global.scss index 31beb9f0..690d1064 100644 --- a/app/src/global.scss +++ b/app/src/global.scss @@ -115,7 +115,8 @@ ngb-typeahead-window { .hover-reveal-parent:hover &, *:hover > &, - &:hover { + &:hover, + &.show { opacity: 1; } } diff --git a/tabby-core/src/api/index.ts b/tabby-core/src/api/index.ts index aae0d4cb..d9eee66a 100644 --- a/tabby-core/src/api/index.ts +++ b/tabby-core/src/api/index.ts @@ -31,6 +31,6 @@ export { ProfilesService } from '../services/profiles.service' export { SelectorService } from '../services/selector.service' export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service' export { UpdaterService } from '../services/updater.service' -export { VaultService, Vault, VaultSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service' +export { VaultService, Vault, VaultSecret, VaultFileSecret, VAULT_SECRET_TYPE_FILE } from '../services/vault.service' export { FileProvidersService } from '../services/fileProviders.service' export * from '../utils' diff --git a/tabby-core/src/services/vault.service.ts b/tabby-core/src/services/vault.service.ts index 7e13e1a1..835a4bd1 100644 --- a/tabby-core/src/services/vault.service.ts +++ b/tabby-core/src/services/vault.service.ts @@ -30,6 +30,13 @@ export interface VaultSecret { value: string } +export interface VaultFileSecret extends VaultSecret { + key: { + id: string + description: string + } +} + export interface Vault { config: any secrets: VaultSecret[] @@ -121,6 +128,10 @@ export class VaultService { return !!_rememberedPassphrase } + forgetPassphrase (): void { + _rememberedPassphrase = null + } + async decrypt (storage: StoredVault, passphrase?: string): Promise { if (!passphrase) { passphrase = await this.getPassphrase() @@ -128,7 +139,7 @@ export class VaultService { try { return await wrapPromise(this.zone, decryptVault(storage, passphrase)) } catch (e) { - _rememberedPassphrase = null + this.forgetPassphrase() if (e.toString().includes('BAD_DECRYPT')) { this.notifications.error('Incorrect passphrase') } @@ -193,6 +204,20 @@ export class VaultService { await this.save(vault) } + async updateSecret (secret: VaultSecret, update: VaultSecret): Promise { + await this.ready$.toPromise() + const vault = await this.load() + if (!vault) { + return + } + const target = vault.secrets.find(s => s.type === secret.type && this.keyMatches(secret.key, s)) + if (!target) { + return + } + Object.assign(target, update) + await this.save(vault) + } + async removeSecret (type: string, key: Record): Promise { await this.ready$.toPromise() const vault = await this.load() @@ -274,7 +299,7 @@ export class VaultFileProvider extends FileProvider { type: VAULT_SECRET_TYPE_FILE, key: { id, - description, + description: `${description} (${transfer.getName()})`, }, value: (await transfer.readAll()).toString('base64'), }) diff --git a/tabby-local/src/tabContextMenu.ts b/tabby-local/src/tabContextMenu.ts index fd9a1dab..9d5081aa 100644 --- a/tabby-local/src/tabContextMenu.ts +++ b/tabby-local/src/tabContextMenu.ts @@ -27,7 +27,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { click: async () => { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = 'New profile name' - const name = (await modal.result)?.name + const name = (await modal.result)?.value if (!name) { return } diff --git a/tabby-settings/src/components/vaultSettingsTab.component.pug b/tabby-settings/src/components/vaultSettingsTab.component.pug index 16585bc8..f6108031 100644 --- a/tabby-settings/src/components/vaultSettingsTab.component.pug +++ b/tabby-settings/src/components/vaultSettingsTab.component.pug @@ -27,8 +27,35 @@ div(*ngIf='vault.isEnabled()') .list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets') i.fas.fa-key .mr-auto {{getSecretLabel(secret)}} - button.btn.btn-link((click)='removeSecret(secret)') - i.fas.fa-trash + + .hover-reveal(ngbDropdown) + button.btn.btn-link(ngbDropdownToggle) + i.fas.fa-ellipsis-v + div(ngbDropdownMenu) + button( + ngbDropdownItem, + *ngIf='secret.type === VAULT_SECRET_TYPE_FILE', + (click)='renameFile(secret)' + ) + i.fas.fa-fw.fa-pencil-alt + span Rename + button( + ngbDropdownItem, + *ngIf='secret.type === VAULT_SECRET_TYPE_FILE', + (click)='replaceFileContent(secret)' + ) + i.fas.fa-fw.fa-file-import + span Replace + button( + ngbDropdownItem, + *ngIf='secret.type === VAULT_SECRET_TYPE_FILE', + (click)='exportFile(secret)' + ) + i.fas.fa-fw.fa-file-export + span Export + button(ngbDropdownItem, (click)='removeSecret(secret)') + i.fas.fa-fw.fa-trash + span Delete h3.mt-5 Options .form-line diff --git a/tabby-settings/src/components/vaultSettingsTab.component.ts b/tabby-settings/src/components/vaultSettingsTab.component.ts index 15e097c8..bd1ed599 100644 --- a/tabby-settings/src/components/vaultSettingsTab.component.ts +++ b/tabby-settings/src/components/vaultSettingsTab.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Component } from '@angular/core' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE } from 'tabby-core' +import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE, PromptModalComponent, VaultFileSecret } from 'tabby-core' import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component' @@ -12,6 +12,7 @@ import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.comp }) export class VaultSettingsTabComponent extends BaseComponent { vaultContents: Vault|null = null + VAULT_SECRET_TYPE_FILE = VAULT_SECRET_TYPE_FILE constructor ( public vault: VaultService, @@ -91,4 +92,51 @@ export class VaultSettingsTabComponent extends BaseComponent { this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret) this.vault.removeSecret(secret.type, secret.key) } + + async replaceFileContent (secret: VaultFileSecret) { + const transfers = await this.platform.startUpload() + if (!transfers.length) { + return + } + await this.vault.updateSecret(secret, { + ...secret, + value: (await transfers[0].readAll()).toString('base64'), + }) + this.loadVault() + } + + async renameFile (secret: VaultFileSecret) { + const modal = this.ngbModal.open(PromptModalComponent) + modal.componentInstance.prompt = 'New name' + modal.componentInstance.value = secret.key.description + + const description = (await modal.result)?.value + if (!description) { + return + } + + await this.vault.updateSecret(secret, { + ...secret, + key: { + ...secret.key, + description, + }, + }) + + this.loadVault() + } + + async exportFile (secret: VaultFileSecret) { + this.vault.forgetPassphrase() + + secret = (await this.vault.getSecret(secret.type, secret.key)) as VaultFileSecret + + const content = Buffer.from(secret.value, 'base64') + const download = await this.platform.startDownload(secret.key.description, 0o600, content.length) + + if (download) { + await download.write(content) + download.close() + } + } }