From b6c9bab15c352d659326fccee3e1faa2304eb94c Mon Sep 17 00:00:00 2001 From: peter Date: Mon, 13 Dec 2021 05:19:46 -0800 Subject: [PATCH] Allow passwords to be sent as the callback to a 'password' event. --- README.md | 2 ++ lib/client.js | 14 +++++++++++++- test/test-userauth.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 757020c4..b950391b 100644 --- a/README.md +++ b/README.md @@ -954,6 +954,8 @@ You can find more examples in the `examples` directory of this repository. * **tryKeyboard** - _boolean_ - Try keyboard-interactive user authentication if primary user authentication method fails. If you set this to `true`, you need to handle the `keyboard-interactive` event. **Default:** `false` + * **passwordPrompt** - _boolean_ - Emit a password event that takes a callback with the password instead of using the password supplied in the password field. You need to handle the `password` event. **Default** `false` + * **username** - _string_ - Username for authentication. **Default:** (none) * **end**() - _(void)_ - Disconnects the socket. diff --git a/lib/client.js b/lib/client.js index 80f372a8..f687018c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -85,6 +85,7 @@ class Client extends EventEmitter { password: undefined, privateKey: undefined, tryKeyboard: undefined, + passwordPrompt: undefined, agent: undefined, allowAgentFwd: undefined, authHandler: undefined, @@ -216,6 +217,7 @@ class Client extends EventEmitter { ? cfg.localUsername : undefined); this.config.tryKeyboard = (cfg.tryKeyboard === true); + this.config.passwordPrompt = (cfg.passwordPrompt === true); if (typeof cfg.agent === 'string' && cfg.agent.length) this.config.agent = createAgent(cfg.agent); else if (isAgent(cfg.agent)) @@ -774,7 +776,7 @@ class Client extends EventEmitter { let curPartial = null; let curAuthsLeft = null; const authsAllowed = ['none']; - if (this.config.password !== undefined) + if ((this.config.password !== undefined) || this.config.passwordPrompt) authsAllowed.push('password'); if (privateKey !== undefined) authsAllowed.push('publickey'); @@ -819,6 +821,16 @@ class Client extends EventEmitter { const username = this.config.username; switch (type) { case 'password': + if(this.config.passwordPrompt) { + this.emit('password', (newPassword) => { + nextAuth = { + type, + username, + password: newPassword + }; + }); + break; + } nextAuth = { type, username, password: this.config.password }; break; case 'publickey': diff --git a/test/test-userauth.js b/test/test-userauth.js index fb2fe9e9..cefe1ec2 100644 --- a/test/test-userauth.js +++ b/test/test-userauth.js @@ -184,6 +184,41 @@ const debug = false; process.nextTick(done, newPassword); })); } +{ + const username = 'Password User'; + const password = 'hi mom'; + const { client, server } = setup( + 'Password (prompt)', + { + client: { username, passwordPrompt: true }, + server: serverCfg, + + debug, + } + ); + + server.on('connection', mustCall((conn) => { + let authAttempt = 0; + conn.on('authentication', mustCall((ctx) => { + assert(ctx.username === username, + `Wrong username: ${ctx.username}`); + if (++authAttempt === 1) { + assert(ctx.method === 'none', `Wrong auth method: ${ctx.method}`); + return ctx.reject(); + } + assert(ctx.method === 'password', + `Wrong auth method: ${ctx.method}`); + assert(ctx.password === password, + `Wrong password: ${ctx.password}`); + ctx.accept(); + }, 2)).on('ready', mustCall(() => { + conn.end(); + })); + })); + client.on('password', mustCall((cb) => { + cb(password); + })); +} // Hostbased ===================================================================