Merge remote-tracking branch 'remotes/billchurch/master'
# Conflicts: # index.js
This commit is contained in:
commit
4eff6128a2
26 changed files with 30516 additions and 227 deletions
|
|
@ -12,5 +12,8 @@
|
||||||
"read-config",
|
"read-config",
|
||||||
"socket.io"
|
"socket.io"
|
||||||
]
|
]
|
||||||
}
|
},
|
||||||
|
"ignore": [
|
||||||
|
"public/src/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
32
ChangeLog.md
32
ChangeLog.md
|
|
@ -1,22 +1,44 @@
|
||||||
# Change Log
|
# Change Log
|
||||||
## [Unreleased]
|
## [0.1.0] 2017-05-27
|
||||||
### Added
|
### Added
|
||||||
- This ChangeLog.md file
|
- This ChangeLog.md file
|
||||||
|
- Support for UTF-8 characters (thanks @bara666)
|
||||||
- Snyk, Bithound, Travis CI
|
- Snyk, Bithound, Travis CI
|
||||||
- Cross platform improvements (path mappings)
|
- Cross platform improvements (path mappings)
|
||||||
- Session fixup between Express and Socket.io
|
- Session fixup between Express and Socket.io
|
||||||
|
- Session secret settings in config.json
|
||||||
- env variable `DEBUG=ssh2` will put the `ssh2` module into debug mode
|
- env variable `DEBUG=ssh2` will put the `ssh2` module into debug mode
|
||||||
- env variable `debug=WebSSH2` will output additional debug messages for functions
|
- env variable `DEBUG=WebSSH2` will output additional debug messages for functions
|
||||||
and events in the application (not including the ssh2 module debug)
|
and events in the application (not including the ssh2 module debug)
|
||||||
|
- using Grunt to pull js and css source files from other modules `npm run build` to rebuild these if changed or updated.
|
||||||
|
- `useminified` option in `config.json` to enable using minified client side javascript (true) defaults to false (non-minified)
|
||||||
|
- sshterm= query option to specify TERM environment variable for host, valid strings are alpha-numeric with a hypen (validated). Otherwise the default ssh.term variable from `config.json` will be used.
|
||||||
|
- validation for host (v4,v6,fqdn,hostname), port (integer 2-65535), and header (sanitized) from URL input
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- erorr handling in public/client.js
|
- error handling in public/client.js
|
||||||
|
- moved socket.io operations to their own file /socket/index.js, more changes like this to come (./socket/index.js)
|
||||||
|
- all session based variables are now under the req.session.ssh property or socket.request.ssh (./index.js)
|
||||||
|
- moved SSH algorithms to config.json and defined as a session variable (..session.ssh.algorithms)
|
||||||
|
-- prep for future feature to define algorithms in header or some other method to enable separate ciphers per host
|
||||||
|
- minified and combined all js files to a single js in `./public/webssh2.min.js` also included a sourcemap `./public/webssh2.min.js` which maps to `./public/webssh2.js` for easier troubleshooting.
|
||||||
|
- combined all css files to a single css in `./public/webssh2.css`
|
||||||
|
- minified all css files to a single css in `./public/webssh2.min.css`
|
||||||
|
- copied all unmodified source css and js to /public/src/css and /public/src/js respectively (for troubleshooting/etc)
|
||||||
|
- sourcemaps of all minified code (in /public/src and /public/src/js)
|
||||||
|
- renamed `client.htm` to `client-full.htm`
|
||||||
|
- created `client-min.htm` to serve minified javascript
|
||||||
|
- if header.text is null in config.json and header is not defined as a get parameter the Header will not be displayed. Both of these must be null / undefined and not specified as get parameters.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Multiple errors may ovewrite status bar which would cause confusion as to what originally caused the error. Example, ssh server disconnects which prompts a cascade of events (conn.on('end'), socket.on('disconnect'), conn.on('close')) and the original reason (conn.on('end')) would be lost and the user would erroneously receive a WEBSOCKET error as the last event to fire would be the websocket connection closing from the app.
|
- Multiple errors may overwrite status bar which would cause confusion as to what originally caused the error. Example, ssh server disconnects which prompts a cascade of events (conn.on('end'), socket.on('disconnect'), conn.on('close')) and the original reason (conn.on('end')) would be lost and the user would erroneously receive a WEBSOCKET error as the last event to fire would be the websocket connection closing from the app.
|
||||||
- ensure ssh session is closed when a browser disconnects from the websocket
|
- ensure ssh session is closed when a browser disconnects from the websocket
|
||||||
|
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
|
||||||
|
|
||||||
## [0.0.5] - 2017-0323
|
### Removed
|
||||||
|
- Express Static References directly to module source directories due to concatenating and minifying js/css
|
||||||
|
|
||||||
|
## [0.0.5] - 2017-03-23
|
||||||
### Added
|
### Added
|
||||||
- Added experimental support for logging (see Readme)
|
- Added experimental support for logging (see Readme)
|
||||||
|
|
||||||
|
|
|
||||||
73
Gruntfile.js
Normal file
73
Gruntfile.js
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
module.exports = function (grunt) {
|
||||||
|
// Project configuration.
|
||||||
|
grunt.initConfig({
|
||||||
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
|
copy: {
|
||||||
|
main: {
|
||||||
|
files: [
|
||||||
|
{
|
||||||
|
expand: true,
|
||||||
|
flatten: true,
|
||||||
|
src: [
|
||||||
|
'node_modules/xterm/dist/xterm.css',
|
||||||
|
'src/css/style.css'
|
||||||
|
],
|
||||||
|
dest: 'public/src/css'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expand: true,
|
||||||
|
flatten: true,
|
||||||
|
src: [
|
||||||
|
'node_modules/xterm/dist/xterm.js',
|
||||||
|
'node_modules/xterm/dist/xterm.js.map',
|
||||||
|
'node_modules/xterm/dist/addons/fit/fit.js',
|
||||||
|
'node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js',
|
||||||
|
'node_modules/socket.io/node_modules/socket.io-client/dist/socket.io.js.map',
|
||||||
|
'src/js/client.js'
|
||||||
|
],
|
||||||
|
dest: 'public/src/js'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
concat: {
|
||||||
|
options: {
|
||||||
|
sourceMap: true,
|
||||||
|
sourceMapName: 'public/src/webssh2.concat.map',
|
||||||
|
sourceMapStyle: 'embed'
|
||||||
|
},
|
||||||
|
css: {
|
||||||
|
src: ['public/src/css/*.css'],
|
||||||
|
dest: 'public/webssh2.css'
|
||||||
|
},
|
||||||
|
js: {
|
||||||
|
src: [
|
||||||
|
'public/src/js/xterm.js',
|
||||||
|
'public/src/js/fit.js',
|
||||||
|
'public/src/js/socket.io.js',
|
||||||
|
'public/src/js/client.js'
|
||||||
|
],
|
||||||
|
dest: 'public/webssh2.js'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
uglify: {
|
||||||
|
options: {
|
||||||
|
banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */\n',
|
||||||
|
sourceMap: true,
|
||||||
|
sourceMapName: 'public/src/webssh2.min.map'
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
src: ['public/src/js/xterm.js', 'public/src/js/fit.js', 'public/src/js/socket.io.js', 'public/src/js/client.js'],
|
||||||
|
dest: 'public/webssh2.min.js'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Load the plugin that provides the "uglify" task.
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-copy')
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-concat')
|
||||||
|
grunt.loadNpmTasks('grunt-contrib-uglify')
|
||||||
|
|
||||||
|
// Default task(s).
|
||||||
|
grunt.registerTask('default', ['copy', 'concat', 'uglify'])
|
||||||
|
}
|
||||||
152
README.md
152
README.md
|
|
@ -1,4 +1,4 @@
|
||||||
# WebSSH2 [](https://badge.fury.io/gh/BillChurch%2FWebSSH2) [](https://travis-ci.org/billchurch/WebSSH2) [](https://snyk.io/test/github/billchurch/webssh2) [](https://www.bithound.io/github/billchurch/WebSSH2) [](https://www.bithound.io/github/billchurch/WebSSH2/master/dependencies/npm) [](https://nodesecurity.io/orgs/billchurch/projects/b0a0d9df-1340-43ef-9736-ef983c057764)
|
# WebSSH2 [](https://badge.fury.io/gh/billchurch%2FWebSSH2) [](https://travis-ci.org/billchurch/WebSSH2) [](https://snyk.io/test/github/billchurch/webssh2) [](https://www.bithound.io/github/billchurch/WebSSH2) [](https://www.bithound.io/github/billchurch/WebSSH2/master/dependencies/npm) [](https://nodesecurity.io/orgs/billchurch/projects/b0a0d9df-1340-43ef-9736-ef983c057764)
|
||||||
Web SSH Client using ssh2, socket.io, xterm.js, and express
|
Web SSH Client using ssh2, socket.io, xterm.js, and express
|
||||||
|
|
||||||
Bare bones example of using SSH2 as a client on a host to proxy a Websocket / Socket.io connection to a SSH2 server.
|
Bare bones example of using SSH2 as a client on a host to proxy a Websocket / Socket.io connection to a SSH2 server.
|
||||||
|
|
@ -8,7 +8,7 @@ Bare bones example of using SSH2 as a client on a host to proxy a Websocket / So
|
||||||
# Instructions
|
# Instructions
|
||||||
To install:
|
To install:
|
||||||
|
|
||||||
1. Clone to a location somewhere and `npm install`
|
1. Clone to a location somewhere and `npm install --production`. If you want to develop and rebuild javascript and other files utilize `npm install` instead.
|
||||||
|
|
||||||
2. If desired, edit config.json to change the listener to your liking. There are also some default options which may be definied for a few of the variables.
|
2. If desired, edit config.json to change the listener to your liking. There are also some default options which may be definied for a few of the variables.
|
||||||
|
|
||||||
|
|
@ -21,52 +21,136 @@ http://localhost:2222/ssh/host/127.0.0.1
|
||||||
|
|
||||||
You will be prompted for credentials to use on the SSH server via HTTP Basic authentcaiton. This is to permit usage with some SSO systems that can replay credentials over HTTP basic.
|
You will be prompted for credentials to use on the SSH server via HTTP Basic authentcaiton. This is to permit usage with some SSO systems that can replay credentials over HTTP basic.
|
||||||
|
|
||||||
# Options (GET request vars)
|
# Options
|
||||||
|
|
||||||
port= - port of SSH server (defaults to 22)
|
## GET request vars
|
||||||
|
|
||||||
header= - optional header to display on page
|
* **port=** - _integer_ - port of SSH server (defaults to 22)
|
||||||
|
|
||||||
headerBackground= - optional background color of header to display on page
|
* **header=** - _string_ - optional header to display on page
|
||||||
|
|
||||||
# Config File Options
|
* **headerBackground=** - _string_ - 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...
|
|
||||||
|
|
||||||
`listen.ip` default `127.0.0.1`
|
## Headers
|
||||||
* IP address node should listen on for client connections.
|
|
||||||
|
|
||||||
`listen.port` default `2222`
|
* **allowreplay** - _boolean_ - Allow use of password replay feature, example `allowreplay: true`
|
||||||
* Port node should listen on for client connections.
|
|
||||||
|
|
||||||
`user.name` default `null`
|
## Config File Options
|
||||||
* Specify user name to authenticate with.
|
`config.json` contains several options which may be specified to customize to your needs, vs editing the javascript directly. This is JSON format so mind your spacing, brackets, etc...
|
||||||
|
|
||||||
`user.password` default `null`
|
* **listen.ip** - _string_ - IP address node should listen on for client connections, defaults to `127.0.0.1`
|
||||||
* Specify password to authenticate with.
|
|
||||||
|
|
||||||
`ssh.host` default `null`
|
* **listen.port** - _integer_ - Port node should listen on for client connections, defaults to `2222`
|
||||||
* Specify host to connect to.
|
|
||||||
|
|
||||||
`ssh.port` default `22`
|
* **user.name** - _string_ - Specify user name to authenticate with. In normal cases this should be left to the default `null` setting.
|
||||||
* Specify SSH port to connect to.
|
|
||||||
|
|
||||||
`ssh.term` default `xterm-color`
|
* **user.password** - _string_ - Specify password to authenticate with. In normal cases this should be left to the default `null` setting.
|
||||||
* Specify terminal emulation to use.
|
|
||||||
|
|
||||||
`header.text`
|
* **ssh.host** - _string_ - Specify host to connect to. May be either hostname or IP address. Defaults to `null`.
|
||||||
* Specify header text, defaults to `My Header` but may also be set to `null`.
|
|
||||||
|
|
||||||
`header.background`
|
* **ssh.port** - _integer_ - Specify SSH port to connect to, defaults to `22`
|
||||||
* Header background, defaults to `green`.
|
|
||||||
|
|
||||||
`session.name`
|
* **ssh.term** - _string_ - Specify terminal emulation to use, defaults to `xterm-color`
|
||||||
* Name of session ID cookie. it's not a horrible idea to make this something unique.
|
|
||||||
|
|
||||||
`session.secret`
|
* **useminified** - _boolean_ - Choose between ./public/client-full.htm (false/non-minified) or ./public/client-min.htm (true/minified js), defaults to false (non-minified version)
|
||||||
* Secret key for cookie encryption. You should change this in production.
|
|
||||||
|
|
||||||
`options.challengeButton`
|
* **header.text** - _string_ - Specify header text, defaults to `My Header` but may also be set to `null`. When set to `null` no header bar will be displayed on the client.
|
||||||
* 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.
|
|
||||||
|
* **header.background** - _string_ - Header background, defaults to `green`.
|
||||||
|
|
||||||
|
* **session.name** - _string_ - Name of session ID cookie. it's not a horrible idea to make this something unique.
|
||||||
|
|
||||||
|
* **session.secret** - _string_ - Secret key for cookie encryption. You should change this in production.
|
||||||
|
|
||||||
|
* **options.challengeButton** - _boolean_ - 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.
|
||||||
|
|
||||||
|
* **algorithms** - _object_ - This option allows you to explicitly override the default transport layer algorithms used for the connection. Each value must be an array of valid algorithms for that category. The order of the algorithms in the arrays are important, with the most favorable being first. Valid keys:
|
||||||
|
|
||||||
|
* **kex** - _array_ - Key exchange algorithms.
|
||||||
|
|
||||||
|
* Default values:
|
||||||
|
|
||||||
|
1. ecdh-sha2-nistp256
|
||||||
|
2. ecdh-sha2-nistp384
|
||||||
|
3. ecdh-sha2-nistp521
|
||||||
|
4. diffie-hellman-group-exchange-sha256
|
||||||
|
5. diffie-hellman-group14-sha1
|
||||||
|
|
||||||
|
* Supported values:
|
||||||
|
|
||||||
|
* ecdh-sha2-nistp256
|
||||||
|
* ecdh-sha2-nistp384
|
||||||
|
* ecdh-sha2-nistp521
|
||||||
|
* diffie-hellman-group-exchange-sha256
|
||||||
|
* diffie-hellman-group14-sha1
|
||||||
|
* diffie-hellman-group-exchange-sha1
|
||||||
|
* diffie-hellman-group1-sha1
|
||||||
|
|
||||||
|
* **cipher** - _array_ - Ciphers.
|
||||||
|
|
||||||
|
* Default values:
|
||||||
|
|
||||||
|
1. aes128-ctr
|
||||||
|
2. aes192-ctr
|
||||||
|
3. aes256-ctr
|
||||||
|
4. aes128-gcm
|
||||||
|
5. aes128-gcm@openssh.com
|
||||||
|
6. aes256-gcm
|
||||||
|
7. aes256-gcm@openssh.com
|
||||||
|
8. aes256-cbc **legacy cipher for backward compatibility, should removed :+1:**
|
||||||
|
|
||||||
|
* Supported values:
|
||||||
|
|
||||||
|
* aes128-ctr
|
||||||
|
* aes192-ctr
|
||||||
|
* aes256-ctr
|
||||||
|
* aes128-gcm
|
||||||
|
* aes128-gcm@openssh.com
|
||||||
|
* aes256-gcm
|
||||||
|
* aes256-gcm@openssh.com
|
||||||
|
* aes256-cbc
|
||||||
|
* aes192-cbc
|
||||||
|
* aes128-cbc
|
||||||
|
* blowfish-cbc
|
||||||
|
* 3des-cbc
|
||||||
|
* arcfour256
|
||||||
|
* arcfour128
|
||||||
|
* cast128-cbc
|
||||||
|
* arcfour
|
||||||
|
|
||||||
|
* **hmac** - _array_ - (H)MAC algorithms.
|
||||||
|
|
||||||
|
* Default values:
|
||||||
|
|
||||||
|
1. hmac-sha2-256
|
||||||
|
2. hmac-sha2-512
|
||||||
|
3. hmac-sha1 **legacy hmac for backward compatibility, should removed :+1:**
|
||||||
|
|
||||||
|
* Supported values:
|
||||||
|
|
||||||
|
* hmac-sha2-256
|
||||||
|
* hmac-sha2-512
|
||||||
|
* hmac-sha1
|
||||||
|
* hmac-md5
|
||||||
|
* hmac-sha2-256-96
|
||||||
|
* hmac-sha2-512-96
|
||||||
|
* hmac-ripemd160
|
||||||
|
* hmac-sha1-96
|
||||||
|
* hmac-md5-96
|
||||||
|
|
||||||
|
* **compress** - _array_ - Compression algorithms.
|
||||||
|
|
||||||
|
* Default values:
|
||||||
|
|
||||||
|
1. none
|
||||||
|
2. zlib@openssh.com
|
||||||
|
3. zlib
|
||||||
|
|
||||||
|
* Supported values:
|
||||||
|
|
||||||
|
* none
|
||||||
|
* zlib@openssh.com
|
||||||
|
* zlib
|
||||||
|
|
||||||
# Experimental client-side logging
|
# Experimental client-side logging
|
||||||
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
|
Clicking `Start logging` on the status bar will log all data to the client. A `Download log` option will appear after starting the logging. You may download at any time to the client. You may stop logging at any time my pressing the `Logging - STOP LOG`. Note that clicking the `Start logging` option again will cause the current log to be overwritten, so be sure to download first.
|
||||||
|
|
@ -75,3 +159,7 @@ Clicking `Start logging` on the status bar will log all data to the client. A `D
|
||||||
|
|
||||||
http://localhost:2222/ssh/host/192.168.1.1?port=2244&header=My%20Header&color=red
|
http://localhost:2222/ssh/host/192.168.1.1?port=2244&header=My%20Header&color=red
|
||||||
|
|
||||||
|
# Tips
|
||||||
|
* If you want to add custom JavaScript to the browser client you can either modify `./public/client-(full|min).html` and add a **<script>** element or check out `Gulpfile.js` and add your custom javascript file to the concat task
|
||||||
|
* BIG-IP Acess Policy Manager (APM) doesn't always care for minified javascript when run in portal mode. Be sure to Set `useminified` option in `config.json` to `false` for these environments
|
||||||
|
* Set `useminified` option in `config.json` to `true` to utilize minified javascript
|
||||||
|
|
|
||||||
36
config.json
36
config.json
|
|
@ -10,17 +10,47 @@
|
||||||
"ssh": {
|
"ssh": {
|
||||||
"host": null,
|
"host": null,
|
||||||
"port": 22,
|
"port": 22,
|
||||||
"term": "xterm-color",
|
"term": "xterm-color"
|
||||||
},
|
},
|
||||||
|
"useminified": false,
|
||||||
"header": {
|
"header": {
|
||||||
"text": "My Header",
|
"text": null,
|
||||||
"background": "green"
|
"background": "green"
|
||||||
},
|
},
|
||||||
"session": {
|
"session": {
|
||||||
"name": "WebSSH2id",
|
"name": "WebSSH2",
|
||||||
"secret": "mysecret"
|
"secret": "mysecret"
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"challengeButton": true
|
"challengeButton": true
|
||||||
|
},
|
||||||
|
"algorithms": {
|
||||||
|
"kex": [
|
||||||
|
"ecdh-sha2-nistp256",
|
||||||
|
"ecdh-sha2-nistp384",
|
||||||
|
"ecdh-sha2-nistp521",
|
||||||
|
"diffie-hellman-group-exchange-sha256",
|
||||||
|
"diffie-hellman-group14-sha1"
|
||||||
|
],
|
||||||
|
"cipher": [
|
||||||
|
"aes128-ctr",
|
||||||
|
"aes192-ctr",
|
||||||
|
"aes256-ctr",
|
||||||
|
"aes128-gcm",
|
||||||
|
"aes128-gcm@openssh.com",
|
||||||
|
"aes256-gcm",
|
||||||
|
"aes256-gcm@openssh.com",
|
||||||
|
"aes256-cbc" // for some legacy systems
|
||||||
|
],
|
||||||
|
"hmac": [
|
||||||
|
"hmac-sha2-256",
|
||||||
|
"hmac-sha2-512",
|
||||||
|
"hmac-sha1" // for some legacy systems
|
||||||
|
],
|
||||||
|
"compress": [
|
||||||
|
"none",
|
||||||
|
"zlib@openssh.com",
|
||||||
|
"zlib"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
208
index.js
208
index.js
|
|
@ -8,40 +8,16 @@ var app = express()
|
||||||
var server = require('http').Server(app)
|
var server = require('http').Server(app)
|
||||||
var io = require('socket.io')(server)
|
var io = require('socket.io')(server)
|
||||||
var path = require('path')
|
var path = require('path')
|
||||||
var SSH = require('ssh2').Client
|
|
||||||
var config = require('read-config')(path.join(__dirname, 'config.json'))
|
var config = require('read-config')(path.join(__dirname, 'config.json'))
|
||||||
var debug = require('debug')
|
var myutil = require('./util')
|
||||||
var debugWebSSH2 = debug('WebSSH2')
|
var socket = require('./socket/index.js')
|
||||||
var util = require('./util')
|
var validator = require('validator')
|
||||||
var SocketUtil = require('./socket')
|
|
||||||
var session = require('express-session')({
|
var session = require('express-session')({
|
||||||
secret: config.session.secret,
|
secret: config.session.secret,
|
||||||
name: config.session.name,
|
name: config.session.name,
|
||||||
resave: true,
|
resave: true,
|
||||||
saveUninitialized: false
|
saveUninitialized: false,
|
||||||
})
|
unset: 'destroy'
|
||||||
var colors = require('colors/safe')
|
|
||||||
var termCols, termRows
|
|
||||||
// var LogPrefix
|
|
||||||
// var dataBuffer = ''
|
|
||||||
|
|
||||||
// server
|
|
||||||
|
|
||||||
server.listen({
|
|
||||||
host: config.listen.ip,
|
|
||||||
port: config.listen.port
|
|
||||||
})
|
|
||||||
|
|
||||||
server.on('error', function (err) {
|
|
||||||
if (err.code === 'EADDRINUSE') {
|
|
||||||
config.listen.port++
|
|
||||||
console.warn('Address in use, retrying on port ' + config.listen.port)
|
|
||||||
setTimeout(function () {
|
|
||||||
server.listen(config.listen.port)
|
|
||||||
}, 250)
|
|
||||||
} else {
|
|
||||||
console.log('server.listen ERROR: ' + err.code)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// express
|
// express
|
||||||
|
|
@ -58,30 +34,32 @@ var expressOptions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
app.use(session)
|
app.use(session)
|
||||||
app.use(util.basicAuth)
|
app.use(myutil.basicAuth)
|
||||||
|
|
||||||
app.disable('x-powered-by')
|
app.disable('x-powered-by')
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
|
|
||||||
|
|
||||||
app.get('/ssh/host/:host?', function (req, res, next) {
|
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', (config.useminified) ? 'client-min.htm' : 'client-full.htm')))
|
||||||
// capture url variables if defined
|
// capture and assign variables
|
||||||
config.ssh.host = req.params.host || config.ssh.host
|
req.session.ssh = {
|
||||||
config.ssh.port = req.query.port || config.ssh.port
|
host: (validator.isIP(req.params.host + '') && req.params.host) || (validator.isFQDN(req.params.host) && req.params.host) || (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.params.host) && req.params.host) || config.ssh.host,
|
||||||
config.header.text = req.query.header || config.header.text
|
port: (validator.isInt(req.query.port + '', {min: 1, max: 65535}) && req.query.port) || config.ssh.port,
|
||||||
config.header.background = req.query.headerBackground || config.header.background
|
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)
|
name: req.query.header || config.header.text,
|
||||||
debugWebSSH2('Headers: ' + colors.yellow(JSON.stringify(req.headers)))
|
background: req.query.headerBackground || config.header.background
|
||||||
config.options.allowreplay = req.headers.allowreplay
|
},
|
||||||
|
algorithms: config.algorithms,
|
||||||
|
term: (/^(([a-z]|[A-Z]|[0-9]|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm) && req.query.sshterm) || config.ssh.term,
|
||||||
|
allowreplay: validator.isBoolean(req.headers.allowreplay + '') || false
|
||||||
|
}
|
||||||
|
req.session.ssh.header.name && validator.escape(req.session.ssh.header.name)
|
||||||
|
req.session.ssh.header.background && validator.escape(req.session.ssh.header.background)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.use('/style', express.static(path.join(__dirname, 'public')))
|
// static files
|
||||||
|
app.use(express.static(path.join(__dirname, 'public'), expressOptions))
|
||||||
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')))
|
|
||||||
|
|
||||||
|
// express error handling
|
||||||
app.use(function (req, res, next) {
|
app.use(function (req, res, next) {
|
||||||
res.status(404).send("Sorry can't find that!")
|
res.status(404).send("Sorry can't find that!")
|
||||||
})
|
})
|
||||||
|
|
@ -92,124 +70,28 @@ app.use(function (err, req, res, next) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// socket.io
|
// socket.io
|
||||||
|
// expose express session with socket.request.session
|
||||||
io.use(function (socket, next) {
|
io.use(function (socket, next) {
|
||||||
if (socket.request.res) {
|
(socket.request.res) ? session(socket.request, socket.request.res, next) : next()
|
||||||
session(socket.request, socket.request.res, next)
|
})
|
||||||
|
|
||||||
|
// bring up socket
|
||||||
|
io.on('connection', socket)
|
||||||
|
|
||||||
|
// server
|
||||||
|
server.listen({
|
||||||
|
host: config.listen.ip,
|
||||||
|
port: config.listen.port
|
||||||
|
})
|
||||||
|
|
||||||
|
server.on('error', function (err) {
|
||||||
|
if (err.code === 'EADDRINUSE') {
|
||||||
|
config.listen.port++
|
||||||
|
console.warn('Address in use, retrying on port ' + config.listen.port)
|
||||||
|
setTimeout(function () {
|
||||||
|
server.listen(config.listen.port)
|
||||||
|
}, 250)
|
||||||
} else {
|
} else {
|
||||||
next()
|
console.log('server.listen ERROR: ' + err.code)
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
io.on('connection', function (socket) {
|
|
||||||
// if websocket connection arrives without an express session, kill it
|
|
||||||
if (!socket.request.session) {
|
|
||||||
socket.disconnect(true)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var socketutil = new SocketUtil(socket, io)
|
|
||||||
var conn = new SSH()
|
|
||||||
socket.on('geometry', function (cols, rows) {
|
|
||||||
termCols = cols
|
|
||||||
termRows = rows
|
|
||||||
})
|
|
||||||
|
|
||||||
conn.on('banner', function (d) {
|
|
||||||
// need to convert to cr/lf for proper formatting
|
|
||||||
d = d.replace(/\r?\n/g, '\r\n')
|
|
||||||
socket.emit('data', d.toString('binary'))
|
|
||||||
})
|
|
||||||
|
|
||||||
conn.on('ready', function () {
|
|
||||||
socket.emit('title', 'ssh://' + config.ssh.host)
|
|
||||||
socket.emit('headerBackground', config.header.background)
|
|
||||||
socket.emit('header', config.header.text)
|
|
||||||
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + config.ssh.host + ':' + config.ssh.port)
|
|
||||||
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
|
||||||
socket.emit('statusBackground', config.header.background)
|
|
||||||
socket.emit('allowreplay', config.options.allowreplay)
|
|
||||||
|
|
||||||
conn.shell({
|
|
||||||
term: config.ssh.term,
|
|
||||||
cols: termCols,
|
|
||||||
rows: termRows
|
|
||||||
}, function (err, stream) {
|
|
||||||
if (err) {
|
|
||||||
socketutil.SSHerror('EXEC ERROR' + err)
|
|
||||||
conn.end()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
socket.on('data', function (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) {
|
|
||||||
switch (controlData) {
|
|
||||||
case 'replayCredentials':
|
|
||||||
stream.write(socket.request.session.userpassword + '\n')
|
|
||||||
/* falls through */
|
|
||||||
default:
|
|
||||||
console.log('controlData: ' + controlData)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('disconnecting', function (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
|
||||||
|
|
||||||
socket.on('disconnect', function (reason) {
|
|
||||||
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
|
|
||||||
err = { message: reason }
|
|
||||||
socketutil.SSHerror('CLIENT SOCKET DISCONNECT', err)
|
|
||||||
conn.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
socket.on('error', function (error) { debugWebSSH2('SOCKET ERROR: ' + JSON.stringify(error)) })
|
|
||||||
|
|
||||||
stream.on('data', function (d) { socket.emit('data', d.toString('utf-8')) })
|
|
||||||
|
|
||||||
stream.on('close', function (code, signal) {
|
|
||||||
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
|
|
||||||
socketutil.SSHerror('STREAM CLOSE', err)
|
|
||||||
conn.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
stream.stderr.on('data', function (data) {
|
|
||||||
console.log('STDERR: ' + data)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
conn.on('end', function (err) { socketutil.SSHerror('CONN END BY HOST', err) })
|
|
||||||
conn.on('close', function (err) { socketutil.SSHerror('CONN CLOSE', err) })
|
|
||||||
conn.on('error', function (err) { socketutil.SSHerror('CONN ERROR', err) })
|
|
||||||
|
|
||||||
conn.on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
|
||||||
debugWebSSH2('Connection :: keyboard-interactive')
|
|
||||||
finish([socket.request.session.userpassword])
|
|
||||||
})
|
|
||||||
if (socket.request.session.username && socket.request.session.userpassword) {
|
|
||||||
conn.connect({
|
|
||||||
host: config.ssh.host,
|
|
||||||
port: config.ssh.port,
|
|
||||||
username: socket.request.session.username,
|
|
||||||
password: socket.request.session.userpassword,
|
|
||||||
tryKeyboard: true,
|
|
||||||
// some cisco routers need the these cipher strings
|
|
||||||
algorithms: {
|
|
||||||
'cipher': ['aes128-cbc', '3des-cbc', 'aes256-cbc', 'aes128-ctr', 'aes192-ctr', 'aes256-ctr'],
|
|
||||||
'hmac': ['hmac-sha1', 'hmac-sha1-96', 'hmac-md5-96']
|
|
||||||
},
|
|
||||||
debug: debug('ssh2')
|
|
||||||
})
|
|
||||||
} 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)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
25
package.json
25
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "WebSSH2",
|
"name": "WebSSH2",
|
||||||
"version": "0.0.5",
|
"version": "0.1.0",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
".gitignore"
|
".gitignore"
|
||||||
],
|
],
|
||||||
|
|
@ -35,14 +35,31 @@
|
||||||
"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",
|
||||||
"xterm": "^2.6.0"
|
"validator": "^7.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node index",
|
"start": "node index",
|
||||||
"test": "snyk test"
|
"test": "snyk test",
|
||||||
|
"watch": "nodemon index.js",
|
||||||
|
"build": "grunt copy concat uglify",
|
||||||
|
"standard": "standard --verbose | snazzy"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"bithound": "^1.7.0",
|
"bithound": "^1.7.0",
|
||||||
"snyk": "^1.30.1"
|
"grunt": "^1.0.1",
|
||||||
|
"grunt-contrib-concat": "^1.0.1",
|
||||||
|
"grunt-contrib-copy": "^1.0.0",
|
||||||
|
"grunt-contrib-uglify": "^3.0.1",
|
||||||
|
"nodemon": "^1.11.0",
|
||||||
|
"snazzy": "^7.0.0",
|
||||||
|
"snyk": "^1.30.1",
|
||||||
|
"standard": "^10.0.2",
|
||||||
|
"xterm": "^2.6.0"
|
||||||
|
},
|
||||||
|
"standard": {
|
||||||
|
"ignore": [
|
||||||
|
"public/webssh2.js",
|
||||||
|
"public/src/js/*.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,8 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Web SSH</title>
|
<title>Web SSH</title>
|
||||||
<link rel="stylesheet" href="/src/xterm.css" />
|
<link rel="stylesheet" href="/webssh2.css" />
|
||||||
<link rel="stylesheet" href="/style/style.css" />
|
<script src="/webssh2.js" defer></script>
|
||||||
<script src="/socket.io/socket.io.js" defer></script>
|
|
||||||
<script src="/src/xterm.js" defer></script>
|
|
||||||
<script src="/addons/fit/fit.js" defer></script>
|
|
||||||
<script src="/client.js" defer></script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
20
public/client-min.htm
Normal file
20
public/client-min.htm
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Web SSH</title>
|
||||||
|
<link rel="stylesheet" href="/webssh2.css" />
|
||||||
|
<script src="/webssh2.min.js" defer></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="box">
|
||||||
|
<div id="header"></div>
|
||||||
|
<div id="terminal-container" class="terminal"></div>
|
||||||
|
<div id="bottomdiv">
|
||||||
|
<div id="footer"></div>
|
||||||
|
<div id="status"></div>
|
||||||
|
<div id="credentials"><a class="credentials" href="javascript:void(0);" onclick="replayCredentials()">CREDENTIALS</a></div>
|
||||||
|
<div id="downloadLog"><a class="downloadLog" href="javascript:void(0);" onclick="downloadLog()">Download Log</a></div>
|
||||||
|
<div id="toggleLog"><a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
2247
public/src/css/xterm.css
Normal file
2247
public/src/css/xterm.css
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,9 @@
|
||||||
|
/* global io, Terminal, Blob */
|
||||||
var sessionLogEnable = false
|
var sessionLogEnable = false
|
||||||
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
|
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
|
||||||
|
|
||||||
// replay password to server, requires
|
// replay password to server, requires
|
||||||
function replayCredentials () {
|
function replayCredentials () { // eslint-disable-line
|
||||||
socket.emit('control', 'replayCredentials')
|
socket.emit('control', 'replayCredentials')
|
||||||
console.log('replaying credentials')
|
console.log('replaying credentials')
|
||||||
return false
|
return false
|
||||||
|
|
@ -10,7 +11,7 @@ function replayCredentials () {
|
||||||
|
|
||||||
// Set variable to toggle log data from client/server to a varialble
|
// Set variable to toggle log data from client/server to a varialble
|
||||||
// for later download
|
// for later download
|
||||||
function toggleLog () {
|
function toggleLog () { // eslint-disable-line
|
||||||
if (sessionLogEnable === true) {
|
if (sessionLogEnable === true) {
|
||||||
sessionLogEnable = false
|
sessionLogEnable = false
|
||||||
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||||
|
|
@ -33,7 +34,7 @@ function toggleLog () {
|
||||||
|
|
||||||
// cross browser method to "download" an element to the local system
|
// cross browser method to "download" an element to the local system
|
||||||
// used for our client-side logging feature
|
// used for our client-side logging feature
|
||||||
function downloadLog () {
|
function downloadLog () { // eslint-disable-line
|
||||||
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
||||||
// regex should eliminate escape sequences from being logged.
|
// regex should eliminate escape sequences from being logged.
|
||||||
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
||||||
|
|
@ -58,7 +59,7 @@ var terminalContainer = document.getElementById('terminal-container')
|
||||||
var term = new Terminal({
|
var term = new Terminal({
|
||||||
cursorBlink: true
|
cursorBlink: true
|
||||||
})
|
})
|
||||||
var socket, termid
|
var socket, termid // eslint-disable-line
|
||||||
term.open(terminalContainer, {
|
term.open(terminalContainer, {
|
||||||
focus: true
|
focus: true
|
||||||
})
|
})
|
||||||
|
|
@ -98,7 +99,7 @@ socket.on('connect', function () {
|
||||||
}).on('statusBackground', function (data) {
|
}).on('statusBackground', function (data) {
|
||||||
document.getElementById('status').style.backgroundColor = data
|
document.getElementById('status').style.backgroundColor = data
|
||||||
}).on('allowreplay', function (data) {
|
}).on('allowreplay', function (data) {
|
||||||
if (data === 'true') {
|
if (data === true) {
|
||||||
console.log('allowreplay: ' + data)
|
console.log('allowreplay: ' + data)
|
||||||
document.getElementById('credentials').style.display = 'inline'
|
document.getElementById('credentials').style.display = 'inline'
|
||||||
} else {
|
} else {
|
||||||
86
public/src/js/fit.js
Normal file
86
public/src/js/fit.js
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
/**
|
||||||
|
* Fit terminal columns and rows to the dimensions of its DOM element.
|
||||||
|
*
|
||||||
|
* ## Approach
|
||||||
|
* - Rows: Truncate the division of the terminal parent element height by the terminal row height.
|
||||||
|
*
|
||||||
|
* - Columns: Truncate the division of the terminal parent element width by the terminal character
|
||||||
|
* width (apply display: inline at the terminal row and truncate its width with the current
|
||||||
|
* number of columns).
|
||||||
|
* @module xterm/addons/fit/fit
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
|
||||||
|
(function (fit) {
|
||||||
|
if (typeof exports === 'object' && typeof module === 'object') {
|
||||||
|
/*
|
||||||
|
* CommonJS environment
|
||||||
|
*/
|
||||||
|
module.exports = fit(require('../../xterm'));
|
||||||
|
} else if (typeof define == 'function') {
|
||||||
|
/*
|
||||||
|
* Require.js is available
|
||||||
|
*/
|
||||||
|
define(['../../xterm'], fit);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Plain browser environment
|
||||||
|
*/
|
||||||
|
fit(window.Terminal);
|
||||||
|
}
|
||||||
|
})(function (Xterm) {
|
||||||
|
var exports = {};
|
||||||
|
|
||||||
|
exports.proposeGeometry = function (term) {
|
||||||
|
if (!term.element.parentElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var parentElementStyle = window.getComputedStyle(term.element.parentElement),
|
||||||
|
parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')),
|
||||||
|
parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17),
|
||||||
|
elementStyle = window.getComputedStyle(term.element),
|
||||||
|
elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')),
|
||||||
|
elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')),
|
||||||
|
availableHeight = parentElementHeight - elementPaddingVer,
|
||||||
|
availableWidth = parentElementWidth - elementPaddingHor,
|
||||||
|
container = term.rowContainer,
|
||||||
|
subjectRow = term.rowContainer.firstElementChild,
|
||||||
|
contentBuffer = subjectRow.innerHTML,
|
||||||
|
characterHeight,
|
||||||
|
rows,
|
||||||
|
characterWidth,
|
||||||
|
cols,
|
||||||
|
geometry;
|
||||||
|
|
||||||
|
subjectRow.style.display = 'inline';
|
||||||
|
subjectRow.innerHTML = 'W'; // Common character for measuring width, although on monospace
|
||||||
|
characterWidth = subjectRow.getBoundingClientRect().width;
|
||||||
|
subjectRow.style.display = ''; // Revert style before calculating height, since they differ.
|
||||||
|
characterHeight = subjectRow.getBoundingClientRect().height;
|
||||||
|
subjectRow.innerHTML = contentBuffer;
|
||||||
|
|
||||||
|
rows = parseInt(availableHeight / characterHeight);
|
||||||
|
cols = parseInt(availableWidth / characterWidth);
|
||||||
|
|
||||||
|
geometry = {cols: cols, rows: rows};
|
||||||
|
return geometry;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.fit = function (term) {
|
||||||
|
var geometry = exports.proposeGeometry(term);
|
||||||
|
|
||||||
|
if (geometry) {
|
||||||
|
term.resize(geometry.cols, geometry.rows);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Xterm.prototype.proposeGeometry = function () {
|
||||||
|
return exports.proposeGeometry(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
Xterm.prototype.fit = function () {
|
||||||
|
return exports.fit(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
return exports;
|
||||||
|
});
|
||||||
8201
public/src/js/socket.io.js
Normal file
8201
public/src/js/socket.io.js
Normal file
File diff suppressed because it is too large
Load diff
1
public/src/js/socket.io.js.map
Normal file
1
public/src/js/socket.io.js.map
Normal file
File diff suppressed because one or more lines are too long
4219
public/src/js/xterm.js
Normal file
4219
public/src/js/xterm.js
Normal file
File diff suppressed because it is too large
Load diff
1
public/src/js/xterm.js.map
Normal file
1
public/src/js/xterm.js.map
Normal file
File diff suppressed because one or more lines are too long
1
public/src/webssh2.concat.map
Normal file
1
public/src/webssh2.concat.map
Normal file
File diff suppressed because one or more lines are too long
1
public/src/webssh2.min.map
Normal file
1
public/src/webssh2.min.map
Normal file
File diff suppressed because one or more lines are too long
2374
public/webssh2.css
Normal file
2374
public/webssh2.css
Normal file
File diff suppressed because it is too large
Load diff
12633
public/webssh2.js
Normal file
12633
public/webssh2.js
Normal file
File diff suppressed because it is too large
Load diff
4
public/webssh2.min.js
vendored
Normal file
4
public/webssh2.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
146
socket/index.js
146
socket/index.js
|
|
@ -1,12 +1,142 @@
|
||||||
var debug = require('debug')('WebSSH2')
|
// private
|
||||||
var myError
|
var debug = require('debug')
|
||||||
|
var debugWebSSH2 = require('debug')('WebSSH2')
|
||||||
|
var SSH = require('ssh2').Client
|
||||||
|
var termCols, termRows
|
||||||
|
|
||||||
module.exports = function (socket, io) {
|
// public
|
||||||
this.SSHerror = function (myFunc, err) {
|
module.exports = function (socket) {
|
||||||
myError = (myError) || ((err) ? err.message : undefined)
|
function SSHerror (myFunc, err) {
|
||||||
var thisError = (myError) ? ': ' + myError : ''
|
socket.request.session.error = (socket.request.session.error) || ((err) ? err.message : undefined)
|
||||||
debug('SSH ' + myFunc + thisError)
|
var theError = (socket.request.session.error) ? ': ' + socket.request.session.error : ''
|
||||||
socket.emit('ssherror', 'SSH ' + myFunc + thisError)
|
// log unsuccessful login attempt
|
||||||
|
if (err && (err.level === 'client-authentication')) {
|
||||||
|
console.log('webssh2 ' + 'error: Authentication failure'.red.bold +
|
||||||
|
' user=' + socket.request.session.username.yellow.bold.underline +
|
||||||
|
' from=' + socket.handshake.address.yellow.bold.underline)
|
||||||
|
}
|
||||||
|
switch (myFunc) {
|
||||||
|
case 'STREAM CLOSE':
|
||||||
|
debugWebSSH2('SSH ' + myFunc + theError.red)
|
||||||
|
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||||
|
socket.disconnect(true)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
debugWebSSH2('SSHerror: default'.red)
|
||||||
|
debugWebSSH2('SSH ' + myFunc + theError)
|
||||||
|
socket.emit('ssherror', 'SSH ' + myFunc + theError)
|
||||||
|
socket.disconnect(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if websocket connection arrives without an express session, kill it
|
||||||
|
if (!socket.request.session) {
|
||||||
|
socket.emit('401 UNAUTHORIZED')
|
||||||
|
debugWebSSH2('SOCKET: No Express Session / REJECTED')
|
||||||
|
socket.disconnect(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var conn = new SSH()
|
||||||
|
socket.on('geometry', function (cols, rows) {
|
||||||
|
termCols = cols
|
||||||
|
termRows = rows
|
||||||
|
})
|
||||||
|
console.log('webssh2 ' + 'IO ON:'.cyan.bold + ' user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||||
|
conn.on('banner', function (d) {
|
||||||
|
// need to convert to cr/lf for proper formatting
|
||||||
|
d = d.replace(/\r?\n/g, '\r\n')
|
||||||
|
socket.emit('data', d.toString('binary'))
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('ready', function () {
|
||||||
|
console.log('webssh2 Login: user=' + socket.request.session.username + ' from=' + socket.handshake.address + ' host=' + socket.request.session.ssh.host + ' port=' + socket.request.session.ssh.port + ' sessionID=' + socket.request.sessionID + '/' + socket.id + ' allowreplay=' + socket.request.session.ssh.allowreplay + ' term=' + socket.request.session.ssh.term)
|
||||||
|
socket.emit('title', 'ssh://' + socket.request.session.ssh.host)
|
||||||
|
socket.request.session.ssh.header.background && socket.emit('headerBackground', socket.request.session.ssh.header.background)
|
||||||
|
socket.request.session.ssh.header.name && socket.emit('header', socket.request.session.ssh.header.name)
|
||||||
|
socket.emit('footer', 'ssh://' + socket.request.session.username + '@' + socket.request.session.ssh.host + ':' + socket.request.session.ssh.port)
|
||||||
|
socket.emit('status', 'SSH CONNECTION ESTABLISHED')
|
||||||
|
socket.emit('statusBackground', 'green')
|
||||||
|
socket.emit('allowreplay', socket.request.session.ssh.allowreplay)
|
||||||
|
conn.shell({
|
||||||
|
term: socket.request.session.ssh.term,
|
||||||
|
cols: termCols,
|
||||||
|
rows: termRows
|
||||||
|
}, function (err, stream) {
|
||||||
|
if (err) {
|
||||||
|
SSHerror('EXEC ERROR' + err)
|
||||||
|
conn.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// poc to log commands from client
|
||||||
|
// var dataBuffer
|
||||||
|
socket.on('data', function (data) {
|
||||||
|
stream.write(data)
|
||||||
|
// poc to log commands from client
|
||||||
|
// if (data === '\r') {
|
||||||
|
// console.log(socket.request.session.id + '/' + socket.id + ' command: ' + socket.request.session.ssh.host + ': ' + dataBuffer)
|
||||||
|
// dataBuffer = undefined
|
||||||
|
// } else {
|
||||||
|
// dataBuffer = (dataBuffer) ? dataBuffer + data : data
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
socket.on('control', function (controlData) {
|
||||||
|
switch (controlData) {
|
||||||
|
case 'replayCredentials':
|
||||||
|
stream.write(socket.request.session.userpassword + '\n')
|
||||||
|
/* falls through */
|
||||||
|
default:
|
||||||
|
console.log('controlData: ' + controlData)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('disconnecting', function (reason) { debugWebSSH2('SOCKET DISCONNECTING: ' + reason) })
|
||||||
|
|
||||||
|
socket.on('disconnect', function (reason) {
|
||||||
|
debugWebSSH2('SOCKET DISCONNECT: ' + reason)
|
||||||
|
err = { message: reason }
|
||||||
|
SSHerror('CLIENT SOCKET DISCONNECT', err)
|
||||||
|
conn.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('error', function (error) { debugWebSSH2('SOCKET ERROR: ' + JSON.stringify(error)) })
|
||||||
|
|
||||||
|
stream.on('data', function (d) { socket.emit('data', d.toString('utf-8')) })
|
||||||
|
|
||||||
|
stream.on('close', function (code, signal) {
|
||||||
|
err = { message: ((code || signal) ? (((code) ? 'CODE: ' + code : '') + ((code && signal) ? ' ' : '') + ((signal) ? 'SIGNAL: ' + signal : '')) : undefined) }
|
||||||
|
SSHerror('STREAM CLOSE', err)
|
||||||
|
conn.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
stream.stderr.on('data', function (data) {
|
||||||
|
console.log('STDERR: ' + data)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.on('end', function (err) { SSHerror('CONN END BY HOST', err) })
|
||||||
|
conn.on('close', function (err) { SSHerror('CONN CLOSE', err) })
|
||||||
|
conn.on('error', function (err) { SSHerror('CONN ERROR', err) })
|
||||||
|
|
||||||
|
conn.on('keyboard-interactive', function (name, instructions, instructionsLang, prompts, finish) {
|
||||||
|
debugWebSSH2('conn.on(\'keyboard-interactive\')')
|
||||||
|
finish([socket.request.session.userpassword])
|
||||||
|
})
|
||||||
|
if (socket.request.session.username && socket.request.session.userpassword) {
|
||||||
|
conn.connect({
|
||||||
|
host: socket.request.session.ssh.host,
|
||||||
|
port: socket.request.session.ssh.port,
|
||||||
|
username: socket.request.session.username,
|
||||||
|
password: socket.request.session.userpassword,
|
||||||
|
tryKeyboard: true,
|
||||||
|
// some cisco routers need the these cipher strings
|
||||||
|
algorithms: socket.request.session.ssh.algorithms,
|
||||||
|
debug: debug('ssh2')
|
||||||
|
})
|
||||||
|
} 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)
|
socket.disconnect(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
124
src/css/style.css
Normal file
124
src/css/style.css
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
body {
|
||||||
|
font-family: helvetica, sans-serif, arial;
|
||||||
|
font-size: 1em;
|
||||||
|
color: #111;
|
||||||
|
background-color: rgb(0, 0, 0);
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(0, 128, 0);
|
||||||
|
width: 100%;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none none solid none;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: center;
|
||||||
|
flex: 0 1 auto;
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#terminal-container {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#terminal-container .terminal {
|
||||||
|
background-color: #000000;
|
||||||
|
color: #fafafa;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#terminal-container .terminal:focus .terminal-cursor {
|
||||||
|
background-color: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottomdiv {
|
||||||
|
width: 100%;
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
border-color: white;
|
||||||
|
border-style: solid none none none;
|
||||||
|
border-width: 1px;
|
||||||
|
z-index: 99;
|
||||||
|
flex: 0 1 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#footer {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
#status {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(50, 50, 50);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none solid;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
#credentials {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(51, 51, 51);
|
||||||
|
background-color: rgb(255, 127, 0);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none none;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
a.credentials {
|
||||||
|
color: rgb(51, 51, 51);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#downloadLog {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(255, 127, 0);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none none;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
a.downloadLog {
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
#toggleLog {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
background-color: rgb(0, 127, 0);
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-color: white;
|
||||||
|
border-style: none solid none none;
|
||||||
|
border-width: 1px;
|
||||||
|
text-align: left;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
a.toggleLog {
|
||||||
|
color: rgb(240, 240, 240);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
125
src/js/client.js
Normal file
125
src/js/client.js
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
/* global io, Terminal, Blob */
|
||||||
|
var sessionLogEnable = false
|
||||||
|
var sessionLog, sessionFooter, logDate, currentDate, myFile, errorExists
|
||||||
|
|
||||||
|
// replay password to server, requires
|
||||||
|
function replayCredentials () { // eslint-disable-line
|
||||||
|
socket.emit('control', 'replayCredentials')
|
||||||
|
console.log('replaying credentials')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set variable to toggle log data from client/server to a varialble
|
||||||
|
// for later download
|
||||||
|
function toggleLog () { // eslint-disable-line
|
||||||
|
if (sessionLogEnable === true) {
|
||||||
|
sessionLogEnable = false
|
||||||
|
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0);" onclick="toggleLog();">Start Log</a>'
|
||||||
|
console.log('stopping log, ' + sessionLogEnable)
|
||||||
|
currentDate = new Date()
|
||||||
|
sessionLog = sessionLog + '\r\n\r\nLog End for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n'
|
||||||
|
logDate = currentDate
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
sessionLogEnable = true
|
||||||
|
document.getElementById('toggleLog').innerHTML = '<a class="toggleLog" href="javascript:void(0)" onclick="toggleLog();">Logging - STOP LOG</a>'
|
||||||
|
document.getElementById('downloadLog').style.display = 'inline'
|
||||||
|
console.log('starting log, ' + sessionLogEnable)
|
||||||
|
currentDate = new Date()
|
||||||
|
sessionLog = 'Log Start for ' + sessionFooter + ': ' + currentDate.getFullYear() + '/' + (currentDate.getMonth() + 1) + '/' + currentDate.getDate() + ' @ ' + currentDate.getHours() + ':' + currentDate.getMinutes() + ':' + currentDate.getSeconds() + '\r\n\r\n'
|
||||||
|
logDate = currentDate
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cross browser method to "download" an element to the local system
|
||||||
|
// used for our client-side logging feature
|
||||||
|
function downloadLog () { // eslint-disable-line
|
||||||
|
myFile = 'WebSSH2-' + logDate.getFullYear() + (logDate.getMonth() + 1) + logDate.getDate() + '_' + logDate.getHours() + logDate.getMinutes() + logDate.getSeconds() + '.log'
|
||||||
|
// regex should eliminate escape sequences from being logged.
|
||||||
|
var blob = new Blob([sessionLog.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, '')], {
|
||||||
|
type: 'text/plain'
|
||||||
|
})
|
||||||
|
if (window.navigator.msSaveOrOpenBlob) {
|
||||||
|
window.navigator.msSaveBlob(blob, myFile)
|
||||||
|
} else {
|
||||||
|
var elem = window.document.createElement('a')
|
||||||
|
elem.href = window.URL.createObjectURL(blob)
|
||||||
|
elem.download = myFile
|
||||||
|
document.body.appendChild(elem)
|
||||||
|
elem.click()
|
||||||
|
document.body.removeChild(elem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('downloadLog').style.display = 'none'
|
||||||
|
document.getElementById('credentials').style.display = 'none'
|
||||||
|
|
||||||
|
var terminalContainer = document.getElementById('terminal-container')
|
||||||
|
var term = new Terminal({
|
||||||
|
cursorBlink: true
|
||||||
|
})
|
||||||
|
var socket, termid // eslint-disable-line
|
||||||
|
term.open(terminalContainer, {
|
||||||
|
focus: true
|
||||||
|
})
|
||||||
|
term.fit()
|
||||||
|
|
||||||
|
if (document.location.pathname) {
|
||||||
|
var parts = document.location.pathname.split('/')
|
||||||
|
var base = parts.slice(0, parts.length - 1).join('/') + '/'
|
||||||
|
var resource = base.substring(1) + 'socket.io'
|
||||||
|
socket = io.connect(null, {
|
||||||
|
resource: resource
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
socket = io.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('connect', function () {
|
||||||
|
socket.emit('geometry', term.cols, term.rows)
|
||||||
|
term.on('data', function (data) {
|
||||||
|
socket.emit('data', data)
|
||||||
|
})
|
||||||
|
socket.on('title', function (data) {
|
||||||
|
document.title = data
|
||||||
|
}).on('status', function (data) {
|
||||||
|
document.getElementById('status').innerHTML = data
|
||||||
|
}).on('ssherror', function (data) {
|
||||||
|
document.getElementById('status').innerHTML = data
|
||||||
|
document.getElementById('status').style.backgroundColor = 'red'
|
||||||
|
errorExists = true
|
||||||
|
}).on('headerBackground', function (data) {
|
||||||
|
document.getElementById('header').style.backgroundColor = data
|
||||||
|
}).on('header', function (data) {
|
||||||
|
document.getElementById('header').innerHTML = data
|
||||||
|
}).on('footer', function (data) {
|
||||||
|
sessionFooter = data
|
||||||
|
document.getElementById('footer').innerHTML = data
|
||||||
|
}).on('statusBackground', function (data) {
|
||||||
|
document.getElementById('status').style.backgroundColor = data
|
||||||
|
}).on('allowreplay', function (data) {
|
||||||
|
if (data === true) {
|
||||||
|
console.log('allowreplay: ' + data)
|
||||||
|
document.getElementById('credentials').style.display = 'inline'
|
||||||
|
} else {
|
||||||
|
document.getElementById('credentials').style.display = 'none'
|
||||||
|
}
|
||||||
|
}).on('data', function (data) {
|
||||||
|
term.write(data)
|
||||||
|
if (sessionLogEnable) {
|
||||||
|
sessionLog = sessionLog + data
|
||||||
|
}
|
||||||
|
}).on('disconnect', function (err) {
|
||||||
|
if (!errorExists) {
|
||||||
|
document.getElementById('status').style.backgroundColor = 'red'
|
||||||
|
document.getElementById('status').innerHTML = 'WEBSOCKET SERVER DISCONNECTED: ' + err
|
||||||
|
}
|
||||||
|
socket.io.reconnection(false)
|
||||||
|
}).on('error', function (err) {
|
||||||
|
if (!errorExists) {
|
||||||
|
document.getElementById('status').style.backgroundColor = 'red'
|
||||||
|
document.getElementById('status').innerHTML = 'ERROR: ' + err
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
@ -1,10 +1,13 @@
|
||||||
|
// private
|
||||||
|
require('colors') // allow for color property extensions in log messages
|
||||||
var debug = require('debug')('WebSSH2')
|
var debug = require('debug')('WebSSH2')
|
||||||
var colors = require('colors')
|
|
||||||
var Auth = require('basic-auth')
|
var Auth = require('basic-auth')
|
||||||
|
var util = require('util')
|
||||||
|
|
||||||
console.warn = makeColorConsole(console.warn, 'yellow')
|
console.warn = makeColorConsole(console.warn, 'yellow')
|
||||||
console.error = makeColorConsole(console.error, 'red')
|
console.error = makeColorConsole(console.error, 'red')
|
||||||
|
|
||||||
|
// public
|
||||||
function makeColorConsole (fct, color) {
|
function makeColorConsole (fct, color) {
|
||||||
return function () {
|
return function () {
|
||||||
for (var i in arguments) {
|
for (var i in arguments) {
|
||||||
|
|
@ -19,7 +22,7 @@ exports.basicAuth = function (req, res, next) {
|
||||||
if (myAuth) {
|
if (myAuth) {
|
||||||
req.session.username = myAuth.name
|
req.session.username = myAuth.name
|
||||||
req.session.userpassword = myAuth.pass
|
req.session.userpassword = myAuth.pass
|
||||||
debug('myAuth.name: ' + myAuth.name + ' and password ' + ((myAuth.pass) ? 'exists' : 'is blank'.underline.red))
|
debug('myAuth.name: ' + myAuth.name.yellow.bold.underline + ' and password ' + ((myAuth.pass) ? 'exists'.yellow.bold.underline : 'is blank'.underline.red.bold))
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
res.statusCode = 401
|
res.statusCode = 401
|
||||||
|
|
@ -28,3 +31,10 @@ exports.basicAuth = function (req, res, next) {
|
||||||
res.end('Username and password required for web SSH service.')
|
res.end('Username and password required for web SSH service.')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// expects headers to be a JSON object, will replace authroization header with 'Sanatized//Exists'
|
||||||
|
// we don't want to log basic auth header since it contains a password...
|
||||||
|
exports.SanatizeHeaders = function (headers) {
|
||||||
|
if (headers.authorization) { headers.authorization = 'Sanitized//Exists' }
|
||||||
|
return (headers)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue