@@ -10,39 +10,48 @@ import Foundation
1010///
1111/// This provider blocks the calling thread while the alert is displayed on the main thread.
1212/// It is intended for interactive SSH sessions where no TOTP secret is configured.
13- final class PromptTOTPProvider : TOTPProvider , @unchecked Sendable {
13+ internal final class PromptTOTPProvider : TOTPProvider , @unchecked Sendable {
1414 func provideCode( ) throws -> String {
15+ if Thread . isMainThread {
16+ return try handleResult ( showAlert ( ) )
17+ }
18+
1519 let semaphore = DispatchSemaphore ( value: 0 )
1620 var code : String ?
17-
1821 DispatchQueue . main. async {
19- let alert = NSAlert ( )
20- alert. messageText = String ( localized: " Verification Code Required " )
21- alert. informativeText = String (
22- localized: " Enter the TOTP verification code for SSH authentication. "
23- )
24- alert. alertStyle = . informational
25- alert. addButton ( withTitle: String ( localized: " Connect " ) )
26- alert. addButton ( withTitle: String ( localized: " Cancel " ) )
27-
28- let textField = NSTextField ( frame: NSRect ( x: 0 , y: 0 , width: 200 , height: 24 ) )
29- textField. placeholderString = " 000000 "
30- alert. accessoryView = textField
31- alert. window. initialFirstResponder = textField
32-
33- let response = alert. runModal ( )
34- if response == . alertFirstButtonReturn {
35- code = textField. stringValue
36- }
22+ code = self . showAlert ( )
3723 semaphore. signal ( )
3824 }
39-
4025 let result = semaphore. wait ( timeout: . now( ) + 120 )
41-
4226 guard result == . success else {
4327 throw SSHTunnelError . connectionTimeout
4428 }
29+ return try handleResult ( code)
30+ }
31+
32+ private func showAlert( ) -> String ? {
33+ let alert = NSAlert ( )
34+ alert. messageText = String ( localized: " Verification Code Required " )
35+ alert. informativeText = String (
36+ localized: " Enter the TOTP verification code for SSH authentication. "
37+ )
38+ alert. alertStyle = . informational
39+ alert. addButton ( withTitle: String ( localized: " Connect " ) )
40+ alert. addButton ( withTitle: String ( localized: " Cancel " ) )
41+
42+ let textField = NSTextField ( frame: NSRect ( x: 0 , y: 0 , width: 200 , height: 24 ) )
43+ textField. placeholderString = " 000000 "
44+ alert. accessoryView = textField
45+ alert. window. initialFirstResponder = textField
46+
47+ let response = alert. runModal ( )
48+ if response == . alertFirstButtonReturn {
49+ return textField. stringValue
50+ }
51+ return nil
52+ }
4553
54+ private func handleResult( _ code: String ? ) throws -> String {
4655 guard let totpCode = code, !totpCode. isEmpty else {
4756 throw SSHTunnelError . authenticationFailed
4857 }
0 commit comments