mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-14 16:40:05 +00:00
allow renaming and replacing files in the vault - fixes #4110
This commit is contained in:
parent
0008b2f022
commit
67bbbd7f65
@ -115,7 +115,8 @@ ngb-typeahead-window {
|
|||||||
|
|
||||||
.hover-reveal-parent:hover &,
|
.hover-reveal-parent:hover &,
|
||||||
*:hover > &,
|
*:hover > &,
|
||||||
&:hover {
|
&:hover,
|
||||||
|
&.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,6 @@ export { ProfilesService } from '../services/profiles.service'
|
|||||||
export { SelectorService } from '../services/selector.service'
|
export { SelectorService } from '../services/selector.service'
|
||||||
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
|
export { TabsService, NewTabParameters, TabComponentType } from '../services/tabs.service'
|
||||||
export { UpdaterService } from '../services/updater.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 { FileProvidersService } from '../services/fileProviders.service'
|
||||||
export * from '../utils'
|
export * from '../utils'
|
||||||
|
@ -30,6 +30,13 @@ export interface VaultSecret {
|
|||||||
value: string
|
value: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface VaultFileSecret extends VaultSecret {
|
||||||
|
key: {
|
||||||
|
id: string
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Vault {
|
export interface Vault {
|
||||||
config: any
|
config: any
|
||||||
secrets: VaultSecret[]
|
secrets: VaultSecret[]
|
||||||
@ -121,6 +128,10 @@ export class VaultService {
|
|||||||
return !!_rememberedPassphrase
|
return !!_rememberedPassphrase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forgetPassphrase (): void {
|
||||||
|
_rememberedPassphrase = null
|
||||||
|
}
|
||||||
|
|
||||||
async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> {
|
async decrypt (storage: StoredVault, passphrase?: string): Promise<Vault> {
|
||||||
if (!passphrase) {
|
if (!passphrase) {
|
||||||
passphrase = await this.getPassphrase()
|
passphrase = await this.getPassphrase()
|
||||||
@ -128,7 +139,7 @@ export class VaultService {
|
|||||||
try {
|
try {
|
||||||
return await wrapPromise(this.zone, decryptVault(storage, passphrase))
|
return await wrapPromise(this.zone, decryptVault(storage, passphrase))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_rememberedPassphrase = null
|
this.forgetPassphrase()
|
||||||
if (e.toString().includes('BAD_DECRYPT')) {
|
if (e.toString().includes('BAD_DECRYPT')) {
|
||||||
this.notifications.error('Incorrect passphrase')
|
this.notifications.error('Incorrect passphrase')
|
||||||
}
|
}
|
||||||
@ -193,6 +204,20 @@ export class VaultService {
|
|||||||
await this.save(vault)
|
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> {
|
async removeSecret (type: string, key: Record<string, any>): Promise<void> {
|
||||||
await this.ready$.toPromise()
|
await this.ready$.toPromise()
|
||||||
const vault = await this.load()
|
const vault = await this.load()
|
||||||
@ -274,7 +299,7 @@ export class VaultFileProvider extends FileProvider {
|
|||||||
type: VAULT_SECRET_TYPE_FILE,
|
type: VAULT_SECRET_TYPE_FILE,
|
||||||
key: {
|
key: {
|
||||||
id,
|
id,
|
||||||
description,
|
description: `${description} (${transfer.getName()})`,
|
||||||
},
|
},
|
||||||
value: (await transfer.readAll()).toString('base64'),
|
value: (await transfer.readAll()).toString('base64'),
|
||||||
})
|
})
|
||||||
|
@ -27,7 +27,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider {
|
|||||||
click: async () => {
|
click: async () => {
|
||||||
const modal = this.ngbModal.open(PromptModalComponent)
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
modal.componentInstance.prompt = 'New profile name'
|
modal.componentInstance.prompt = 'New profile name'
|
||||||
const name = (await modal.result)?.name
|
const name = (await modal.result)?.value
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return
|
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')
|
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
|
||||||
i.fas.fa-key
|
i.fas.fa-key
|
||||||
.mr-auto {{getSecretLabel(secret)}}
|
.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
|
h3.mt-5 Options
|
||||||
.form-line
|
.form-line
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Component } from '@angular/core'
|
import { Component } from '@angular/core'
|
||||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
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'
|
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
|
||||||
|
|
||||||
|
|
||||||
@ -12,6 +12,7 @@ import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.comp
|
|||||||
})
|
})
|
||||||
export class VaultSettingsTabComponent extends BaseComponent {
|
export class VaultSettingsTabComponent extends BaseComponent {
|
||||||
vaultContents: Vault|null = null
|
vaultContents: Vault|null = null
|
||||||
|
VAULT_SECRET_TYPE_FILE = VAULT_SECRET_TYPE_FILE
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
public vault: VaultService,
|
public vault: VaultService,
|
||||||
@ -91,4 +92,51 @@ export class VaultSettingsTabComponent extends BaseComponent {
|
|||||||
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
|
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
|
||||||
this.vault.removeSecret(secret.type, secret.key)
|
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