mirror of
https://github.com/Eugeny/tabby.git
synced 2025-06-29 15:59:54 +00:00
fixed #1790 - remember answers to password prompts in keyboard-interactive authentication
This commit is contained in:
parent
b0fbc33963
commit
c3df6be8c7
@ -14,11 +14,19 @@ input.form-control.mt-2(
|
|||||||
)
|
)
|
||||||
|
|
||||||
.d-flex.mt-3
|
.d-flex.mt-3
|
||||||
button.btn.btn-secondary(
|
checkbox(
|
||||||
|
*ngIf='isPassword()',
|
||||||
|
[(ngModel)]='remember',
|
||||||
|
[text]='"Save password"|translate'
|
||||||
|
)
|
||||||
|
|
||||||
|
.ms-auto
|
||||||
|
|
||||||
|
button.btn.btn-secondary.me-3(
|
||||||
*ngIf='step > 0',
|
*ngIf='step > 0',
|
||||||
(click)='previous()'
|
(click)='previous()'
|
||||||
)
|
)
|
||||||
.ms-auto
|
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
(click)='next()'
|
(click)='next()'
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core'
|
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy } from '@angular/core'
|
||||||
import { KeyboardInteractivePrompt } from '../session/ssh'
|
import { KeyboardInteractivePrompt } from '../session/ssh'
|
||||||
|
import { SSHProfile } from '../api'
|
||||||
|
import { PasswordStorageService } from '../services/passwordStorage.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'keyboard-interactive-auth-panel',
|
selector: 'keyboard-interactive-auth-panel',
|
||||||
@ -9,13 +10,17 @@ import { KeyboardInteractivePrompt } from '../session/ssh'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class KeyboardInteractiveAuthComponent {
|
export class KeyboardInteractiveAuthComponent {
|
||||||
|
@Input() profile: SSHProfile
|
||||||
@Input() prompt: KeyboardInteractivePrompt
|
@Input() prompt: KeyboardInteractivePrompt
|
||||||
@Input() step = 0
|
@Input() step = 0
|
||||||
@Output() done = new EventEmitter()
|
@Output() done = new EventEmitter()
|
||||||
@ViewChild('input') input: ElementRef
|
@ViewChild('input') input: ElementRef
|
||||||
|
remember = false
|
||||||
|
|
||||||
|
constructor (private passwordStorage: PasswordStorageService) {}
|
||||||
|
|
||||||
isPassword (): boolean {
|
isPassword (): boolean {
|
||||||
return this.prompt.prompts[this.step].prompt.toLowerCase().includes('password') || !this.prompt.prompts[this.step].echo
|
return this.prompt.isAPasswordPrompt(this.step)
|
||||||
}
|
}
|
||||||
|
|
||||||
previous (): void {
|
previous (): void {
|
||||||
@ -26,6 +31,10 @@ export class KeyboardInteractiveAuthComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
next (): void {
|
next (): void {
|
||||||
|
if (this.isPassword() && this.remember) {
|
||||||
|
this.passwordStorage.savePassword(this.profile, this.prompt.responses[this.step])
|
||||||
|
}
|
||||||
|
|
||||||
if (this.step === this.prompt.prompts.length - 1) {
|
if (this.step === this.prompt.prompts.length - 1) {
|
||||||
this.prompt.respond()
|
this.prompt.respond()
|
||||||
this.done.emit()
|
this.done.emit()
|
||||||
|
@ -51,6 +51,7 @@ sftp-panel.bg-dark(
|
|||||||
keyboard-interactive-auth-panel.bg-dark(
|
keyboard-interactive-auth-panel.bg-dark(
|
||||||
*ngIf='activeKIPrompt',
|
*ngIf='activeKIPrompt',
|
||||||
[prompt]='activeKIPrompt',
|
[prompt]='activeKIPrompt',
|
||||||
|
[profile]='profile',
|
||||||
(click)='$event.stopPropagation()',
|
(click)='$event.stopPropagation()',
|
||||||
(done)='activeKIPrompt = null; frontend?.focus()'
|
(done)='activeKIPrompt = null; frontend?.focus()'
|
||||||
)
|
)
|
||||||
|
@ -26,7 +26,13 @@ export interface Prompt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AuthMethod = {
|
type AuthMethod = {
|
||||||
type: 'none'|'password'|'keyboard-interactive'|'hostbased'
|
type: 'none'|'prompt-password'|'hostbased'
|
||||||
|
} | {
|
||||||
|
type: 'keyboard-interactive',
|
||||||
|
savedPassword?: string
|
||||||
|
} | {
|
||||||
|
type: 'saved-password',
|
||||||
|
password: string
|
||||||
} | {
|
} | {
|
||||||
type: 'publickey'
|
type: 'publickey'
|
||||||
name: string
|
name: string
|
||||||
@ -62,6 +68,10 @@ export class KeyboardInteractivePrompt {
|
|||||||
this.responses = new Array(this.prompts.length).fill('')
|
this.responses = new Array(this.prompts.length).fill('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isAPasswordPrompt (index: number): boolean {
|
||||||
|
return this.prompts[index].prompt.toLowerCase().includes('password') && !this.prompts[index].echo
|
||||||
|
}
|
||||||
|
|
||||||
respond (): void {
|
respond (): void {
|
||||||
this._resolve(this.responses)
|
this._resolve(this.responses)
|
||||||
}
|
}
|
||||||
@ -93,7 +103,6 @@ export class SSHSession {
|
|||||||
private serviceMessage = new Subject<string>()
|
private serviceMessage = new Subject<string>()
|
||||||
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
|
private keyboardInteractivePrompt = new Subject<KeyboardInteractivePrompt>()
|
||||||
private willDestroy = new Subject<void>()
|
private willDestroy = new Subject<void>()
|
||||||
private keychainPasswordUsed = false
|
|
||||||
|
|
||||||
private passwordStorage: PasswordStorageService
|
private passwordStorage: PasswordStorageService
|
||||||
private ngbModal: NgbModal
|
private ngbModal: NgbModal
|
||||||
@ -168,9 +177,20 @@ export class SSHSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
|
if (!this.profile.options.auth || this.profile.options.auth === 'password') {
|
||||||
this.remainingAuthMethods.push({ type: 'password' })
|
if (this.profile.options.password) {
|
||||||
|
this.remainingAuthMethods.push({ type: 'saved-password', password: this.profile.options.password })
|
||||||
|
}
|
||||||
|
const password = await this.passwordStorage.loadPassword(this.profile)
|
||||||
|
if (password) {
|
||||||
|
this.remainingAuthMethods.push({ type: 'saved-password', password })
|
||||||
|
}
|
||||||
|
this.remainingAuthMethods.push({ type: 'prompt-password' })
|
||||||
}
|
}
|
||||||
if (!this.profile.options.auth || this.profile.options.auth === 'keyboardInteractive') {
|
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.remainingAuthMethods.push({ type: 'keyboard-interactive', savedPassword })
|
||||||
|
}
|
||||||
this.remainingAuthMethods.push({ type: 'keyboard-interactive' })
|
this.remainingAuthMethods.push({ type: 'keyboard-interactive' })
|
||||||
}
|
}
|
||||||
this.remainingAuthMethods.push({ type: 'hostbased' })
|
this.remainingAuthMethods.push({ type: 'hostbased' })
|
||||||
@ -276,7 +296,7 @@ export class SSHSession {
|
|||||||
},
|
},
|
||||||
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
|
keepaliveIntervalSeconds: Math.round((this.profile.options.keepaliveInterval ?? 15000) / 1000),
|
||||||
keepaliveCountMax: this.profile.options.keepaliveCountMax,
|
keepaliveCountMax: this.profile.options.keepaliveCountMax,
|
||||||
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : null,
|
connectionTimeoutSeconds: this.profile.options.readyTimeout ? Math.round(this.profile.options.readyTimeout / 1000) : undefined,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -470,27 +490,14 @@ export class SSHSession {
|
|||||||
this.logger.info('Server does not support auth method', method.type)
|
this.logger.info('Server does not support auth method', method.type)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (method.type === 'password') {
|
if (method.type === 'saved-password') {
|
||||||
if (this.profile.options.password) {
|
this.emitServiceMessage(this.translate.instant('Using saved password'))
|
||||||
this.emitServiceMessage(this.translate.instant('Using preset password'))
|
const result = await this.ssh.authenticateWithPassword(this.authUsername, method.password)
|
||||||
const result = await this.ssh.authenticateWithPassword(this.authUsername, this.profile.options.password)
|
if (result) {
|
||||||
if (result) {
|
return result
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (!this.keychainPasswordUsed && this.profile.options.user) {
|
if (method.type === 'prompt-password') {
|
||||||
const password = await this.passwordStorage.loadPassword(this.profile)
|
|
||||||
if (password) {
|
|
||||||
this.emitServiceMessage(this.translate.instant('Trying saved password'))
|
|
||||||
this.keychainPasswordUsed = true
|
|
||||||
const result = await this.ssh.authenticateWithPassword(this.authUsername, password)
|
|
||||||
if (result) {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const modal = this.ngbModal.open(PromptModalComponent)
|
const modal = this.ngbModal.open(PromptModalComponent)
|
||||||
modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}`
|
modal.componentInstance.prompt = `Password for ${this.authUsername}@${this.profile.options.host}`
|
||||||
modal.componentInstance.password = true
|
modal.componentInstance.password = true
|
||||||
@ -544,6 +551,17 @@ export class SSHSession {
|
|||||||
state.instructions,
|
state.instructions,
|
||||||
state.prompts(),
|
state.prompts(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (method.savedPassword) {
|
||||||
|
// eslint-disable-next-line max-depth
|
||||||
|
for (let i = 0; i < prompt.prompts.length; i++) {
|
||||||
|
// eslint-disable-next-line max-depth
|
||||||
|
if (prompt.isAPasswordPrompt(i)) {
|
||||||
|
prompt.responses[i] = method.savedPassword
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.emitKeyboardInteractivePrompt(prompt)
|
this.emitKeyboardInteractivePrompt(prompt)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user