@linxin666/dsh-ssh 0.3.5 → 0.3.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/routes.ts CHANGED
@@ -15,7 +15,7 @@ import { tmpdir } from 'node:os'
15
15
  import { randomBytes } from 'node:crypto'
16
16
  import { WebSocket, WebSocketServer } from 'ws'
17
17
  import type { WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver'
18
- import type { SshEngine, ShellSession } from './engine.ts'
18
+ import type { SshEngine, ShellSession, KeyboardInteractiveHandler } from './engine.ts'
19
19
  import { readJsonBody, writeJson } from './http.ts'
20
20
  import { isLoopbackRequest } from './loopback.ts'
21
21
  import { SSH_API, type HostPayload, type TerminalClientFrame, type TerminalServerFrame } from './protocol.ts'
@@ -466,6 +466,7 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
466
466
  let session: ShellSession | undefined
467
467
  let closed = false
468
468
  let paused = false
469
+ let pendingAuthFinish: ((responses: string[]) => void) | undefined
469
470
  // Resume the shell once the socket's send buffer drains below the
470
471
  // low-water mark (transport backpressure).
471
472
  const resume = (): void => {
@@ -483,14 +484,38 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
483
484
  }
484
485
  }
485
486
  const closeSession = (): void => {
487
+ if (pendingAuthFinish !== undefined) {
488
+ const fn = pendingAuthFinish
489
+ pendingAuthFinish = undefined
490
+ try { fn([]) } catch { /* ignore */ }
491
+ }
486
492
  const opened = session
487
493
  session = undefined
488
494
  if (opened !== undefined) opened.close()
489
495
  }
496
+
497
+ const onKeyboardInteractive: KeyboardInteractiveHandler = (name, instructions, _lang, prompts, finish) => {
498
+ const entry = store.find(alias)
499
+ // Auto-answer password if configured and prompt is a password request
500
+ if (entry?.auth.kind === 'password' && entry.auth.password !== undefined && prompts.length > 0 && prompts.every(p => /password/i.test(p.prompt))) {
501
+ const password = entry.auth.password
502
+ finish(prompts.map(() => password))
503
+ return
504
+ }
505
+ // Interactive 2FA prompt sent to the client terminal
506
+ pendingAuthFinish = finish
507
+ sendFrame({
508
+ type: 'auth_prompt',
509
+ name,
510
+ instructions,
511
+ prompts: prompts.map(p => ({ prompt: p.prompt, echo: p.echo })),
512
+ })
513
+ }
514
+
490
515
  engine.openShell(alias, {
491
516
  cols: Number.isFinite(cols) ? cols : 80,
492
517
  rows: Number.isFinite(rows) ? rows : 24,
493
- }).then((opened) => {
518
+ }, onKeyboardInteractive).then((opened) => {
494
519
  if (ws.readyState !== WebSocket.OPEN) {
495
520
  opened.close()
496
521
  return
@@ -519,6 +544,12 @@ const maxUploadBytes = deps.maxUploadBytes ?? MAX_UPLOAD_BYTES
519
544
  session?.send(frame.data)
520
545
  } else if (frame.type === 'resize') {
521
546
  session?.resize(Math.max(2, frame.cols), Math.max(1, frame.rows))
547
+ } else if (frame.type === 'auth_response') {
548
+ if (pendingAuthFinish !== undefined) {
549
+ const fn = pendingAuthFinish
550
+ pendingAuthFinish = undefined
551
+ fn(frame.responses)
552
+ }
522
553
  }
523
554
  })
524
555
  ws.on('close', () => {