瀏覽代碼

Pr askeverytime fix https://github.com/Eugeny/tabby/issues/10681 (#10786)

Florian Kefferpütz 6 天之前
父節點
當前提交
264f1cbe85

+ 2 - 2
tabby-auto-sudo-password/src/decorator.ts

@@ -44,7 +44,7 @@ export class AutoSudoPasswordMiddleware extends SessionMiddleware {
 
     async handlePrompt (username: string): Promise<void> {
         console.log(`Detected sudo prompt for user: ${username}`)
-        const pw = await this.ps.loadPassword(this.profile)
+        const pw = await this.ps.loadPassword(this.profile, username)
         if (pw) {
             this.outputToTerminal.next(Buffer.from(this.pasteHint))
             this.pendingPasswordToPaste = pw
@@ -55,7 +55,7 @@ export class AutoSudoPasswordMiddleware extends SessionMiddleware {
         if (this.profile.options.user !== username) {
             return null
         }
-        return this.ps.loadPassword(this.profile)
+        return this.ps.loadPassword(this.profile, username)
     }
 }
 

+ 23 - 11
tabby-ssh/src/services/passwordStorage.service.ts

@@ -10,33 +10,45 @@ export const VAULT_SECRET_TYPE_PASSPHRASE = 'ssh:key-passphrase'
 export class PasswordStorageService {
     constructor (private vault: VaultService) { }
 
-    async savePassword (profile: SSHProfile, password: string): Promise<void> {
+    async savePassword (profile: SSHProfile, password: string, username?: string): Promise<void> {
+        const account = username ?? profile.options.user
         if (this.vault.isEnabled()) {
-            const key = this.getVaultKeyForConnection(profile)
+            const key = this.getVaultKeyForConnection(profile, account)
             this.vault.addSecret({ type: VAULT_SECRET_TYPE_PASSWORD, key, value: password })
         } else {
+            if (!account) {
+                return
+            }
             const key = this.getKeytarKeyForConnection(profile)
-            return keytar.setPassword(key, profile.options.user, password)
+            return keytar.setPassword(key, account, password)
         }
     }
 
-    async deletePassword (profile: SSHProfile): Promise<void> {
+    async deletePassword (profile: SSHProfile, username?: string): Promise<void> {
+        const account = username ?? profile.options.user
         if (this.vault.isEnabled()) {
-            const key = this.getVaultKeyForConnection(profile)
+            const key = this.getVaultKeyForConnection(profile, account)
             this.vault.removeSecret(VAULT_SECRET_TYPE_PASSWORD, key)
         } else {
+            if (!account) {
+                return
+            }
             const key = this.getKeytarKeyForConnection(profile)
-            await keytar.deletePassword(key, profile.options.user)
+            await keytar.deletePassword(key, account)
         }
     }
 
-    async loadPassword (profile: SSHProfile): Promise<string|null> {
+    async loadPassword (profile: SSHProfile, username?: string): Promise<string|null> {
+        const account = username ?? profile.options.user
         if (this.vault.isEnabled()) {
-            const key = this.getVaultKeyForConnection(profile)
+            const key = this.getVaultKeyForConnection(profile, account)
             return (await this.vault.getSecret(VAULT_SECRET_TYPE_PASSWORD, key))?.value ?? null
         } else {
+            if (!account) {
+                return null
+            }
             const key = this.getKeytarKeyForConnection(profile)
-            return keytar.getPassword(key, profile.options.user)
+            return keytar.getPassword(key, account)
         }
     }
 
@@ -82,9 +94,9 @@ export class PasswordStorageService {
         return `ssh-private-key:${id}`
     }
 
-    private getVaultKeyForConnection (profile: SSHProfile) {
+    private getVaultKeyForConnection (profile: SSHProfile, username?: string) {
         return {
-            user: profile.options.user,
+            user: username ?? profile.options.user,
             host: profile.options.host,
             port: profile.options.port,
         }

+ 1 - 1
tabby-ssh/src/services/ssh.service.ts

@@ -29,7 +29,7 @@ export class SSHService {
 
     async getWinSCPURI (profile: SSHProfile, cwd?: string, username?: string): Promise<string> {
         let uri = `scp://${username ?? profile.options.user}`
-        const password = await this.passwordStorage.loadPassword(profile)
+        const password = await this.passwordStorage.loadPassword(profile, username)
         if (password) {
             uri += ':' + encodeURIComponent(password)
         }

+ 38 - 9
tabby-ssh/src/session/ssh.ts

@@ -200,15 +200,10 @@ export class SSHSession {
             if (this.profile.options.password) {
                 this.allAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
             }
-            const password = await this.passwordStorage.loadPassword(this.profile)
-            if (password) {
-                this.allAuthMethods.push({ type: 'saved-password', password })
-            }
         }
         if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
-            const savedPassword = this.profile.options.password ?? await this.passwordStorage.loadPassword(this.profile)
-            if (savedPassword) {
-                this.allAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
+            if (this.profile.options.password) {
+                this.allAuthMethods.push({ type: 'keyboard-interactive', savedPassword: this.profile.options.password })
             }
             this.allAuthMethods.push({ type: 'keyboard-interactive' })
         }
@@ -218,6 +213,38 @@ export class SSHSession {
         this.allAuthMethods.push({ type: 'hostbased' })
     }
 
+    private async populateStoredPasswordsForResolvedUsername (): Promise<void> {
+        if (!this.authUsername) {
+            return
+        }
+
+        const storedPassword = await this.passwordStorage.loadPassword(this.profile, this.authUsername)
+        if (!storedPassword) {
+            return
+        }
+
+        if (!this.profile.options.auth || this.profile.options.auth === 'password') {
+            const hasSavedPassword = this.allAuthMethods.some(method => method.type === 'saved-password' && method.password === storedPassword)
+            if (!hasSavedPassword) {
+                const promptIndex = this.allAuthMethods.findIndex(method => method.type === 'prompt-password')
+                const insertIndex = promptIndex >= 0 ? promptIndex : this.allAuthMethods.length
+                this.allAuthMethods.splice(insertIndex, 0, { type: 'saved-password', password: storedPassword })
+            }
+        }
+
+        if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
+            const existingSaved = this.allAuthMethods.find(method => method.type === 'keyboard-interactive' && method.savedPassword === storedPassword)
+            if (!existingSaved) {
+                const updatable = this.allAuthMethods.find(method => method.type === 'keyboard-interactive' && method.savedPassword === undefined)
+                if (updatable && updatable.type === 'keyboard-interactive') {
+                    updatable.savedPassword = storedPassword
+                } else {
+                    this.allAuthMethods.push({ type: 'keyboard-interactive', savedPassword: storedPassword })
+                }
+            }
+        }
+    }
+
     private async getAgentConnectionSpec (): Promise<russh.AgentConnectionSpec|null> {
         if (this.hostApp.platform === Platform.Windows) {
             if (this.config.store.ssh.agentType === 'auto') {
@@ -363,12 +390,14 @@ export class SSHSession {
             }
         }
 
+        await this.populateStoredPasswordsForResolvedUsername()
+
         const authenticatedClient = await this.handleAuth()
         if (authenticatedClient) {
             this.ssh = authenticatedClient
         } else {
             this.ssh.disconnect()
-            this.passwordStorage.deletePassword(this.profile)
+            this.passwordStorage.deletePassword(this.profile, this.authUsername ?? undefined)
             // eslint-disable-next-line @typescript-eslint/no-base-to-string
             throw new Error('Authentication rejected')
         }
@@ -376,7 +405,7 @@ export class SSHSession {
         // auth success
 
         if (this.savedPassword) {
-            this.passwordStorage.savePassword(this.profile, this.savedPassword)
+            this.passwordStorage.savePassword(this.profile, this.savedPassword, this.authUsername ?? undefined)
         }
 
         for (const fw of this.profile.options.forwardedPorts ?? []) {