Make keyboard-interactive auth URLs clickable (#11327)

Co-authored-by: pratyushjaiswal0806-dot <pratyushjaiswal0806@gmail.com>
This commit is contained in:
Puneet Dixit
2026-06-09 12:54:02 +05:30
committed by GitHub
parent 49644f645e
commit f49ca51be8
2 changed files with 74 additions and 1 deletions
@@ -2,7 +2,15 @@
strong(translate) Keyboard-interactive auth
.ms-2 {{prompt.name}}
.prompt-text {{prompt.prompts[step].prompt}}
.prompt-text
span(*ngFor='let part of getPromptParts()')
a(
*ngIf='part.url',
[href]='part.url',
rel='noreferrer noopener',
(click)='openPromptLink(part.url, $event)'
) {{part.text}}
span(*ngIf='!part.url') {{part.text}}
input.form-control.mt-2(
#input,
@@ -1,8 +1,17 @@
import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, ChangeDetectionStrategy, OnInit, ChangeDetectorRef } from '@angular/core'
import { PlatformService } from 'tabby-core'
import { KeyboardInteractivePrompt } from '../session/ssh'
import { SSHProfile } from '../api'
import { PasswordStorageService } from '../services/passwordStorage.service'
const PROMPT_URL_REGEX = /https?:\/\/[^\s<>"']+/g
const TRAILING_PROMPT_URL_PUNCTUATION = /[),.;:!?]+$/
interface PromptPart {
text: string
url?: string
}
@Component({
selector: 'keyboard-interactive-auth-panel',
templateUrl: './keyboardInteractiveAuthPanel.component.pug',
@@ -19,6 +28,7 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
constructor (
private passwordStorage: PasswordStorageService,
private platform: PlatformService,
private cdr: ChangeDetectorRef,
) {}
@@ -42,6 +52,52 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
return this.prompt.prompts[this.step].echo ?? false
}
getPromptParts (): PromptPart[] {
return this.parsePromptText(this.prompt.prompts[this.step].prompt)
}
parsePromptText (text: string): PromptPart[] {
const parts: PromptPart[] = []
let lastIndex = 0
for (const match of text.matchAll(PROMPT_URL_REGEX)) {
const matchedText = match[0]
const matchIndex = match.index ?? 0
const punctuationMatch = TRAILING_PROMPT_URL_PUNCTUATION.exec(matchedText)
const trailingPunctuation = punctuationMatch?.[0] ?? ''
const url = trailingPunctuation ? matchedText.slice(0, -trailingPunctuation.length) : matchedText
if (matchIndex > lastIndex) {
parts.push({ text: text.slice(lastIndex, matchIndex) })
}
if (this.isPromptUrl(url)) {
parts.push({ text: url, url })
if (trailingPunctuation) {
parts.push({ text: trailingPunctuation })
}
} else {
parts.push({ text: matchedText })
}
lastIndex = matchIndex + matchedText.length
}
if (lastIndex < text.length) {
parts.push({ text: text.slice(lastIndex) })
}
return parts.length ? parts : [{ text }]
}
openPromptLink (url: string|undefined, event: Event): void {
event.preventDefault()
if (url) {
this.platform.openExternal(url)
}
}
previous (): void {
if (this.step > 0) {
this.step--
@@ -62,4 +118,13 @@ export class KeyboardInteractiveAuthComponent implements OnInit {
this.step++
this.input.nativeElement.focus()
}
private isPromptUrl (url: string): boolean {
try {
const parsedUrl = new URL(url)
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:'
} catch {
return false
}
}
}