mirror of
https://github.com/Eugeny/tabby.git
synced 2025-03-19 15:30:40 +08:00
allow renaming and replacing files in the vault - fixes #4110
This commit is contained in:
parent
0008b2f022
commit
67bbbd7f65
app/src
tabby-core/src
tabby-local/src
tabby-settings/src/components
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user