allow renaming and replacing files in the vault - fixes #4110

上级 0008b2f0
......@@ -115,7 +115,8 @@ ngb-typeahead-window {
.hover-reveal-parent:hover &,
*:hover > &,
&:hover {
&:hover,
&.show {
opacity: 1;
}
}
......
......@@ -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'
......@@ -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<Vault> {
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<void> {
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<string, any>): Promise<void> {
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'),
})
......
......@@ -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
}
......
......@@ -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
......
/* 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()
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册