feat: support uploading of ssh-rsa private key from client for authentication #381
This commit is contained in:
parent
c48fadba08
commit
2f4083ff4f
3 changed files with 32 additions and 45 deletions
|
@ -25,6 +25,8 @@ class WebSSH2Socket extends EventEmitter {
|
|||
authenticated: false,
|
||||
username: null,
|
||||
password: null,
|
||||
privatekey: null,
|
||||
keyPassword: null,
|
||||
host: null,
|
||||
port: null,
|
||||
term: null,
|
||||
|
@ -110,9 +112,16 @@ class WebSSH2Socket extends EventEmitter {
|
|||
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskSensitiveData(creds))
|
||||
|
||||
if (isValidCredentials(creds)) {
|
||||
// Set term if provided, otherwise use config default
|
||||
this.sessionState.term = validateSshTerm(creds.term)
|
||||
? creds.term
|
||||
: this.config.ssh.term
|
||||
|
||||
// Map the client's privateKey field to our internal privatekey field if present
|
||||
if (creds.privateKey) {
|
||||
creds.privatekey = creds.privateKey
|
||||
}
|
||||
|
||||
this.initializeConnection(creds)
|
||||
} else {
|
||||
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
|
||||
|
@ -129,9 +138,8 @@ class WebSSH2Socket extends EventEmitter {
|
|||
maskSensitiveData(creds)
|
||||
)
|
||||
|
||||
// Add private key from config if available
|
||||
// Add private key from config if available and not provided in creds
|
||||
if (this.config.user.privatekey && !creds.privatekey) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
creds.privatekey = this.config.user.privatekey
|
||||
}
|
||||
|
||||
|
@ -143,6 +151,7 @@ class WebSSH2Socket extends EventEmitter {
|
|||
username: creds.username,
|
||||
password: creds.password,
|
||||
privatekey: creds.privatekey,
|
||||
keyPassword: creds.keyPassword,
|
||||
host: creds.host,
|
||||
port: creds.port
|
||||
})
|
||||
|
@ -170,21 +179,16 @@ class WebSSH2Socket extends EventEmitter {
|
|||
debug(
|
||||
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
|
||||
)
|
||||
handleError(new SSHConnectionError(`${err.message}`))
|
||||
this.socket.emit("ssherror", `${err.message}`)
|
||||
const errorMessage =
|
||||
err instanceof SSHConnectionError
|
||||
? err.message
|
||||
: "SSH connection failed"
|
||||
this.socket.emit("authentication", {
|
||||
action: "auth_result",
|
||||
success: false,
|
||||
message: errorMessage
|
||||
})
|
||||
})
|
||||
|
||||
// Set up password prompt handler
|
||||
this.ssh.on("password-prompt", (data) => {
|
||||
this.socket.emit("authentication", {
|
||||
action: "password_prompt",
|
||||
message: `Key authentication failed. Please enter password for ${data.username}@${data.host}`
|
||||
})
|
||||
})
|
||||
|
||||
this.socket.on("password_response", (password) => {
|
||||
this.ssh.emit("password-response", password)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
29
app/ssh.js
29
app/ssh.js
|
@ -30,24 +30,19 @@ class SSHConnection extends EventEmitter {
|
|||
* @returns {boolean} - Whether the key appears to be valid
|
||||
*/
|
||||
validatePrivateKey(key) {
|
||||
const keyStart = "-----BEGIN RSA PRIVATE KEY-----"
|
||||
const keyEnd = "-----END RSA PRIVATE KEY-----"
|
||||
return (
|
||||
typeof key === "string" &&
|
||||
key.includes(keyStart) &&
|
||||
key.includes(keyEnd) &&
|
||||
key.trim().length > keyStart.length + keyEnd.length
|
||||
)
|
||||
const keyPattern =
|
||||
/^-----BEGIN (?:RSA )?PRIVATE KEY-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END (?:RSA )?PRIVATE KEY-----\r?\n?$/
|
||||
return keyPattern.test(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Connects to the SSH server using the provided credentials.
|
||||
* @param {Object} creds - The credentials object containing host, port, username, and optional password.
|
||||
* @returns {Promise<SSH>} - A promise that resolves with the SSH connection instance.
|
||||
* Attempts to connect using the provided credentials
|
||||
* @param {Object} creds - The credentials object
|
||||
* @returns {Promise<Object>} - A promise that resolves with the SSH connection
|
||||
*/
|
||||
connect(creds) {
|
||||
this.creds = creds
|
||||
debug("connect: %O", maskSensitiveData(creds))
|
||||
this.creds = creds
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.conn) {
|
||||
this.conn.end()
|
||||
|
@ -58,6 +53,7 @@ class SSHConnection extends EventEmitter {
|
|||
|
||||
// First try with key authentication if available
|
||||
const sshConfig = this.getSSHConfig(creds, true)
|
||||
debug("Initial connection config: %O", maskSensitiveData(sshConfig))
|
||||
|
||||
this.setupConnectionHandlers(resolve, reject)
|
||||
|
||||
|
@ -80,14 +76,6 @@ class SSHConnection extends EventEmitter {
|
|||
resolve(this.conn)
|
||||
})
|
||||
|
||||
this.conn.on("end", () => {
|
||||
debug("connect: end")
|
||||
})
|
||||
|
||||
this.conn.on("close", () => {
|
||||
debug("connect: close")
|
||||
})
|
||||
|
||||
this.conn.on("error", (err) => {
|
||||
debug(`connect: error: ${err.message}`)
|
||||
|
||||
|
@ -114,7 +102,6 @@ class SSHConnection extends EventEmitter {
|
|||
this.setupConnectionHandlers(resolve, reject)
|
||||
this.conn.connect(passwordConfig)
|
||||
} else {
|
||||
// No password available, emit event to request password
|
||||
debug("No password available, requesting password from client")
|
||||
this.emit("password-prompt", {
|
||||
host: this.creds.host,
|
||||
|
|
12
app/utils.js
12
app/utils.js
|
@ -87,14 +87,9 @@ function getValidatedPort(portInput) {
|
|||
* - port (number)
|
||||
* AND either:
|
||||
* - password (string) OR
|
||||
* - privatekey (string)
|
||||
* - privatekey/privateKey (string)
|
||||
*
|
||||
* @param {Object} creds - The credentials object.
|
||||
* @param {string} creds.username - The username.
|
||||
* @param {string} [creds.password] - The password.
|
||||
* @param {string} [creds.privatekey] - The private key.
|
||||
* @param {string} creds.host - The host.
|
||||
* @param {number} creds.port - The port.
|
||||
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
||||
*/
|
||||
function isValidCredentials(creds) {
|
||||
|
@ -109,9 +104,10 @@ function isValidCredentials(creds) {
|
|||
return false
|
||||
}
|
||||
|
||||
// Must have either password or privatekey
|
||||
// Must have either password or privatekey/privateKey
|
||||
const hasPassword = typeof creds.password === "string"
|
||||
const hasPrivateKey = typeof creds.privatekey === "string"
|
||||
const hasPrivateKey =
|
||||
typeof creds.privatekey === "string" || typeof creds.privateKey === "string"
|
||||
|
||||
return hasPassword || hasPrivateKey
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue