|
3 | 3 | import { l10n } from "vscode"; |
4 | 4 |
|
5 | 5 | import { |
| 6 | + AgentAuthMethod, |
6 | 7 | AuthHandlerMiddleware, |
7 | 8 | AuthenticationType, |
8 | 9 | Client, |
9 | 10 | ClientChannel, |
10 | 11 | ConnectConfig, |
| 12 | + KeyboardInteractiveAuthMethod, |
11 | 13 | NextAuthHandler, |
| 14 | + PasswordAuthMethod, |
| 15 | + PublicKeyAuthMethod, |
12 | 16 | } from "ssh2"; |
13 | 17 |
|
14 | 18 | import { BaseConfig, RunResult } from ".."; |
@@ -87,14 +91,17 @@ export class SSHSession extends Session { |
87 | 91 | return; |
88 | 92 | } |
89 | 93 |
|
| 94 | + const authHandlerFn = this.handleSSHAuthentication(); |
90 | 95 | const cfg: ConnectConfig = { |
91 | 96 | host: this._config.host, |
92 | 97 | port: this._config.port, |
93 | 98 | username: this._config.username, |
94 | 99 | readyTimeout: CONNECT_READY_TIMEOUT, |
95 | 100 | keepaliveInterval: KEEPALIVE_INTERVAL, |
96 | 101 | keepaliveCountMax: KEEPALIVE_UNANSWERED_THRESHOLD, |
97 | | - authHandler: this.handleSSHAuthentication, |
| 102 | + authHandler: (methodsLeft, partialSuccess, callback) => ( |
| 103 | + authHandlerFn(methodsLeft, partialSuccess, callback), undefined |
| 104 | + ), |
98 | 105 | }; |
99 | 106 |
|
100 | 107 | if (!this._conn) { |
@@ -305,71 +312,74 @@ export class SSHSession extends Session { |
305 | 312 | */ |
306 | 313 | private clearAuthState = (): void => { |
307 | 314 | this._sessionReady = false; |
| 315 | + this._authsLeft = []; |
308 | 316 | }; |
309 | 317 |
|
310 | | - private handleSSHAuthentication: AuthHandlerMiddleware = ( |
311 | | - authsLeft: AuthenticationType[], |
312 | | - partialSuccess: boolean, //used in scenarios which require multiple auth methods to denote partial success |
313 | | - nextAuth: NextAuthHandler, |
314 | | - ) => { |
315 | | - if (!authsLeft) { |
316 | | - nextAuth("none"); //sending none will prompt the server to send supported auth methods |
317 | | - return; |
318 | | - } |
319 | | - |
320 | | - if (authsLeft.length === 0) { |
321 | | - this._reject?.( |
322 | | - new Error(l10n.t("Could not authenticate to the SSH server.")), |
323 | | - ); |
324 | | - this.clearAuthState(); |
325 | | - return false; //returning false will stop the authentication process |
326 | | - } |
| 318 | + private handleSSHAuthentication = (): AuthHandlerMiddleware => { |
| 319 | + //The ssh2 library supports sending false to stop the authentication process |
| 320 | + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions |
| 321 | + const END_AUTH = false as unknown as AuthenticationType; |
| 322 | + return async ( |
| 323 | + authsLeft: AuthenticationType[], |
| 324 | + partialSuccess: boolean, //used in scenarios which require multiple auth methods to denote partial success |
| 325 | + nextAuth: NextAuthHandler, |
| 326 | + ) => { |
| 327 | + if (!authsLeft) { |
| 328 | + return nextAuth({ type: "none", username: this._config.username }); //sending none will prompt the server to send supported auth methods |
| 329 | + } else { |
| 330 | + if (authsLeft.length === 0) { |
| 331 | + return nextAuth(END_AUTH); |
| 332 | + } |
327 | 333 |
|
328 | | - if (this._authsLeft.length === 0 || partialSuccess) { |
329 | | - this._authsLeft = authsLeft; |
330 | | - } |
| 334 | + if (this._authsLeft.length === 0 || partialSuccess) { |
| 335 | + this._authsLeft = authsLeft; |
| 336 | + } |
331 | 337 |
|
332 | | - const authMethod = this._authsLeft.shift(); |
333 | | - |
334 | | - switch (authMethod) { |
335 | | - case "publickey": { |
336 | | - //user set a keyfile path in profile config |
337 | | - if (this._config.privateKeyFilePath) { |
338 | | - this._authHandler |
339 | | - .privateKeyAuth( |
340 | | - nextAuth, |
341 | | - this._config.privateKeyFilePath, |
342 | | - this._config.username, |
343 | | - ) |
344 | | - .catch((e) => { |
345 | | - this._reject?.(e); |
346 | | - return false; |
347 | | - }); |
348 | | - } else if (process.env.SSH_AUTH_SOCK) { |
349 | | - this._authHandler.sshAgentAuth(nextAuth, this._config.username); |
| 338 | + const authMethod = this._authsLeft.shift(); |
| 339 | + |
| 340 | + try { |
| 341 | + let authPayload: |
| 342 | + | PublicKeyAuthMethod |
| 343 | + | AgentAuthMethod |
| 344 | + | PasswordAuthMethod |
| 345 | + | KeyboardInteractiveAuthMethod; |
| 346 | + |
| 347 | + switch (authMethod) { |
| 348 | + case "publickey": { |
| 349 | + //user set a keyfile path in profile config |
| 350 | + if (this._config.privateKeyFilePath) { |
| 351 | + authPayload = await this._authHandler.privateKeyAuth( |
| 352 | + this._config.privateKeyFilePath, |
| 353 | + this._config.username, |
| 354 | + ); |
| 355 | + } else if (process.env.SSH_AUTH_SOCK) { |
| 356 | + authPayload = this._authHandler.sshAgentAuth( |
| 357 | + this._config.username, |
| 358 | + ); |
| 359 | + } |
| 360 | + break; |
| 361 | + } |
| 362 | + case "password": { |
| 363 | + authPayload = await this._authHandler.passwordAuth( |
| 364 | + this._config.username, |
| 365 | + ); |
| 366 | + break; |
| 367 | + } |
| 368 | + case "keyboard-interactive": { |
| 369 | + authPayload = await this._authHandler.keyboardInteractiveAuth( |
| 370 | + this._config.username, |
| 371 | + ); |
| 372 | + break; |
| 373 | + } |
| 374 | + default: |
| 375 | + nextAuth(authMethod); |
| 376 | + } |
| 377 | + return nextAuth(authPayload); |
| 378 | + } catch (e) { |
| 379 | + this._reject?.(e); |
| 380 | + return nextAuth(END_AUTH); |
350 | 381 | } |
351 | | - break; |
352 | 382 | } |
353 | | - case "password": { |
354 | | - this._authHandler |
355 | | - .passwordAuth(nextAuth, this._config.username) |
356 | | - .catch((e) => { |
357 | | - this._reject?.(e); |
358 | | - return false; |
359 | | - }); |
360 | | - break; |
361 | | - } |
362 | | - case "keyboard-interactive": { |
363 | | - this._authHandler |
364 | | - .keyboardInteractiveAuth(nextAuth, this._config.username) |
365 | | - .catch((e) => { |
366 | | - this._reject?.(e); |
367 | | - return false; |
368 | | - }); |
369 | | - break; |
370 | | - } |
371 | | - default: |
372 | | - nextAuth(authMethod); |
373 | | - } |
| 383 | + }; |
374 | 384 | }; |
375 | 385 | } |
0 commit comments