Refactoring, Session maintenance
- start of some refactoring - linking socket.io and express sessions - cleaning up some potential error conditions todo: - re-work status updates on client side for unexpected websocket disconnects while not overwriting ssh server errors un termination
This commit is contained in:
parent
b466d100ae
commit
7f55613de8
7 changed files with 227 additions and 105 deletions
22
.snyk
Normal file
22
.snyk
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||||
|
version: v1.7.1
|
||||||
|
# ignores vulnerabilities until expiry date; change duration by modifying expiry date
|
||||||
|
ignore:
|
||||||
|
'npm:ms:20170412':
|
||||||
|
- socket.io > socket.io-adapter > socket.io-parser > debug > ms:
|
||||||
|
reason: no patch avail
|
||||||
|
expires: '2017-06-18T14:21:30.266Z'
|
||||||
|
- standard > eslint-plugin-import > eslint-module-utils > debug > ms:
|
||||||
|
reason: no patch avail
|
||||||
|
expires: '2017-06-18T14:21:30.266Z'
|
||||||
|
- socket.io > socket.io-adapter > debug > ms:
|
||||||
|
reason: no patch avail
|
||||||
|
expires: '2017-06-18T14:21:30.267Z'
|
||||||
|
- socket.io > socket.io-client > debug > ms:
|
||||||
|
reason: no patch avail
|
||||||
|
expires: '2017-06-18T14:21:30.267Z'
|
||||||
|
'npm:shelljs:20140723':
|
||||||
|
- standard > eslint > shelljs:
|
||||||
|
reason: no patch avail
|
||||||
|
expires: '2017-06-18T14:21:30.267Z'
|
||||||
|
patch: {}
|
24
README.md
24
README.md
|
@ -33,31 +33,37 @@ headerBackground= - optional background color of header to display on page
|
||||||
config.json contains several options which may be specified to customize to your needs, vs editing the javascript direclty. This is JSON format so mind your spacing, brackets, etc...
|
config.json contains several options which may be specified to customize to your needs, vs editing the javascript direclty. This is JSON format so mind your spacing, brackets, etc...
|
||||||
|
|
||||||
`listen.ip` default `127.0.0.1`
|
`listen.ip` default `127.0.0.1`
|
||||||
* IP address node should listen on for client connections
|
* IP address node should listen on for client connections.
|
||||||
|
|
||||||
`listen.port` default `2222`
|
`listen.port` default `2222`
|
||||||
* Port node should listen on for client connections
|
* Port node should listen on for client connections.
|
||||||
|
|
||||||
`user.name` default `null`
|
`user.name` default `null`
|
||||||
* Specify user name to authenticate with
|
* Specify user name to authenticate with.
|
||||||
|
|
||||||
`user.password` default `null`
|
`user.password` default `null`
|
||||||
* Specify password to authenticate with
|
* Specify password to authenticate with.
|
||||||
|
|
||||||
`ssh.host` default `null`
|
`ssh.host` default `null`
|
||||||
* Specify host to connect to
|
* Specify host to connect to.
|
||||||
|
|
||||||
`ssh.port` default `22`
|
`ssh.port` default `22`
|
||||||
* Specify SSH port to connect to
|
* Specify SSH port to connect to.
|
||||||
|
|
||||||
`ssh.term` default `xterm-color`
|
`ssh.term` default `xterm-color`
|
||||||
* Specify terminal emulation to use
|
* Specify terminal emulation to use.
|
||||||
|
|
||||||
`header.text`
|
`header.text`
|
||||||
* Specify header text, defaults to `My Header` but may also be set to `null`
|
* Specify header text, defaults to `My Header` but may also be set to `null`.
|
||||||
|
|
||||||
`header.background`
|
`header.background`
|
||||||
* Header background, defaults to `green`
|
* Header background, defaults to `green`.
|
||||||
|
|
||||||
|
`session.name`
|
||||||
|
* Name of session ID cookie. it's not a horrible idea to make this something unique.
|
||||||
|
|
||||||
|
`session.secret`
|
||||||
|
* Secret key for cookie encryption. You should change this in production.
|
||||||
|
|
||||||
`options.challengeButton`
|
`options.challengeButton`
|
||||||
* Challenge button. This option, which is still under development, allows the user to resend the password to the server (in cases of step-up authentication for things like `sudo` or a router `enable` command.
|
* Challenge button. This option, which is still under development, allows the user to resend the password to the server (in cases of step-up authentication for things like `sudo` or a router `enable` command.
|
||||||
|
|
|
@ -16,6 +16,10 @@
|
||||||
"text": "My Header",
|
"text": "My Header",
|
||||||
"background": "green"
|
"background": "green"
|
||||||
},
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "WebSSH2id",
|
||||||
|
"secret": "mysecret"
|
||||||
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"challengeButton": true
|
"challengeButton": true
|
||||||
}
|
}
|
||||||
|
|
188
index.js
188
index.js
|
@ -1,73 +1,96 @@
|
||||||
/*
|
/*
|
||||||
* WebSSH2 - Web to SSH2 gateway
|
* WebSSH2 - Web to SSH2 gateway
|
||||||
* Bill Church - https://github.com/billchurch - April 2016
|
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
var express = require('express'),
|
var express = require('express')
|
||||||
app = express(),
|
var app = express()
|
||||||
cookieParser = require('cookie-parser'),
|
var server = require('http').Server(app)
|
||||||
server = require('http').Server(app),
|
var io = require('socket.io')(server)
|
||||||
io = require('socket.io')(server),
|
var path = require('path')
|
||||||
path = require('path'),
|
var SSH = require('ssh2').Client
|
||||||
basicAuth = require('basic-auth'),
|
var config = require('read-config')(path.join(__dirname, 'config.json'))
|
||||||
SSH = require('ssh2').Client,
|
var debug = require('debug')
|
||||||
readConfig = require('read-config'),
|
var dataBuffer = ''
|
||||||
config = readConfig(path.join(__dirname, 'config.json')),
|
var util = require('./util')
|
||||||
myError = ' - ',
|
var session = require('express-session')({
|
||||||
termCols,
|
secret: config.session.secret,
|
||||||
termRows
|
name: config.session.name,
|
||||||
|
resave: true,
|
||||||
|
saveUninitialized: false
|
||||||
|
})
|
||||||
|
var LogPrefix, termCols, termRows, myError
|
||||||
|
|
||||||
// function logErrors (err, req, res, next) {
|
var expressOptions = {
|
||||||
// console.error(err.stack)
|
dotfiles: 'ignore',
|
||||||
// next(err)
|
etag: false,
|
||||||
// }
|
extensions: ['htm', 'html'],
|
||||||
|
index: false,
|
||||||
|
maxAge: '1s',
|
||||||
|
redirect: false,
|
||||||
|
setHeaders: function (res, path, stat) {
|
||||||
|
res.set('x-timestamp', Date.now())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
server.listen({
|
server.listen({
|
||||||
host: config.listen.ip,
|
host: config.listen.ip,
|
||||||
port: config.listen.port
|
port: config.listen.port
|
||||||
}).on('error', function (err) {
|
})
|
||||||
|
|
||||||
|
server.on('error', function (err) {
|
||||||
if (err.code === 'EADDRINUSE') {
|
if (err.code === 'EADDRINUSE') {
|
||||||
config.listen.port++
|
config.listen.port++
|
||||||
console.log('Address in use, retrying on port ' + config.listen.port)
|
console.warn('Address in use, retrying on port ' + config.listen.port)
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
server.listen(config.listen.port)
|
server.listen(config.listen.port)
|
||||||
}, 250)
|
}, 250)
|
||||||
|
} else {
|
||||||
|
console.log('server.listen ERROR: ' + err.code)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, 'public'))).use(function (req, res, next) {
|
app.use(session)
|
||||||
var myAuth = basicAuth(req)
|
app.use(util.basicAuth)
|
||||||
if (myAuth === undefined) {
|
|
||||||
res.statusCode = 401
|
io.use(function (socket, next) {
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
|
if (socket.request.res) {
|
||||||
res.end('Username and password required for web SSH service.')
|
session(socket.request, socket.request.res, next)
|
||||||
} else if (myAuth.name === '') {
|
|
||||||
res.statusCode = 401
|
|
||||||
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
|
|
||||||
res.end('Username and password required for web SSH service.')
|
|
||||||
} else {
|
} else {
|
||||||
config.user.name = myAuth.name
|
|
||||||
config.user.password = myAuth.pass
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
}).use(cookieParser()).get('/ssh/host/:host?', function (req, res) {
|
})
|
||||||
|
|
||||||
|
app.disable('x-powered-by')
|
||||||
|
|
||||||
|
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
|
||||||
|
|
||||||
|
app.get('/ssh/host/:host?', function (req, res, next) {
|
||||||
res.sendFile(path.join(path.join(__dirname, 'public', 'client.htm')))
|
res.sendFile(path.join(path.join(__dirname, 'public', 'client.htm')))
|
||||||
config.ssh.host = req.params.host
|
// capture url variables if defined
|
||||||
if (typeof req.query.port !== 'undefined' && req.query.port !== null) {
|
config.ssh.host = req.params.host || config.ssh.host
|
||||||
config.ssh.port = req.query.port
|
config.ssh.port = req.query.port || config.ssh.port
|
||||||
}
|
config.header.text = req.query.header || config.header.text
|
||||||
if (typeof req.query.header !== 'undefined' && req.query.header !== null) {
|
config.header.background = req.query.headerBackground || config.header.background
|
||||||
config.header.text = req.query.header
|
console.log('webssh2 Login: user=' + req.session.username + ' from=' + req.ip + ' host=' + config.ssh.host + ' port=' + config.ssh.port + ' sessionID=' + req.sessionID + ' allowreplay=' + req.headers.allowreplay)
|
||||||
}
|
LogPrefix = req.session.username + '@' + req.ip + ' ssh://' + config.ssh.host + ':' + config.ssh.port + '/' + req.sessionID
|
||||||
if (typeof req.query.headerBackground !== 'undefined' && req.query.headerBackground !== null) {
|
// console.log('Headers: ' + JSON.stringify(req.headers))
|
||||||
config.header.background = req.query.headerBackground
|
|
||||||
}
|
|
||||||
console.log('webssh2 Login: user=' + config.user.name + ' from=' + req.ip + ' host=' + config.ssh.host + ' port=' + config.ssh.port + ' sessionID=' + req.headers.sessionid + ' allowreplay=' + req.headers.allowreplay)
|
|
||||||
console.log('Headers: ' + JSON.stringify(req.headers))
|
|
||||||
config.options.allowreplay = req.headers.allowreplay
|
config.options.allowreplay = req.headers.allowreplay
|
||||||
}).use('/style', express.static(path.join(__dirname, 'public'))).use('/src', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist'))).use('/addons', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist', 'addons')))
|
})
|
||||||
|
|
||||||
|
app.use('/style', express.static(path.join(__dirname, 'public')))
|
||||||
|
|
||||||
|
app.use('/src', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist')))
|
||||||
|
|
||||||
|
app.use('/addons', express.static(path.join(__dirname, 'node_modules', 'xterm', 'dist', 'addons')))
|
||||||
|
|
||||||
io.on('connection', function (socket) {
|
io.on('connection', function (socket) {
|
||||||
|
// if websocket connection arrives without an express session, kill it
|
||||||
|
if (!socket.request.session) {
|
||||||
|
socket.disconnect(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var conn = new SSH()
|
var conn = new SSH()
|
||||||
socket.on('geometry', function (cols, rows) {
|
socket.on('geometry', function (cols, rows) {
|
||||||
termCols = cols
|
termCols = cols
|
||||||
|
@ -78,13 +101,15 @@ io.on('connection', function (socket) {
|
||||||
// need to convert to cr/lf for proper formatting
|
// need to convert to cr/lf for proper formatting
|
||||||
d = d.replace(/\r?\n/g, '\r\n')
|
d = d.replace(/\r?\n/g, '\r\n')
|
||||||
socket.emit('data', d.toString('binary'))
|
socket.emit('data', d.toString('binary'))
|
||||||
}).on('ready', function () {
|
})
|
||||||
|
|
||||||
|
conn.on('ready', function () {
|
||||||
socket.emit('title', 'ssh://' + config.ssh.host)
|
socket.emit('title', 'ssh://' + config.ssh.host)
|
||||||
socket.emit('headerBackground', config.header.background)
|
socket.emit('headerBackground', config.header.background)
|
||||||
socket.emit('header', config.header.text)
|
socket.emit('header', config.header.text)
|
||||||
socket.emit('footer', 'ssh://' + config.user.name + '@' + config.ssh.host + ':' + config.ssh.port)
|
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + config.ssh.host + ':' + config.ssh.port)
|
||||||
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
||||||
socket.emit('statusBackground', 'green')
|
socket.emit('statusBackground', config.header.background)
|
||||||
socket.emit('allowreplay', config.options.allowreplay)
|
socket.emit('allowreplay', config.options.allowreplay)
|
||||||
|
|
||||||
conn.shell({
|
conn.shell({
|
||||||
|
@ -94,16 +119,26 @@ io.on('connection', function (socket) {
|
||||||
}, function (err, stream) {
|
}, function (err, stream) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err.message)
|
console.log(err.message)
|
||||||
myError = myError + err.message
|
myError = err.message
|
||||||
return socket.emit('status', 'SSH EXEC ERROR: ' + err.message).emit('statusBackground', 'red')
|
socket.emit('status', 'SSH EXEC ERROR: ' + err.message)
|
||||||
|
socket.emit('statusBackground', 'red')
|
||||||
|
console.log('conn.shell err: ' + err.message)
|
||||||
|
return socket.close(true)
|
||||||
}
|
}
|
||||||
socket.on('data', function (data) {
|
socket.on('data', function (data) {
|
||||||
stream.write(data)
|
stream.write(data)
|
||||||
|
// poc to log commands from client
|
||||||
|
// if (data === '\r') {
|
||||||
|
// console.log(LogPrefix + ': ' + dataBuffer)
|
||||||
|
// dataBuffer = ''
|
||||||
|
// } else {
|
||||||
|
// dataBuffer = dataBuffer + data
|
||||||
|
// }
|
||||||
})
|
})
|
||||||
socket.on('control', function (controlData) {
|
socket.on('control', function (controlData) {
|
||||||
switch (controlData) {
|
switch (controlData) {
|
||||||
case 'replayCredentials':
|
case 'replayCredentials':
|
||||||
stream.write(config.user.password + '\n')
|
stream.write(socket.request.session.userpassword + '\n')
|
||||||
/* falls through */
|
/* falls through */
|
||||||
default:
|
default:
|
||||||
console.log('controlData: ' + controlData)
|
console.log('controlData: ' + controlData)
|
||||||
|
@ -112,40 +147,61 @@ io.on('connection', function (socket) {
|
||||||
|
|
||||||
stream.on('data', function (d) {
|
stream.on('data', function (d) {
|
||||||
socket.emit('data', d.toString('binary'))
|
socket.emit('data', d.toString('binary'))
|
||||||
}).on('close', function (code, signal) {
|
})
|
||||||
|
|
||||||
|
stream.on('close', function (code, signal) {
|
||||||
console.log('Stream :: close :: code: ' + code + ', signal: ' + signal)
|
console.log('Stream :: close :: code: ' + code + ', signal: ' + signal)
|
||||||
conn.end()
|
conn.end()
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}).stderr.on('data', function (data) {
|
})
|
||||||
|
|
||||||
|
stream.stderr.on('data', function (data) {
|
||||||
console.log('STDERR: ' + data)
|
console.log('STDERR: ' + data)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}).on('end', function () {
|
})
|
||||||
|
|
||||||
|
conn.on('end', function () {
|
||||||
socket.emit('status', 'SSH CONNECTION CLOSED BY HOST ' + myError)
|
socket.emit('status', 'SSH CONNECTION CLOSED BY HOST ' + myError)
|
||||||
socket.emit('statusBackground', 'red')
|
socket.emit('statusBackground', 'red')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}).on('close', function () {
|
})
|
||||||
|
|
||||||
|
conn.on('close', function () {
|
||||||
socket.emit('status', 'SSH CONNECTION CLOSE ' + myError)
|
socket.emit('status', 'SSH CONNECTION CLOSE ' + myError)
|
||||||
socket.emit('statusBackground', 'red')
|
socket.emit('statusBackground', 'red')
|
||||||
socket.disconnect()
|
socket.disconnect()
|
||||||
}).on('error', function (err) {
|
})
|
||||||
myError = myError + err
|
|
||||||
|
conn.on('error', function (err) {
|
||||||
|
myError = err
|
||||||
socket.emit('status', 'SSH CONNECTION ERROR ' + myError)
|
socket.emit('status', 'SSH CONNECTION ERROR ' + myError)
|
||||||
socket.emit('statusBackground', 'red')
|
socket.emit('statusBackground', 'red')
|
||||||
console.log('on.error' + myError)
|
console.error('conn.on(\'error\'): ' + myError)
|
||||||
}).on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
})
|
||||||
|
|
||||||
|
conn.on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
||||||
console.log('Connection :: keyboard-interactive')
|
console.log('Connection :: keyboard-interactive')
|
||||||
finish([config.user.password])
|
finish([socket.request.session.userpassword])
|
||||||
}).connect({
|
})
|
||||||
|
if (socket.request.session.username && socket.request.session.userpassword) {
|
||||||
|
conn.connect({
|
||||||
host: config.ssh.host,
|
host: config.ssh.host,
|
||||||
port: config.ssh.port,
|
port: config.ssh.port,
|
||||||
username: config.user.name,
|
username: socket.request.session.username,
|
||||||
password: config.user.password,
|
password: socket.request.session.userpassword,
|
||||||
tryKeyboard: true,
|
tryKeyboard: true,
|
||||||
// some cisco routers need the these cipher strings
|
// some cisco routers need the these cipher strings
|
||||||
algorithms: {
|
algorithms: {
|
||||||
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc'],
|
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr'],
|
||||||
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
|
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
|
||||||
|
},
|
||||||
|
debug: debug('WebSSH2:debug')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.warn('Attempt to connect without session.username/password defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ' + JSON.stringify(socket.handshake))
|
||||||
|
socket.emit('statusBackground', 'red')
|
||||||
|
socket.emit('status', 'WEBSOCKET ERROR - Reload and try again')
|
||||||
|
socket.disconnect(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
15
package.json
15
package.json
|
@ -28,15 +28,20 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"basic-auth": "^1.1.0",
|
"basic-auth": "^1.1.0",
|
||||||
"cookie-parser": "^1.4.3",
|
"colors": "^1.1.2",
|
||||||
"express": "^4.14.1",
|
"debug": "^2.6.7",
|
||||||
|
"express": "^4.15.3",
|
||||||
|
"express-session": "^1.15.3",
|
||||||
"read-config": "^1.6.0",
|
"read-config": "^1.6.0",
|
||||||
"socket.io": "^1.6.0",
|
"socket.io": "^1.6.0",
|
||||||
"ssh2": "^0.5.4",
|
"ssh2": "^0.5.4",
|
||||||
"strip-ansi": "^3.0.1",
|
"xterm": "^2.6.0"
|
||||||
"xterm": "^2.4.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index"
|
"start": "node index",
|
||||||
|
"test": "snyk test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"snyk": "^1.30.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,9 @@ var terminalContainer = document.getElementById('terminal-container'),
|
||||||
}),
|
}),
|
||||||
socket,
|
socket,
|
||||||
termid
|
termid
|
||||||
term.open(terminalContainer)
|
term.open(terminalContainer, {
|
||||||
|
focus: true
|
||||||
|
})
|
||||||
term.fit()
|
term.fit()
|
||||||
|
|
||||||
if (document.location.pathname) {
|
if (document.location.pathname) {
|
||||||
|
@ -106,12 +108,12 @@ socket.on('connect', function () {
|
||||||
if (sessionLogEnable) {
|
if (sessionLogEnable) {
|
||||||
sessionLog = sessionLog + data
|
sessionLog = sessionLog + data
|
||||||
}
|
}
|
||||||
}).on('disconnect', function (err) {
|
})// .on('disconnect', function (err) {
|
||||||
document.getElementById('status').style.backgroundColor = 'red'
|
// document.getElementById('status').style.backgroundColor = 'red'
|
||||||
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED' + err
|
// document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED' + err
|
||||||
socket.io.reconnection(false)
|
// socket.io.reconnection(false)
|
||||||
}).on('error', function (err) {
|
// })//.on('error', function (err) {
|
||||||
document.getElementById('status').style.backgroundColor = 'red'
|
// document.getElementById('status').style.backgroundColor = 'red'
|
||||||
document.getElementById('status').innerHTML = 'ERROR ' + err
|
// document.getElementById('status').innerHTML = 'ERROR ' + err
|
||||||
})
|
// })
|
||||||
})
|
})
|
||||||
|
|
27
util/index.js
Normal file
27
util/index.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
var colors = require('colors');
|
||||||
|
var Auth = require('basic-auth')
|
||||||
|
|
||||||
|
console.warn = makeColorConsole(console.warn, 'yellow')
|
||||||
|
console.error = makeColorConsole(console.error, 'red')
|
||||||
|
|
||||||
|
function makeColorConsole(fct, color){
|
||||||
|
return function(){
|
||||||
|
for (var i in arguments)
|
||||||
|
if (arguments[i] instanceof Object)
|
||||||
|
arguments[i] = sys.inspect(arguments[i]);
|
||||||
|
fct(Array.prototype.join.call(arguments," ")[color]);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.basicAuth = function (req, res, next) {
|
||||||
|
var myAuth = Auth(req)
|
||||||
|
if (myAuth) {
|
||||||
|
req.session.username = myAuth.name
|
||||||
|
req.session.userpassword = myAuth.pass
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
res.statusCode = 401
|
||||||
|
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"')
|
||||||
|
res.end('Username and password required for web SSH service.')
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue