From 02216ff4e7a0c3a34338c8a52dbd12b40e373cc5 Mon Sep 17 00:00:00 2001 From: syrexjohnson <50872744+syrexjohnson@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:15:19 +0200 Subject: [PATCH] feat(ssh): pre-populate vault password in auth prompts for MFA (fixes #9911) (#11170) * feat(ssh): pre-populate vault password in auth prompts for MFA use cases When a keyboard-interactive password prompt is shown and the response field is empty, load and pre-fill the saved vault password. This lets users with hardware tokens (e.g. YubiKey) press their token to append a TOTP to the pre-filled base password without re-typing it. Also pre-fill the prompt-password modal with the vault password so the same workflow applies when SSH password auth is used with MFA. Closes #9911 Co-Authored-By: Claude Sonnet 4.6 * fix(ssh): remove unnecessary null coalescing in loadPassword call Co-Authored-By: Claude Sonnet 4.6 --------- Co-authored-by: Claude Sonnet 4.6 --- .../keyboardInteractiveAuthPanel.component.ts | 21 ++++++++++++++++--- tabby-ssh/src/session/ssh.ts | 4 ++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.ts b/tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.ts index bb2a851d..734aff8c 100644 --- a/tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.ts +++ b/tabby-ssh/src/components/keyboardInteractiveAuthPanel.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core' +import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core' import { KeyboardInteractivePrompt } from '../session/ssh' import { SSHProfile } from '../api' import { PasswordStorageService } from '../services/passwordStorage.service' @@ -9,7 +9,7 @@ import { PasswordStorageService } from '../services/passwordStorage.service' styleUrls: ['./keyboardInteractiveAuthPanel.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class KeyboardInteractiveAuthComponent { +export class KeyboardInteractiveAuthComponent implements OnInit { @Input() profile: SSHProfile @Input() prompt: KeyboardInteractivePrompt @Input() step = 0 @@ -17,7 +17,22 @@ export class KeyboardInteractiveAuthComponent { @ViewChild('input') input: ElementRef remember = false - constructor (private passwordStorage: PasswordStorageService) {} + constructor ( + private passwordStorage: PasswordStorageService, + private cdr: ChangeDetectorRef, + ) {} + + async ngOnInit (): Promise { + const savedPassword = await this.passwordStorage.loadPassword(this.profile) + if (savedPassword) { + for (let i = 0; i < this.prompt.prompts.length; i++) { + if (this.prompt.isAPasswordPrompt(i) && !this.prompt.responses[i]) { + this.prompt.responses[i] = savedPassword + } + } + this.cdr.markForCheck() + } + } isPassword (): boolean { return this.prompt.isAPasswordPrompt(this.step) diff --git a/tabby-ssh/src/session/ssh.ts b/tabby-ssh/src/session/ssh.ts index c25e290d..6756241c 100644 --- a/tabby-ssh/src/session/ssh.ts +++ b/tabby-ssh/src/session/ssh.ts @@ -657,6 +657,10 @@ export class SSHSession { modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}` modal.componentInstance.password = true modal.componentInstance.showRememberCheckbox = true + const prefilledPassword = await this.passwordStorage.loadPassword(this.profile, this.authUsername) + if (prefilledPassword) { + modal.componentInstance.value = prefilledPassword + } try { const promptResult = await modal.result.catch(() => null)