Skip to content
This repository was archived by the owner on Aug 26, 2025. It is now read-only.

Commit c7fd875

Browse files
authored
Merge pull request #21 from beekeeper-studio/master
Passwords, Windows, and Error bubbling, oh my
2 parents 6687733 + b2a2df4 commit c7fd875

File tree

2 files changed

+42
-11
lines changed

2 files changed

+42
-11
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,17 @@ await sshConnection.executeCommand('uptime')
8383

8484
#### `new SSHConnection(options)`
8585

86-
Options are an object with following properties:
86+
Options are an object with following properties:
8787

8888
* `username` (optional): The username used for your ssh connection (equivalent to `-l` option). If not set, it first looks for an `SSH_USERNAME` environment variable. If that is not set, it fallbacks to `USER` environment variable.
8989
* `privateKey` (optional): Can be a `string` or `Buffer` that contains a private key. If not set, it fallbacks to `~/.ssh/id_rsa`
90-
* `agentForward` (optional): Is a `boolean` which uses the `ssh-agent` for connection (defaults to `false`.
90+
* `skipAutoPrivateKey` (optional): Don't try and read `~/.ssh/id_rsa` if no private key is provided
91+
* `agentForward` (optional): Is a `boolean` which uses the `ssh-agent` for connection (defaults to `false`). If set defaults to the value of env.SSH_AUTH_SOCK (all platforms), then `pageant` [on Windows](https://github.com/mscdex/ssh2#client-methods) if no SSH_AUTH_SOCK is present.
92+
* `agentSocket` (optional): Provide your own path to the SSH Agent Socket you want to use. Useful if your app doesn't have access to ENV directly.
9193
* `endHost` (required): The host you want to end up on (connect to)
9294
* `endPort` (optional): Port number of the server. Needed in case the server runs on a custom port (defaults to `22`)
9395
* `bastionHost` (optional): You can specify a bastion host if you want
94-
* `passphrase` (optional): You can specify the passphrase when you have an encrypted private key. If you don't specify the passphrase and you use an encrypted private key, you get prompted in the command line.
96+
* `passphrase` (optional): You can specify the passphrase when you have an encrypted private key. If you don't specify the passphrase and you use an encrypted private key, you get prompted in the command line.
9597

9698
#### `connection.executeCommand(command: string): Promise<void>`
9799

src/Connection.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ import * as debug from 'debug'
2525

2626
interface Options {
2727
username?: string
28+
password?: string
2829
privateKey?: string | Buffer
2930
agentForward? : boolean
3031
bastionHost?: string
3132
passphrase?: string
3233
endPort?: number
3334
endHost: string
35+
agentSocket?: string,
36+
skipAutoPrivateKey?: boolean
3437
}
3538

3639
interface ForwardingOptions {
@@ -44,6 +47,7 @@ class SSHConnection {
4447
private debug
4548
private server
4649
private connections: Client[] = []
50+
private isWindows = process.platform === 'win32'
4751

4852
constructor(private options: Options) {
4953
this.debug = debug('ssh')
@@ -53,8 +57,11 @@ class SSHConnection {
5357
if (!options.endPort) {
5458
this.options.endPort = 22
5559
}
56-
if (!options.privateKey) {
57-
this.options.privateKey = fs.readFileSync(`${os.homedir()}${path.sep}.ssh${path.sep}id_rsa`)
60+
if (!options.privateKey && !options.agentForward && !options.skipAutoPrivateKey) {
61+
const defaultFilePath = path.join(os.homedir(), '.ssh', 'id_rsa' )
62+
if (fs.existsSync(defaultFilePath)) {
63+
this.options.privateKey = fs.readFileSync(defaultFilePath)
64+
}
5865
}
5966
}
6067

@@ -138,19 +145,31 @@ class SSHConnection {
138145
private async connect(host: string, stream?: NodeJS.ReadableStream): Promise<Client> {
139146
this.debug('Connecting to "%s"', host)
140147
const connection = new Client()
141-
return new Promise<Client>(async (resolve) => {
148+
return new Promise<Client>(async (resolve, reject) => {
149+
142150
const options = {
143151
host,
144152
port: this.options.endPort,
145153
username: this.options.username,
154+
password: this.options.password,
146155
privateKey: this.options.privateKey
147156
}
148157
if (this.options.agentForward) {
149158
options['agentForward'] = true
150-
// guaranteed to give the ssh agent sock if the agent is running
151-
const agentSock = process.env['SSH_AUTH_SOCK']
152-
if (agentSock === undefined) {
153-
throw new Error('SSH Agent is not running and not set in the SSH_AUTH_SOCK env variable')
159+
160+
// see https://github.com/mscdex/ssh2#client for agents on Windows
161+
// guaranteed to give the ssh agent sock if the agent is running (posix)
162+
let agentDefault = process.env['SSH_AUTH_SOCK']
163+
if (this.isWindows) {
164+
// null or undefined
165+
if (agentDefault == null) {
166+
agentDefault = 'pageant'
167+
}
168+
}
169+
170+
const agentSock = this.options.agentSocket ? this.options.agentSocket : agentDefault
171+
if (agentSock == null) {
172+
throw new Error('SSH Agent Socket is not provided, or is not set in the SSH_AUTH_SOCK env variable')
154173
}
155174
options['agent'] = agentSock
156175
}
@@ -160,11 +179,21 @@ class SSHConnection {
160179
if (options.privateKey && options.privateKey.toString().toLowerCase().includes('encrypted')) {
161180
options['passphrase'] = (this.options.passphrase) ? this.options.passphrase : await this.getPassphrase()
162181
}
163-
connection.connect(options)
164182
connection.on('ready', () => {
165183
this.connections.push(connection)
166184
return resolve(connection)
167185
})
186+
187+
connection.on('error', (error) => {
188+
reject(error)
189+
})
190+
try {
191+
connection.connect(options)
192+
} catch (error) {
193+
reject(error)
194+
}
195+
196+
168197
})
169198
}
170199

0 commit comments

Comments
 (0)