mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-13 07:59:59 +00:00
added the vault
This commit is contained in:
parent
17471096f2
commit
7f18396926
@ -138,3 +138,17 @@ ngb-typeahead-window {
|
|||||||
transform: translateX(0);
|
transform: translateX(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown {
|
||||||
|
.dropdown-toggle::after {
|
||||||
|
vertical-align: 0.15em;
|
||||||
|
margin-left: .5em;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
i + span {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"bootstrap": "^4.1.3",
|
"bootstrap": "^4.1.3",
|
||||||
"clone-deep": "^4.0.1",
|
"clone-deep": "^4.0.1",
|
||||||
"core-js": "^3.1.2",
|
"core-js": "^3.1.2",
|
||||||
|
"deep-equal": "^2.0.5",
|
||||||
"deepmerge": "^4.1.1",
|
"deepmerge": "^4.1.1",
|
||||||
"electron-updater": "^4.0.6",
|
"electron-updater": "^4.0.6",
|
||||||
"js-yaml": "^4.0.0",
|
"js-yaml": "^4.0.0",
|
||||||
|
@ -27,4 +27,5 @@ export { NotificationsService } from '../services/notifications.service'
|
|||||||
export { ThemesService } from '../services/themes.service'
|
export { ThemesService } from '../services/themes.service'
|
||||||
export { TabsService } from '../services/tabs.service'
|
export { TabsService } from '../services/tabs.service'
|
||||||
export { UpdaterService } from '../services/updater.service'
|
export { UpdaterService } from '../services/updater.service'
|
||||||
|
export { VaultService, Vault, VaultSecret } from '../services/vault.service'
|
||||||
export * from '../utils'
|
export * from '../utils'
|
||||||
|
29
terminus-core/src/components/unlockVaultModal.component.pug
Normal file
29
terminus-core/src/components/unlockVaultModal.component.pug
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
.modal-body
|
||||||
|
.d-flex.align-items-center.mb-3
|
||||||
|
h3.m-0 Vault is locked
|
||||||
|
.ml-auto(ngbDropdown, placement='bottom-right')
|
||||||
|
button.btn.btn-link(ngbDropdownToggle, (click)='$event.stopPropagation()')
|
||||||
|
span(*ngIf='rememberFor') Remember for {{rememberFor}} min
|
||||||
|
span(*ngIf='!rememberFor') Do not remember
|
||||||
|
div(ngbDropdownMenu)
|
||||||
|
button.dropdown-item(
|
||||||
|
(click)='rememberFor = 0',
|
||||||
|
) Do not remember
|
||||||
|
button.dropdown-item(
|
||||||
|
*ngFor='let x of rememberOptions',
|
||||||
|
(click)='rememberFor = x',
|
||||||
|
) {{x}} min
|
||||||
|
|
||||||
|
.input-group
|
||||||
|
input.form-control.form-control-lg(
|
||||||
|
type='password',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='passphrase',
|
||||||
|
#input,
|
||||||
|
placeholder='Master passphrase',
|
||||||
|
(keyup.enter)='ok()',
|
||||||
|
(keyup.esc)='cancel()',
|
||||||
|
)
|
||||||
|
.input-group-append
|
||||||
|
button.btn.btn-secondary((click)='ok()', *ngIf='passphrase')
|
||||||
|
i.fas.fa-check
|
36
terminus-core/src/components/unlockVaultModal.component.ts
Normal file
36
terminus-core/src/components/unlockVaultModal.component.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Component, ViewChild, ElementRef } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./unlockVaultModal.component.pug'),
|
||||||
|
})
|
||||||
|
export class UnlockVaultModalComponent {
|
||||||
|
passphrase: string
|
||||||
|
rememberFor = 1
|
||||||
|
rememberOptions = [1, 5, 15, 60]
|
||||||
|
@ViewChild('input') input: ElementRef
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit (): void {
|
||||||
|
this.rememberFor = window.localStorage.vaultRememberPassphraseFor ?? 0
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ok (): void {
|
||||||
|
window.localStorage.vaultRememberPassphraseFor = this.rememberFor
|
||||||
|
this.modalInstance.close({
|
||||||
|
passphrase: this.passphrase,
|
||||||
|
rememberFor: this.rememberFor,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel (): void {
|
||||||
|
this.modalInstance.close(null)
|
||||||
|
}
|
||||||
|
}
|
@ -22,3 +22,4 @@ electronFlags:
|
|||||||
- ['force_discrete_gpu', '0']
|
- ['force_discrete_gpu', '0']
|
||||||
enableAutomaticUpdates: true
|
enableAutomaticUpdates: true
|
||||||
version: 1
|
version: 1
|
||||||
|
vault: null
|
||||||
|
@ -19,6 +19,7 @@ import { RenameTabModalComponent } from './components/renameTabModal.component'
|
|||||||
import { SelectorModalComponent } from './components/selectorModal.component'
|
import { SelectorModalComponent } from './components/selectorModal.component'
|
||||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||||
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
import { SplitTabSpannerComponent } from './components/splitTabSpanner.component'
|
||||||
|
import { UnlockVaultModalComponent } from './components/unlockVaultModal.component'
|
||||||
import { WelcomeTabComponent } from './components/welcomeTab.component'
|
import { WelcomeTabComponent } from './components/welcomeTab.component'
|
||||||
|
|
||||||
import { AutofocusDirective } from './directives/autofocus.directive'
|
import { AutofocusDirective } from './directives/autofocus.directive'
|
||||||
@ -78,6 +79,7 @@ const PROVIDERS = [
|
|||||||
SelectorModalComponent,
|
SelectorModalComponent,
|
||||||
SplitTabComponent,
|
SplitTabComponent,
|
||||||
SplitTabSpannerComponent,
|
SplitTabSpannerComponent,
|
||||||
|
UnlockVaultModalComponent,
|
||||||
WelcomeTabComponent,
|
WelcomeTabComponent,
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
@ -85,6 +87,7 @@ const PROVIDERS = [
|
|||||||
SafeModeModalComponent,
|
SafeModeModalComponent,
|
||||||
SelectorModalComponent,
|
SelectorModalComponent,
|
||||||
SplitTabComponent,
|
SplitTabComponent,
|
||||||
|
UnlockVaultModalComponent,
|
||||||
WelcomeTabComponent,
|
WelcomeTabComponent,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
|
210
terminus-core/src/services/vault.service.ts
Normal file
210
terminus-core/src/services/vault.service.ts
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import * as crypto from 'crypto'
|
||||||
|
import { promisify } from 'util'
|
||||||
|
import { Injectable, NgZone } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { AsyncSubject, Observable } from 'rxjs'
|
||||||
|
import { ConfigService } from '../services/config.service'
|
||||||
|
import { UnlockVaultModalComponent } from '../components/unlockVaultModal.component'
|
||||||
|
import { NotificationsService } from '../services/notifications.service'
|
||||||
|
|
||||||
|
const PBKDF_ITERATIONS = 100000
|
||||||
|
const PBKDF_DIGEST = 'sha512'
|
||||||
|
const PBKDF_SALT_LENGTH = 64 / 8
|
||||||
|
const CRYPT_ALG = 'aes-256-cbc'
|
||||||
|
const CRYPT_KEY_LENGTH = 256 / 8
|
||||||
|
const CRYPT_IV_LENGTH = 128 / 8
|
||||||
|
|
||||||
|
interface StoredVault {
|
||||||
|
version: number
|
||||||
|
contents: string
|
||||||
|
keySalt: string
|
||||||
|
iv: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VaultSecret {
|
||||||
|
type: string
|
||||||
|
key: Record<string, any>
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Vault {
|
||||||
|
secrets: VaultSecret[]
|
||||||
|
}
|
||||||
|
|
||||||
|
function migrateVaultContent (content: any): Vault {
|
||||||
|
return {
|
||||||
|
secrets: content.secrets ?? [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deriveVaultKey (passphrase: string, salt: Buffer): Promise<Buffer> {
|
||||||
|
return promisify(crypto.pbkdf2)(
|
||||||
|
Buffer.from(passphrase),
|
||||||
|
salt,
|
||||||
|
PBKDF_ITERATIONS,
|
||||||
|
CRYPT_KEY_LENGTH,
|
||||||
|
PBKDF_DIGEST,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function encryptVault (content: Vault, passphrase: string): Promise<StoredVault> {
|
||||||
|
const keySalt = await promisify(crypto.randomBytes)(PBKDF_SALT_LENGTH)
|
||||||
|
const iv = await promisify(crypto.randomBytes)(CRYPT_IV_LENGTH)
|
||||||
|
const key = await deriveVaultKey(passphrase, keySalt)
|
||||||
|
|
||||||
|
const plaintext = JSON.stringify(content)
|
||||||
|
const cipher = crypto.createCipheriv(CRYPT_ALG, key, iv)
|
||||||
|
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()])
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: 1,
|
||||||
|
contents: encrypted.toString('base64'),
|
||||||
|
keySalt: keySalt.toString('hex'),
|
||||||
|
iv: iv.toString('hex'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decryptVault (vault: StoredVault, passphrase: string): Promise<Vault> {
|
||||||
|
if (vault.version !== 1) {
|
||||||
|
throw new Error(`Unsupported vault format version ${vault.version}`)
|
||||||
|
}
|
||||||
|
const keySalt = Buffer.from(vault.keySalt, 'hex')
|
||||||
|
const key = await deriveVaultKey(passphrase, keySalt)
|
||||||
|
const iv = Buffer.from(vault.iv, 'hex')
|
||||||
|
const encrypted = Buffer.from(vault.contents, 'base64')
|
||||||
|
|
||||||
|
const decipher = crypto.createDecipheriv(CRYPT_ALG, key, iv)
|
||||||
|
const plaintext = decipher.update(encrypted, undefined, 'utf-8') + decipher.final('utf-8')
|
||||||
|
return migrateVaultContent(JSON.parse(plaintext))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't make it accessible through VaultService fields
|
||||||
|
let _rememberedPassphrase: string|null = null
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class VaultService {
|
||||||
|
/** Fires once when the config is loaded */
|
||||||
|
get ready$ (): Observable<boolean> { return this.ready }
|
||||||
|
|
||||||
|
enabled = false
|
||||||
|
private ready = new AsyncSubject<boolean>()
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
private constructor (
|
||||||
|
private config: ConfigService,
|
||||||
|
private zone: NgZone,
|
||||||
|
private notifications: NotificationsService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
) {
|
||||||
|
config.ready$.toPromise().then(() => {
|
||||||
|
this.onConfigChange()
|
||||||
|
this.ready.next(true)
|
||||||
|
this.ready.complete()
|
||||||
|
config.changed$.subscribe(() => {
|
||||||
|
this.onConfigChange()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async setEnabled (enabled: boolean, passphrase?: string): Promise<void> {
|
||||||
|
if (enabled) {
|
||||||
|
if (!this.config.store.vault) {
|
||||||
|
await this.save(migrateVaultContent({}), passphrase)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.config.store.vault = null
|
||||||
|
await this.config.save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isOpen (): boolean {
|
||||||
|
return !!_rememberedPassphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
async load (passphrase?: string): Promise<Vault|null> {
|
||||||
|
if (!this.config.store.vault) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!passphrase) {
|
||||||
|
passphrase = await this.getPassphrase()
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await this.wrapPromise(decryptVault(this.config.store.vault, passphrase))
|
||||||
|
} catch (e) {
|
||||||
|
_rememberedPassphrase = null
|
||||||
|
if (e.toString().includes('BAD_DECRYPT')) {
|
||||||
|
this.notifications.error('Incorrect passphrase')
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async save (vault: Vault, passphrase?: string): Promise<void> {
|
||||||
|
if (!passphrase) {
|
||||||
|
passphrase = await this.getPassphrase()
|
||||||
|
}
|
||||||
|
if (_rememberedPassphrase) {
|
||||||
|
_rememberedPassphrase = passphrase
|
||||||
|
}
|
||||||
|
this.config.store.vault = await this.wrapPromise(encryptVault(vault, passphrase))
|
||||||
|
await this.config.save()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPassphrase (): Promise<string> {
|
||||||
|
if (!_rememberedPassphrase) {
|
||||||
|
const modal = this.ngbModal.open(UnlockVaultModalComponent)
|
||||||
|
const { passphrase, rememberFor } = await modal.result
|
||||||
|
setTimeout(() => {
|
||||||
|
_rememberedPassphrase = null
|
||||||
|
}, rememberFor * 60000)
|
||||||
|
_rememberedPassphrase = passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
return _rememberedPassphrase!
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSecret (type: string, key: Record<string, any>): Promise<VaultSecret|null> {
|
||||||
|
const vault = await this.load()
|
||||||
|
if (!vault) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return vault.secrets.find(s => s.type === type && this.keyMatches(key, s)) ?? null
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSecret (secret: VaultSecret): Promise<void> {
|
||||||
|
const vault = await this.load()
|
||||||
|
if (!vault) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vault.secrets = vault.secrets.filter(s => s.type !== secret.type || !this.keyMatches(secret.key, s))
|
||||||
|
vault.secrets.push(secret)
|
||||||
|
await this.save(vault)
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeSecret (type: string, key: Record<string, any>): Promise<void> {
|
||||||
|
const vault = await this.load()
|
||||||
|
if (!vault) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vault.secrets = vault.secrets.filter(s => s.type !== type || !this.keyMatches(key, s))
|
||||||
|
await this.save(vault)
|
||||||
|
}
|
||||||
|
|
||||||
|
private keyMatches (key: Record<string, any>, secret: VaultSecret): boolean {
|
||||||
|
return Object.keys(key).every(k => secret.key[k] === key[k])
|
||||||
|
}
|
||||||
|
|
||||||
|
private onConfigChange () {
|
||||||
|
this.enabled = !!this.config.store.vault
|
||||||
|
}
|
||||||
|
|
||||||
|
private wrapPromise <T> (promise: Promise<T>): Promise<T> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
promise.then(result => {
|
||||||
|
this.zone.run(() => resolve(result))
|
||||||
|
}).catch(error => {
|
||||||
|
this.zone.run(() => reject(error))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -24,6 +24,11 @@ argparse@^2.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||||
|
|
||||||
|
available-typed-arrays@^1.0.2:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.4.tgz#9e0ae84ecff20caae6a94a1c3bc39b955649b7a9"
|
||||||
|
integrity sha512-SA5mXJWrId1TaQjfxUYghbqQ/hYioKmLJvPJyDuYRtXXenFNMjj4hSSt1Cf1xsuXSXrtxrVC5Ot4eU6cOtBDdA==
|
||||||
|
|
||||||
bootstrap@^4.1.3:
|
bootstrap@^4.1.3:
|
||||||
version "4.5.3"
|
version "4.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6"
|
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.3.tgz#c6a72b355aaf323920be800246a6e4ef30997fe6"
|
||||||
@ -37,6 +42,14 @@ builder-util-runtime@8.7.5:
|
|||||||
debug "^4.3.2"
|
debug "^4.3.2"
|
||||||
sax "^1.2.4"
|
sax "^1.2.4"
|
||||||
|
|
||||||
|
call-bind@^1.0.0, call-bind@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||||
|
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
get-intrinsic "^1.0.2"
|
||||||
|
|
||||||
clone-deep@^4.0.1:
|
clone-deep@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
|
||||||
@ -65,11 +78,39 @@ debug@^4.3.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "2.1.2"
|
ms "2.1.2"
|
||||||
|
|
||||||
|
deep-equal@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-2.0.5.tgz#55cd2fe326d83f9cbf7261ef0e060b3f724c5cb9"
|
||||||
|
integrity sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
es-get-iterator "^1.1.1"
|
||||||
|
get-intrinsic "^1.0.1"
|
||||||
|
is-arguments "^1.0.4"
|
||||||
|
is-date-object "^1.0.2"
|
||||||
|
is-regex "^1.1.1"
|
||||||
|
isarray "^2.0.5"
|
||||||
|
object-is "^1.1.4"
|
||||||
|
object-keys "^1.1.1"
|
||||||
|
object.assign "^4.1.2"
|
||||||
|
regexp.prototype.flags "^1.3.0"
|
||||||
|
side-channel "^1.0.3"
|
||||||
|
which-boxed-primitive "^1.0.1"
|
||||||
|
which-collection "^1.0.1"
|
||||||
|
which-typed-array "^1.1.2"
|
||||||
|
|
||||||
deepmerge@^4.1.1:
|
deepmerge@^4.1.1:
|
||||||
version "4.2.2"
|
version "4.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
|
||||||
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
|
||||||
|
|
||||||
|
define-properties@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||||
|
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||||
|
dependencies:
|
||||||
|
object-keys "^1.0.12"
|
||||||
|
|
||||||
electron-updater@^4.0.6:
|
electron-updater@^4.0.6:
|
||||||
version "4.3.9"
|
version "4.3.9"
|
||||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.9.tgz#247c660bafad7c07935e1b81acd3e9a5fd733154"
|
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-4.3.9.tgz#247c660bafad7c07935e1b81acd3e9a5fd733154"
|
||||||
@ -84,6 +125,56 @@ electron-updater@^4.0.6:
|
|||||||
lodash.isequal "^4.5.0"
|
lodash.isequal "^4.5.0"
|
||||||
semver "^7.3.5"
|
semver "^7.3.5"
|
||||||
|
|
||||||
|
es-abstract@^1.18.0-next.1, es-abstract@^1.18.0-next.2:
|
||||||
|
version "1.18.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0"
|
||||||
|
integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
es-to-primitive "^1.2.1"
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
get-intrinsic "^1.1.1"
|
||||||
|
has "^1.0.3"
|
||||||
|
has-symbols "^1.0.2"
|
||||||
|
is-callable "^1.2.3"
|
||||||
|
is-negative-zero "^2.0.1"
|
||||||
|
is-regex "^1.1.3"
|
||||||
|
is-string "^1.0.6"
|
||||||
|
object-inspect "^1.10.3"
|
||||||
|
object-keys "^1.1.1"
|
||||||
|
object.assign "^4.1.2"
|
||||||
|
string.prototype.trimend "^1.0.4"
|
||||||
|
string.prototype.trimstart "^1.0.4"
|
||||||
|
unbox-primitive "^1.0.1"
|
||||||
|
|
||||||
|
es-get-iterator@^1.1.1:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7"
|
||||||
|
integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
get-intrinsic "^1.1.0"
|
||||||
|
has-symbols "^1.0.1"
|
||||||
|
is-arguments "^1.1.0"
|
||||||
|
is-map "^2.0.2"
|
||||||
|
is-set "^2.0.2"
|
||||||
|
is-string "^1.0.5"
|
||||||
|
isarray "^2.0.5"
|
||||||
|
|
||||||
|
es-to-primitive@^1.2.1:
|
||||||
|
version "1.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
|
||||||
|
integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
|
||||||
|
dependencies:
|
||||||
|
is-callable "^1.1.4"
|
||||||
|
is-date-object "^1.0.1"
|
||||||
|
is-symbol "^1.0.2"
|
||||||
|
|
||||||
|
foreach@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||||
|
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
|
||||||
|
|
||||||
fs-extra@^10.0.0:
|
fs-extra@^10.0.0:
|
||||||
version "10.0.0"
|
version "10.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
|
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1"
|
||||||
@ -93,11 +184,42 @@ fs-extra@^10.0.0:
|
|||||||
jsonfile "^6.0.1"
|
jsonfile "^6.0.1"
|
||||||
universalify "^2.0.0"
|
universalify "^2.0.0"
|
||||||
|
|
||||||
|
function-bind@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
|
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
|
||||||
|
|
||||||
|
get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
|
||||||
|
integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
has "^1.0.3"
|
||||||
|
has-symbols "^1.0.1"
|
||||||
|
|
||||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||||
version "4.2.4"
|
version "4.2.4"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
|
||||||
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
|
||||||
|
|
||||||
|
has-bigints@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
|
||||||
|
integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
|
||||||
|
|
||||||
|
has-symbols@^1.0.1, has-symbols@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
|
||||||
|
integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
|
||||||
|
|
||||||
|
has@^1.0.3:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||||
|
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
https-proxy-agent@5.0.0:
|
https-proxy-agent@5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
|
||||||
@ -111,6 +233,50 @@ inherits@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||||
|
|
||||||
|
is-arguments@^1.0.4, is-arguments@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
|
||||||
|
integrity sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
|
||||||
|
is-bigint@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a"
|
||||||
|
integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==
|
||||||
|
|
||||||
|
is-boolean-object@^1.1.0:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8"
|
||||||
|
integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
|
||||||
|
is-callable@^1.1.4, is-callable@^1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e"
|
||||||
|
integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==
|
||||||
|
|
||||||
|
is-date-object@^1.0.1, is-date-object@^1.0.2:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5"
|
||||||
|
integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==
|
||||||
|
|
||||||
|
is-map@^2.0.1, is-map@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127"
|
||||||
|
integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==
|
||||||
|
|
||||||
|
is-negative-zero@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.1.tgz#3de746c18dda2319241a53675908d8f766f11c24"
|
||||||
|
integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==
|
||||||
|
|
||||||
|
is-number-object@^1.0.4:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb"
|
||||||
|
integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==
|
||||||
|
|
||||||
is-plain-object@^2.0.4:
|
is-plain-object@^2.0.4:
|
||||||
version "2.0.4"
|
version "2.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
|
||||||
@ -118,6 +284,57 @@ is-plain-object@^2.0.4:
|
|||||||
dependencies:
|
dependencies:
|
||||||
isobject "^3.0.1"
|
isobject "^3.0.1"
|
||||||
|
|
||||||
|
is-regex@^1.1.1, is-regex@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
|
||||||
|
integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
has-symbols "^1.0.2"
|
||||||
|
|
||||||
|
is-set@^2.0.1, is-set@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec"
|
||||||
|
integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==
|
||||||
|
|
||||||
|
is-string@^1.0.5, is-string@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f"
|
||||||
|
integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==
|
||||||
|
|
||||||
|
is-symbol@^1.0.2, is-symbol@^1.0.3:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
|
||||||
|
integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
|
||||||
|
dependencies:
|
||||||
|
has-symbols "^1.0.2"
|
||||||
|
|
||||||
|
is-typed-array@^1.1.3:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.5.tgz#f32e6e096455e329eb7b423862456aa213f0eb4e"
|
||||||
|
integrity sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==
|
||||||
|
dependencies:
|
||||||
|
available-typed-arrays "^1.0.2"
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
es-abstract "^1.18.0-next.2"
|
||||||
|
foreach "^2.0.5"
|
||||||
|
has-symbols "^1.0.1"
|
||||||
|
|
||||||
|
is-weakmap@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-weakmap/-/is-weakmap-2.0.1.tgz#5008b59bdc43b698201d18f62b37b2ca243e8cf2"
|
||||||
|
integrity sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==
|
||||||
|
|
||||||
|
is-weakset@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-weakset/-/is-weakset-2.0.1.tgz#e9a0af88dbd751589f5e50d80f4c98b780884f83"
|
||||||
|
integrity sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==
|
||||||
|
|
||||||
|
isarray@^2.0.5:
|
||||||
|
version "2.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
|
||||||
|
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
|
||||||
|
|
||||||
isobject@^3.0.1:
|
isobject@^3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
@ -192,6 +409,34 @@ ngx-perfect-scrollbar@^10.1.0:
|
|||||||
resize-observer-polyfill "^1.5.0"
|
resize-observer-polyfill "^1.5.0"
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
|
object-inspect@^1.10.3, object-inspect@^1.9.0:
|
||||||
|
version "1.10.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
|
||||||
|
integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
|
||||||
|
|
||||||
|
object-is@^1.1.4:
|
||||||
|
version "1.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
|
||||||
|
integrity sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
define-properties "^1.1.3"
|
||||||
|
|
||||||
|
object-keys@^1.0.12, object-keys@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||||
|
integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
|
||||||
|
|
||||||
|
object.assign@^4.1.2:
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
|
||||||
|
integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
define-properties "^1.1.3"
|
||||||
|
has-symbols "^1.0.1"
|
||||||
|
object-keys "^1.1.1"
|
||||||
|
|
||||||
perfect-scrollbar@1.5.0:
|
perfect-scrollbar@1.5.0:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
|
resolved "https://registry.yarnpkg.com/perfect-scrollbar/-/perfect-scrollbar-1.5.0.tgz#821d224ed8ff61990c23f26db63048cdc75b6b83"
|
||||||
@ -206,6 +451,14 @@ readable-stream@3.6.0:
|
|||||||
string_decoder "^1.1.1"
|
string_decoder "^1.1.1"
|
||||||
util-deprecate "^1.0.1"
|
util-deprecate "^1.0.1"
|
||||||
|
|
||||||
|
regexp.prototype.flags@^1.3.0:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26"
|
||||||
|
integrity sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
define-properties "^1.1.3"
|
||||||
|
|
||||||
resize-observer-polyfill@^1.5.0:
|
resize-observer-polyfill@^1.5.0:
|
||||||
version "1.5.1"
|
version "1.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
@ -235,6 +488,31 @@ shallow-clone@^3.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
kind-of "^6.0.2"
|
kind-of "^6.0.2"
|
||||||
|
|
||||||
|
side-channel@^1.0.3:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||||
|
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
get-intrinsic "^1.0.2"
|
||||||
|
object-inspect "^1.9.0"
|
||||||
|
|
||||||
|
string.prototype.trimend@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80"
|
||||||
|
integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
define-properties "^1.1.3"
|
||||||
|
|
||||||
|
string.prototype.trimstart@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz#b36399af4ab2999b4c9c648bd7a3fb2bb26feeed"
|
||||||
|
integrity sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.2"
|
||||||
|
define-properties "^1.1.3"
|
||||||
|
|
||||||
string_decoder@^1.1.1:
|
string_decoder@^1.1.1:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||||
@ -247,6 +525,16 @@ tslib@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
|
||||||
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
|
||||||
|
|
||||||
|
unbox-primitive@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471"
|
||||||
|
integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
has-bigints "^1.0.1"
|
||||||
|
has-symbols "^1.0.2"
|
||||||
|
which-boxed-primitive "^1.0.2"
|
||||||
|
|
||||||
universalify@^2.0.0:
|
universalify@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||||
@ -262,6 +550,40 @@ uuid@^8.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
|
||||||
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
|
||||||
|
|
||||||
|
which-boxed-primitive@^1.0.1, which-boxed-primitive@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
|
||||||
|
integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
|
||||||
|
dependencies:
|
||||||
|
is-bigint "^1.0.1"
|
||||||
|
is-boolean-object "^1.1.0"
|
||||||
|
is-number-object "^1.0.4"
|
||||||
|
is-string "^1.0.5"
|
||||||
|
is-symbol "^1.0.3"
|
||||||
|
|
||||||
|
which-collection@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.1.tgz#70eab71ebbbd2aefaf32f917082fc62cdcb70906"
|
||||||
|
integrity sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==
|
||||||
|
dependencies:
|
||||||
|
is-map "^2.0.1"
|
||||||
|
is-set "^2.0.1"
|
||||||
|
is-weakmap "^2.0.1"
|
||||||
|
is-weakset "^2.0.1"
|
||||||
|
|
||||||
|
which-typed-array@^1.1.2:
|
||||||
|
version "1.1.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.4.tgz#8fcb7d3ee5adf2d771066fba7cf37e32fe8711ff"
|
||||||
|
integrity sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==
|
||||||
|
dependencies:
|
||||||
|
available-typed-arrays "^1.0.2"
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
es-abstract "^1.18.0-next.1"
|
||||||
|
foreach "^2.0.5"
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
has-symbols "^1.0.1"
|
||||||
|
is-typed-array "^1.1.3"
|
||||||
|
|
||||||
yallist@^4.0.0:
|
yallist@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
h3.modal-header.m-0.pb-0 Set master passphrase
|
||||||
|
.modal-body
|
||||||
|
.mb-2 You can change it later, but it's unrecoverable if forgotten.
|
||||||
|
.input-group
|
||||||
|
input.form-control.form-control-lg(
|
||||||
|
[type]='showPassphrase ? "text" : "password"',
|
||||||
|
autofocus,
|
||||||
|
[(ngModel)]='passphrase',
|
||||||
|
#input,
|
||||||
|
placeholder='Master passphrase',
|
||||||
|
(keyup.enter)='ok()',
|
||||||
|
(keyup.esc)='cancel()',
|
||||||
|
)
|
||||||
|
.input-group-append
|
||||||
|
button.btn.btn-secondary((click)='showPassphrase = !showPassphrase')
|
||||||
|
i.fas.fa-eye
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-outline-primary((click)='ok()') Set passphrase
|
||||||
|
button.btn.btn-outline-danger((click)='cancel()') Cancel
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Component, ViewChild, ElementRef } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
template: require('./setVaultPassphraseModal.component.pug'),
|
||||||
|
})
|
||||||
|
export class SetVaultPassphraseModalComponent {
|
||||||
|
passphrase: string
|
||||||
|
@ViewChild('input') input: ElementRef
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit (): void {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.input.nativeElement.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ok (): void {
|
||||||
|
this.modalInstance.close(this.passphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel (): void {
|
||||||
|
this.modalInstance.close(null)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
.text-center(*ngIf='!vault.enabled')
|
||||||
|
i.fas.fa-key.fa-3x.m-3
|
||||||
|
h3.m-3 Vault is not configured
|
||||||
|
.m-3 Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
|
||||||
|
button.btn.btn-primary.m-2((click)='enableVault()') Set master passphrase
|
||||||
|
|
||||||
|
div(*ngIf='vault.enabled')
|
||||||
|
.d-flex.align-items-center.mb-3
|
||||||
|
h3.m-0 Vault
|
||||||
|
.d-flex.ml-auto(ngbDropdown, *ngIf='vault.enabled')
|
||||||
|
button.btn.btn-secondary(ngbDropdownToggle) Options
|
||||||
|
div(ngbDropdownMenu)
|
||||||
|
a(ngbDropdownItem, (click)='changePassphrase()')
|
||||||
|
i.fas.fa-fw.fa-key
|
||||||
|
span Change the master passphrase
|
||||||
|
a(ngbDropdownItem, (click)='disableVault()')
|
||||||
|
i.fas.fa-fw.fa-radiation-alt
|
||||||
|
span Erase the vault
|
||||||
|
|
||||||
|
div(*ngIf='vaultContents')
|
||||||
|
.text-center(*ngIf='!vaultContents.secrets.length')
|
||||||
|
i.fas.fa-empty-set.fa-3x
|
||||||
|
h3.m-3 Vault is empty
|
||||||
|
|
||||||
|
.list-group
|
||||||
|
.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
|
||||||
|
|
||||||
|
.text-center(*ngIf='!vaultContents')
|
||||||
|
i.fas.fa-key.fa-3x
|
||||||
|
h3.m-3 Vault is locked
|
||||||
|
button.btn.btn-primary.m-2((click)='loadVault()') Show vault contents
|
@ -0,0 +1,80 @@
|
|||||||
|
/* 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 } from 'terminus-core'
|
||||||
|
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
|
||||||
|
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Component({
|
||||||
|
selector: 'vault-settings-tab',
|
||||||
|
template: require('./vaultSettingsTab.component.pug'),
|
||||||
|
})
|
||||||
|
export class VaultSettingsTabComponent extends BaseComponent {
|
||||||
|
vaultContents: Vault|null = null
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
public vault: VaultService,
|
||||||
|
private platform: PlatformService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
) {
|
||||||
|
super()
|
||||||
|
if (vault.isOpen()) {
|
||||||
|
this.loadVault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadVault (): Promise<void> {
|
||||||
|
this.vaultContents = await this.vault.load()
|
||||||
|
}
|
||||||
|
|
||||||
|
async enableVault () {
|
||||||
|
const modal = this.ngbModal.open(SetVaultPassphraseModalComponent)
|
||||||
|
const newPassphrase = await modal.result
|
||||||
|
await this.vault.setEnabled(true, newPassphrase)
|
||||||
|
this.vaultContents = await this.vault.load(newPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
async disableVault () {
|
||||||
|
if ((await this.platform.showMessageBox(
|
||||||
|
{
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Delete vault contents?',
|
||||||
|
buttons: ['Keep', 'Delete'],
|
||||||
|
defaultId: 1,
|
||||||
|
}
|
||||||
|
)).response === 1) {
|
||||||
|
await this.vault.setEnabled(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async changePassphrase () {
|
||||||
|
if (!this.vaultContents) {
|
||||||
|
this.vaultContents = await this.vault.load()
|
||||||
|
}
|
||||||
|
if (!this.vaultContents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const modal = this.ngbModal.open(SetVaultPassphraseModalComponent)
|
||||||
|
const newPassphrase = await modal.result
|
||||||
|
this.vault.save(this.vaultContents, newPassphrase)
|
||||||
|
}
|
||||||
|
|
||||||
|
getSecretLabel (secret: VaultSecret) {
|
||||||
|
if (secret.type === 'ssh:password') {
|
||||||
|
return `SSH password for ${secret.key.user}@${secret.key.host}:${secret.key.port}`
|
||||||
|
}
|
||||||
|
if (secret.type === 'ssh:key-passphrase') {
|
||||||
|
return `Passphrase for a private key with hash ${secret.key.hash.substring(0, 8)}...`
|
||||||
|
}
|
||||||
|
return `Unknown secret of type ${secret.type} for ${JSON.stringify(secret.key)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
removeSecret (secret: VaultSecret) {
|
||||||
|
if (!this.vaultContents) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
|
||||||
|
this.vault.removeSecret(secret.type, secret.key)
|
||||||
|
}
|
||||||
|
}
|
@ -11,12 +11,14 @@ import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.compone
|
|||||||
import { SettingsTabComponent } from './components/settingsTab.component'
|
import { SettingsTabComponent } from './components/settingsTab.component'
|
||||||
import { SettingsTabBodyComponent } from './components/settingsTabBody.component'
|
import { SettingsTabBodyComponent } from './components/settingsTabBody.component'
|
||||||
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
|
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
|
||||||
|
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
|
||||||
|
import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component'
|
||||||
|
|
||||||
import { SettingsTabProvider } from './api'
|
import { SettingsTabProvider } from './api'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
import { SettingsHotkeyProvider } from './hotkeys'
|
import { SettingsHotkeyProvider } from './hotkeys'
|
||||||
import { SettingsConfigProvider } from './config'
|
import { SettingsConfigProvider } from './config'
|
||||||
import { HotkeySettingsTabProvider, WindowSettingsTabProvider } from './settings'
|
import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabProvider } from './settings'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -32,11 +34,14 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider } from './settings
|
|||||||
{ provide: HotkeyProvider, useClass: SettingsHotkeyProvider, multi: true },
|
{ provide: HotkeyProvider, useClass: SettingsHotkeyProvider, multi: true },
|
||||||
{ provide: SettingsTabProvider, useClass: HotkeySettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: HotkeySettingsTabProvider, multi: true },
|
||||||
{ provide: SettingsTabProvider, useClass: WindowSettingsTabProvider, multi: true },
|
{ provide: SettingsTabProvider, useClass: WindowSettingsTabProvider, multi: true },
|
||||||
|
{ provide: SettingsTabProvider, useClass: VaultSettingsTabProvider, multi: true },
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
HotkeyInputModalComponent,
|
HotkeyInputModalComponent,
|
||||||
HotkeySettingsTabComponent,
|
HotkeySettingsTabComponent,
|
||||||
SettingsTabComponent,
|
SettingsTabComponent,
|
||||||
|
SetVaultPassphraseModalComponent,
|
||||||
|
VaultSettingsTabComponent,
|
||||||
WindowSettingsTabComponent,
|
WindowSettingsTabComponent,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -45,6 +50,8 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider } from './settings
|
|||||||
MultiHotkeyInputComponent,
|
MultiHotkeyInputComponent,
|
||||||
SettingsTabComponent,
|
SettingsTabComponent,
|
||||||
SettingsTabBodyComponent,
|
SettingsTabBodyComponent,
|
||||||
|
SetVaultPassphraseModalComponent,
|
||||||
|
VaultSettingsTabComponent,
|
||||||
WindowSettingsTabComponent,
|
WindowSettingsTabComponent,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -2,6 +2,7 @@ import { Injectable } from '@angular/core'
|
|||||||
import { SettingsTabProvider } from './api'
|
import { SettingsTabProvider } from './api'
|
||||||
import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component'
|
import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component'
|
||||||
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
|
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
|
||||||
|
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -27,3 +28,16 @@ export class WindowSettingsTabProvider extends SettingsTabProvider {
|
|||||||
return WindowSettingsTabComponent
|
return WindowSettingsTabComponent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** @hidden */
|
||||||
|
@Injectable()
|
||||||
|
export class VaultSettingsTabProvider extends SettingsTabProvider {
|
||||||
|
id = 'vault'
|
||||||
|
icon = 'key'
|
||||||
|
title = 'Vault'
|
||||||
|
|
||||||
|
getComponentType (): any {
|
||||||
|
return VaultSettingsTabComponent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -152,7 +152,7 @@ export class SSHTabComponent extends BaseTerminalTabComponent {
|
|||||||
) {
|
) {
|
||||||
// User closed the session
|
// User closed the session
|
||||||
this.destroy()
|
this.destroy()
|
||||||
} else {
|
} else if (this.frontend) {
|
||||||
// Session was closed abruptly
|
// Session was closed abruptly
|
||||||
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${session.connection.host}: session closed\r\n`)
|
this.write('\r\n' + colors.black.bgWhite(' SSH ') + ` ${session.connection.host}: session closed\r\n`)
|
||||||
if (!this.reconnectOffered) {
|
if (!this.reconnectOffered) {
|
||||||
|
@ -1,40 +1,76 @@
|
|||||||
|
import * as keytar from 'keytar'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { SSHConnection } from '../api'
|
import { SSHConnection } from '../api'
|
||||||
import * as keytar from 'keytar'
|
import { VaultService } from 'terminus-core'
|
||||||
|
|
||||||
|
export const VAULT_SECRET_TYPE_PASSWORD = 'ssh:password'
|
||||||
|
export const VAULT_SECRET_TYPE_PASSPHRASE = 'ssh:key-passphrase'
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class PasswordStorageService {
|
export class PasswordStorageService {
|
||||||
|
constructor (private vault: VaultService) { }
|
||||||
|
|
||||||
async savePassword (connection: SSHConnection, password: string): Promise<void> {
|
async savePassword (connection: SSHConnection, password: string): Promise<void> {
|
||||||
const key = this.getKeyForConnection(connection)
|
if (this.vault.enabled) {
|
||||||
return keytar.setPassword(key, connection.user, password)
|
const key = this.getVaultKeyForConnection(connection)
|
||||||
|
this.vault.addSecret({ type: VAULT_SECRET_TYPE_PASSWORD, key, value: password })
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForConnection(connection)
|
||||||
|
return keytar.setPassword(key, connection.user, password)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePassword (connection: SSHConnection): Promise<void> {
|
async deletePassword (connection: SSHConnection): Promise<void> {
|
||||||
const key = this.getKeyForConnection(connection)
|
if (this.vault.enabled) {
|
||||||
await keytar.deletePassword(key, connection.user)
|
const key = this.getVaultKeyForConnection(connection)
|
||||||
|
this.vault.removeSecret(VAULT_SECRET_TYPE_PASSWORD, key)
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForConnection(connection)
|
||||||
|
await keytar.deletePassword(key, connection.user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPassword (connection: SSHConnection): Promise<string|null> {
|
async loadPassword (connection: SSHConnection): Promise<string|null> {
|
||||||
const key = this.getKeyForConnection(connection)
|
if (this.vault.enabled) {
|
||||||
return keytar.getPassword(key, connection.user)
|
const key = this.getVaultKeyForConnection(connection)
|
||||||
|
return (await this.vault.getSecret(VAULT_SECRET_TYPE_PASSWORD, key))?.value ?? null
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForConnection(connection)
|
||||||
|
return keytar.getPassword(key, connection.user)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async savePrivateKeyPassword (id: string, password: string): Promise<void> {
|
async savePrivateKeyPassword (id: string, password: string): Promise<void> {
|
||||||
const key = this.getKeyForPrivateKey(id)
|
if (this.vault.enabled) {
|
||||||
return keytar.setPassword(key, 'user', password)
|
const key = this.getVaultKeyForPrivateKey(id)
|
||||||
|
this.vault.addSecret({ type: VAULT_SECRET_TYPE_PASSPHRASE, key, value: password })
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForPrivateKey(id)
|
||||||
|
return keytar.setPassword(key, 'user', password)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deletePrivateKeyPassword (id: string): Promise<void> {
|
async deletePrivateKeyPassword (id: string): Promise<void> {
|
||||||
const key = this.getKeyForPrivateKey(id)
|
if (this.vault.enabled) {
|
||||||
await keytar.deletePassword(key, 'user')
|
const key = this.getVaultKeyForPrivateKey(id)
|
||||||
|
this.vault.removeSecret(VAULT_SECRET_TYPE_PASSPHRASE, key)
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForPrivateKey(id)
|
||||||
|
await keytar.deletePassword(key, 'user')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadPrivateKeyPassword (id: string): Promise<string|null> {
|
async loadPrivateKeyPassword (id: string): Promise<string|null> {
|
||||||
const key = this.getKeyForPrivateKey(id)
|
if (this.vault.enabled) {
|
||||||
return keytar.getPassword(key, 'user')
|
const key = this.getVaultKeyForPrivateKey(id)
|
||||||
|
return (await this.vault.getSecret(VAULT_SECRET_TYPE_PASSPHRASE, key))?.value ?? null
|
||||||
|
} else {
|
||||||
|
const key = this.getKeytarKeyForPrivateKey(id)
|
||||||
|
return keytar.getPassword(key, 'user')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getKeyForConnection (connection: SSHConnection): string {
|
private getKeytarKeyForConnection (connection: SSHConnection): string {
|
||||||
let key = `ssh@${connection.host}`
|
let key = `ssh@${connection.host}`
|
||||||
if (connection.port) {
|
if (connection.port) {
|
||||||
key = `ssh@${connection.host}:${connection.port}`
|
key = `ssh@${connection.host}:${connection.port}`
|
||||||
@ -42,7 +78,19 @@ export class PasswordStorageService {
|
|||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
private getKeyForPrivateKey (id: string): string {
|
private getKeytarKeyForPrivateKey (id: string): string {
|
||||||
return `ssh-private-key:${id}`
|
return `ssh-private-key:${id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getVaultKeyForConnection (connection: SSHConnection) {
|
||||||
|
return {
|
||||||
|
user: connection.user,
|
||||||
|
host: connection.host,
|
||||||
|
port: connection.port,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getVaultKeyForPrivateKey (id: string) {
|
||||||
|
return { hash: id }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user