import { Injectable, InjectFlags, Injector } from '@angular/core' import { ProfileProvider, NewTabParameters, PartialProfile, TranslateService } from 'tabby-core' import * as ALGORITHMS from 'ssh2/lib/protocol/constants' import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component' import { SSHTabComponent } from './components/sshTab.component' import { PasswordStorageService } from './services/passwordStorage.service' import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api' import { SSHProfileImporter } from './api/importer' @Injectable({ providedIn: 'root' }) export class SSHProfilesService extends ProfileProvider { id = 'ssh' name = 'SSH' supportsQuickConnect = true settingsComponent = SSHProfileSettingsComponent configDefaults = { options: { host: null, port: 22, user: 'root', auth: null, password: null, privateKeys: [], keepaliveInterval: 5000, keepaliveCountMax: 10, readyTimeout: null, x11: false, skipBanner: false, jumpHost: null, agentForward: false, warnOnClose: null, algorithms: { hmac: [], kex: [], cipher: [], serverHostKey: [], }, proxyCommand: null, forwardedPorts: [], scripts: [], socksProxyHost: null, socksProxyPort: null, httpProxyHost: null, httpProxyPort: null, reuseSession: true, input: { backspace: 'backspace' }, }, } constructor ( private passwordStorage: PasswordStorageService, private translate: TranslateService, private injector: Injector, ) { super() for (const k of Object.values(SSHAlgorithmType)) { const defaultAlg = { [SSHAlgorithmType.KEX]: 'DEFAULT_KEX', [SSHAlgorithmType.HOSTKEY]: 'DEFAULT_SERVER_HOST_KEY', [SSHAlgorithmType.CIPHER]: 'DEFAULT_CIPHER', [SSHAlgorithmType.HMAC]: 'DEFAULT_MAC', }[k] this.configDefaults.options.algorithms[k] = ALGORITHMS[defaultAlg].filter(x => !ALGORITHM_BLACKLIST.includes(x)) this.configDefaults.options.algorithms[k].sort() } } async getBuiltinProfiles (): Promise[]> { const importers = this.injector.get(SSHProfileImporter as any, [], InjectFlags.Optional) let imported: PartialProfile[] = [] for (const importer of importers) { try { imported = imported.concat(await importer.getProfiles()) } catch (e) { console.warn('Could not import SSH profiles:', e) } } return [ { id: `ssh:template`, type: 'ssh', name: this.translate.instant('SSH connection'), icon: 'fas fa-desktop', options: { host: '', port: 22, user: 'root', }, isBuiltin: true, isTemplate: true, weight: -1, }, ...imported.map(p => ({ ...p, isBuiltin: true, })), ] } async getNewTabParameters (profile: SSHProfile): Promise> { return { type: SSHTabComponent, inputs: { profile }, } } getSuggestedName (profile: SSHProfile): string { return `${profile.options.user}@${profile.options.host}:${profile.options.port}` } getDescription (profile: PartialProfile): string { return profile.options?.host ?? '' } deleteProfile (profile: SSHProfile): void { this.passwordStorage.deletePassword(profile) } quickConnect (query: string): PartialProfile { let user: string|undefined = undefined let host = query let port = 22 if (host.includes('@')) { const parts = host.split(/@/g) host = parts[parts.length - 1] user = parts.slice(0, parts.length - 1).join('@') } if (host.includes('[')) { port = parseInt(host.split(']')[1].substring(1)) host = host.split(']')[0].substring(1) } else if (host.includes(':')) { port = parseInt(host.split(/:/g)[1]) host = host.split(':')[0] } return { name: query, type: 'ssh', options: { host, user, port, }, } } intoQuickConnectString (profile: SSHProfile): string|null { let s = profile.options.host if (profile.options.user !== 'root') { s = `${profile.options.user}@${s}` } if (profile.options.port !== 22) { s = `${s}:${profile.options.port}` } return s } }