Browse Source

fixed #10034 - added auto-sudo-password plugin

Eugene 4 months ago
parent
commit
5a7a06e529

+ 1 - 0
scripts/vars.mjs

@@ -31,6 +31,7 @@ export const builtinPlugins = [
     'tabby-electron',
     'tabby-plugin-manager',
     'tabby-linkifier',
+    'tabby-auto-sudo-password',
 ]
 
 export const packagesWithDocs = [

+ 23 - 0
tabby-auto-sudo-password/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "tabby-auto-sudo-password",
+  "version": "1.0.197-nightly.1",
+  "description": "Offers to automatically paste saved sudo password in SSH sessions",
+  "keywords": [
+    "tabby-builtin-plugin"
+  ],
+  "main": "dist/index.js",
+  "typings": "typings/index.d.ts",
+  "scripts": {
+    "build": "webpack --progress --color --display-modules",
+    "watch": "webpack --progress --color --watch"
+  },
+  "files": [
+    "dist",
+    "typings"
+  ],
+  "devDependencies": {
+    "ansi-colors": "^4.1.1"
+  },
+  "author": "Eugene Pankov",
+  "license": "MIT"
+}

+ 89 - 0
tabby-auto-sudo-password/src/decorator.ts

@@ -0,0 +1,89 @@
+import colors from 'ansi-colors'
+import { Injectable } from '@angular/core'
+import { TerminalDecorator, BaseTerminalTabComponent, XTermFrontend, SessionMiddleware } from 'tabby-terminal'
+import { SSHProfile, SSHTabComponent, PasswordStorageService } from 'tabby-ssh'
+
+const SUDO_PROMPT_REGEX = /^\[sudo\] password for ([^:]+):\s*$/im
+
+export class AutoSudoPasswordMiddleware extends SessionMiddleware {
+    private pendingPasswordToPaste: string | null = null
+    private pasteHint = `${colors.black.bgBlackBright(' Tabby ')} ${colors.gray('Press Enter to paste saved password')}`
+    private pasteHintLength = colors.stripColor(this.pasteHint).length
+
+    constructor (
+        private profile: SSHProfile,
+        private ps: PasswordStorageService,
+    ) { super() }
+
+    feedFromSession (data: Buffer): void {
+        const text = data.toString('utf-8')
+        const match = SUDO_PROMPT_REGEX.exec(text)
+        if (match) {
+            const username = match[1]
+            this.handlePrompt(username)
+        }
+        this.outputToTerminal.next(data)
+    }
+
+    feedFromTerminal (data: Buffer): void {
+        if (this.pendingPasswordToPaste) {
+            const backspaces = Buffer.alloc(this.pasteHintLength, 8) // backspace
+            const spaces = Buffer.alloc(this.pasteHintLength, 32) // space
+            const clear = Buffer.concat([backspaces, spaces, backspaces])
+            this.outputToTerminal.next(clear)
+            if (data.length === 1 && data[0] === 13) { // Enter key
+                this.outputToSession.next(Buffer.from(this.pendingPasswordToPaste + '\n'))
+                this.pendingPasswordToPaste = null
+                return
+            } else {
+                this.pendingPasswordToPaste = null
+            }
+        }
+        this.outputToSession.next(data)
+    }
+
+    async handlePrompt (username: string): Promise<void> {
+        console.log(`Detected sudo prompt for user: ${username}`)
+        const pw = await this.ps.loadPassword(this.profile)
+        if (pw) {
+            this.outputToTerminal.next(Buffer.from(this.pasteHint))
+            this.pendingPasswordToPaste = pw
+        }
+    }
+
+    async loadPassword (username: string): Promise<string| null> {
+        if (this.profile.options.user !== username) {
+            return null
+        }
+        return this.ps.loadPassword(this.profile)
+    }
+}
+
+@Injectable()
+export class AutoSudoPasswordDecorator extends TerminalDecorator {
+    constructor (
+        private ps: PasswordStorageService,
+    ) {
+        super()
+    }
+
+    private attachToSession (tab: SSHTabComponent) {
+        if (!tab.session) {
+            return
+        }
+        tab.session.middleware.unshift(new AutoSudoPasswordMiddleware(tab.profile, this.ps))
+    }
+
+    attach (tab: BaseTerminalTabComponent<any>): void {
+        if (!(tab.frontend instanceof XTermFrontend) || !(tab instanceof SSHTabComponent)) {
+            return
+        }
+
+        setTimeout(() => {
+            this.attachToSession(tab)
+            this.subscribeUntilDetached(tab, tab.sessionChanged$.subscribe(() => {
+                this.attachToSession(tab)
+            }))
+        })
+    }
+}

+ 16 - 0
tabby-auto-sudo-password/src/index.ts

@@ -0,0 +1,16 @@
+/* eslint-disable @typescript-eslint/no-extraneous-class */
+import { NgModule } from '@angular/core'
+import { ToastrModule } from 'ngx-toastr'
+import { TerminalDecorator } from 'tabby-terminal'
+
+import { AutoSudoPasswordDecorator } from './decorator'
+
+@NgModule({
+    imports: [
+        ToastrModule,
+    ],
+    providers: [
+        { provide: TerminalDecorator, useClass: AutoSudoPasswordDecorator, multi: true },
+    ],
+})
+export default class AutoSudoPasswordModule { }

+ 7 - 0
tabby-auto-sudo-password/tsconfig.json

@@ -0,0 +1,7 @@
+{
+  "extends": "../tsconfig.json",
+  "exclude": ["node_modules", "dist"],
+  "compilerOptions": {
+    "baseUrl": "src",
+  }
+}

+ 14 - 0
tabby-auto-sudo-password/tsconfig.typings.json

@@ -0,0 +1,14 @@
+{
+  "extends": "../tsconfig.json",
+  "exclude": ["node_modules", "dist", "typings"],
+  "compilerOptions": {
+    "baseUrl": "src",
+    "emitDeclarationOnly": true,
+    "declaration": true,
+    "declarationDir": "./typings",
+    "paths": {
+      "tabby-*": ["../../tabby-*"],
+      "*": ["../../app/node_modules/*"]
+    }
+  }
+}

+ 10 - 0
tabby-auto-sudo-password/webpack.config.mjs

@@ -0,0 +1,10 @@
+import * as path from 'path'
+import * as url from 'url'
+const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
+
+import config from '../webpack.plugin.config.mjs'
+
+export default () => config({
+    name: 'auto-sudo-password',
+    dirname: __dirname,
+})

+ 8 - 0
tabby-auto-sudo-password/yarn.lock

@@ -0,0 +1,8 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ansi-colors@^4.1.1:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
+  integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==

+ 1 - 0
tabby-ssh/src/components/sshTab.component.ts

@@ -179,6 +179,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent<SSHProfile>
             try {
                 await this.initializeSessionMaybeMultiplex(false)
             } catch (e) {
+                console.error('SSH session initialization failed', e)
                 this.write(colors.black.bgRed(' X ') + ' ' + colors.red(e.message) + '\r\n')
                 return
             }

+ 2 - 1
tabby-ssh/src/index.ts

@@ -66,4 +66,5 @@ export default class SSHModule { }
 
 export * from './api'
 export { SFTPFile, SFTPSession } from './session/sftp'
-export { SFTPPanelComponent }
+export { SFTPPanelComponent, SSHTabComponent }
+export { PasswordStorageService } from './services/passwordStorage.service'