Compare commits
171 commits
main
...
bigip-serv
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e3d6ec8a15 | ||
![]() |
0c5de9fb97 | ||
![]() |
049b8fb1f6 | ||
![]() |
358d56e49a | ||
![]() |
8c710e76f2 | ||
![]() |
6b1930da03 | ||
![]() |
a6e74bf29a | ||
![]() |
796145186b | ||
![]() |
056e87b40d | ||
![]() |
b4b74297ea | ||
![]() |
5a77fc2082 | ||
![]() |
829b5cdd75 | ||
![]() |
b511ce5eae | ||
![]() |
a176167505 | ||
![]() |
3315df1b0b | ||
![]() |
2f4083ff4f | ||
![]() |
c48fadba08 | ||
![]() |
4baff571de | ||
![]() |
1477a2b9d0 | ||
![]() |
a1b2e56760 | ||
![]() |
8acc405232 | ||
![]() |
6ec049059b | ||
![]() |
e77b7ac506 | ||
![]() |
bc2d018764 | ||
![]() |
43d041a8ad | ||
![]() |
13baf95d1e | ||
![]() |
a51b457014 | ||
![]() |
c7dfad08bc | ||
![]() |
8fa1631196 | ||
![]() |
44ba13cf67 | ||
![]() |
1d813f6e6c | ||
![]() |
402b678b37 | ||
![]() |
03d286a56a | ||
![]() |
9f4419e21a | ||
![]() |
e13006441a | ||
![]() |
536c88a638 | ||
![]() |
2932d049af | ||
![]() |
3aeedb2ec8 | ||
![]() |
8c55c834ee | ||
![]() |
4185df77f6 | ||
![]() |
eb8f6203bb | ||
![]() |
54a7ef1af7 | ||
![]() |
52a989bbb2 | ||
![]() |
6ff58c55f5 | ||
![]() |
8d515f6a77 | ||
![]() |
f336cc0df6 | ||
![]() |
f8be9e1704 | ||
![]() |
27d9bfb97e | ||
![]() |
3133ad8e97 | ||
![]() |
f24693b672 | ||
![]() |
34afe70dd5 | ||
![]() |
0d1aaef4fb | ||
![]() |
63a7ab8061 | ||
![]() |
2f5a2275d6 | ||
![]() |
fdb846bd7d | ||
![]() |
0f3c7ab230 | ||
![]() |
1c4bfc2680 | ||
![]() |
d96b299e7d | ||
![]() |
12e5431a34 | ||
![]() |
06063a8009 | ||
![]() |
60eae6a165 | ||
![]() |
66e0eeccde | ||
![]() |
ca0c33004f | ||
![]() |
83fb54d98f | ||
![]() |
c19dbf019d | ||
![]() |
87d5f5ee3f | ||
![]() |
eb551d1c4a | ||
![]() |
1a5ebc649d | ||
![]() |
0899cb0efa | ||
![]() |
2bd225e12e | ||
![]() |
01ddac958e | ||
![]() |
6f694a96a7 | ||
![]() |
2c3a89b5dc | ||
![]() |
47910dd066 | ||
![]() |
17bc82db85 | ||
![]() |
cd56b3c51f | ||
![]() |
4196ef3554 | ||
![]() |
5a65b6e91d | ||
![]() |
13fd49e008 | ||
![]() |
66ce643dd9 | ||
![]() |
e30c0c1c9b | ||
![]() |
76b2f787fd | ||
![]() |
edceab346d | ||
![]() |
d387cb796d | ||
![]() |
da21c89f20 | ||
![]() |
dbcfc30cd0 | ||
![]() |
e06fabc2a7 | ||
![]() |
46338e4f2c | ||
![]() |
01e312d701 | ||
![]() |
a7384a8126 | ||
![]() |
b469cb63c7 | ||
![]() |
5cf06dd46e | ||
![]() |
e3ec6f08eb | ||
![]() |
3e45c98c62 | ||
![]() |
a530f59704 | ||
![]() |
96ee27fe2b | ||
![]() |
324e209df5 | ||
![]() |
e1fc4fee1b | ||
![]() |
cfe9b5cb3d | ||
![]() |
60d2528437 | ||
![]() |
252e4f16d6 | ||
![]() |
c9591d637d | ||
![]() |
9cfccb109a | ||
![]() |
9b94627cbd | ||
![]() |
7017aef56b | ||
![]() |
589f12b72c | ||
![]() |
b5ee677ac6 | ||
![]() |
ad627e6596 | ||
![]() |
b4cbfb4b46 | ||
![]() |
aab1a35bc9 | ||
![]() |
28f329e315 | ||
![]() |
82c0da0ff7 | ||
![]() |
72d747763c | ||
![]() |
5c8c1a2333 | ||
![]() |
303f53dc43 | ||
![]() |
40f6bf232b | ||
![]() |
418af1bcfa | ||
![]() |
e2ea06866e | ||
![]() |
e3f97ad6b6 | ||
![]() |
ea017016b7 | ||
![]() |
f14f0bccf5 | ||
![]() |
8671180f18 | ||
![]() |
fc102380cb | ||
![]() |
b9ca79e7cf | ||
![]() |
266f9876d3 | ||
![]() |
cc8b014af8 | ||
![]() |
a0affca261 | ||
![]() |
aec8be86b4 | ||
![]() |
1fc35f74da | ||
![]() |
aa633aef0b | ||
![]() |
40715023b2 | ||
![]() |
d7b73f95d2 | ||
![]() |
c573b9b989 | ||
![]() |
a7bc5e2da1 | ||
![]() |
66c75643d7 | ||
![]() |
617ce151c0 | ||
![]() |
650f4eb8f0 | ||
![]() |
20c3915832 | ||
![]() |
bb0b6cca58 | ||
![]() |
f9e6fd8351 | ||
![]() |
39dfdcb5ae | ||
![]() |
8686215ad1 | ||
![]() |
a1f4c7b985 | ||
![]() |
beee8e63e8 | ||
![]() |
afe462b180 | ||
![]() |
fe7248e056 | ||
![]() |
e39fb885fd | ||
![]() |
8fcf4b7b75 | ||
![]() |
2d19f49091 | ||
![]() |
7d80e10604 | ||
![]() |
32af90bc3f | ||
![]() |
27e79df8b1 | ||
![]() |
c887a64f83 | ||
![]() |
4360f546ee | ||
![]() |
d9931334de | ||
![]() |
1ecf19c5df | ||
![]() |
50f1769fd5 | ||
![]() |
ff978703da | ||
![]() |
bf50fca786 | ||
![]() |
b6e5089ee6 | ||
![]() |
25f52b3f1e | ||
![]() |
3ecda672ba | ||
![]() |
ea12cc8b7e | ||
![]() |
cfa097bd0e | ||
![]() |
170047517b | ||
![]() |
cd64cc0637 | ||
![]() |
533f719cca | ||
![]() |
b8782c565a | ||
![]() |
0dda8d56d9 | ||
![]() |
24c94aed98 | ||
![]() |
7223f2cd8f |
|
@ -1,2 +0,0 @@
|
|||
--exclude-exts=.min.css
|
||||
--ignore=adjoining-classes,box-model,ids,order-alphabetical,unqualified-attributes
|
|
@ -1,37 +1,39 @@
|
|||
{
|
||||
"name": "Node.js & TypeScript",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:jammy",
|
||||
"name": "Debian Bullseye Node.js DevContainer",
|
||||
"image": "mcr.microsoft.com/vscode/devcontainers/base:bullseye",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "6.9.1"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
|
||||
},
|
||||
// mount the ssh public identity file for the this project
|
||||
// I limit to just what I need and not the whole ~/.ssh folder
|
||||
"mounts": [
|
||||
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh/personal_id_rsa.pub,target=/home/vscode/.hostssh/id_rsa.pub,readonly,type=bind,consistency=cached"
|
||||
],
|
||||
|
||||
"mounts": [
|
||||
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.ssh/personal_id_rsa.pub,target=/home/vscode/.hostssh/id_rsa.pub,readonly,type=bind,consistency=cached"
|
||||
],
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/node-asdf:0": {},
|
||||
},
|
||||
// Configure tool-specific properties.
|
||||
"customizations": {
|
||||
// Configure properties specific to VS Code.
|
||||
"vscode": {
|
||||
// Add the IDs of extensions you want installed when the container is created.
|
||||
"extensions": [
|
||||
"ms-vscode-remote.remote-containers",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"GitHub.copilot",
|
||||
"GitHub.copilot-chat",
|
||||
"esbenp.prettier-vscode",
|
||||
"rvest.vs-code-prettier-eslint",
|
||||
"bierner.markdown-mermaid",
|
||||
"stylelint.vscode-stylelint"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "/bin/bash ./.devcontainer/scripts/tools.sh >> ~/post-create-tools.log",
|
||||
|
||||
// Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"bierner.markdown-mermaid",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"GitHub.copilot-chat",
|
||||
"GitHub.copilot",
|
||||
"mohsen1.prettify-json",
|
||||
"mechatroner.rainbow-csv",
|
||||
"ms-vscode-remote.remote-containers",
|
||||
"oderwat.indent-rainbow",
|
||||
"rvest.vs-code-prettier-eslint",
|
||||
"stylelint.vscode-stylelint",
|
||||
"vivaxy.vscode-conventional-commits"
|
||||
]
|
||||
},
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash"
|
||||
}
|
||||
},
|
||||
"postCreateCommand": "/bin/bash ./.devcontainer/scripts/tools.sh >> ~/post-create-tools.log",
|
||||
"remoteUser": "vscode"
|
||||
}
|
||||
|
|
|
@ -9,4 +9,35 @@ Host github.com
|
|||
IdentityFile ~/.hostssh/id_rsa.pub
|
||||
EOF
|
||||
|
||||
sudo chown -R vscode:vscode ~/.ssh
|
||||
sudo chown -R vscode:vscode ~/.ssh && \
|
||||
sudo chmod 600 ~/.ssh/config && \
|
||||
sudo chmod 600 ~/.ssh/known_hosts
|
||||
|
||||
git config --global --add safe.directory ${PWD}
|
||||
|
||||
# Get the signing key from git config
|
||||
signing_key=$(git config --get user.signingkey)
|
||||
if [ -z "$signing_key" ]; then
|
||||
echo "No signing key found in git config."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the user email from git config
|
||||
user_email=$(git config --get user.email)
|
||||
if [ -z "$user_email" ]; then
|
||||
echo "No user email found in git config."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create the ~/.ssh directory if it doesn't exist
|
||||
mkdir -p ~/.ssh
|
||||
|
||||
# Write the signing key and email to the allowed_signers file
|
||||
echo "$user_email $signing_key" > ~/.ssh/allowed_signers
|
||||
|
||||
# Set the correct permissions for the allowed_signers file
|
||||
chmod 644 ~/.ssh/allowed_signers
|
||||
|
||||
echo "allowed_signers file created successfully."
|
||||
|
||||
npm install
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
.git
|
||||
.cache
|
||||
app/node_modules
|
||||
app/client/src
|
||||
app/scripts
|
||||
app/.*
|
||||
app/*.sample
|
||||
app/client/tsconfig.json
|
||||
app/CHANGELOG.md
|
1
.eslintignore
Normal file
|
@ -0,0 +1 @@
|
|||
**/*{.,-}min.js
|
29
.eslintrc.yaml
Normal file
|
@ -0,0 +1,29 @@
|
|||
extends:
|
||||
- airbnb-base
|
||||
- prettier
|
||||
- plugin:node/recommended
|
||||
- plugin:jest/recommended
|
||||
|
||||
plugins:
|
||||
- prettier
|
||||
- jest
|
||||
|
||||
env:
|
||||
jest/globals: true
|
||||
|
||||
rules:
|
||||
prettier/prettier: error
|
||||
no-unused-vars: warn
|
||||
no-console: off
|
||||
func-names: off
|
||||
no-process-exit: off
|
||||
object-shorthand: off
|
||||
class-methods-use-this: off
|
||||
semi: [2, never]
|
||||
strict: off
|
||||
|
||||
overrides:
|
||||
- files:
|
||||
- "**/*.test.js"
|
||||
env:
|
||||
jest: true
|
75
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -1,75 +0,0 @@
|
|||
name: Bug Report
|
||||
description: File a bug report
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
assignees:
|
||||
- billchurch
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Depending on the type of issue, please include the follwing information:
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Also tell us, what did you expect to happen?
|
||||
placeholder: Tell us what you see!
|
||||
value: "A bug happened!"
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: node_ver
|
||||
attributes:
|
||||
label: Node Version
|
||||
description: version of Node this problem occurs on
|
||||
placeholder: npm -v
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: npm_ver
|
||||
attributes:
|
||||
label: NPM Version
|
||||
description: version of NPM this problem occurs on
|
||||
placeholder: npm -v
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: server_ver
|
||||
attributes:
|
||||
label: Server OS Version
|
||||
description: Server OS Version / Distribution / Processor Architecture
|
||||
placeholder: uname -a;cat /etc/os-release
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: webssh2_ver
|
||||
attributes:
|
||||
label: WebSSH2 release version
|
||||
description: Version of WebSSH you are using
|
||||
placeholder: grep version app/package.json
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: sshhost_ver
|
||||
attributes:
|
||||
label: OS and Version of SSH server
|
||||
description: OS and Version of SSH server connecting to
|
||||
placeholder: 'on target server run: uname -a;sshd -v'
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
id: browser_ver
|
||||
attributes:
|
||||
label: Browser Version
|
||||
description: Information from brwoser's About... or a screenshot of the about screen.
|
||||
placeholder:
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||
render: shell
|
||||
|
10
.github/ISSUE_TEMPLATE/custom.md
vendored
|
@ -1,10 +0,0 @@
|
|||
---
|
||||
name: Question
|
||||
about: General how-to questions
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
@ -1,20 +0,0 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
65
.github/workflows/action-test.yml
vendored
|
@ -1,65 +0,0 @@
|
|||
name: Manually Release Previous Tag
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Repo Branch/Tag'
|
||||
default: 'main'
|
||||
type: 'string'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.tag }}
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
DOCKER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${GITHUB_REPOSITORY#*/}
|
||||
|
||||
VERSION=${{ inputs.tag }}
|
||||
VERSION="${VERSION//v}"
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}"
|
||||
|
||||
# If the VERSION looks like a version number, assume that
|
||||
# this is the most recent version of the image and also
|
||||
# tag it 'latest'.
|
||||
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
TAGS="$TAGS,${DOCKER_IMAGE}"
|
||||
fi
|
||||
|
||||
|
||||
# Set output parameters.
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/ppc64le
|
||||
push: true
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
|
@ -66,4 +66,4 @@ jobs:
|
|||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/ppc64le
|
||||
push: true
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
67
.github/workflows/docker-multiplatform.yml
vendored
|
@ -1,67 +0,0 @@
|
|||
---
|
||||
name: 'Build Docker Images'
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout'
|
||||
uses: actions/checkout@v3
|
||||
- name: Prepare
|
||||
id: prep
|
||||
run: |
|
||||
DOCKER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${GITHUB_REPOSITORY#*/}
|
||||
|
||||
# If this is git tag, use the tag name as a docker tag
|
||||
if [[ $GITHUB_REF == refs/tags/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/tags/webssh2-v}
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
||||
fi
|
||||
|
||||
# If this is git branch, use the branch name as a docker tag
|
||||
if [[ $GITHUB_REF == refs/heads/* ]]; then
|
||||
VERSION=${GITHUB_REF#refs/heads/}
|
||||
TAGS="${DOCKER_IMAGE}:${VERSION}"
|
||||
fi
|
||||
|
||||
# If the VERSION looks like a version number, assume that
|
||||
# this is the most recent version of the image and also
|
||||
# tag it 'latest'. This is done by just specifying the ${DOCKER_IMAGE}
|
||||
# without a tag.
|
||||
if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
|
||||
TAGS="$TAGS,${DOCKER_IMAGE}"
|
||||
fi
|
||||
|
||||
# Set output parameters.
|
||||
echo ::set-output name=tags::${TAGS}
|
||||
echo ::set-output name=docker_image::${DOCKER_IMAGE}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64,linux/arm64,linux/ppc64le
|
||||
push: true
|
||||
tags: ${{ steps.prep.outputs.tags }}
|
47
.github/workflows/release.yml
vendored
|
@ -1,47 +0,0 @@
|
|||
---
|
||||
name: 'Create Release'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.devcontainer/**'
|
||||
- '.**'
|
||||
- '**.md'
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
paths_released: ${{ steps.manifest_release.outputs.paths_released }}
|
||||
steps:
|
||||
- uses: google-github-actions/release-please-action@v3
|
||||
id: manifest_release
|
||||
with:
|
||||
token: ${{ secrets.RELEASE_PLEASE_UAT }}
|
||||
command: manifest
|
||||
package-name: webssh2
|
||||
path: app
|
||||
default-branch: main
|
||||
release-type: node
|
||||
publish:
|
||||
runs-on: ubuntu-20.04
|
||||
needs: release
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
path: ${{fromJson(needs.release.outputs.paths_released)}}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
- name: publish-to-npm
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
run: |
|
||||
cd ${{ matrix.path }}
|
||||
npm install
|
||||
npx lerna bootstrap
|
||||
npx lerna publish from-package --no-push --no-private --yes
|
2
.gitignore
vendored
|
@ -49,4 +49,4 @@ jspm_packages
|
|||
.DS_Store
|
||||
|
||||
Build/Release
|
||||
app/bob_rsa
|
||||
app/todo.md
|
||||
|
|
5
.prettierrc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"semi": false,
|
||||
"singleQuote": false,
|
||||
"trailingComma": "none"
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"app": "0.5.0-pre-4"
|
||||
}
|
1
.tool-versions
Normal file
|
@ -0,0 +1 @@
|
|||
nodejs 23.2.0
|
|
@ -1,6 +0,0 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- 14
|
||||
- 16
|
||||
before_install:
|
||||
- npm i -g snyk
|
39
BUILD.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
## Issues with building
|
||||
Working with node 6.9.1 has uncovered some challenges from time to time. Some of these are related to node 6.9.1 and others may not be, however these are some of the issues and fixes I've come across:
|
||||
|
||||
### npm ERR! Error: EPERM: operation not permitted, utime
|
||||
This error with `npm` is related to be related to file permission issues, specifically the `EPERM` (Error: Operation not permitted) error. This is typically caused by the `npm` process trying to modify or access files in a way that the user running the process doesn't have permission to do.
|
||||
|
||||
Here are some steps you can try to resolve this issue:
|
||||
|
||||
1. **Ensure Correct File Permissions**:
|
||||
- Check the ownership and permissions of the directory `/workspaces/webssh2/node_modules/.staging/esquery-7b94f06a/dist` and its parent directories.
|
||||
- You can try running:
|
||||
```bash
|
||||
sudo chown -R $(whoami) /workspaces/webssh2/node_modules
|
||||
```
|
||||
- Alternatively, ensure that the `vscode` user has the necessary permissions to access and modify these files.
|
||||
|
||||
2. **Run npm install as the correct user**:
|
||||
- Make sure you're not accidentally running `npm` as a root or another user within the Docker container. If you're running it as the `vscode` user, ensure that the `vscode` user has the appropriate permissions in the `/workspaces/webssh2` directory.
|
||||
|
||||
3. **Clear npm cache**:
|
||||
- Sometimes this error can be caused by a corrupt npm cache. You can try clearing the cache:
|
||||
```bash
|
||||
npm cache clean --force
|
||||
```
|
||||
- After clearing the cache, try running `npm install` again.
|
||||
|
||||
4. **Check for Staging Issues**:
|
||||
- The error is occurring in a `.staging` directory, which is used by `npm` during the installation process. If the `.staging` directory is corrupted or incomplete, it can cause issues. You can remove the `node_modules` directory and try reinstalling:
|
||||
```bash
|
||||
rm -rf node_modules
|
||||
npm install
|
||||
```
|
||||
|
||||
5. **Try Running as Root (Not Recommended for Production)**:
|
||||
- As a last resort, you can try running `npm install` as root within the container. However, this is not recommended due to potential security risks:
|
||||
```bash
|
||||
sudo npm install
|
||||
```
|
13
BUILDING.md
|
@ -1,13 +0,0 @@
|
|||
# Buliding
|
||||
|
||||
To rebuild the client files, you need at least Node v14.
|
||||
|
||||
The source of the client files are located in `./app/client/src`
|
||||
|
||||
`npm run build` will compile the source files there into `./app/client/public/`. This directory is considered to be volitile and is deleted every time `npm run build` is invoked.
|
||||
|
||||
WebPack is used for building and the configuration is located in `./app/scripts`
|
||||
|
||||
If one wishes to make changes to the javascript, the html, or the css it should be done in `./app/client/src` and then complied using `npm run build`
|
||||
|
||||
For development purposes, you may also utilize `npm run builddev` which will not minimize the source and allow you to more easily troubleshoot while making customizations.
|
|
@ -1,46 +0,0 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at wmchurch@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
185
CONFIG.md
Normal file
|
@ -0,0 +1,185 @@
|
|||
# Breaking Changes in Configuration Format
|
||||
|
||||
This document outlines the breaking changes and updates to the configuration format between versions. These changes require manual updates to your existing `config.json` files.
|
||||
|
||||
## Major Structure Changes
|
||||
|
||||
### Removed Sections
|
||||
The following sections have been completely removed:
|
||||
- `socketio` - Socket.IO configuration is now handled internally
|
||||
- `terminal` - Terminal configuration moved to client-side
|
||||
- `serverlog` - Logging configuration simplified
|
||||
- `algorithms` - Moved under the `ssh` section
|
||||
- `accesslog` - Removed
|
||||
- `verify` - Removed
|
||||
- `safeShutdownDuration` - Removed
|
||||
|
||||
### Renamed and Restructured Sections
|
||||
|
||||
#### HTTP Configuration
|
||||
- Old: `socketio.origins`
|
||||
- New: `http.origins`
|
||||
```diff
|
||||
- "socketio": {
|
||||
- "serveClient": false,
|
||||
- "path": "/ssh/socket.io",
|
||||
- "origins": ["localhost:2222"]
|
||||
- }
|
||||
+ "http": {
|
||||
+ "origins": ["*.*"]
|
||||
+ }
|
||||
```
|
||||
|
||||
#### SSH Algorithms
|
||||
- Old: Root-level `algorithms` object
|
||||
- New: Moved to `ssh.algorithms`
|
||||
```diff
|
||||
- "algorithms": {
|
||||
+ "ssh": {
|
||||
+ "algorithms": {
|
||||
"kex": [...],
|
||||
"cipher": [...],
|
||||
"hmac": [...],
|
||||
"compress": [...]
|
||||
+ "serverHostKey": [...]
|
||||
}
|
||||
+ }
|
||||
```
|
||||
|
||||
#### Session Configuration
|
||||
```diff
|
||||
"session": {
|
||||
- "name": "WebSSH2",
|
||||
+ "name": "webssh2",
|
||||
"secret": "secret"
|
||||
}
|
||||
```
|
||||
|
||||
### New Options
|
||||
|
||||
#### SSH Configuration
|
||||
Added under the `ssh` section:
|
||||
```json
|
||||
{
|
||||
"ssh": {
|
||||
"alwaysSendKeyboardInteractivePrompts": false,
|
||||
"disableInteractiveAuth": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Feature Options
|
||||
Renamed and expanded options:
|
||||
```diff
|
||||
"options": {
|
||||
"challengeButton": true,
|
||||
- "allowreauth": false
|
||||
+ "autoLog": false,
|
||||
+ "allowReauth": true,
|
||||
+ "allowReconnect": true,
|
||||
+ "allowReplay": true
|
||||
}
|
||||
```
|
||||
|
||||
## Detailed Changes
|
||||
|
||||
### 1. Authentication Options
|
||||
- Added support for SSH private key authentication via `user.privateKey` and passphrase encrypted private keys via `user.passphrase`
|
||||
- Removed `user.overridebasic` option
|
||||
- Added keyboard-interactive authentication controls
|
||||
|
||||
### 2. Server Settings
|
||||
- Default port changed from 2224 to 2222
|
||||
- Socket.IO path is now fixed at "/ssh/socket.io"
|
||||
- Added server host key algorithm configurations
|
||||
|
||||
### 3. Terminal Configuration
|
||||
All terminal-specific configurations have been removed from server config:
|
||||
```diff
|
||||
- "terminal": {
|
||||
- "cursorBlink": true,
|
||||
- "scrollback": 10000,
|
||||
- "tabStopWidth": 8,
|
||||
- "bellStyle": "sound",
|
||||
- "fontSize": 14
|
||||
- }
|
||||
```
|
||||
These settings are now managed client-side.
|
||||
|
||||
## Migration Guide
|
||||
|
||||
1. Create a new `config.json` file based on the new format
|
||||
2. Move your existing settings to their new locations
|
||||
3. Remove any deprecated options
|
||||
4. Add new required options
|
||||
5. Test your configuration before deploying to production
|
||||
|
||||
## Default Configuration Example
|
||||
|
||||
```json
|
||||
{
|
||||
"listen": {
|
||||
"ip": "0.0.0.0",
|
||||
"port": 2222
|
||||
},
|
||||
"http": {
|
||||
"origins": ["*.*"]
|
||||
},
|
||||
"user": {
|
||||
"name": null,
|
||||
"password": null,
|
||||
"privateKey": null,
|
||||
"passphrase": null
|
||||
},
|
||||
"ssh": {
|
||||
"host": null,
|
||||
"port": 22,
|
||||
"term": "xterm-color",
|
||||
"readyTimeout": 20000,
|
||||
"keepaliveInterval": 120000,
|
||||
"keepaliveCountMax": 10,
|
||||
"algorithms": {
|
||||
"cipher": [
|
||||
"aes128-ctr",
|
||||
"aes192-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-gcm",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes256-gcm",
|
||||
"aes256-gcm@openssh.com",
|
||||
"aes256-cbc"
|
||||
],
|
||||
"compress": [
|
||||
"none",
|
||||
"zlib@openssh.com",
|
||||
"zlib"
|
||||
],
|
||||
"hmac": [
|
||||
"hmac-sha2-256",
|
||||
"hmac-sha2-512",
|
||||
"hmac-sha1"
|
||||
],
|
||||
"kex": [
|
||||
"ecdh-sha2-nistp256",
|
||||
"ecdh-sha2-nistp384",
|
||||
"ecdh-sha2-nistp521",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
"diffie-hellman-group14-sha1"
|
||||
],
|
||||
"serverHostKey": [
|
||||
"ecdsa-sha2-nistp256",
|
||||
"ecdsa-sha2-nistp384",
|
||||
"ecdsa-sha2-nistp521",
|
||||
"ssh-rsa"
|
||||
]
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"challengeButton": true,
|
||||
"autoLog": false,
|
||||
"allowReauth": true,
|
||||
"allowReconnect": true,
|
||||
"allowReplay": true
|
||||
}
|
||||
}
|
||||
```
|
|
@ -1,94 +0,0 @@
|
|||
Guidelines for contributing code:
|
||||
|
||||
Make sure code passes linting from (StandardJS)[https://standardjs.com/]
|
||||
|
||||
|
||||
# Contributing
|
||||
|
||||
When contributing to this repository, please first discuss the change you wish to make via issue,
|
||||
email, or any other method with the owners of this repository before making a change.
|
||||
|
||||
Please note we have a code of conduct, please follow it in all your interactions with the project.
|
||||
|
||||
## Pull Request Process
|
||||
|
||||
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
||||
build.
|
||||
2. Make sure code passes linting from (StandardJS)[https://standardjs.com/]
|
||||
3. Explain what you're trying to accomplish in your commits
|
||||
4. Update changelog and Readme if needed.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
### Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of experience,
|
||||
nationality, personal appearance, race, religion, or sexual identity and
|
||||
orientation.
|
||||
|
||||
### Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
### Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
### Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
### Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at [INSERT EMAIL ADDRESS]. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
### Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
401
ChangeLog-Old.md
Normal file
|
@ -0,0 +1,401 @@
|
|||
<a name="0.2.17"></a>
|
||||
## 0.2.17 (2024-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct handling of sshTerm query parameters ([b9ca79e](https://github.com/billchurch/WebSSH2/commit/b9ca79e))
|
||||
* enable `autoConnect` only on `/ssh/host/` ([c9591d6](https://github.com/billchurch/WebSSH2/commit/c9591d6))
|
||||
* handle http basic auth in `/ssh/host/` route ([1fc35f7](https://github.com/billchurch/WebSSH2/commit/1fc35f7))
|
||||
* honor `ssh.term` settings as default when url param `sshTerm` is undefined ([303f53d](https://github.com/billchurch/WebSSH2/commit/303f53d))
|
||||
* sanitize object no longer mutates original object ([ea01701](https://github.com/billchurch/WebSSH2/commit/ea01701))
|
||||
* Serve the static files from the webssh2_client module with a custom prefix '/ssh/assets' instead of just '/ssh'. ([8fcf4b7](https://github.com/billchurch/WebSSH2/commit/8fcf4b7))
|
||||
* vareiable scoping for conn and stream would prevent multiple user sessions ([650f4eb](https://github.com/billchurch/WebSSH2/commit/650f4eb))
|
||||
* version comment in client.html ([ea12cc8](https://github.com/billchurch/WebSSH2/commit/ea12cc8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `Switch User` or `reauth` feature for Basic Auth sessions ([3e45c98](https://github.com/billchurch/WebSSH2/commit/3e45c98))
|
||||
* `Switch User` or `reauth` feature for Basic Auth sessions ([a530f59](https://github.com/billchurch/WebSSH2/commit/a530f59))
|
||||
* add allowReconnect, allowReauth, and autoLog features, normalize debug logs ([e2ea068](https://github.com/billchurch/WebSSH2/commit/e2ea068))
|
||||
* Add session-based authentication for SSH connections using HTTP Basic auth and express.js ([afe462b](https://github.com/billchurch/WebSSH2/commit/afe462b))
|
||||
* Add SSH routes and connection handler ([2d19f49](https://github.com/billchurch/WebSSH2/commit/2d19f49))
|
||||
* get HTTP session secret from `WEBSSH_SESSION_SECRET` env if available. ([17bc82d](https://github.com/billchurch/WebSSH2/commit/17bc82d))
|
||||
* HTTP Basic Authentication and auto-connection with /ssh/host/<hostIP> ([a0affca](https://github.com/billchurch/WebSSH2/commit/a0affca))
|
||||
* Inject SSH host and port into webssh2 configuration ([e39fb88](https://github.com/billchurch/WebSSH2/commit/e39fb88))
|
||||
* routes.js validate input from url parameters ([72d7477](https://github.com/billchurch/WebSSH2/commit/72d7477))
|
||||
* Update connectionHandler.js and routes.js to propmpt for basic credentials when accessing `/ssh/host/<address>` and pre-populate credentials and host info AND auto-connect to server. ([fe7248e](https://github.com/billchurch/WebSSH2/commit/fe7248e))
|
||||
* update webssh2_client 0.2.20 ([9b94627](https://github.com/billchurch/WebSSH2/commit/9b94627))
|
||||
* update webssh2_client 0.2.21 ([9cfccb1](https://github.com/billchurch/WebSSH2/commit/9cfccb1))
|
||||
* update webssh2_client to 0.2.19 ([418af1b](https://github.com/billchurch/WebSSH2/commit/418af1b))
|
||||
* update webssh2_client@0.2.23 ([e06fabc](https://github.com/billchurch/WebSSH2/commit/e06fabc))
|
||||
* validate handleResize ([b4cbfb4](https://github.com/billchurch/WebSSH2/commit/b4cbfb4))
|
||||
* validate handleTerminal ([aab1a35](https://github.com/billchurch/WebSSH2/commit/aab1a35))
|
||||
* validateSshTerm checks if term is undefined or null before validation ([28f329e](https://github.com/billchurch/WebSSH2/commit/28f329e))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.12"></a>
|
||||
## 0.2.12 (2024-07-10)
|
||||
|
||||
BIG-IP Specific version
|
||||
|
||||
### Changes
|
||||
|
||||
- `[ctrl]+[shift]+[6]` or `[ctrl]+[^]` now sends `RS` or `0x1E`
|
||||
|
||||
## [0.2.11] 2
|
||||
|
||||
|
||||
<a name="0.2.11"></a>
|
||||
## 0.2.11 (2021-05-12)
|
||||
BIG-IP Specific version
|
||||
|
||||
### BREAKING
|
||||
|
||||
- Not compatible with versions of ephemeral_auth before 0.4.8 due to child resources moving under /ssh
|
||||
|
||||
### Changes
|
||||
|
||||
- in `config.json.sample` - `allowreauth` set to `false` by default
|
||||
- in `config.json.sample` - potential future proofing for CORS support `http.origins`
|
||||
- `ssh` module updated to 0.8.9
|
||||
- Move all child resources to start from under /ssh
|
||||
- /socket.io -> /ssh/socket.io
|
||||
- /webssh2.css -> /ssh/webssh2.css
|
||||
- /webssh2.bundle.js -> /ssh/webssh2.bundle.js
|
||||
- /reauth -> /ssh/reauth
|
||||
- perhaps more
|
||||
|
||||
|
||||
|
||||
<a name="0.2.9"></a>
|
||||
## 0.2.9 (2019-06-13)
|
||||
|
||||
### Changes
|
||||
|
||||
- Missing require('fs') in `server/app.js` See issue [#135](../../issues/135)
|
||||
- Patched read-config to mitigate vulnerability in js-yaml
|
||||
- issue not exploitable on webssh2 implementation
|
||||
- patched anyway
|
||||
- sending my patch upstream to read-config, webssh2 package.json points to patched version in my repository https://github.com/billchurch/nodejs-read-config
|
||||
- See https://github.com/nodeca/js-yaml/issues/475 for more detail
|
||||
|
||||
|
||||
|
||||
<a name="0.2.8"></a>
|
||||
## 0.2.8 (2019-05-26)
|
||||
|
||||
### Changes
|
||||
|
||||
- Fixes issue if no password is entered, browser must be closed and restart to attempt to re-auth. See issue [#118](../../issues/118). Thanks @smilesm2 for the idea.
|
||||
- fixes broken `npm run (build|builddev)`
|
||||
- update font-awesome fonts to 5.6.3
|
||||
- update webpack and dependancies
|
||||
- update xterm to 3.8.0
|
||||
|
||||
### Fixes
|
||||
|
||||
- ILX
|
||||
|
||||
|
||||
<a name="0.2.7"></a>
|
||||
## 0.2.7 (2018-11-11)
|
||||
|
||||
### Changes
|
||||
|
||||
- `config.reauth` was not respected if initial auth presented was incorrect, regardless of `reauth` setting in `config.json` reauth would always be attempted. fixes [#117](../../issues/117)
|
||||
- **BREAKING** moved app files to /app, this may be a breaking change
|
||||
- Updated dockerfile for new app path
|
||||
- Updated app dependancies
|
||||
- xterm v3.8.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.8.0
|
||||
- basic-auth v2.0.1
|
||||
- https://github.com/jshttp/basic-auth/releases/tag/v2.0.1
|
||||
- express v4.16.4
|
||||
- https://github.com/expressjs/express/releases/tag/4.16.4
|
||||
- validator v10.9.0
|
||||
- https://github.com/chriso/validator.js/releases/tag/10.9.0
|
||||
- Updated dev dependancies
|
||||
- snazzy v8.0.0
|
||||
- standard v12.0.1
|
||||
- uglifyjs-webpack-plugin v2.0.1
|
||||
- ajv v6.5.5
|
||||
- copy-webpack-plugin v4.6.0
|
||||
- css-loader v1.0.1
|
||||
- nodemon v1.18.6
|
||||
- postcss-discard-comments v4.0.1
|
||||
- snyk v1.108.2
|
||||
- url-loader v1.1.2
|
||||
- webpack v4.25.1
|
||||
- webpack-cli v3.1.2
|
||||
|
||||
<a name="0.2.6"></a>
|
||||
## 0.2.6 (2018-11-07)
|
||||
|
||||
### Changes
|
||||
|
||||
- Reauth didn't work if intial auth presented was incorrect, (see issue #112) fixed thanks @vvalchev
|
||||
- Update node version supported to >=6 (PR #115) thanks @perlun
|
||||
- Update packages
|
||||
- developer dependencies
|
||||
|
||||
|
||||
|
||||
<a name="0.2.5"></a>
|
||||
## 0.2.5 (2018-09-11)
|
||||
|
||||
### Added
|
||||
|
||||
- Reauth function thanks to @vbeskrovny and @vvalchev (9bbc116)
|
||||
- Controlled by `config.json` option `options.allowreauth` true presents reauth dialog and false hides dialog
|
||||
|
||||
### Changed
|
||||
|
||||
- `options.challengeButton` enabled
|
||||
- previously this configuration option did nothing, this now enables the Credentials button site-wide regardless of the `allowreplay` header value
|
||||
- Updated debug module to v4
|
||||
|
||||
|
||||
<a name="0.2.4"></a>
|
||||
## 0.2.4 (2018-07-18)
|
||||
|
||||
### Added
|
||||
|
||||
- Browser title window now changes with xterm escape sequences (see http://tldp.org/HOWTO/Xterm-Title-3.html)
|
||||
- Added bellStyle options
|
||||
- `GET var`: **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
|
||||
- `config.json`: **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
|
||||
- `workspace` folder on GITHUB for BIG-IP specific fixes/changes
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 3.1.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.1.0
|
||||
- Default listen IP in `config.json` changed back to 127.0.0.1
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- ESC]0; is now removed from log files when using the browser-side logging feature
|
||||
|
||||
* **package:** update ssh2 to version 0.6.1 ([bf15b3e](https://github.com/billchurch/WebSSH2/commit/bf15b3e)), closes [#55](https://github.com/billchurch/WebSSH2/issues/55)
|
||||
* **package:** update validator to version 10.1.0 ([1a15fa5](https://github.com/billchurch/WebSSH2/commit/1a15fa5)), closes [#62](https://github.com/billchurch/WebSSH2/issues/62)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
# 0.2.0 (2018-02-10)
|
||||
|
||||
Mostly client (browser) related changes in this release
|
||||
|
||||
### Added
|
||||
|
||||
- Menu system
|
||||
- Fontawesome icons
|
||||
- Resizing browser window sends resize events to terminal container as well as SSH session (pty)
|
||||
- New terminal options (config.json as well as GET vars)
|
||||
- terminal.cursorBlink - boolean - Cursor blinks (true), does not (false) Default: true.
|
||||
- terminal.scrollback - integer - Lines in the scrollback buffer. Default: 10000.
|
||||
- terminal.tabStopWidth - integer - Tab stops at n characters Default: 8.
|
||||
- New serverside (nodejs) terminal configuration options (cursorBlink, scrollback, tabStopWidth)
|
||||
- Logging of MRH session (unassigned if not present)
|
||||
- Express compression feature
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.1
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.0
|
||||
- Moved javascript events out of html into javascript
|
||||
- Changed asset packaging from grunt to Webpack to be inline with xterm.js direction
|
||||
- Moved logging and credentials buttons to menu system
|
||||
- Removed non-minified options (if you need to disable minification, modify webpack scripts and 'npm run build')
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resolved loss of terminal foucs when interacting with option buttons (Logging, etc...)
|
||||
|
||||
|
||||
<a name="0.1.4"></a>
|
||||
## 0.1.4 (2018-01-30)
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved socket and util out of folders into .js in root.
|
||||
- added keepaliveInterval and keepaliveCountMax config options
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package.json to reduce vulnerabilities ([196d769](https://github.com/billchurch/WebSSH2/commit/196d769))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.3"></a>
|
||||
## 0.1.3 (2017-09-28)
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade to debug@3.1 to eliminate ReDoS in %o formatter
|
||||
- Upgrade Express to 4.15.5 for ReDOS
|
||||
- Upgrade basic-auth to v2.0
|
||||
|
||||
<a name="0.1.2"></a>
|
||||
## 0.1.2 (2017-08-21)
|
||||
|
||||
### Added
|
||||
|
||||
- ssh.readyTimeout option in config.json (time in ms, default 20000, 20sec)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 2.9.2 from 2.6.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.2
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.7.0
|
||||
- Updated ssh2 to 0.5.5 to keep current, no fixes impacting WebSSH2
|
||||
- ssh-streams to 0.1.19 from 0.1.16
|
||||
- Updated validator.js to 8.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/chriso/validator.js/releases/tag/8.0.0
|
||||
- Updated Express to 4.15.4, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/express/releases/tag/4.15.4
|
||||
- Updated Express-session to 1.15.5, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/session/releases/tag/v1.15.5
|
||||
- Updated Debug to 3.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/visionmedia/debug/releases/tag/3.0.0
|
||||
- Running in strict mode ('use strict';)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package.json to reduce vulnerabilities ([e65a964](https://github.com/billchurch/WebSSH2/commit/e65a964))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.1"></a>
|
||||
## 0.1.1 (2017-06-03)
|
||||
|
||||
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
|
||||
- morgan express middleware for logging
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated socket.io to 1.7.4
|
||||
- continued refactoring, breaking up `index.js`
|
||||
- revised error handling methods
|
||||
- revised session termination methods
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
|
||||
- color console decorations from `util/index.js`
|
||||
- SanatizeHeaders function from `util/index.js`
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# 0.1.0 (2017-05-27)
|
||||
|
||||
### Added
|
||||
|
||||
- This ChangeLog.md file
|
||||
- Support for UTF-8 characters (thanks @bara666)
|
||||
- Snyk, Bithound, Travis CI
|
||||
- Cross platform improvements (path mappings)
|
||||
- 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=WebSSH2` will output additional debug messages for functions
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
|
||||
|
||||
### Removed
|
||||
|
||||
- Express Static References directly to module source directories due to concatenating and minifying js/css
|
||||
|
||||
|
||||
|
||||
<a name="0.0.5"></a>
|
||||
## 0.0.5 (2017-03-23)
|
||||
|
||||
### Added
|
||||
|
||||
- Added experimental support for logging (see Readme)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Terminal geometry now properly fills the browser screen and communicates this to the ssh session. Tested with IE 11 and recent versions of Chrome/Safari/Firefox.
|
||||
|
||||
|
||||
|
||||
<a name="0.0.4"></a>
|
||||
## 0.0.4 (2017-03-23)
|
||||
### Added
|
||||
|
||||
- Set default terminal to xterm-color
|
||||
- Mouse event support
|
||||
- New config option, config.ssh.term to set terminal
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Xterm.js 2.4.0
|
||||
- Minor code formatting cleanup
|
||||
|
||||
|
||||
<a name="0.0.3"></a>
|
||||
## 0.0.3 (2017-02-16)
|
||||
### Changed
|
||||
|
||||
- Update xterm to latest (2.3.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed misspelled config.ssh.port property
|
||||
|
||||
|
||||
<a name="0.0.2"></a>
|
||||
## 0.0.2 (2017-02-01)
|
||||
### Changed
|
||||
|
||||
- Moving terminal emulation to xterm.js
|
||||
- updating module version dependencies
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with banners not being displayed properly from UNIX hosts when only lf is used
|
||||
|
||||
|
||||
<a name="0.0.1"></a>
|
||||
## 0.0.1 (2016-07-28)
|
||||
### Added
|
||||
|
||||
- Initial proof of concept and release. For historical purposes only.
|
583
ChangeLog.md
|
@ -1,3 +1,582 @@
|
|||
# Changelog Moved
|
||||
# Change Log
|
||||
|
||||
See [app/CHANGELOG.md](app/CHANGELOG.md)
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
<a name="0.2.24"></a>
|
||||
## [0.2.24](https://github.com/billchurch/WebSSH2/compare/v0.2.23...v0.2.24) (2024-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* config.json.sample had `disableInteractiveAuth` set to `true`, changed to `false` ([0c5de9f](https://github.com/billchurch/WebSSH2/commit/0c5de9f))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.23"></a>
|
||||
## [0.2.23](https://github.com/billchurch/WebSSH2/compare/v0.2.20...v0.2.23) (2024-12-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes document: config file moved from /usr/src to /usr/src/app [#372](https://github.com/billchurch/WebSSH2/issues/372) ([bc2d018](https://github.com/billchurch/WebSSH2/commit/bc2d018))
|
||||
* bug: support /ssh/host without a hostname [#373](https://github.com/billchurch/WebSSH2/issues/373) ([8c55c83](https://github.com/billchurch/WebSSH2/commit/8c55c83))
|
||||
* config change `privatekey` to `privateKey` for consistency with ssh2 module ([a176167](https://github.com/billchurch/WebSSH2/commit/a176167))
|
||||
* config move algorithims to ssh property ([52a989b](https://github.com/billchurch/WebSSH2/commit/52a989b))
|
||||
* pass full ssh error to browser ([27d9bfb](https://github.com/billchurch/WebSSH2/commit/27d9bfb))
|
||||
* username/password in config file no longer honored [#374](https://github.com/billchurch/WebSSH2/issues/374) ([4185df7](https://github.com/billchurch/WebSSH2/commit/4185df7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* accept private key from client [#381](https://github.com/billchurch/WebSSH2/issues/381) ([829b5cd](https://github.com/billchurch/WebSSH2/commit/829b5cd))
|
||||
* add `ssh.disableInteractiveAuth` feature in support of [#379](https://github.com/billchurch/WebSSH2/issues/379) ([c7dfad0](https://github.com/billchurch/WebSSH2/commit/c7dfad0))
|
||||
* allow passphrase encrypted ssh keys from client [#381](https://github.com/billchurch/WebSSH2/issues/381) ([056e87b](https://github.com/billchurch/WebSSH2/commit/056e87b))
|
||||
* Allow setting environment variables from the URL [#371](https://github.com/billchurch/WebSSH2/issues/371) ([6ec0490](https://github.com/billchurch/WebSSH2/commit/6ec0490))
|
||||
* implement ssh private key auth [#379](https://github.com/billchurch/WebSSH2/issues/379) ([402b678](https://github.com/billchurch/WebSSH2/commit/402b678))
|
||||
* passphrase encrypted private key authentication [#382](https://github.com/billchurch/WebSSH2/issues/382) ([7961451](https://github.com/billchurch/WebSSH2/commit/7961451))
|
||||
* support uploading of ssh-rsa private key from client for authentication [#381](https://github.com/billchurch/WebSSH2/issues/381) ([2f4083f](https://github.com/billchurch/WebSSH2/commit/2f4083f))
|
||||
* update jsmasker to v1.4.0 ([3315df1](https://github.com/billchurch/WebSSH2/commit/3315df1))
|
||||
* update webssh_client to 0.2.26 ([a1b2e56](https://github.com/billchurch/WebSSH2/commit/a1b2e56))
|
||||
* update webssh2_client to 0.2.27 ([b511ce5](https://github.com/billchurch/WebSSH2/commit/b511ce5))
|
||||
* webssh2_client to 0.2.28 ([b4b7429](https://github.com/billchurch/WebSSH2/commit/b4b7429))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.22"></a>
|
||||
## [0.2.22](https://github.com/billchurch/WebSSH2/compare/v0.2.21...v0.2.22) (2024-11-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fixes document: config file moved from /usr/src to /usr/src/app [#372](https://github.com/billchurch/WebSSH2/issues/372) ([bc2d018](https://github.com/billchurch/WebSSH2/commit/bc2d018))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Allow setting environment variables from the URL [#371](https://github.com/billchurch/WebSSH2/issues/371) ([6ec0490](https://github.com/billchurch/WebSSH2/commit/6ec0490))
|
||||
* update webssh_client to 0.2.26 ([a1b2e56](https://github.com/billchurch/WebSSH2/commit/a1b2e56))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.21"></a>
|
||||
## [0.2.21](https://github.com/billchurch/WebSSH2/compare/v0.2.20...v0.2.21) (2024-11-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bug: support /ssh/host without a hostname [#373](https://github.com/billchurch/WebSSH2/issues/373) ([8c55c83](https://github.com/billchurch/WebSSH2/commit/8c55c83))
|
||||
* config move algorithims to ssh property ([52a989b](https://github.com/billchurch/WebSSH2/commit/52a989b))
|
||||
* pass full ssh error to browser ([27d9bfb](https://github.com/billchurch/WebSSH2/commit/27d9bfb))
|
||||
* username/password in config file no longer honored [#374](https://github.com/billchurch/WebSSH2/issues/374) ([4185df7](https://github.com/billchurch/WebSSH2/commit/4185df7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add `ssh.disableInteractiveAuth` feature in support of [#379](https://github.com/billchurch/WebSSH2/issues/379) ([c7dfad0](https://github.com/billchurch/WebSSH2/commit/c7dfad0))
|
||||
* implement ssh private key auth [#379](https://github.com/billchurch/WebSSH2/issues/379) ([402b678](https://github.com/billchurch/WebSSH2/commit/402b678))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.20"></a>
|
||||
## [0.2.20](https://github.com/billchurch/WebSSH2/compare/v0.2.19...v0.2.20) (2024-08-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.19"></a>
|
||||
## [0.2.19](https://github.com/billchurch/WebSSH2/compare/v0.2.18...v0.2.19) (2024-08-22)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.18"></a>
|
||||
## [0.2.18](https://github.com/billchurch/WebSSH2/compare/v0.2.17...v0.2.18) (2024-08-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* express.js session secret configurable in docker with WEBSSH_SESSION_SECRET env variable ([12e5431](https://github.com/billchurch/WebSSH2/commit/12e5431))
|
||||
* ssh keyboard-interactive authentication support ([0f3c7ab](https://github.com/billchurch/WebSSH2/commit/0f3c7ab))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.17"></a>
|
||||
## 0.2.17 (2024-08-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* correct handling of sshTerm query parameters ([b9ca79e](https://github.com/billchurch/WebSSH2/commit/b9ca79e))
|
||||
* enable `autoConnect` only on `/ssh/host/` ([c9591d6](https://github.com/billchurch/WebSSH2/commit/c9591d6))
|
||||
* handle http basic auth in `/ssh/host/` route ([1fc35f7](https://github.com/billchurch/WebSSH2/commit/1fc35f7))
|
||||
* honor `ssh.term` settings as default when url param `sshTerm` is undefined ([303f53d](https://github.com/billchurch/WebSSH2/commit/303f53d))
|
||||
* sanitize object no longer mutates original object ([ea01701](https://github.com/billchurch/WebSSH2/commit/ea01701))
|
||||
* Serve the static files from the webssh2_client module with a custom prefix '/ssh/assets' instead of just '/ssh'. ([8fcf4b7](https://github.com/billchurch/WebSSH2/commit/8fcf4b7))
|
||||
* vareiable scoping for conn and stream would prevent multiple user sessions ([650f4eb](https://github.com/billchurch/WebSSH2/commit/650f4eb))
|
||||
* version comment in client.html ([ea12cc8](https://github.com/billchurch/WebSSH2/commit/ea12cc8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* `Switch User` or `reauth` feature for Basic Auth sessions ([3e45c98](https://github.com/billchurch/WebSSH2/commit/3e45c98))
|
||||
* `Switch User` or `reauth` feature for Basic Auth sessions ([a530f59](https://github.com/billchurch/WebSSH2/commit/a530f59))
|
||||
* add allowReconnect, allowReauth, and autoLog features, normalize debug logs ([e2ea068](https://github.com/billchurch/WebSSH2/commit/e2ea068))
|
||||
* Add session-based authentication for SSH connections using HTTP Basic auth and express.js ([afe462b](https://github.com/billchurch/WebSSH2/commit/afe462b))
|
||||
* Add SSH routes and connection handler ([2d19f49](https://github.com/billchurch/WebSSH2/commit/2d19f49))
|
||||
* get HTTP session secret from `WEBSSH_SESSION_SECRET` env if available. ([17bc82d](https://github.com/billchurch/WebSSH2/commit/17bc82d))
|
||||
* HTTP Basic Authentication and auto-connection with /ssh/host/<hostIP> ([a0affca](https://github.com/billchurch/WebSSH2/commit/a0affca))
|
||||
* Inject SSH host and port into webssh2 configuration ([e39fb88](https://github.com/billchurch/WebSSH2/commit/e39fb88))
|
||||
* routes.js validate input from url parameters ([72d7477](https://github.com/billchurch/WebSSH2/commit/72d7477))
|
||||
* Update connectionHandler.js and routes.js to propmpt for basic credentials when accessing `/ssh/host/<address>` and pre-populate credentials and host info AND auto-connect to server. ([fe7248e](https://github.com/billchurch/WebSSH2/commit/fe7248e))
|
||||
* update webssh2_client 0.2.20 ([9b94627](https://github.com/billchurch/WebSSH2/commit/9b94627))
|
||||
* update webssh2_client 0.2.21 ([9cfccb1](https://github.com/billchurch/WebSSH2/commit/9cfccb1))
|
||||
* update webssh2_client to 0.2.19 ([418af1b](https://github.com/billchurch/WebSSH2/commit/418af1b))
|
||||
* update webssh2_client@0.2.23 ([e06fabc](https://github.com/billchurch/WebSSH2/commit/e06fabc))
|
||||
* validate handleResize ([b4cbfb4](https://github.com/billchurch/WebSSH2/commit/b4cbfb4))
|
||||
* validate handleTerminal ([aab1a35](https://github.com/billchurch/WebSSH2/commit/aab1a35))
|
||||
* validateSshTerm checks if term is undefined or null before validation ([28f329e](https://github.com/billchurch/WebSSH2/commit/28f329e))
|
||||
|
||||
|
||||
|
||||
<a name="0.2.12"></a>
|
||||
## 0.2.12 (2024-07-10)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.11"></a>
|
||||
## 0.2.11 (2021-05-12)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.9"></a>
|
||||
## 0.2.9 (2019-06-13)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.8"></a>
|
||||
## 0.2.8 (2019-05-26)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.7"></a>
|
||||
## 0.2.7 (2018-11-11)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.6"></a>
|
||||
## 0.2.6 (2018-11-07)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.5"></a>
|
||||
## 0.2.5 (2018-09-11)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.4"></a>
|
||||
## 0.2.4 (2018-07-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **package:** update ssh2 to version 0.6.1 ([bf15b3e](https://github.com/billchurch/WebSSH2/commit/bf15b3e)), closes [#55](https://github.com/billchurch/WebSSH2/issues/55)
|
||||
* **package:** update validator to version 10.1.0 ([1a15fa5](https://github.com/billchurch/WebSSH2/commit/1a15fa5)), closes [#62](https://github.com/billchurch/WebSSH2/issues/62)
|
||||
|
||||
|
||||
|
||||
<a name="0.2.0"></a>
|
||||
# 0.2.0 (2018-02-10)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.4"></a>
|
||||
## 0.1.4 (2018-01-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package.json to reduce vulnerabilities ([196d769](https://github.com/billchurch/WebSSH2/commit/196d769))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.3"></a>
|
||||
## 0.1.3 (2017-09-28)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.2"></a>
|
||||
## 0.1.2 (2017-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* package.json to reduce vulnerabilities ([e65a964](https://github.com/billchurch/WebSSH2/commit/e65a964))
|
||||
|
||||
|
||||
|
||||
<a name="0.1.1"></a>
|
||||
## 0.1.1 (2017-06-03)
|
||||
|
||||
|
||||
|
||||
<a name="0.1.0"></a>
|
||||
# 0.1.0 (2017-05-27)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.5"></a>
|
||||
## 0.0.5 (2017-03-23)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.4"></a>
|
||||
## 0.0.4 (2017-03-23)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.3"></a>
|
||||
## 0.0.3 (2017-02-16)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.2"></a>
|
||||
## 0.0.2 (2017-02-01)
|
||||
|
||||
|
||||
|
||||
<a name="0.0.1"></a>
|
||||
## 0.0.1 (2016-07-28)
|
||||
|
||||
|
||||
|
||||
# Change Log
|
||||
|
||||
## [0.2.13] 2024-07-11
|
||||
|
||||
BIG-IP Specific version
|
||||
|
||||
### Fixes
|
||||
|
||||
- fixed missing reference to `read-config-ng` switchover which could prevent `config.json` from being read
|
||||
|
||||
## [0.2.12] 2024-07-10
|
||||
|
||||
BIG-IP Specific version
|
||||
|
||||
### Changes
|
||||
|
||||
- `[ctrl]+[shift]+[6]` or `[ctrl]+[^]` now sends `RS` or `0x1E`
|
||||
|
||||
## [0.2.11] 2020-05-12
|
||||
|
||||
BIG-IP Specific version
|
||||
|
||||
### BREAKING
|
||||
|
||||
- Not compatible with versions of ephemeral_auth before 0.4.8 due to child resources moving under /ssh
|
||||
|
||||
### Changes
|
||||
|
||||
- in `config.json.sample` - `allowreauth` set to `false` by default
|
||||
- in `config.json.sample` - potential future proofing for CORS support `http.origins`
|
||||
- `ssh` module updated to 0.8.9
|
||||
- Move all child resources to start from under /ssh
|
||||
- /socket.io -> /ssh/socket.io
|
||||
- /webssh2.css -> /ssh/webssh2.css
|
||||
- /webssh2.bundle.js -> /ssh/webssh2.bundle.js
|
||||
- /reauth -> /ssh/reauth
|
||||
- perhaps more
|
||||
|
||||
## [0.2.10] not actually released
|
||||
|
||||
## [0.2.9] 2019-06-13
|
||||
|
||||
### Changes
|
||||
|
||||
- Missing require('fs') in `server/app.js` See issue [#135](../../issues/135)
|
||||
- Patched read-config to mitigate vulnerability in js-yaml
|
||||
- issue not exploitable on webssh2 implementation
|
||||
- patched anyway
|
||||
- sending my patch upstream to read-config, webssh2 package.json points to patched version in my repository https://github.com/billchurch/nodejs-read-config
|
||||
- See https://github.com/nodeca/js-yaml/issues/475 for more detail
|
||||
|
||||
## [0.2.8] 2019-05-25
|
||||
|
||||
### Changes
|
||||
|
||||
- Fixes issue if no password is entered, browser must be closed and restart to attempt to re-auth. See issue [#118](../../issues/118). Thanks @smilesm2 for the idea.
|
||||
- fixes broken `npm run (build|builddev)`
|
||||
- update font-awesome fonts to 5.6.3
|
||||
- update webpack and dependancies
|
||||
- update xterm to 3.8.0
|
||||
|
||||
### Fixes
|
||||
|
||||
- ILX workspace may not always import properly due to symbolic links (specifically ./node_modules/.bin). This is removed from the ILX package
|
||||
|
||||
## [0.2.7] 2018-11-11
|
||||
|
||||
### Changes
|
||||
|
||||
- `config.reauth` was not respected if initial auth presented was incorrect, regardless of `reauth` setting in `config.json` reauth would always be attempted. fixes [#117](../../issues/117)
|
||||
- **BREAKING** moved app files to /app, this may be a breaking change
|
||||
- Updated dockerfile for new app path
|
||||
- Updated app dependancies
|
||||
- xterm v3.8.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.8.0
|
||||
- basic-auth v2.0.1
|
||||
- https://github.com/jshttp/basic-auth/releases/tag/v2.0.1
|
||||
- express v4.16.4
|
||||
- https://github.com/expressjs/express/releases/tag/4.16.4
|
||||
- validator v10.9.0
|
||||
- https://github.com/chriso/validator.js/releases/tag/10.9.0
|
||||
- Updated dev dependancies
|
||||
- snazzy v8.0.0
|
||||
- standard v12.0.1
|
||||
- uglifyjs-webpack-plugin v2.0.1
|
||||
- ajv v6.5.5
|
||||
- copy-webpack-plugin v4.6.0
|
||||
- css-loader v1.0.1
|
||||
- nodemon v1.18.6
|
||||
- postcss-discard-comments v4.0.1
|
||||
- snyk v1.108.2
|
||||
- url-loader v1.1.2
|
||||
- webpack v4.25.1
|
||||
- webpack-cli v3.1.2
|
||||
|
||||
## [0.2.6] 2018-11-09
|
||||
|
||||
### Changes
|
||||
|
||||
- Reauth didn't work if intial auth presented was incorrect, (see issue #112) fixed thanks @vvalchev
|
||||
- Update node version supported to >=6 (PR #115) thanks @perlun
|
||||
- Update packages
|
||||
- developer dependencies
|
||||
|
||||
## [0.2.5] 2018-09-11
|
||||
|
||||
### Added
|
||||
|
||||
- Reauth function thanks to @vbeskrovny and @vvalchev (9bbc116)
|
||||
- Controlled by `config.json` option `options.allowreauth` true presents reauth dialog and false hides dialog
|
||||
|
||||
### Changed
|
||||
|
||||
- `options.challengeButton` enabled
|
||||
- previously this configuration option did nothing, this now enables the Credentials button site-wide regardless of the `allowreplay` header value
|
||||
- Updated debug module to v4
|
||||
|
||||
## [0.2.4] 2018-07-18
|
||||
|
||||
### Added
|
||||
|
||||
- Browser title window now changes with xterm escape sequences (see http://tldp.org/HOWTO/Xterm-Title-3.html)
|
||||
- Added bellStyle options
|
||||
- `GET var`: **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
|
||||
- `config.json`: **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
|
||||
- `workspace` folder on GITHUB for BIG-IP specific fixes/changes
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 3.1.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.1.0
|
||||
- Default listen IP in `config.json` changed back to 127.0.0.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- ESC]0; is now removed from log files when using the browser-side logging feature
|
||||
|
||||
## [0.2.3] unreleased
|
||||
|
||||
### Fixed
|
||||
|
||||
- ESC]0; is now removed from log files when using the browser-side logging feature
|
||||
|
||||
## [0.2.0] 2018-02-10
|
||||
|
||||
Mostly client (browser) related changes in this release
|
||||
|
||||
### Added
|
||||
|
||||
- Menu system
|
||||
- Fontawesome icons
|
||||
- Resizing browser window sends resize events to terminal container as well as SSH session (pty)
|
||||
- New terminal options (config.json as well as GET vars)
|
||||
- terminal.cursorBlink - boolean - Cursor blinks (true), does not (false) Default: true.
|
||||
- terminal.scrollback - integer - Lines in the scrollback buffer. Default: 10000.
|
||||
- terminal.tabStopWidth - integer - Tab stops at n characters Default: 8.
|
||||
- New serverside (nodejs) terminal configuration options (cursorBlink, scrollback, tabStopWidth)
|
||||
- Logging of MRH session (unassigned if not present)
|
||||
- Express compression feature
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.1
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.0
|
||||
- Moved javascript events out of html into javascript
|
||||
- Changed asset packaging from grunt to Webpack to be inline with xterm.js direction
|
||||
- Moved logging and credentials buttons to menu system
|
||||
- Removed non-minified options (if you need to disable minification, modify webpack scripts and 'npm run build')
|
||||
|
||||
### Fixed
|
||||
|
||||
- Resolved loss of terminal foucs when interacting with option buttons (Logging, etc...)
|
||||
|
||||
# Change Log
|
||||
|
||||
## [0.1.4] 2018-01-30
|
||||
|
||||
### Changed
|
||||
|
||||
- Moved socket and util out of folders into .js in root.
|
||||
- added keepaliveInterval and keepaliveCountMax config options
|
||||
|
||||
## [0.1.3] 2017-09-28
|
||||
|
||||
### Changed
|
||||
|
||||
- Upgrade to debug@3.1 to eliminate ReDoS in %o formatter
|
||||
- Upgrade Express to 4.15.5 for ReDOS
|
||||
- Upgrade basic-auth to v2.0
|
||||
|
||||
## [0.1.2] 2017-07-31
|
||||
|
||||
### Added
|
||||
|
||||
- ssh.readyTimeout option in config.json (time in ms, default 20000, 20sec)
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated xterm.js to 2.9.2 from 2.6.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.2
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.7.0
|
||||
- Updated ssh2 to 0.5.5 to keep current, no fixes impacting WebSSH2
|
||||
- ssh-streams to 0.1.19 from 0.1.16
|
||||
- Updated validator.js to 8.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/chriso/validator.js/releases/tag/8.0.0
|
||||
- Updated Express to 4.15.4, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/express/releases/tag/4.15.4
|
||||
- Updated Express-session to 1.15.5, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/session/releases/tag/v1.15.5
|
||||
- Updated Debug to 3.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/visionmedia/debug/releases/tag/3.0.0
|
||||
- Running in strict mode ('use strict';)
|
||||
|
||||
## [0.1.1] 2017-06-03
|
||||
|
||||
### Added
|
||||
|
||||
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
|
||||
- morgan express middleware for logging
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated socket.io to 1.7.4
|
||||
- continued refactoring, breaking up `index.js`
|
||||
- revised error handling methods
|
||||
- revised session termination methods
|
||||
|
||||
### Fixed
|
||||
|
||||
### Removed
|
||||
|
||||
- color console decorations from `util/index.js`
|
||||
- SanatizeHeaders function from `util/index.js`
|
||||
|
||||
## [0.1.0] 2017-05-27
|
||||
|
||||
### Added
|
||||
|
||||
- This ChangeLog.md file
|
||||
- Support for UTF-8 characters (thanks @bara666)
|
||||
- Snyk, Bithound, Travis CI
|
||||
- Cross platform improvements (path mappings)
|
||||
- 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=WebSSH2` will output additional debug messages for functions
|
||||
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
|
||||
|
||||
- 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
|
||||
|
||||
- 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
|
||||
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
|
||||
|
||||
### Removed
|
||||
|
||||
- Express Static References directly to module source directories due to concatenating and minifying js/css
|
||||
|
||||
## [0.0.5] - 2017-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- Added experimental support for logging (see Readme)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Terminal geometry now properly fills the browser screen and communicates this to the ssh session. Tested with IE 11 and recent versions of Chrome/Safari/Firefox.
|
||||
|
||||
## [0.0.4] - 2017-03-23
|
||||
|
||||
### Added
|
||||
|
||||
- Set default terminal to xterm-color
|
||||
- Mouse event support
|
||||
- New config option, config.ssh.term to set terminal
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Xterm.js 2.4.0
|
||||
- Minor code formatting cleanup
|
||||
|
||||
## [0.0.3] - 2017-02-16
|
||||
|
||||
### Changed
|
||||
|
||||
- Update xterm to latest (2.3.0)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed misspelled config.ssh.port property
|
||||
|
||||
## [0.0.2] - 2017-02-01
|
||||
|
||||
### Changed
|
||||
|
||||
- Moving terminal emulation to xterm.js
|
||||
- updating module version dependencies
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issue with banners not being displayed properly from UNIX hosts when only lf is used
|
||||
|
||||
## [0.0.1] - 2016-06-28
|
||||
|
||||
### Added
|
||||
|
||||
- Initial proof of concept and release. For historical purposes only.
|
||||
|
|
65
DEPRECATED.md
Normal file
|
@ -0,0 +1,65 @@
|
|||
# Deprecated Features in WebSSH2
|
||||
|
||||
This document outlines features, configuration options, and parameters that have been deprecated in the this version of WebSSH2. Please review this information to ensure your setup remains compatible and to make necessary adjustments.
|
||||
|
||||
## Removed `config.json` Options
|
||||
|
||||
See [CONFIG.md](./CONFIG.md) for a list of removed or changed options.
|
||||
|
||||
### Logging Configuration
|
||||
|
||||
- `serverlog.client` (boolean): Enabled or disabled client logging.
|
||||
- `serverlog.server` (boolean): Enabled or disabled server logging.
|
||||
- `accesslog` (boolean): Controlled whether access logging was enabled.
|
||||
|
||||
### Other
|
||||
|
||||
- `verify` (boolean): This option was never implemented and has been removed.
|
||||
|
||||
## Removed GET Parameters
|
||||
|
||||
The following GET parameters have been **removed** from the application:
|
||||
|
||||
### Terminal Configuration Parameters
|
||||
|
||||
These have been replaced with client-side terminal configuration handling in the browser:
|
||||
|
||||
- `readyTimeout=`
|
||||
- `cursorBlink=`
|
||||
- `scrollback=`
|
||||
- `tabStopWidth=`
|
||||
- `bellStyle=`
|
||||
|
||||
### Header Parameters
|
||||
|
||||
- `allowReplay=` (boolean): Controlled the use of the password replay feature. This is now exclusively controlled from server-side `config.json`.
|
||||
- `mrhsession=` (string): Used to pass an APM session for event correlation. This unused option has been removed.
|
||||
|
||||
## Required Actions
|
||||
|
||||
1. **Review and Update Configuration Files:**
|
||||
- Remove references to deprecated options in your `config.json` file.
|
||||
- If you relied on any of the removed terminal configuration options, implement client-side configurations instead.
|
||||
|
||||
2. **Update Integrations:**
|
||||
- If your integrations or workflows use any of the removed GET parameters, update them to remove these references.
|
||||
|
||||
3. **Logging and Verification Adjustments:**
|
||||
- If you relied on `serverlog`, `accesslog`, or `verify` options, you may need to implement custom solutions for logging and verification.
|
||||
|
||||
4. **Client-Side Terminal Configuration:**
|
||||
- Implement client-side terminal configurations to replace the removed server-side options.
|
||||
|
||||
5. **Review Header Configurations:**
|
||||
- Update any configurations or integrations that relied on `allowReplay` or `mrhsession` GET parameters.
|
||||
|
||||
6. **Test Your Setup:**
|
||||
- After making these changes, thoroughly test your WebSSH2 setup to ensure everything works as expected with the new configuration.
|
||||
|
||||
## Additional Notes
|
||||
|
||||
- The removal of these options is part of our effort to simplify the codebase and improve performance.
|
||||
- If you encounter any issues after updating, please refer to the latest documentation or open an issue on our GitHub repository.
|
||||
- For the most up-to-date information on configuration options, always refer to the current README.md and configuration files in the repository.
|
||||
|
||||
I appreciate your understanding and cooperation as we continue to improve WebSSH2. If you have any questions or need assistance with these changes, please don't hesitate to reach out to the project maintainers.
|
161
DEVELOPMENT.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# WebSSH2 Development Guide
|
||||
|
||||
This guide explains how to set up and run the WebSSH2 client and server components for development.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js (>= 6.9.1)
|
||||
- npm
|
||||
- Git
|
||||
- Two terminal windows/sessions
|
||||
|
||||
## Project Setup
|
||||
|
||||
1. Create a development directory and clone both repositories:
|
||||
|
||||
```bash
|
||||
mkdir webssh2-dev
|
||||
cd webssh2-dev
|
||||
|
||||
# Clone the client repository
|
||||
git clone https://github.com/billchurch/webssh2_client.git
|
||||
|
||||
# Clone the server repository
|
||||
git clone https://github.com/billchurch/webssh2.git webssh2_server
|
||||
```
|
||||
|
||||
2. Install dependencies for both projects:
|
||||
|
||||
```bash
|
||||
# Install client dependencies
|
||||
cd webssh2_client
|
||||
npm install
|
||||
|
||||
# Install server dependencies
|
||||
cd ../webssh2_server
|
||||
npm install
|
||||
```
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Starting the Server Component
|
||||
|
||||
1. In your first terminal window, navigate to the server directory:
|
||||
|
||||
```bash
|
||||
cd webssh2_server
|
||||
```
|
||||
|
||||
2. Start the server in development mode:
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
This will:
|
||||
- Start the WebSSH2 server on port `2222`
|
||||
- Enable automatic reloading when server files change
|
||||
- Allow CORS for the development client
|
||||
|
||||
### Starting the Client Component
|
||||
|
||||
1. In your second terminal window, navigate to the client directory:
|
||||
|
||||
```bash
|
||||
cd webssh2_client
|
||||
```
|
||||
|
||||
2. Start the client in development mode:
|
||||
|
||||
```bash
|
||||
npm run watch
|
||||
```
|
||||
|
||||
This will:
|
||||
- Start a development server on port `3000`
|
||||
- Run `NODE_ENV=development npm-run-all --parallel start watch:build`
|
||||
- Watch for file changes and rebuild automatically
|
||||
- Inject development configuration into the client HTML
|
||||
|
||||
The development configuration is automatically injected through webpack.common.js when `NODE_ENV=development`:
|
||||
|
||||
```javascript
|
||||
webssh2Config: {
|
||||
socket: {
|
||||
url: 'http://localhost:2222',
|
||||
path: '/ssh/socket.io'
|
||||
},
|
||||
ssh: {
|
||||
port: 22
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Accessing the Development Environment
|
||||
|
||||
1. Open your web browser and navigate to:
|
||||
```
|
||||
http://localhost:3000
|
||||
```
|
||||
|
||||
2. The client will automatically connect to the development server at `localhost:2222`
|
||||
|
||||
## Development Architecture
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser as Browser<br/>(localhost:3000)
|
||||
participant Client as WebSSH2 Client<br/>(Port 3000)
|
||||
participant Server as WebSSH2 Server<br/>(Port 2222)
|
||||
participant SSH as SSH Server<br/>(Port 22)
|
||||
|
||||
Note over Browser,SSH: Development Data Flow
|
||||
|
||||
Browser->>+Client: HTTP Request
|
||||
Client->>-Browser: Serve Client Files
|
||||
|
||||
Browser->>+Client: WebSocket Connection
|
||||
Client->>+Server: Socket.IO Connection
|
||||
Server->>+SSH: SSH Connection
|
||||
|
||||
Note over Browser,SSH: Bidirectional Communication
|
||||
|
||||
SSH-->>-Server: SSH Data
|
||||
Server-->>-Client: Socket.IO Events
|
||||
Client-->>-Browser: WebSocket Events
|
||||
```
|
||||
|
||||
## File Watching and Auto-Reload
|
||||
|
||||
Both client and server components support file watching and automatic reloading:
|
||||
|
||||
- Client changes will trigger webpack to rebuild
|
||||
- Server changes will trigger nodemon to restart
|
||||
|
||||
## Important Notes
|
||||
|
||||
1. The server and client components must use Socket.IO v2.2.0 due to current Node.js v6.9.1 compatibility requirements
|
||||
2. Client development server (3000) and WebSSH2 server (2222) must run simultaneously
|
||||
3. CORS is automatically handled in development mode
|
||||
4. The development configuration is only injected in development mode
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Ensure both servers are running (`npm run watch` in both directories)
|
||||
2. Check the browser console for client-side errors
|
||||
3. Check terminal output for server-side errors
|
||||
4. Verify the ports (3000 and 2222) are available
|
||||
5. Clear browser cache if changes aren't reflecting
|
||||
|
||||
## Building for Production (client)
|
||||
|
||||
When ready to build for production:
|
||||
|
||||
```bash
|
||||
cd webssh2_client
|
||||
npm run build
|
||||
```
|
||||
|
||||
This will create production-ready files in the `client/public` directory without the development configuration injection.
|
56
Dockerfile
|
@ -1,9 +1,51 @@
|
|||
FROM node:16-alpine
|
||||
# Use debian:bookworm-slim runtime as a parent image
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apk update && apk add bash
|
||||
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
|
||||
|
||||
WORKDIR /usr/src
|
||||
COPY app/ /usr/src/
|
||||
RUN npm ci --audit=false --bin-links=false --fund=false
|
||||
EXPOSE 2222/tcp
|
||||
ENTRYPOINT [ "/usr/local/bin/node", "index.js" ]
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y curl \
|
||||
&& apt-get -y autoclean
|
||||
|
||||
# nvm environment variables
|
||||
ENV NVM_DIR /usr/local/nvm
|
||||
ENV NODE_VERSION 6.9.1
|
||||
|
||||
RUN mkdir -p $NVM_DIR
|
||||
|
||||
# install nvm
|
||||
# https://github.com/creationix/nvm#install-script
|
||||
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
|
||||
|
||||
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
|
||||
ENV PATH $NVM_DIR/versions/node/v$NODE_VERSION/bin:$PATH
|
||||
|
||||
RUN echo "source $NVM_DIR/nvm.sh && \
|
||||
nvm install $NODE_VERSION && \
|
||||
nvm alias default $NODE_VERSION && \
|
||||
nvm use default" | bash
|
||||
|
||||
# Set the working directory in the container
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy package.json and package-lock.json (if available)
|
||||
COPY package*.json index.js ./
|
||||
|
||||
# Install production dependencies
|
||||
# RUN npm install --production
|
||||
RUN npm i --audit=false --bin-links=false --fund=false --production
|
||||
|
||||
COPY app/ ./app/
|
||||
|
||||
COPY config.json.sample config.json
|
||||
|
||||
# Set environment variables
|
||||
ENV PORT=2222
|
||||
ENV DEBUG=
|
||||
ENV WEBSSH_SESSION_SECRET=
|
||||
|
||||
# Make port 2222 available to the world outside this container
|
||||
EXPOSE 2222
|
||||
|
||||
# Run the app when the container launches
|
||||
CMD ["npm", "start"]
|
33
EventFlow.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
# Typical Event Flow
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Socket.IO
|
||||
participant WebSSH2 Server
|
||||
participant SSH Server
|
||||
|
||||
Client->>Socket.IO: Connect
|
||||
Socket.IO->>WebSSH2 Server: io.on connection
|
||||
Note over WebSSH2 Server: Socket Established
|
||||
WebSSH2 Server->>Client: Emit request_auth
|
||||
Client->>WebSSH2 Server: Send authentication data
|
||||
Note over WebSSH2 Server: handleAuthenticate
|
||||
Note over WebSSH2 Server: initializeConnection
|
||||
WebSSH2 Server->>SSH Server: Connect (ssh.connect)
|
||||
SSH Server-->>WebSSH2 Server: Connection ready
|
||||
Note over WebSSH2 Server: conn.on ready
|
||||
WebSSH2 Server->>Client: Emit authentication success
|
||||
WebSSH2 Server->>Client: Emit permissions
|
||||
WebSSH2 Server->>Client: Update footer element
|
||||
Client->>WebSSH2 Server: Send terminal data
|
||||
Note over WebSSH2 Server: handleTerminal
|
||||
Note over WebSSH2 Server: Set term, rows, cols
|
||||
Note over WebSSH2 Server: Ready for SSH communication
|
||||
Note over WebSSH2 Server: createShell
|
||||
WebSSH2 Server->>SSH Server: open shell
|
||||
SSH Server-->>WebSSH2 Server: stream.on('data')
|
||||
WebSSH2 Server-->>Client: socket.emit('data')
|
||||
Client-->>WebSSH2 Server: socket.on('data')
|
||||
WebSSH2 Server-->>SSH Server: stream.write('data')
|
||||
```
|
39
FLOWS.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Application Flow
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client
|
||||
participant Express
|
||||
participant SocketIO
|
||||
participant SSHConnection
|
||||
participant SSHServer
|
||||
|
||||
Client->>Express: HTTP Request
|
||||
Express->>Client: Send client files
|
||||
Client->>SocketIO: Establish Socket.IO connection
|
||||
alt HTTP Basic Auth used
|
||||
SocketIO->>SSHConnection: Jump to "Connect with credentials"
|
||||
else No pre-existing credentials
|
||||
SocketIO->>Client: Emit "authentication" (request_auth)
|
||||
Client->>SocketIO: Emit "authenticate" (with credentials)
|
||||
end
|
||||
SocketIO->>SSHConnection: Connect with credentials
|
||||
SSHConnection->>SSHServer: Establish SSH connection
|
||||
alt Keyboard Interactive Auth
|
||||
SSHServer->>SSHConnection: Request additional auth
|
||||
SSHConnection->>SocketIO: Emit "authentication" (keyboard-interactive)
|
||||
SocketIO->>Client: Forward auth request
|
||||
Client->>SocketIO: Send auth response
|
||||
SocketIO->>SSHConnection: Forward auth response
|
||||
SSHConnection->>SSHServer: Complete authentication
|
||||
end
|
||||
SSHServer->>SSHConnection: Connection established
|
||||
SSHConnection->>SocketIO: Connection successful
|
||||
SocketIO->>Client: Emit "authentication" (success)
|
||||
Client->>SocketIO: Emit "terminal" (with specs)
|
||||
SocketIO->>SSHConnection: Create shell with specs
|
||||
SSHConnection->>SSHServer: Create shell session
|
||||
SSHConnection->>SocketIO: Shell created
|
||||
SocketIO->>Client: Ready for input/output
|
||||
Note over Client,SSHServer: Bidirectional data flow established
|
||||
```
|
2
Makefile
|
@ -1,2 +0,0 @@
|
|||
test:
|
||||
cd ./app; npm run test
|
599
README.md
|
@ -1,367 +1,446 @@
|
|||
# WebSSH2
|
||||
# WebSSH2 - Web SSH Client
|
||||
|
||||
[](https://travis-ci.com/billchurch/webssh2) [](https://github.com/billchurch/webssh2/releases/latest) [](https://github.com/billchurch/webssh2/actions/workflows/docker-multiplatform.yml)
|
||||

|
||||
|
||||
[](https://www.buymeacoffee.com/billchurch)
|
||||
WebSSH2 is an HTML5 web-based terminal emulator and SSH client. It uses SSH2 as a client on a host to proxy a Websocket / Socket.io connection to an SSH2 server.
|
||||
|
||||
Web SSH Client using ssh2, socket.io, xterm.js, and express
|
||||

|
||||
|
||||
A bare bones example of an HTML5 web-based terminal emulator and SSH client. We use SSH2 as a client on a host to proxy a Websocket / Socket.io connection to a SSH2 server.
|
||||

|
||||
|
||||
<img width="600" height="340" alt="WebSSH2 v0.2.0 demo" src="https://user-images.githubusercontent.com/1668075/182425293-acc8741e-cc92-4105-afdc-9538e1685d4b.gif">
|
||||
## Table of Contents
|
||||
|
||||
# Requirements
|
||||
Node v14.x or above. If using <v14.x you should be able to run by replacing the "read-config" package to @1 like this (after a clone):
|
||||
- [Requirements](#requirements)
|
||||
- [Breaking Changes](#breaking-changes)
|
||||
- [Installation](#installation)
|
||||
- [Docker Setup](#docker-setup)
|
||||
- [Usage](#usage)
|
||||
- [Configuration](#configuration)
|
||||
- [Features](#features)
|
||||
- [Routes](#routes)
|
||||
- [Client-Side Module](#client-side-module)
|
||||
- [Tips](#tips)
|
||||
- [Support](#support)
|
||||
|
||||
`npm install --save read-config@1
|
||||
`
|
||||
## Requirements
|
||||
|
||||
Just keep in mind that there is no intention to ensure compatability with Node < v14
|
||||
- Node.js 6.9.1
|
||||
|
||||
# Instructions
|
||||
The instructions previously showed cloning directly from main, while normally safe may include "work in progress". It's recommended that after you clone, you switch to a particular tag.
|
||||
## Breaking Changes
|
||||
- See [CONFIG.md](./CONFIG.md) for a list of breaking changes to the config.json file.
|
||||
- See [DEPRECATED.md](./DEPRECATED.md) for a list of deprecated options.
|
||||
|
||||
To install:
|
||||
## Installation
|
||||
|
||||
1. Clone to a location somewhere
|
||||
1. Clone the repository:
|
||||
```
|
||||
git clone https://github.com/billchurch/webssh2.git
|
||||
cd webssh2
|
||||
```
|
||||
|
||||
2. Checkout to the `current` tag using `git checkout current` or choose a particular [release](https://github.com/billchurch/webssh2/releases/) tag
|
||||
2. Install dependencies:
|
||||
```
|
||||
npm install --production
|
||||
```
|
||||
For development purposes, use `npm install` instead.
|
||||
|
||||
3. `cd app` and `npm install --production`. If you want to develop and rebuild javascript and other files utilize `npm install` instead.
|
||||
3. Configure the application by editing `config.json` if needed.
|
||||
|
||||
4. If desired, edit app/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.
|
||||
4. Start the server:
|
||||
```
|
||||
npm start
|
||||
```
|
||||
|
||||
5. Run `npm start`
|
||||
## Docker Setup
|
||||
|
||||
6. Fire up a browser, navigate to IP/port of your choice and specify a host (https isn't used here because it's assumed it will be off-loaded to some sort of proxy):
|
||||
1. Build and run the Docker container (with debug messages):
|
||||
```bash
|
||||
docker build -t webssh2 .
|
||||
docker run --name webssh2 --rm -it -p 2222:2222 -e "DEBUG=webssh2*,-webssh2:ssh2" webssh2
|
||||
```
|
||||
|
||||
### Important Note About Configuration Files
|
||||
|
||||
As of recent versions, the configuration file location has moved from `/usr/src` to `/usr/src/app` in the Docker container. This change improves organization but requires attention when mounting custom configuration files.
|
||||
|
||||
To use a custom configuration file with the Docker container, mount your `config.json` to the new location using:
|
||||
|
||||
```bash
|
||||
docker run -it --rm --name webssh2 \
|
||||
-p 2222:2222 \
|
||||
-e DEBUG="webssh2*" \
|
||||
-v "$(pwd)/config.json:/usr/src/app/config.json" \
|
||||
billchurch/webssh2:bigip-server
|
||||
```
|
||||
|
||||
Note:
|
||||
- If you previously mounted your configuration file to `/usr/src/config.json`, it will not be detected. The container will silently fall back to default configuration.
|
||||
- The new location at `/usr/src/app/config.json` is the only valid path for custom configuration files.
|
||||
- The default configuration provides basic functionality, so missing custom configurations may not be immediately apparent.
|
||||
|
||||
## Usage
|
||||
|
||||
Access the web client by navigating to:
|
||||
|
||||
```
|
||||
http://localhost:2222/ssh
|
||||
```
|
||||
|
||||
You'll be prompted for host details and SSH credentials.
|
||||
|
||||
Alternatively you may use the `/ssh/host/<host>` route:
|
||||
|
||||
```
|
||||
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'll be prompted for SSH credentials via HTTP Basic Authentication.
|
||||
P
|
||||
## Configuration
|
||||
|
||||
Alternatively in main for testing, you can send credentials via POST with the variables "username" and "userpassword".
|
||||
### GET Parameters
|
||||
|
||||
# Customizing client files
|
||||
- `port=` - _integer_ - SSH server port (default: `22`)
|
||||
- `header=` - _string_ - Optional header text
|
||||
- `headerBackground=` - _string_ - Optional background color (default: `"green"`)
|
||||
- `sshterm=` - _string_ - Terminal type for pty (default: xterm-color)
|
||||
|
||||
See [BUILDING.md](BUILDING.md) for more details.
|
||||
### Config File Options
|
||||
|
||||
# Docker
|
||||
Edit `config.json` to customize the following options:
|
||||
|
||||
## NOTICE
|
||||
Docker versions differ from what is in `main` and are release dependant.
|
||||
- `listen.ip` - _string_ - IP address to listen on (default: `"127.0.0.1"`)
|
||||
- `listen.port` - _integer_ - Port to listen on (default: `2222`)
|
||||
- `http.origins` - _array_ - CORS origins for socket.io (default: `["*:*"]`)
|
||||
- `user.name` - _string_ - Default SSH username (default: `null`)
|
||||
- `user.password` - _string_ - Default SSH password (default: `null`)
|
||||
- `ssh.host` - _string_ - Default SSH host (default: `null`)
|
||||
- `user.privateKey` - _string_ - Default SSH private key (default: `null`)
|
||||
- `ssh.port` - _integer_ - Default SSH port (default: `22`)
|
||||
- `ssh.term` - _string_ - Terminal emulation (default: `"xterm-color"`)
|
||||
- `ssh.readyTimeout` - _integer_ - SSH handshake timeout in ms (default: `20000`)
|
||||
- `ssh.keepaliveInterval` - _integer_ - SSH keepalive interval in ms (default: `120000`)
|
||||
- `ssh.keepaliveCountMax` - _integer_ - Max SSH keepalive packets (default: `10`)
|
||||
- `ssh.disableInteractiveAuth` - _boolean_ - When set to `true`, prevents interactive authentication through the web interface. Users must use Basic Authentication via the `/ssh/host/<host>` route. (default: `false`)
|
||||
- `ssh.algorithms.cipher` - _array_ - Supported cipher algorithms (default: `["aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm", "aes128-gcm@openssh.com", "aes256-gcm", "aes256-gcm@openssh.com", "aes256-cbc"]`)
|
||||
- `ssh.algorithms.compress` - _array_ - Supported compression methods (default: `["none", "zlib@openssh.com", "zlib"]`)
|
||||
- `ssh.algorithms.hmac` - _array_ - Supported HMAC algorithms (default: `["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"]`)
|
||||
- `ssh.algorithms.kex` - _array_ - Supported key exchange methods (default: `["ecdh-sha2-nistp256", "ecdh-sha2-nistp384", "ecdh-sha2-nistp521", "diffie-hellman-group-exchange-sha256", "diffie-hellman-group14-sha1"]`)
|
||||
- `ssh.algorithms.serverHostKey` - _array_ - Supported host key types (default: `["ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", "ssh-rsa"]`)
|
||||
- `header.text` - _string_ - Header text (default: `null`)
|
||||
- `header.background` - _string_ - Header background color (default: `"green"`)
|
||||
- `session.name` - _string_ - Session cookie name (default: `"webssh2.sid"`)
|
||||
- `session.secret` - _string_ - Session secret key (default: `crypto.randomBytes(32).toString("hex")`)
|
||||
- `options.challengeButton` - _boolean_ - Enable challenge button (default: `true`)
|
||||
- `options.autoLog` - _boolean_ - Enable auto-logging (default: `false`)
|
||||
- `options.allowReauth` - _boolean_ - Allow reauthentication (default: `true`)
|
||||
- `options.allowReconnect` - _boolean_ - Allow reconnection (default: `true`)
|
||||
- `options.allowReplay` - _boolean_ - Allow credential replay (default: `true`)
|
||||
|
||||
Meaning billchurch/webssh2:latest is the latest official release. This does NOT sync with what is in `main` on this repo. `main` is development and will change until it gets a release tag.
|
||||
For detailed SSH algorithm configurations, refer to the full config file.
|
||||
|
||||
On occasion, examples or instructions on `main` will drift from what is released. You should refer to the tag of the version you're using to ensure you are following the proper guidance.
|
||||
## Features
|
||||
|
||||
That being said the most current release version is [0.4.6](https://github.com/billchurch/webssh2/tree/0.4.6), see that tag for details.
|
||||
### Keyboard Interactive Authentication
|
||||
|
||||
## Instructions
|
||||
Keyboard Interactive authentication provides a flexible way to handle various authentication scenarios, including multi-factor authentication.
|
||||
|
||||
Some configuration options are available as [Environment Variables](#environment-variables). If there is a configuration option you require which does not have an environment variable please [open an issue requesting](../../issues/new/choose).
|
||||
#### How it works
|
||||
|
||||
[webssh2 images are available in docker hub](https://hub.docker.com/repository/docker/billchurch/webssh2).
|
||||
1. When the SSH server requests Keyboard Interactive authentication, WebSSH2 can handle it in two ways:
|
||||
a) Automatically (default behavior)
|
||||
b) By prompting the user through the web interface
|
||||
|
||||
the `latest` tag will pull the most recent stable release, otherwise you can pull individual tags/releases/versions of this repo by using a particular version in the tag.
|
||||
2. In automatic mode:
|
||||
- If all prompts contain the word "password" (case-insensitive), WebSSH2 will automatically respond using the password provided during the initial connection attempt.
|
||||
- If any prompt doesn't contain "password", all prompts will be forwarded to the web client for user input.
|
||||
|
||||
For instance:
|
||||
3. When prompts are sent to the web client:
|
||||
- A dialog box appears in the user's browser, displaying all prompts from the SSH server.
|
||||
- The user can input responses for each prompt.
|
||||
- Responses are sent back to the SSH server to complete the authentication process.
|
||||
|
||||
```docker pull billchurch/webssh2:0.4.6```
|
||||
#### Configuration Options
|
||||
|
||||
or
|
||||
You can customize the Keyboard Interactive authentication behavior using the following option in your `config.json`:
|
||||
|
||||
```docker pull billchurch/webssh2:0.4.7-alpha.2```
|
||||
|
||||
or
|
||||
|
||||
```docker pull billchurch/webssh2```
|
||||
|
||||
for the most recent
|
||||
|
||||
If you want to play around localy:
|
||||
|
||||
Copy app/config.json.template to app/config.json and modify the latter:
|
||||
|
||||
```js
|
||||
```json
|
||||
{
|
||||
// ...
|
||||
"listen": {
|
||||
"ip": "0.0.0.0",
|
||||
"port": 2222
|
||||
"ssh": {
|
||||
"alwaysSendKeyboardInteractivePrompts": false
|
||||
}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Rebuild and run
|
||||
- `alwaysSendKeyboardInteractivePrompts` (boolean, default: false):
|
||||
- When set to `true`, all Keyboard Interactive prompts will always be sent to the web client, regardless of their content.
|
||||
- When set to `false` (default), WebSSH2 will attempt to automatically handle password prompts and only send non-password prompts to the web client.
|
||||
|
||||
```bash
|
||||
docker build -t webssh2 .
|
||||
docker run --name webssh2 -d -p 2222:2222 webssh2
|
||||
#### Use Cases
|
||||
|
||||
1. **Simple Password Authentication**:
|
||||
With default settings, if the SSH server uses Keyboard Interactive for password authentication, WebSSH2 will automatically handle it without additional user interaction.
|
||||
|
||||
2. **Multi-Factor Authentication**:
|
||||
For SSH servers requiring additional factors (e.g., OTP), WebSSH2 will present prompts to the user through the web interface.
|
||||
|
||||
3. **Always Prompt User**:
|
||||
By setting `alwaysSendKeyboardInteractivePrompts` to `true`, you can ensure that users always see and respond to all authentication prompts, which can be useful for security-sensitive environments or for debugging purposes.
|
||||
|
||||
#### Security Considerations
|
||||
|
||||
- The automatic password handling feature is designed for convenience but may not be suitable for high-security environments. Consider setting `alwaysSendKeyboardInteractivePrompts` to `true` if you want users to explicitly enter their credentials for each session.
|
||||
- Ensure that your WebSSH2 installation uses HTTPS to protect the communication between the web browser and the WebSSH2 server.
|
||||
|
||||
For more information on SSH keyboard-interactive authentication, refer to [RFC 4256](https://tools.ietf.org/html/rfc4256).
|
||||
|
||||
### SSH Private Key Authentication
|
||||
|
||||
WebSSH2 supports SSH private key authentication when using the `/ssh/host/` endpoint with a private key configured in the server settings or via the interactive method with the `/ssh/` endpoint.
|
||||
|
||||
#### Configuration
|
||||
|
||||
Private key authentication can only be configured through the `config.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"name": "myuser",
|
||||
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nYour-Private-Key-Here\n-----END RSA PRIVATE KEY-----",
|
||||
"password": "optional-fallback-password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Alternatively if you don't want to rebuild, mount the config at runtime:
|
||||
#### Key Requirements
|
||||
|
||||
- Only `ssh-rsa` type keys are supported
|
||||
- Passphrase encryption is supported, and if used the `passphrase` must be provided
|
||||
- The private key must be in PEM format
|
||||
- The key in `config.json` must be on a single line with `\n` as line separators
|
||||
- Must include the appropriate header and footer:
|
||||
```
|
||||
-----BEGIN RSA PRIVATE KEY-----\n[... key content ...]\n-----END RSA PRIVATE KEY-----
|
||||
```
|
||||
or for encrypted keys:
|
||||
```
|
||||
-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,5930F19760F7FBBC865400940A89D954\n\n[... key content ...]\n-----END RSA PRIVATE KEY-----
|
||||
```
|
||||
#### Generating a Private Key
|
||||
To generate a new SSH private key, you can use the following command:
|
||||
|
||||
```bash
|
||||
docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/config.json webssh2
|
||||
ssh-keygen -m PEM -t rsa -b 4096 -f ~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
Alternatively if you don't want to build either and mount the config at runtime relying on the community image :
|
||||
|
||||
#### Converting Your Private Key
|
||||
|
||||
Keys uploaded or pasted using the interactive mode through the `/ssh` endpoint can work as-is, however if using a key with `config.json` you must convert your existing SSH private key into the correct format (single line). A bash one-liner you can to accomplish this is:
|
||||
|
||||
```bash
|
||||
docker run --name webssh2 -d -p 2222:2222 -v `pwd`/app/config.json:/usr/src/config.json billchurch/webssh2
|
||||
echo ' "privateKey": "'$(cat ~/.ssh/id_rsa | tr '\n' '~' | sed 's/~/\\n/g')'"'
|
||||
```
|
||||
|
||||
# Options
|
||||
|
||||
## Environment Variables
|
||||
Environment variables introduced in 0.4.7 will override anything specified in `config.json`. This is useful for settings that may be per-node, or in a container environment. These are optional and will default to whatever their peer config.json settings are
|
||||
This command:
|
||||
1. Reads your private key file
|
||||
2. Converts newlines to temporary characters
|
||||
3. Replaces those characters with `\n`
|
||||
4. Wraps the result in quotes
|
||||
5. Outputs the key in a format ready to paste into your `config.json`
|
||||
|
||||
* **LISTEN** - _string_ - IP address node should listen on for client connections, defaults to `127.0.0.1`. Peer is **listen.ip**
|
||||
* **PORT** - _integer_ - Port node should listen on for client connections, defaults to `2222`. Peer is **listen.port**
|
||||
* **SOCKETIO_ORIGINS** - _array_ - COORS origins to allow connections from to socket.io server, defaults to `localhost:2222`. Changed in 0.3.1, to enable previous, less secure, default behavior of everything use `*:*` (not recommended). Check [#240](../../issues/240). Peer is **socketio.origins**
|
||||
* **SOCKETIO_PATH** _string_ - Path to socket.io client files. Default: `/ssh/socket.io`. Peer is **socketio.path**
|
||||
* **SOCKETIO_SERVECLIENT** _boolean_ - serve the socket.io client. This is built into the custom javascript, so you shouldn't need this. Kept as an option just in case. Default: `false`. Peer is **socketio.serveClient**
|
||||
#### Authentication Process
|
||||
|
||||
## POST request vars (in main branch for testing)
|
||||
1. When connecting through the `/ssh/host/` endpoint, WebSSH2 will first attempt to authenticate using the private key specified in `config.json`
|
||||
2. If key authentication fails and `user.password` is configured, the system will automatically attempt password authentication
|
||||
3. If both authentication methods fail, you'll receive an authentication error
|
||||
|
||||
* **username** - _string_ - username to log into ssh with
|
||||
#### Endpoint Support
|
||||
|
||||
* **userpassword** _string_ password to log into ssh with
|
||||
- `/ssh/host/:host` - Supports private key authentication configured via `config.json`
|
||||
- `/ssh` - Does NOT support private key authentication
|
||||
|
||||
* **port=** - _integer_ - port of SSH server (defaults to 22)
|
||||
#### Security Considerations
|
||||
|
||||
* **header=** - _string_ - optional header to display on page
|
||||
- Store private keys securely in your server configuration
|
||||
- Use appropriate file permissions for your `config.json` file
|
||||
- Consider using encrypted private keys for additional security
|
||||
- Always use HTTPS when accessing the WebSSH2 service
|
||||
|
||||
* **headerBackground=** - _string_ - optional background color of header to display on page
|
||||
#### Example Usage
|
||||
|
||||
* **sshterm=** - _string_ - optional specify terminal emulation to use, defaults to `ssh.term` in `config.json` or `vt100` if that is null
|
||||
1. Convert and configure your private key:
|
||||
```bash
|
||||
# First, convert your key
|
||||
echo '"'$(cat ~/.ssh/id_rsa | tr '\n' '~' | sed 's/~/\\n/g')'"'
|
||||
|
||||
# Copy the output and paste it into config.json
|
||||
```
|
||||
|
||||
* **readyTimeout=** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. **Enforced Values:** Min: 1, Max: 300000
|
||||
2. Configure `config.json`:
|
||||
```json
|
||||
{
|
||||
"user": {
|
||||
"name": "myuser",
|
||||
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpA...[rest of key]...Yh5Q==\n-----END RSA PRIVATE KEY-----",
|
||||
"password": "fallback-password"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
* **cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
|
||||
3. Access the service via the `/ssh/host/` endpoint:
|
||||
```
|
||||
https://your-server:2222/ssh/host/target-server
|
||||
```
|
||||
|
||||
* **scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000. **Enforced Values:** Min: 1, Max: 200000
|
||||
#### Troubleshooting
|
||||
|
||||
* **tabStopWidth** - _integer_ - Tab stops at _n_ characters **Default:** 8. **Enforced Values:** Min: 1, Max: 100
|
||||
If key authentication fails, check:
|
||||
- Key type is `ssh-rsa`
|
||||
- Key format in `config.json` is properly escaped with `\n` line separators
|
||||
- Key permissions on the target SSH server
|
||||
- Server's `authorized_keys` file configuration
|
||||
- SSH server logs for specific authentication failure reasons
|
||||
|
||||
* **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
|
||||
For additional support or troubleshooting, please open an issue on the GitHub repository.
|
||||
|
||||
* **fontSize** - _number_ - Size of terminal font. **Default:** 12
|
||||
### Environment Variables via URL
|
||||
|
||||
* **fontFamily** - _string_ - Font family
|
||||
WebSSH2 supports passing environment variables through URL parameters, allowing you to customize the SSH session environment. This feature enables scenarios like automatically opening specific files or setting custom environment variables.
|
||||
|
||||
* **letterSpacing** - _number_ - Letter spacing
|
||||
#### Server Configuration
|
||||
|
||||
* **lineHeight** - _number_ - Line height
|
||||
Before using this feature, you must configure your SSH server to accept the environment variables you want to pass. Edit your `/etc/ssh/sshd_config` file to include the desired variables in the `AcceptEnv` directive:
|
||||
|
||||
## GET request vars
|
||||
```bash
|
||||
# Allow client to pass locale environment variables and custom vars
|
||||
AcceptEnv LANG LC_* VIM_FILE CUSTOM_ENV
|
||||
```
|
||||
|
||||
* **port=** - _integer_ - port of SSH server (defaults to 22)
|
||||
Remember to restart your SSH server after making changes:
|
||||
```bash
|
||||
sudo systemctl restart sshd # For systemd-based systems
|
||||
# or
|
||||
sudo service sshd restart # For init.d-based systems
|
||||
```
|
||||
|
||||
* **header=** - _string_ - optional header to display on page
|
||||
#### Usage
|
||||
|
||||
* **headerBackground=** - _string_ - optional background color of header to display on page
|
||||
Pass environment variables using the `env` query parameter:
|
||||
|
||||
* **sshterm=** - _string_ - optional specify terminal emulation to use, defaults to `ssh.term` in `config.json` or `vt100` if that is null
|
||||
```bash
|
||||
# Single environment variable
|
||||
http://localhost:2222/ssh/host/example.com?env=VIM_FILE:config.txt
|
||||
|
||||
* **readyTimeout=** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000. **Enforced Values:** Min: 1, Max: 300000
|
||||
# Multiple environment variables
|
||||
http://localhost:2222/ssh/host/example.com?env=VIM_FILE:config.txt,CUSTOM_ENV:test
|
||||
```
|
||||
|
||||
* **cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
|
||||
#### Security Considerations
|
||||
|
||||
* **scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000. **Enforced Values:** Min: 1, Max: 200000
|
||||
To maintain security, environment variables must meet these criteria:
|
||||
|
||||
* **tabStopWidth** - _integer_ - Tab stops at _n_ characters **Default:** 8. **Enforced Values:** Min: 1, Max: 100
|
||||
- Variable names must:
|
||||
- Start with a capital letter
|
||||
- Contain only uppercase letters, numbers, and underscores
|
||||
- Be listed in the SSH server's `AcceptEnv` directive
|
||||
- Variable values cannot contain shell special characters (;, &, |, `, $)
|
||||
|
||||
* **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound", "none"
|
||||
Invalid environment variables will be silently ignored.
|
||||
|
||||
* **fontSize** - _number_ - Size of terminal font. **Default:** "12"
|
||||
#### Example Usage
|
||||
|
||||
* **fontFamily** - _string_ - Font family
|
||||
1. Configure your SSH server as shown above.
|
||||
|
||||
* **letterSpacing** - _number_ - Letter spacing
|
||||
2. Create a URL with environment variables:
|
||||
```
|
||||
http://localhost:2222/ssh/host/example.com?env=VIM_FILE:settings.conf,CUSTOM_ENV:production
|
||||
```
|
||||
|
||||
* **lineHeight** - _integer_ - Line height
|
||||
3. In your remote server's `.bashrc` or shell initialization file:
|
||||
```bash
|
||||
if [ ! -z "$VIM_FILE" ]; then
|
||||
vim "$VIM_FILE"
|
||||
fi
|
||||
|
||||
## Headers
|
||||
if [ ! -z "$CUSTOM_ENV" ]; then
|
||||
echo "Running in $CUSTOM_ENV environment"
|
||||
fi
|
||||
```
|
||||
|
||||
* **allowreplay** - _boolean_ - Allow use of password replay feature, example `allowreplay: true`
|
||||
#### Troubleshooting
|
||||
|
||||
* **mrhsession** - _string_ - Can be used to pass APM session for event correlation `mrhsession: abc123`
|
||||
If environment variables aren't being set:
|
||||
|
||||
## Config File Options
|
||||
`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...
|
||||
1. Verify the variables are permitted in `/etc/ssh/sshd_config`
|
||||
2. Check SSH server logs for any related errors
|
||||
3. Ensure variable names and values meet the security requirements
|
||||
4. Test with a simple variable first to isolate any issues
|
||||
|
||||
* **listen.ip** - _string_ - IP address node should listen on for client connections, defaults to `127.0.0.1`
|
||||
## Routes
|
||||
|
||||
* **listen.port** - _integer_ - Port node should listen on for client connections, defaults to `2222`
|
||||
WebSSH2 provides two main routes:
|
||||
|
||||
* **socketio.serveClient** - _boolean_ - serve the socket.io client. This is built into the custom javascript, so you shouldn't need this. Kept as an option just in case. Default: `false`
|
||||
### 1. `/ssh`
|
||||
|
||||
* **socketio.path** - _string_ - Path to socket.io client files. Default: `/ssh/socket.io`
|
||||
- **URL:** `http(s)://your-webssh2-server/ssh`
|
||||
- **Features:**
|
||||
- Interactive login form
|
||||
|
||||
* **socketio.origins** - _array_ - COORS origins to allow connections from to socket.io server, defaults to `localhost:2222`. Changed in 0.3.1, to enable previous, less secure, default behavior of everything use `*:*` (not recommended). Check [#240](../../issues/240)
|
||||
<img width="341" alt="image" src="https://github.com/user-attachments/assets/829d1776-3bc5-4315-b0c6-9e96a648ce06">
|
||||
- Terminal configuration options
|
||||
|
||||
* **user.name** - _string_ - Specify user name to authenticate with. In normal cases this should be left to the default `null` setting.
|
||||
<img width="341" alt="image" src="https://github.com/user-attachments/assets/bf60f5ba-7221-4177-8d64-946907aed5ff">
|
||||
|
||||
* **user.password** - _string_ - Specify password to authenticate with. In normal cases this should be left to the default `null` setting.
|
||||
### 2. `/ssh/host/:host`
|
||||
|
||||
* **user.overridebasic** - _boolean_ - When set to `true` ignores `Authorization: Basic` header sent from client and use credentials defined in `user.name` and `user.password` instead. Defaults to `false`. [issue 242](../../issues/242) for more information.
|
||||
- **URL:** `http(s)://your-webssh2-server/ssh/host/:host`
|
||||
- **Authentication:** HTTP Basic Auth
|
||||
- **Features:**
|
||||
- Quick connections to specific hosts
|
||||
- Optional `port` parameter (e.g., `?port=2222`)
|
||||
|
||||
* **ssh.host** - _string_ - Specify host to connect to. May be either hostname or IP address. Defaults to `null`.
|
||||
## Client-Side Module
|
||||
WebSSH2 uses a companion module called `webssh2_client` which provides the browser-side terminal interface and WebSocket communication layer.
|
||||
|
||||
* **ssh.port** - _integer_ - Specify SSH port to connect to, defaults to `22`
|
||||
### About webssh2_client
|
||||
|
||||
* **ssh.term** - _string_ - Specify terminal emulation to use, defaults to `vt100` if null
|
||||
- **Repository**: [https://github.com/billchurch/webssh2_client](https://github.com/billchurch/webssh2_client)
|
||||
- **Purpose**: Provides the browser-based terminal emulator and WebSocket client
|
||||
- **Integration**: Automatically included as a dependency in package.json
|
||||
- **Version**: The compatible version is managed through the package.json dependency
|
||||
|
||||
* **ssh.readyTimeout** - _integer_ - How long (in milliseconds) to wait for the SSH handshake to complete. **Default:** 20000.
|
||||
### Features
|
||||
|
||||
* **ssh.keepaliveInterval** - _integer_ - How often (in milliseconds) to send SSH-level keepalive packets to the server (in a similar way as OpenSSH's ServerAliveInterval config option). Set to 0 to disable. **Default:** 120000.
|
||||
The client module provides:
|
||||
- Terminal emulation using xterm.js
|
||||
- WebSocket communication with the WebSSH2 server
|
||||
- User interface for SSH connections
|
||||
- Terminal configuration and customization
|
||||
- Session management
|
||||
- File transfer capabilities
|
||||
|
||||
* **ssh.keepaliveCountMax** - _integer_ - How many consecutive, unanswered SSH-level keepalive packets that can be sent to the server before disconnection (similar to OpenSSH's ServerAliveCountMax config option). **Default:** 10.
|
||||
### Client-Server Communication
|
||||
|
||||
* **allowedSubnets** - _array_ - A list of subnets that the server is allowed to connect to via SSH. An empty array means all subnets are permitted; no restriction. **Default:** empty array.
|
||||
The server integrates with the client module by:
|
||||
1. Serving the client's static files from `/client/public`
|
||||
2. Injecting server configuration into the client via `window.webssh2Config`
|
||||
3. Managing WebSocket connections for terminal I/O
|
||||
4. Handling authentication and session management
|
||||
|
||||
* **terminal.cursorBlink** - _boolean_ - Cursor blinks (true), does not (false) **Default:** true.
|
||||
### Customization
|
||||
|
||||
* **terminal.scrollback** - _integer_ - Lines in the scrollback buffer. **Default:** 10000.
|
||||
More to follow...
|
||||
|
||||
* **terminal.tabStopWidth** - _integer_ - Tab stops at _n_ characters **Default:** 8.
|
||||
## Tips
|
||||
|
||||
* **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
|
||||
- For security, use HTTPS when transmitting credentials via HTTP Basic Auth.
|
||||
- Terminal settings for `/ssh/host/:host` can be customized after login via `Menu | Settings` and persist across sessions.
|
||||
- You can enable debug from the console by passing the `DEBUG` environment variable to your start script: `DEBUG=webssh*,-webssh2:ssh2 npm run start`. The `webssh2:ssh2` namespace is very chatty and shows all of the SSH protocol information, the `-webssh2:ssh2` excludes that namespace from the line above, otherwise `DEBUG=webssh*` will capture all of the WebSSH2 specific bits. You may also debug Socket.IO and Express related events with `engine`, `socket` and `express` namespaces, or go for broke and debug everything with `DEBUG=*`.
|
||||
- For development information see [DEVELOPMENT.md](./DEVELOPMENT.md).
|
||||
|
||||
* **terminal.fontSize** - _number_ - Size of terminal font. **Default:** 14.
|
||||
For more detailed information on configuration and usage, please refer to the full documentation or open an issue on GitHub.
|
||||
|
||||
* **terminal.fontFamily** - _string_ - Font family
|
||||
## Support
|
||||
If you like what I do, and want to support me you can [buy me a coffee](https://www.buymeacoffee.com/billchurch)!
|
||||
|
||||
* **terminal.letterSpacing** - _number_ - Letter spacing
|
||||
|
||||
* **terminal.lineHeight** - _number_ - Line height
|
||||
|
||||
* **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.
|
||||
|
||||
* **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.
|
||||
|
||||
* **options.allowreauth** - _boolean_ - Reauth button. This option creates an option to provide a button to create a new session with new credentials. See [issue 51](../../issues/51) and [pull 85](../../pull/85) for more detail.
|
||||
|
||||
* **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 **(node v0.11.14 or newer)**
|
||||
2. ecdh-sha2-nistp384 **(node v0.11.14 or newer)**
|
||||
3. ecdh-sha2-nistp521 **(node v0.11.14 or newer)**
|
||||
4. diffie-hellman-group-exchange-sha256 **(node v0.11.12 or newer)**
|
||||
5. diffie-hellman-group14-sha1
|
||||
|
||||
* Supported values:
|
||||
|
||||
* ecdh-sha2-nistp256 **(node v0.11.14 or newer)**
|
||||
* ecdh-sha2-nistp384 **(node v0.11.14 or newer)**
|
||||
* ecdh-sha2-nistp521 **(node v0.11.14 or newer)**
|
||||
* diffie-hellman-group-exchange-sha256 **(node v0.11.12 or newer)**
|
||||
* diffie-hellman-group14-sha1
|
||||
* diffie-hellman-group-exchange-sha1 **(node v0.11.12 or newer)**
|
||||
* diffie-hellman-group1-sha1
|
||||
|
||||
* **cipher** - _array_ - Ciphers.
|
||||
|
||||
* Default values:
|
||||
|
||||
1. aes128-ctr
|
||||
2. aes192-ctr
|
||||
3. aes256-ctr
|
||||
4. aes128-gcm **(node v0.11.12 or newer)**
|
||||
5. aes128-gcm@openssh.com **(node v0.11.12 or newer)**
|
||||
6. aes256-gcm **(node v0.11.12 or newer)**
|
||||
7. aes256-gcm@openssh.com **(node v0.11.12 or newer)**
|
||||
|
||||
* Supported values:
|
||||
|
||||
* aes128-ctr
|
||||
* aes192-ctr
|
||||
* aes256-ctr
|
||||
* aes128-gcm **(node v0.11.12 or newer)**
|
||||
* aes128-gcm@openssh.com **(node v0.11.12 or newer)**
|
||||
* aes256-gcm **(node v0.11.12 or newer)**
|
||||
* aes256-gcm@openssh.com **(node v0.11.12 or newer)**
|
||||
* 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
|
||||
|
||||
* 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
|
||||
|
||||
* **serverlog.client** - _boolean_ - Enables client command logging on server log (console.log). Very simple at this point, buffers data from client until it receives a line-feed then dumps buffer to console.log with session information for tracking. Will capture anything send from client, including passwords, so use for testing only... Default: false. Example:
|
||||
* _serverlog.client: GcZDThwA4UahDiKO2gkMYd7YPIfVAEFW/mnf0NUugLMFRHhsWAAAA host: 192.168.99.80 command: ls -lat_
|
||||
|
||||
* **serverlog.server** - _boolean_ - not implemented, default: false.
|
||||
|
||||
* **accesslog** - _boolean_ - http style access logging to console.log, default: false
|
||||
|
||||
* **safeShutdownDuration** - _integer_ - maximum delay, in seconds, given to users before the server stops when doing a safe shutdown
|
||||
|
||||
# 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.
|
||||
|
||||
# Example:
|
||||
|
||||
http://localhost:2222/ssh/host/192.168.1.1?port=2244&header=My%20Header&headerBackground=red
|
||||
|
||||
# CONTRIBUTING
|
||||
As of 0.4.0, we're trying our best to conform to the [Airbnb Javascript Style Guide](https://airbnb.io/projects/javascript/). I'm hoping this will make contributions easier and keep the code readable. I love shortcuts more than anyone but I've found when making changes to code I've not looked at in a while, it can take me a few momements to deconstruct what was being done due to readbility issues. While I don't agree with every decision in the style guide (semi-colons, yuk), it is a good base to keep the code consistent.
|
||||
|
||||
If you've not used it before, I recommend installing the [vscode extensions](https://blog.echobind.com/integrating-prettier-eslint-airbnb-style-guide-in-vscode-47f07b5d7d6a) for that and [Prettier](https://prettier.io/) and getting familiar. The autocorrections are great (especially if you hate dealing with semi-colons...)
|
||||
|
||||
All contributions are welcome, all may not make it into a release... To increase the chances of your contribution making it into a release, try your best to conform to the style guides and targets of the project.
|
||||
|
||||
# Tips
|
||||
* You can enable extended debug messages in the browser Java console using:
|
||||
* `localStorage.debug = '*';` - Debug Everything (a lot of messages)
|
||||
* `localStorage.debug = 'WebSSH2';` - Debug potentially interesting WebSSH2 related messages (replaying credentials, resizing data, other control messages)
|
||||
* If you want to add custom JavaScript to the browser client you can either modify `./src/client.html` and add a **\<script\>** element, modify `./src/index.js` directly, or check out `webpack.*.js` and add your custom javascript file to a task there (best option).
|
||||
[](https://www.buymeacoffee.com/billchurch)
|
16
SECURITY.md
|
@ -1,16 +0,0 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
The following versions will get security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 0.4.x | :white_check_mark: |
|
||||
| 0.3.x | :x: |
|
||||
| 0.2.x | :x: |
|
||||
| 0.1.x | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
If you find a vulnerability, simply [open an issue](../../issues/new) with the details, use the label `security`.
|
138
SERVER_API.md
Normal file
|
@ -0,0 +1,138 @@
|
|||
# WebSSH2 Server API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
The WebSSH2 server provides a WebSocket interface for establishing SSH connections. This API documentation outlines the events and data structures used for communication between the client and the server.
|
||||
|
||||
Currently this implementation requires Socket.IO v2.2.0 due to this instance targeting node 6.9.1 for a legacy project. Future releases will not have this limitation.
|
||||
|
||||
## Connection
|
||||
|
||||
The server uses Socket.IO for real-time communication. Connect to the WebSocket server at the same host and port as the HTTP server, with the path `/ssh/socket.io`.
|
||||
|
||||
## Events
|
||||
|
||||
### Server to Client Events
|
||||
|
||||
1. `authentication`
|
||||
- Emitted to request authentication or provide authentication results.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
action: string, // "request_auth" or "auth_result"
|
||||
success?: boolean, // Only present for "auth_result"
|
||||
message?: string // Error message if authentication fails
|
||||
}
|
||||
```
|
||||
|
||||
2. `permissions`
|
||||
- Emitted after successful authentication to provide allowed actions.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
autoLog: boolean,
|
||||
allowReplay: boolean,
|
||||
allowReconnect: boolean,
|
||||
allowReauth: boolean
|
||||
}
|
||||
```
|
||||
|
||||
3. `getTerminal`
|
||||
- Emitted to request terminal specifications from the client.
|
||||
- Payload: `true`
|
||||
|
||||
4. `data`
|
||||
- Emitted when there's output from the SSH session.
|
||||
- Payload: `string` (UTF-8 encoded terminal output)
|
||||
|
||||
5. `ssherror`
|
||||
- Emitted when an SSH-related error occurs.
|
||||
- Payload: `string` (Error message)
|
||||
|
||||
6. `updateUI`
|
||||
- Emitted to update specific UI elements.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
element: string, // UI element identifier
|
||||
value: any // New value for the element
|
||||
}
|
||||
```
|
||||
|
||||
### Client to Server Events
|
||||
|
||||
1. `authenticate`
|
||||
- Emit this event to provide authentication credentials.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
username: string,
|
||||
password: string,
|
||||
host: string,
|
||||
port: number,
|
||||
term?: string // Optional terminal type
|
||||
}
|
||||
```
|
||||
|
||||
2. `terminal`
|
||||
- Emit this event to provide terminal specifications.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
term: string, // e.g., "xterm-256color"
|
||||
cols: number,
|
||||
rows: number
|
||||
}
|
||||
```
|
||||
|
||||
3. `data`
|
||||
- Emit this event to send user input to the SSH session.
|
||||
- Payload: `string` (UTF-8 encoded user input)
|
||||
|
||||
4. `resize`
|
||||
- Emit this event when the terminal size changes.
|
||||
- Payload:
|
||||
```javascript
|
||||
{
|
||||
cols: number,
|
||||
rows: number
|
||||
}
|
||||
```
|
||||
|
||||
5. `control`
|
||||
- Emit this event for special control commands.
|
||||
- Payload: `string` ("replayCredentials" or "reauth")
|
||||
|
||||
6. `disconnect`
|
||||
- Emit this event to close the connection.
|
||||
- No payload required
|
||||
|
||||
## Authentication Flow
|
||||
|
||||
1. The server emits `authentication` with `action: "request_auth"`.
|
||||
2. The client emits `authenticate` with credentials.
|
||||
3. The server may emit `authentication` with `action: "keyboard-interactive"` for additional authentication steps.
|
||||
4. The server emits `authentication` with `action: "auth_result"` and `success: true/false`.
|
||||
|
||||
## Establishing SSH Session
|
||||
|
||||
1. After successful authentication, the server emits `getTerminal`.
|
||||
2. The client emits `terminal` with terminal specifications.
|
||||
3. The server establishes the SSH connection and starts emitting `data` events with terminal output.
|
||||
4. The client can now send `data` events with user input.
|
||||
|
||||
## Error Handling
|
||||
|
||||
- The client should listen for `ssherror` events and handle them appropriately (e.g., displaying error messages to the user).
|
||||
|
||||
## UI Updates
|
||||
|
||||
- The client should listen for `updateUI` events and update the corresponding UI elements.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Handle connection errors and implement reconnection logic.
|
||||
2. Implement proper error handling and user feedback.
|
||||
3. Securely manage authentication credentials.
|
||||
4. Handle terminal resizing appropriately.
|
||||
5. Implement support for special control commands (replay credentials, reauthentication).
|
|
@ -1,24 +0,0 @@
|
|||
{
|
||||
"ignorePatterns": ["**/*{.,-}min.js"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"airbnb-base",
|
||||
"prettier"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": [
|
||||
"@typescript-eslint",
|
||||
"prettier"
|
||||
],
|
||||
"rules": {
|
||||
"prettier/prettier": ["error"]
|
||||
}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
tag-version-prefix=""
|
|
@ -1,4 +0,0 @@
|
|||
{
|
||||
"printWidth": 100,
|
||||
"singleQuote": true
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
|
||||
version: v1.22.1
|
||||
ignore: {}
|
||||
patch: {}
|
461
app/CHANGELOG.md
|
@ -1,461 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [0.5.0-pre-4](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-4...webssh2-v0.5.0-pre-4) (2022-08-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* test change for release ([476b566](https://github.com/billchurch/webssh2/commit/476b566c08a84bd35aaccf847253875b2c3afb10))
|
||||
|
||||
## [0.4.7-pre-4](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-3...webssh2-v0.4.7-pre-4) (2022-08-03)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 0.4.7-pre-4 ([7d4ba87](https://github.com/billchurch/webssh2/commit/7d4ba87bc1c198600ea33ee220553ef46ea2a103))
|
||||
|
||||
## [0.4.7-pre-3](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.7-pre-2...webssh2-v0.4.7-pre-3) (2022-08-03)
|
||||
|
||||
|
||||
### Miscellaneous Chores
|
||||
|
||||
* release 0.4.7-pre-3 ([0c78c1f](https://github.com/billchurch/webssh2/commit/0c78c1f31cc6380b7f0706822fc418cfede11413))
|
||||
|
||||
## [0.4.7-pre-2](https://github.com/billchurch/webssh2/compare/webssh2-v0.4.6...webssh2-v0.4.7-pre-2) (2022-08-02)
|
||||
|
||||
|
||||
### ⚠ BREAKING CHANGES
|
||||
|
||||
* validate referer to /reauth is valid
|
||||
* bump xterm to 4.18.0
|
||||
* consistent logging messages see #286
|
||||
* config system changes #284 (#285)
|
||||
|
||||
### Features
|
||||
|
||||
* add additional params for POST requests [#290](https://github.com/billchurch/webssh2/issues/290) ([46c1560](https://github.com/billchurch/webssh2/commit/46c1560e3c126376e18124e14e5c7fb8c029a0a1))
|
||||
* add additional vars to POST requests [#290](https://github.com/billchurch/webssh2/issues/290) ([0a4e419](https://github.com/billchurch/webssh2/commit/0a4e419fb371ae95340fa890497022a2aa9d063a))
|
||||
* add fontFamily, letterSpacing, lineHeight ([97f3088](https://github.com/billchurch/webssh2/commit/97f3088780744e13a6724a4967a4896aac3f20d8))
|
||||
* add fontSize option [#292](https://github.com/billchurch/webssh2/issues/292) ([5e78812](https://github.com/billchurch/webssh2/commit/5e788129744d326e78ec91bda86ed5cecfd70d3f))
|
||||
* config system changes [#284](https://github.com/billchurch/webssh2/issues/284) ([#285](https://github.com/billchurch/webssh2/issues/285)) ([9c99b09](https://github.com/billchurch/webssh2/commit/9c99b0940ec726193deae3c4999d25a297874d67))
|
||||
* consistent logging messages see [#286](https://github.com/billchurch/webssh2/issues/286) ([50cfcb9](https://github.com/billchurch/webssh2/commit/50cfcb97788cbd3409b4605adceef3d47e370e38))
|
||||
* credentials over http post for [#290](https://github.com/billchurch/webssh2/issues/290) ([5b8f88c](https://github.com/billchurch/webssh2/commit/5b8f88cfef1745c88748277217204e6c38c7ff7e))
|
||||
* reorder viewport setup at ssh handshake [#292](https://github.com/billchurch/webssh2/issues/292) ([140e1e2](https://github.com/billchurch/webssh2/commit/140e1e24b14d6b74848e9d250c2b44f806ad627d))
|
||||
* validate referer to /reauth is valid ([0dcaa6e](https://github.com/billchurch/webssh2/commit/0dcaa6e15062cdc3252ce52abd9057caf4c00a30))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Fix the parameter passing problem of setDefaultCredentials to make it perform data initialization normally ([#288](https://github.com/billchurch/webssh2/issues/288)) ([40cbb35](https://github.com/billchurch/webssh2/commit/40cbb35616fa17c1c36520690f40ebce0b488153))
|
||||
* invalid css in style.css ([ffab534](https://github.com/billchurch/webssh2/commit/ffab5345dcb568fa2bb50a96f403174ad3728286))
|
||||
|
||||
|
||||
### package
|
||||
|
||||
* bump xterm to 4.18.0 ([84c09ec](https://github.com/billchurch/webssh2/commit/84c09ec8a1909e4bbd0051debdbb905276a4245e))
|
||||
|
||||
### [0.4.6](https://github.com/billchurch/WebSSH2/compare/v0.2.10-0...v0.4.6) (2022-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add SIGTERM to safe shutdown feature ([675b4f5](https://github.com/billchurch/WebSSH2/commit/675b4f5a3a92b187b620684eb1ce1b7afa0e2e08))
|
||||
* **auth:** ssh private key auth implemented via config.json ([#161](https://github.com/billchurch/WebSSH2/issues/161)) ([342df8e](https://github.com/billchurch/WebSSH2/commit/342df8eb9cafba52eb63b50a60e11e1431d6fbd4))
|
||||
* **config:** specify local source address and port for client connections fixes [#152](https://github.com/billchurch/WebSSH2/issues/152) ([#158](https://github.com/billchurch/WebSSH2/issues/158)) ([65d6ec6](https://github.com/billchurch/WebSSH2/commit/65d6ec68452b80c42fd62534355e456ce1f16a32))
|
||||
* CORS support ([b324f33](https://github.com/billchurch/WebSSH2/commit/b324f338adeb3518322941639fb83ba9370814cc)), closes [#240](https://github.com/billchurch/WebSSH2/issues/240)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* deprecated term.setOption ([d903da8](https://github.com/billchurch/WebSSH2/commit/d903da87c41882a3736683c7de497cb8bd37f885))
|
||||
* dockerignore ([#272](https://github.com/billchurch/WebSSH2/issues/272)) ([8a68cca](https://github.com/billchurch/WebSSH2/commit/8a68ccaffa374584b5d9531f9dbeae616bd971f5))
|
||||
* fixes default for allowreauth ([#239](https://github.com/billchurch/WebSSH2/issues/239)) ([dcfd81b](https://github.com/billchurch/WebSSH2/commit/dcfd81b454b9fe66edec489266dc35a765464c6b)), closes [#238](https://github.com/billchurch/WebSSH2/issues/238)
|
||||
* missing ENTRYPOINT for Dockerfile ([6a3a47a](https://github.com/billchurch/WebSSH2/commit/6a3a47a13de3cd70d603379a27e055f08a6ee62c))
|
||||
* obey host ssh.host in config fixes [#190](https://github.com/billchurch/WebSSH2/issues/190) ([7b7e8e7](https://github.com/billchurch/WebSSH2/commit/7b7e8e753358ed48f52eb9aa2fc359bf758f304b))
|
||||
* subnet unauthorized now emits "ssherror" which persists across websocket termination ([e796f9f](https://github.com/billchurch/WebSSH2/commit/e796f9fb5874d6557433f25e8976b7aa58fa8144))
|
||||
* update config.json.sample ([#177](https://github.com/billchurch/WebSSH2/issues/177)) ([42f973b](https://github.com/billchurch/WebSSH2/commit/42f973b4796f7f50237dc8ce613e477aa89352ca))
|
||||
* update read-config-ng to 3.0.5, fixes [#277](https://github.com/billchurch/WebSSH2/issues/277) ([3e82c0d](https://github.com/billchurch/WebSSH2/commit/3e82c0dc4d31d1c97a7cf98139ef8e6dc0213b22))
|
||||
* update xterm.js fixes [#261](https://github.com/billchurch/WebSSH2/issues/261) ([c801ef9](https://github.com/billchurch/WebSSH2/commit/c801ef9e5826e13a403a6462241cf8a4ff456d45))
|
||||
|
||||
## 0.4.5 [20220417]
|
||||
### Fixes
|
||||
- update read-config-ng to 3.0.5, fixes [#277](../../issues/277)
|
||||
## 0.4.5 [20220331]
|
||||
### Fixes
|
||||
- Update socket.io to 4.2.0
|
||||
- Update read-config-ng to 3.0.4
|
||||
|
||||
## 0.4.4 [20211209]
|
||||
### Fixes
|
||||
- Add ./node_modules to .dockerignore [#240](../../issues/240) thanks @UncleSamSwiss
|
||||
- validator to 13.7.0 [to mitigate potential Regular Expression Denial of Service (ReDoS)](https://snyk.io/vuln/SNYK-JS-VALIDATOR-1090600)
|
||||
- cidr-matcher should be [re-installed to pickup >json-schema@4.0.0 due to prototype pollution vulnerability](https://snyk.io/vuln/SNYK-JS-JSONSCHEMA-1920922)
|
||||
- Update xterm.js to 4.15.0 [#261](../../issues/261)
|
||||
- Replace deprecated term.setOptions with term.options
|
||||
### Changes
|
||||
- update README.md for additional Docker methods thanks @Utopiah
|
||||
|
||||
## 0.4.3 [20211019]
|
||||
- update dependencies
|
||||
- ssh2 to 1.4.0 [to mitigate potential command injection in windows](https://snyk.io/vuln/SNYK-JS-SSH2-1656673)
|
||||
## 0.4.2 [20210813]
|
||||
### changes
|
||||
- update dependencies
|
||||
- socket.io to 4.1.1
|
||||
- read-config-ng to 3.0.2
|
||||
- debug to 4.3.1
|
||||
## 0.4.1 [20210703]
|
||||
### Fixes
|
||||
- lost comma in config.json.sample 71fe377
|
||||
### Changes
|
||||
- bump ws@7.4.6 to [mitigate potential ReDoS vulnerability](https://github.com/websockets/ws/releases/tag/7.4.6)
|
||||
- dev: update CI tools
|
||||
- dev: update dev tools
|
||||
- dev: update build tools
|
||||
|
||||
## 0.4.0 [20210519]
|
||||
### BREAKING
|
||||
- Disabled ssh.serverlog.client option, this disables the POC which allowed for logging of the data sent between the client/server to the console.log.
|
||||
- Dropping support for node versions under 14
|
||||
### Changes
|
||||
- Removed HTML menu code from ./app/server/socket.js, the menu is now fully laid out in the ./app/client/src/index.html and the option elements are hidden by default. Not sure why it wasn't done this way from the start, but there it is.
|
||||
- Updated socket.io to v4.1.1
|
||||
- Client javascript `./app/client/src/js/index.ts` is now built on TypeScript (`npm run build` will generate javascript for client and place into `app/client/public/webssh2.bundle.js` as before)
|
||||
- Build environment changes
|
||||
- removed unused xterm-addon-search, xterm-addon-weblinks, standard, postcss-discard-comments
|
||||
- added prettier 2.3.0, typescript modules, socket.io-client 4.1.1, airbnb linting tools
|
||||
### Added
|
||||
- Lookup ip address for hostname in URL, fixes #199 thanks to @zwiy
|
||||
- Ability to override `Authorization: Basic` header and replace with credentials specified in `config.json` fixes #243. New config.json option `user.overridebasic`
|
||||
### CONTRIBUTING
|
||||
In this release, we're trying our best to conform to the [Airbnb Javascript Style Guide](https://airbnb.io/projects/javascript/). I'm hoping this will make contributions easier and keep the code readable. I love shortcuts more than anyone but I've found when making changes to code I've not looked at in a while, it can take me a few momements to deconstruct what was being done due to readbility issues. While I don't agree with every decision in the style guide (semi-colons, yuk), it is a good base to keep the code consistent.
|
||||
|
||||
If you've not used it before, I recommend installing the [vscode extensions](https://blog.echobind.com/integrating-prettier-eslint-airbnb-style-guide-in-vscode-47f07b5d7d6a) for that and [Prettier](https://prettier.io/) and getting familiar. The autocorrections are great (especially if you hate dealing with semi-colons...)
|
||||
|
||||
As of 0.4.0-testing-0, the client code is written in [TypeScript](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes.html). It's not that much different from JavaScript, and the introduction strong typing will ultimately help to produce better code. Eventually we want to move the whole project to TypeScript but that make take a bit more time. Take a moment to look at ./app/client/src/js/index.ts to see what TypeScript looks like.
|
||||
## 0.3.1 [20210513]
|
||||
### BREAKING
|
||||
- Ability to configure CORS settings for socket.io see [#240](../../issues/240) for more information on how this may break existing deployments. Default settings in example `config.json` are currently permissive `http.origins: ["*:*"]` please note that if a `config.json` is not present, the default is `http.origins: ["localhost:2222"]
|
||||
### Added
|
||||
- Safe Shutdown Feature - thanks to @edgarogh
|
||||
- Sending SIGINT or SIGTERM to node process responsible for WebSSH2 or Docker process will result in a "safe" shutdown
|
||||
- Timer is configured in config.safeShutdownDuration
|
||||
- feat: Use docker build to create multi-arch images (#202)
|
||||
### Fixed
|
||||
- obey host ssh.host in config fixes #190
|
||||
### Changed
|
||||
- `config.json.sample`: `allowreauth` now defaults to `false` fixes #238
|
||||
- update ssh2 to 0.8.8 -> 0.8.9 - [comparison at ssh2 repo](https://github.com/mscdex/ssh2/compare/v0.8.8...v0.8.9)
|
||||
- update xterm to 4.12.0 [comparison at xtermjs repo](https://github.com/xtermjs/xterm.js/compare/4.4.0...4.12.0)
|
||||
- update read-config-ng to 3.0.2
|
||||
- update morgan to 1.10.0
|
||||
- update debug to 4.3.1
|
||||
- update express-session to 1.17.1
|
||||
- update validator to 13.6.0
|
||||
- development tools updates (build environment requires minimum of Node 10, only needed for customization)
|
||||
- update @fortawesome/fontawesome-svg-core to 1.2.35
|
||||
- update @fortawesome/free-solid-svg-icons to 5.15.3
|
||||
- update copy-webpack-plugin to 8.1.1
|
||||
- update cross-env to 7.0.3
|
||||
- update css-loader to 5.2.4
|
||||
- update file-loader to 6.2.0
|
||||
- update mini-css-extract-plugin to 1.6.0
|
||||
- update postcss-discard-comments to 5.0.0
|
||||
- update snazzy to 9.0.0
|
||||
- update standard to 16.0.3
|
||||
- update standard-version to 9.3.0
|
||||
- update style-loader to 2.0.0
|
||||
- update terser-webpack-plugin to 5.1.1
|
||||
- update url-loader to 4.1.1
|
||||
- update webpack to 5.37.0
|
||||
- update webpack-cli to 4.7.0
|
||||
- update webpack-merge to 5.7.3
|
||||
- update webpack-stream to 6.1.2
|
||||
- update xterm-addon-fit to 0.5.0
|
||||
- update xterm-addon-search to 0.8.0
|
||||
- update xterm-addon-web-links to 0.4.0
|
||||
- update ssri from 6.0.1 to 6.0.2 [#233](../../pull/233)
|
||||
- update hosted-git-info from 2.8.5 to 2.8.9 [#237](../../pull/237)
|
||||
- update lodash from 4.17.19 to 4.17.21 [#236](../../pull/236)
|
||||
- update handlebars from 4.7.6 to 4.7.7 [#235](../../pull/235)
|
||||
- update y18n from 4.0.0 to 4.0.1 [#230](../../pull/230)
|
||||
- update elliptic from 6.5.3 to 6.5.4 [#228](../../pull/222833)
|
||||
- update ini from 1.3.5 to 1.3.8 [#217](../../pull/217)
|
||||
## 0.3.0 [20200315]
|
||||
🍀🍀🍀
|
||||
### Added
|
||||
- Add configuration option to restrict connections to specified subnets thanks to @Mierdin
|
||||
- favicon
|
||||
- added module `serve-favicon` to serve favicon from root if pre-fetched by browser
|
||||
- added `link rel=icon` line in client.htm to serve favico.ico out of /ssh/
|
||||
|
||||
### Changed
|
||||
- Using new repo for read-config -> read-config-ng-
|
||||
- removed express compression feature, added no real value.
|
||||
- module updates
|
||||
- ssh2 to 0.8.6 -> 0.8.8 - [comparison at ssh2 repo](https://github.com/mscdex/ssh2/compare/v0.8.6...v0.8.8)
|
||||
- xterm 4.2.0 -> 4.4.0 - [comparison at xtermjs repo](https://github.com/xtermjs/xterm.js/compare/4.2.0...4.4.0)
|
||||
- read-config-ng 3.0.1 - (taking over abandoned repo)n
|
||||
- development module updates (does not impact production, only for development and rebuilding)
|
||||
- fortawesome/fontawesome-svg-core 1.2.27
|
||||
- fortawesome/free-solid-svg-icons 5.12.1
|
||||
- standard-version 7.1.0
|
||||
- webpack 4.42.0
|
||||
- webpack-cli 3.3.11
|
||||
- terser-webpack-plugin 2.3.5
|
||||
- copy-webpack-plugin 5.1.1
|
||||
- cross-env 7.0.2
|
||||
- css-loader 3.4.2
|
||||
- file-loader 5.1.0
|
||||
- style-loader 1.1.3
|
||||
- url-loader 3.0.0
|
||||
|
||||
### Potentially Breaking Changes
|
||||
- Move all child resources to start from under /ssh
|
||||
- /socket.io -> /ssh/socket.io
|
||||
- /webssh2.css -> /ssh/webssh2.css
|
||||
- /webssh2.bundle.js -> /ssh/webssh2.bundle.js
|
||||
- /reauth -> /ssh/reauth
|
||||
- perhaps more
|
||||
|
||||
### Fixes
|
||||
- Typo in config.json.sample, thanks @wuchihsu, fixes #173
|
||||
|
||||
### Housekeeping
|
||||
- Removed irrelavant build scripts from /scripts
|
||||
|
||||
## 0.2.9 [2019-06-13]
|
||||
### Changes
|
||||
- Missing require('fs') in `server/app.js` See issue [#135](../../issues/135)
|
||||
- Patched read-config to mitigate vulnerability in js-yaml
|
||||
- issue not exploitable on webssh2 implementation
|
||||
- patched anyway
|
||||
- sending my patch upstream to read-config, webssh2 package.json points to patched version in my repository https://github.com/billchurch/nodejs-read-config
|
||||
- See https://github.com/nodeca/js-yaml/issues/475 for more detail
|
||||
|
||||
## 0.2.8 [2019-05-25]
|
||||
### Changes
|
||||
- Fixes issue if no password is entered, browser must be closed and restart to attempt to re-auth. See issue [#118](../../issues/118). Thanks @smilesm2 for the idea.
|
||||
- fixes broken `npm run (build|builddev)`
|
||||
- update font-awesome fonts to 5.6.3
|
||||
- update webpack and dependancies
|
||||
- update xterm to 3.8.0
|
||||
|
||||
### Fixes
|
||||
- ILX workspace may not always import properly due to symbolic links (specifically ./node_modules/.bin). This is removed from the ILX package
|
||||
|
||||
## 0.2.7 [2018-11-11]
|
||||
### Changes
|
||||
- `config.reauth` was not respected if initial auth presented was incorrect, regardless of `reauth` setting in `config.json` reauth would always be attempted. fixes [#117](../../issues/117)
|
||||
- **BREAKING** moved app files to /app, this may be a breaking change
|
||||
- Updated dockerfile for new app path
|
||||
- Updated app dependancies
|
||||
- xterm v3.8.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.8.0
|
||||
- basic-auth v2.0.1
|
||||
- https://github.com/jshttp/basic-auth/releases/tag/v2.0.1
|
||||
- express v4.16.4
|
||||
- https://github.com/expressjs/express/releases/tag/4.16.4
|
||||
- validator v10.9.0
|
||||
- https://github.com/chriso/validator.js/releases/tag/10.9.0
|
||||
- Updated dev dependancies
|
||||
- snazzy v8.0.0
|
||||
- standard v12.0.1
|
||||
- uglifyjs-webpack-plugin v2.0.1
|
||||
- ajv v6.5.5
|
||||
- copy-webpack-plugin v4.6.0
|
||||
- css-loader v1.0.1
|
||||
- nodemon v1.18.6
|
||||
- postcss-discard-comments v4.0.1
|
||||
- snyk v1.108.2
|
||||
- url-loader v1.1.2
|
||||
- webpack v4.25.1
|
||||
- webpack-cli v3.1.2
|
||||
|
||||
## 0.2.6 [2018-11-09]
|
||||
### Changes
|
||||
- Reauth didn't work if intial auth presented was incorrect, (see issue #112) fixed thanks @vvalchev
|
||||
- Update node version supported to >=6 (PR #115) thanks @perlun
|
||||
- Update packages
|
||||
- developer dependencies
|
||||
|
||||
## 0.2.5 [2018-09-11]
|
||||
### Added
|
||||
- Reauth function thanks to @vbeskrovny and @vvalchev (9bbc116)
|
||||
- Controlled by `config.json` option `options.allowreauth` true presents reauth dialog and false hides dialog
|
||||
|
||||
### Changed
|
||||
- `options.challengeButton` enabled
|
||||
- previously this configuration option did nothing, this now enables the Credentials button site-wide regardless of the `allowreplay` header value
|
||||
- Updated debug module to v4
|
||||
|
||||
## 0.2.4 [2018-07-18]
|
||||
### Added
|
||||
- Browser title window now changes with xterm escape sequences (see http://tldp.org/HOWTO/Xterm-Title-3.html)
|
||||
- Added bellStyle options
|
||||
- `GET var`: **bellStyle** - _string_ - Style of terminal bell: ("sound"|"none"). **Default:** "sound". **Enforced Values:** "sound "none"
|
||||
- `config.json`: **terminal.bellStyle** - _string_ - Style of terminal bell: (sound|none). **Default:** "sound".
|
||||
- `workspace` folder on GITHUB for BIG-IP specific fixes/changes
|
||||
### Changed
|
||||
- Updated xterm.js to 3.1.0
|
||||
- https://github.com/xtermjs/xterm.js/releases/tag/3.1.0
|
||||
- Default listen IP in `config.json` changed back to 127.0.0.1
|
||||
### Fixed
|
||||
- ESC]0; is now removed from log files when using the browser-side logging feature
|
||||
|
||||
## 0.2.3 unreleased
|
||||
|
||||
### Fixed
|
||||
- ESC]0; is now removed from log files when using the browser-side logging feature
|
||||
|
||||
## 0.2.0 [2018-02-10]
|
||||
Mostly client (browser) related changes in this release
|
||||
|
||||
### Added
|
||||
- Menu system
|
||||
- Fontawesome icons
|
||||
- Resizing browser window sends resize events to terminal container as well as SSH session (pty)
|
||||
- New terminal options (config.json as well as GET vars)
|
||||
- terminal.cursorBlink - boolean - Cursor blinks (true), does not (false) Default: true.
|
||||
- terminal.scrollback - integer - Lines in the scrollback buffer. Default: 10000.
|
||||
- terminal.tabStopWidth - integer - Tab stops at n characters Default: 8.
|
||||
- New serverside (nodejs) terminal configuration options (cursorBlink, scrollback, tabStopWidth)
|
||||
- Logging of MRH session (unassigned if not present)
|
||||
- Express compression feature
|
||||
|
||||
### Changed
|
||||
- Updated xterm.js to 3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.2
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.1
|
||||
- See https://github.com/xtermjs/xterm.js/releases/tag/3.0.0
|
||||
- Moved javascript events out of html into javascript
|
||||
- Changed asset packaging from grunt to Webpack to be inline with xterm.js direction
|
||||
- Moved logging and credentials buttons to menu system
|
||||
- Removed non-minified options (if you need to disable minification, modify webpack scripts and 'npm run build')
|
||||
|
||||
### Fixed
|
||||
- Resolved loss of terminal foucs when interacting with option buttons (Logging, etc...)
|
||||
|
||||
## 0.1.4 [2018-01-30]
|
||||
### Changed
|
||||
- Moved socket and util out of folders into .js in root.
|
||||
- added keepaliveInterval and keepaliveCountMax config options
|
||||
|
||||
## 0.1.3 [2017-09-28]
|
||||
### Changed
|
||||
- Upgrade to debug@3.1 to eliminate ReDoS in %o formatter
|
||||
- Upgrade Express to 4.15.5 for ReDOS
|
||||
- Upgrade basic-auth to v2.0
|
||||
## 0.1.2 [2017-07-31]
|
||||
### Added
|
||||
- ssh.readyTimeout option in config.json (time in ms, default 20000, 20sec)
|
||||
### Changed
|
||||
- Updated xterm.js to 2.9.2 from 2.6.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.2
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.9.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.1
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.8.0
|
||||
- See https://github.com/sourcelair/xterm.js/releases/tag/2.7.0
|
||||
- Updated ssh2 to 0.5.5 to keep current, no fixes impacting WebSSH2
|
||||
- ssh-streams to 0.1.19 from 0.1.16
|
||||
- Updated validator.js to 8.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/chriso/validator.js/releases/tag/8.0.0
|
||||
- Updated Express to 4.15.4, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/express/releases/tag/4.15.4
|
||||
- Updated Express-session to 1.15.5, no fixes impacting WebSSH2
|
||||
- https://github.com/expressjs/session/releases/tag/v1.15.5
|
||||
- Updated Debug to 3.0.0, no fixes impacting WebSSH2
|
||||
- https://github.com/visionmedia/debug/releases/tag/3.0.0
|
||||
- Running in strict mode ('use strict';)
|
||||
|
||||
|
||||
## 0.1.1 [2017-06-03]
|
||||
### Added
|
||||
- `serverlog.client` and `serverlog.server` options added to `config.json` to enable logging of client commands to server log (only client portion implemented at this time)
|
||||
- morgan express middleware for logging
|
||||
### Changed
|
||||
- Updated socket.io to 1.7.4
|
||||
- continued refactoring, breaking up `index.js`
|
||||
- revised error handling methods
|
||||
- revised session termination methods
|
||||
### Fixed
|
||||
### Removed
|
||||
- color console decorations from `util/index.js`
|
||||
- SanatizeHeaders function from `util/index.js`
|
||||
|
||||
## 0.1.0 [2017-05-27]
|
||||
### Added
|
||||
- This ChangeLog.md file
|
||||
- Support for UTF-8 characters (thanks @bara666)
|
||||
- Snyk, Bithound, Travis CI
|
||||
- Cross platform improvements (path mappings)
|
||||
- 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=WebSSH2` will output additional debug messages for functions
|
||||
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
|
||||
- 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
|
||||
- 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
|
||||
- if headerBackground is changed, status background is changed to the same color (typo, fixed)
|
||||
|
||||
### Removed
|
||||
- Express Static References directly to module source directories due to concatenating and minifying js/css
|
||||
|
||||
## 0.0.5 - [2017-03-23]
|
||||
### Added
|
||||
- Added experimental support for logging (see Readme)
|
||||
|
||||
### Fixed
|
||||
- Terminal geometry now properly fills the browser screen and communicates this to the ssh session. Tested with IE 11 and recent versions of Chrome/Safari/Firefox.
|
||||
|
||||
## 0.0.4 - [2017-03-23]
|
||||
### Added
|
||||
- Set default terminal to xterm-color
|
||||
- Mouse event support
|
||||
- New config option, config.ssh.term to set terminal
|
||||
|
||||
### Changed
|
||||
- Update to Xterm.js 2.4.0
|
||||
- Minor code formatting cleanup
|
||||
|
||||
## 0.0.3 - [2017-02-16]
|
||||
### Changed
|
||||
- Update xterm to latest (2.3.0)
|
||||
### Fixed
|
||||
- Fixed misspelled config.ssh.port property
|
||||
|
||||
## 0.0.2 - [2017-02-01]
|
||||
### Changed
|
||||
- Moving terminal emulation to xterm.js
|
||||
- updating module version dependencies
|
||||
|
||||
### Fixed
|
||||
- Fixed issue with banners not being displayed properly from UNIX hosts when only lf is used
|
||||
|
||||
## 0.0.1 - [2016-06-28]
|
||||
### Added
|
||||
- Initial proof of concept and release. For historical purposes only.
|
21
app/LICENSE
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2017 Bill Church
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
71
app/app.js
Normal file
|
@ -0,0 +1,71 @@
|
|||
// server
|
||||
// app/app.js
|
||||
|
||||
const express = require("express")
|
||||
const config = require("./config")
|
||||
const SSHConnection = require("./ssh")
|
||||
const socketHandler = require("./socket")
|
||||
const sshRoutes = require("./routes")(config)
|
||||
const { applyMiddleware } = require("./middleware")
|
||||
const { createServer, startServer } = require("./server")
|
||||
const { configureSocketIO } = require("./io")
|
||||
const { handleError, ConfigError } = require("./errors")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { DEFAULTS, MESSAGES } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("app")
|
||||
|
||||
/**
|
||||
* Creates and configures the Express application
|
||||
* @returns {Object} An object containing the app and sessionMiddleware
|
||||
*/
|
||||
function createApp() {
|
||||
const app = express()
|
||||
|
||||
try {
|
||||
// Resolve the correct path to the webssh2_client module
|
||||
const clientPath = DEFAULTS.WEBSSH2_CLIENT_PATH
|
||||
|
||||
// Apply middleware
|
||||
const { sessionMiddleware } = applyMiddleware(app, config)
|
||||
|
||||
// Serve static files from the webssh2_client module with a custom prefix
|
||||
app.use("/ssh/assets", express.static(clientPath))
|
||||
|
||||
// Use the SSH routes
|
||||
app.use("/ssh", sshRoutes)
|
||||
|
||||
return { app: app, sessionMiddleware: sessionMiddleware }
|
||||
} catch (err) {
|
||||
throw new ConfigError(
|
||||
`${MESSAGES.EXPRESS_APP_CONFIG_ERROR}: ${err.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes and starts the server
|
||||
* @returns {Object} An object containing the server, io, and app instances
|
||||
*/
|
||||
function initializeServer() {
|
||||
try {
|
||||
const { app, sessionMiddleware } = createApp()
|
||||
const server = createServer(app)
|
||||
const io = configureSocketIO(server, sessionMiddleware, config)
|
||||
|
||||
// Set up Socket.IO listeners
|
||||
socketHandler(io, config, SSHConnection)
|
||||
|
||||
// Start the server
|
||||
startServer(server, config)
|
||||
|
||||
debug("Server initialized")
|
||||
|
||||
return { server: server, io: io, app: app }
|
||||
} catch (err) {
|
||||
handleError(err)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { initializeServer: initializeServer, config: config }
|
BIN
app/bun.lockb
|
@ -1,32 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSSH2</title>
|
||||
<style>
|
||||
html, body {background-color: #000;height: 100%;margin: 0;}.dropup-content {display: none;}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/ssh/webssh2.css" />
|
||||
<link rel="icon" href="/ssh/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div id="header"></div>
|
||||
<div id="terminal-container" class="terminal"></div>
|
||||
<div id="bottomdiv">
|
||||
<div class="dropup" id="menu">
|
||||
<i class="fas fa-bars fa-fw"></i> Menu
|
||||
<div id="dropupContent" class="dropup-content">
|
||||
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
||||
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
||||
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
||||
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
<div id="countdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/ssh/webssh2.bundle.js" defer></script>
|
||||
</body>
|
||||
</html>
|
Before Width: | Height: | Size: 15 KiB |
|
@ -1,366 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
||||
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
||||
* https://github.com/chjj/term.js
|
||||
* @license MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Originally forked from (with the author's permission):
|
||||
* Fabrice Bellard's javascript vt100 for jslinux:
|
||||
* http://bellard.org/jslinux/
|
||||
* Copyright (c) 2011 Fabrice Bellard
|
||||
* The original design remains. The terminal itself
|
||||
* has been extended to include xterm CSI codes, among
|
||||
* other features.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default styles for xterm.js
|
||||
*/
|
||||
|
||||
.xterm {
|
||||
cursor: text;
|
||||
position: relative;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.xterm.focus,
|
||||
.xterm:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.xterm .xterm-helpers {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
/**
|
||||
* The z-index of the helpers must be higher than the canvases in order for
|
||||
* IMEs to appear on top.
|
||||
*/
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.xterm .xterm-helper-textarea {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
/* Move textarea out of the screen to the far left, so that the cursor is not visible */
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
left: -9999em;
|
||||
top: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
z-index: -5;
|
||||
/** Prevent wrapping so the IME appears against the textarea at the correct position */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.xterm .composition-view {
|
||||
/* TODO: Composition position got messed up somewhere */
|
||||
background: #000;
|
||||
color: #FFF;
|
||||
display: none;
|
||||
position: absolute;
|
||||
white-space: nowrap;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.xterm .composition-view.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport {
|
||||
/* On OS X this is required in order for the scroll bar to appear fully opaque */
|
||||
background-color: #000;
|
||||
overflow-y: scroll;
|
||||
cursor: default;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.xterm .xterm-screen canvas {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.xterm .xterm-scroll-area {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.xterm-char-measure-element {
|
||||
display: inline-block;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -9999em;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.xterm.enable-mouse-events {
|
||||
/* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer,
|
||||
.xterm .xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.xterm.column-select.focus {
|
||||
/* Column selection mode */
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility:not(.debug),
|
||||
.xterm .xterm-message {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 10;
|
||||
color: transparent;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-tree:not(.debug) *::selection {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.xterm .xterm-accessibility-tree {
|
||||
user-select: text;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.xterm .live-region {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.xterm-dim {
|
||||
/* Dim should not apply to background, so the opacity of the foreground color is applied
|
||||
* explicitly in the generated class and reset to 1 here */
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.xterm-underline-1 { text-decoration: underline; }
|
||||
.xterm-underline-2 { text-decoration: double underline; }
|
||||
.xterm-underline-3 { text-decoration: wavy underline; }
|
||||
.xterm-underline-4 { text-decoration: dotted underline; }
|
||||
.xterm-underline-5 { text-decoration: dashed underline; }
|
||||
|
||||
.xterm-overline {
|
||||
text-decoration: overline;
|
||||
}
|
||||
|
||||
.xterm-overline.xterm-underline-1 { text-decoration: overline underline; }
|
||||
.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; }
|
||||
.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; }
|
||||
.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; }
|
||||
.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; }
|
||||
|
||||
.xterm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.xterm-screen .xterm-decoration-container .xterm-decoration {
|
||||
z-index: 6;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer {
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.xterm-decoration-overview-ruler {
|
||||
z-index: 8;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.xterm-decoration-top {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
body, html {
|
||||
font-family: helvetica, sans-serif, arial;
|
||||
font-size: 1em;
|
||||
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;
|
||||
height:19px;
|
||||
display: none;
|
||||
}
|
||||
.box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
#terminal-container {
|
||||
display: block;
|
||||
width: calc(100% - 1px);
|
||||
margin: 0 auto;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal {
|
||||
background-color: #000000;
|
||||
color: #fafafa;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal:focus .terminal-cursor {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
#bottomdiv {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(50, 50, 50);
|
||||
border-color: white;
|
||||
border-style: solid none none none;
|
||||
border-width: 1px;
|
||||
z-index: 99;
|
||||
height: 19px;
|
||||
}
|
||||
#footer {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-color: white;
|
||||
border-style: none none none solid;
|
||||
border-width: 1px;
|
||||
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;
|
||||
}
|
||||
#countdown {
|
||||
display: none;
|
||||
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;
|
||||
}
|
||||
#countdown.active {
|
||||
display: inline-block;
|
||||
animation: countdown infinite alternate 200ms;
|
||||
}
|
||||
@keyframes countdown {
|
||||
from {
|
||||
background-color: rgb(255, 255, 0);
|
||||
}
|
||||
to {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
#menu {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
padding-left: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
#logBtn, #credentialsBtn, #reauthBtn {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropup {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f1f1f1;
|
||||
font-size: 16px;
|
||||
min-width: 160px;
|
||||
bottom: 18px;
|
||||
z-index: 101;
|
||||
}
|
||||
.dropup-content a {
|
||||
color: #777;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.dropup-content a:hover {
|
||||
background-color: #ccc
|
||||
}
|
||||
.dropup:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:active .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:hover .dropbtn {
|
||||
background-color: #3e8e41;
|
||||
}
|
||||
|
|
@ -1 +0,0 @@
|
|||
Customizations and modifications to the client (browser) go here. Then run "npm run build" to integrate into ../public (where client files are served from). Note that ../public is a flat directory structure. ../public directory is deleted and refreshed eatch thime "npm run build" is run.
|
|
@ -1,32 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebSSH2</title>
|
||||
<style>
|
||||
html, body {background-color: #000;height: 100%;margin: 0;}.dropup-content {display: none;}
|
||||
</style>
|
||||
<link rel="stylesheet" href="/ssh/webssh2.css" />
|
||||
<link rel="icon" href="/ssh/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="box">
|
||||
<div id="header"></div>
|
||||
<div id="terminal-container" class="terminal"></div>
|
||||
<div id="bottomdiv">
|
||||
<div class="dropup" id="menu">
|
||||
<i class="fas fa-bars fa-fw"></i> Menu
|
||||
<div id="dropupContent" class="dropup-content">
|
||||
<a id="logBtn"><i class="fas fa-clipboard fa-fw"></i> Start Log</a>
|
||||
<a id="downloadLogBtn"><i class="fas fa-download fa-fw"></i> Download Log</a>
|
||||
<a id="reauthBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Switch User</a>
|
||||
<a id="credentialsBtn" style="display: none;"><i class="fas fa-key fa-fw"></i> Credentials</a>
|
||||
</div>
|
||||
</div>
|
||||
<div id="footer"></div>
|
||||
<div id="status"></div>
|
||||
<div id="countdown"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="/ssh/webssh2.bundle.js" defer></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,146 +0,0 @@
|
|||
body, html {
|
||||
font-family: helvetica, sans-serif, arial;
|
||||
font-size: 1em;
|
||||
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;
|
||||
height:19px;
|
||||
display: none;
|
||||
}
|
||||
.box {
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
#terminal-container {
|
||||
display: block;
|
||||
width: calc(100% - 1px);
|
||||
margin: 0 auto;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal {
|
||||
background-color: #000000;
|
||||
color: #fafafa;
|
||||
padding: 2px;
|
||||
height: calc(100% - 19px);
|
||||
}
|
||||
#terminal-container .terminal:focus .terminal-cursor {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
#bottomdiv {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
background-color: rgb(50, 50, 50);
|
||||
border-color: white;
|
||||
border-style: solid none none none;
|
||||
border-width: 1px;
|
||||
z-index: 99;
|
||||
height: 19px;
|
||||
}
|
||||
#footer {
|
||||
display: inline-block;
|
||||
color: rgb(240, 240, 240);
|
||||
background-color: rgb(50, 50, 50);
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
border-color: white;
|
||||
border-style: none none none solid;
|
||||
border-width: 1px;
|
||||
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;
|
||||
}
|
||||
#countdown {
|
||||
display: none;
|
||||
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;
|
||||
}
|
||||
#countdown.active {
|
||||
display: inline-block;
|
||||
animation: countdown infinite alternate 200ms;
|
||||
}
|
||||
@keyframes countdown {
|
||||
from {
|
||||
background-color: rgb(255, 255, 0);
|
||||
}
|
||||
to {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
#menu {
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
color: rgb(255, 255, 255);
|
||||
padding-left: 10px;
|
||||
z-index: 100;
|
||||
}
|
||||
#menu:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
#logBtn, #credentialsBtn, #reauthBtn {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.dropup {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
}
|
||||
.dropup-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f1f1f1;
|
||||
font-size: 16px;
|
||||
min-width: 160px;
|
||||
bottom: 18px;
|
||||
z-index: 101;
|
||||
}
|
||||
.dropup-content a {
|
||||
color: #777;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
}
|
||||
.dropup-content a:hover {
|
||||
background-color: #ccc
|
||||
}
|
||||
.dropup:hover .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:active .dropup-content {
|
||||
display: block;
|
||||
}
|
||||
.dropup:hover .dropbtn {
|
||||
background-color: #3e8e41;
|
||||
}
|
Before Width: | Height: | Size: 15 KiB |
|
@ -1,285 +0,0 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import { io } from 'socket.io-client';
|
||||
import { Terminal } from '@xterm/xterm';
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { library, dom } from '@fortawesome/fontawesome-svg-core';
|
||||
import { faBars, faClipboard, faDownload, faKey, faCog } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
library.add(faBars, faClipboard, faDownload, faKey, faCog);
|
||||
dom.watch();
|
||||
|
||||
const debug = require('debug')('WebSSH2');
|
||||
require('@xterm/xterm/css/xterm.css');
|
||||
require('../css/style.css');
|
||||
|
||||
/* global Blob, logBtn, credentialsBtn, reauthBtn, downloadLogBtn */ // eslint-disable-line
|
||||
let sessionLogEnable = false;
|
||||
let loggedData = false;
|
||||
let allowreplay = false;
|
||||
let allowreauth = false;
|
||||
let sessionLog: string;
|
||||
let sessionFooter: any;
|
||||
let logDate: {
|
||||
getFullYear: () => any;
|
||||
getMonth: () => number;
|
||||
getDate: () => any;
|
||||
getHours: () => any;
|
||||
getMinutes: () => any;
|
||||
getSeconds: () => any;
|
||||
};
|
||||
let currentDate: Date;
|
||||
let myFile: string;
|
||||
let errorExists: boolean;
|
||||
const term = new Terminal();
|
||||
// DOM properties
|
||||
const logBtn = document.getElementById('logBtn');
|
||||
const credentialsBtn = document.getElementById('credentialsBtn');
|
||||
const reauthBtn = document.getElementById('reauthBtn');
|
||||
const downloadLogBtn = document.getElementById('downloadLogBtn');
|
||||
const status = document.getElementById('status');
|
||||
const header = document.getElementById('header');
|
||||
const footer = document.getElementById('footer');
|
||||
const countdown = document.getElementById('countdown');
|
||||
const fitAddon = new FitAddon();
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
term.loadAddon(fitAddon);
|
||||
term.open(terminalContainer);
|
||||
term.focus();
|
||||
fitAddon.fit();
|
||||
|
||||
const socket = io({
|
||||
path: '/ssh/socket.io',
|
||||
});
|
||||
|
||||
// reauthenticate
|
||||
function reauthSession () { // eslint-disable-line
|
||||
debug('re-authenticating');
|
||||
socket.emit('control', 'reauth');
|
||||
window.location.href = '/ssh/reauth';
|
||||
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
|
||||
if (loggedData === true) {
|
||||
myFile = `WebSSH2-${logDate.getFullYear()}${
|
||||
logDate.getMonth() + 1
|
||||
}${logDate.getDate()}_${logDate.getHours()}${logDate.getMinutes()}${logDate.getSeconds()}.log`;
|
||||
// regex should eliminate escape sequences from being logged.
|
||||
const blob = new Blob(
|
||||
[
|
||||
sessionLog.replace(
|
||||
// eslint-disable-next-line no-control-regex
|
||||
/[\u001b\u009b][[\]()#;?]*(?:\d{1,4}(?:;\d{0,4})*)?[0-9A-ORZcf-nqry=><;]/g,
|
||||
''
|
||||
),
|
||||
],
|
||||
{
|
||||
// eslint-disable-line no-control-regex
|
||||
type: 'text/plain',
|
||||
}
|
||||
);
|
||||
const elem = window.document.createElement('a');
|
||||
elem.href = window.URL.createObjectURL(blob);
|
||||
elem.download = myFile;
|
||||
document.body.appendChild(elem);
|
||||
elem.click();
|
||||
document.body.removeChild(elem);
|
||||
}
|
||||
term.focus();
|
||||
}
|
||||
// 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;
|
||||
loggedData = true;
|
||||
logBtn.innerHTML = '<i class="fas fa-clipboard fa-fw"></i> Start Log';
|
||||
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;
|
||||
term.focus();
|
||||
return false;
|
||||
}
|
||||
sessionLogEnable = true;
|
||||
loggedData = true;
|
||||
logBtn.innerHTML = '<i class="fas fa-cog fa-spin fa-fw"></i> Stop Log';
|
||||
downloadLogBtn.style.color = '#000';
|
||||
downloadLogBtn.addEventListener('click', downloadLog);
|
||||
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;
|
||||
term.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// replay password to server, requires
|
||||
function replayCredentials () { // eslint-disable-line
|
||||
socket.emit('control', 'replayCredentials');
|
||||
debug(`control: replayCredentials`);
|
||||
term.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
// draw/re-draw menu and reattach listeners
|
||||
// when dom is changed, listeners are abandonded
|
||||
function drawMenu() {
|
||||
logBtn.addEventListener('click', toggleLog);
|
||||
if (allowreauth) {
|
||||
reauthBtn.addEventListener('click', reauthSession);
|
||||
reauthBtn.style.display = 'block';
|
||||
}
|
||||
if (allowreplay) {
|
||||
credentialsBtn.addEventListener('click', replayCredentials);
|
||||
credentialsBtn.style.display = 'block';
|
||||
}
|
||||
if (loggedData) {
|
||||
downloadLogBtn.addEventListener('click', downloadLog);
|
||||
downloadLogBtn.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function resizeScreen() {
|
||||
fitAddon.fit();
|
||||
socket.emit('resize', { cols: term.cols, rows: term.rows });
|
||||
debug(`resize: ${JSON.stringify({ cols: term.cols, rows: term.rows })}`);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', resizeScreen, false);
|
||||
|
||||
term.onData((data) => {
|
||||
socket.emit('data', data);
|
||||
});
|
||||
|
||||
socket.on('data', (data: string | Uint8Array) => {
|
||||
term.write(data);
|
||||
if (sessionLogEnable) {
|
||||
sessionLog += data;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', () => {
|
||||
socket.emit('geometry', term.cols, term.rows);
|
||||
debug(`geometry: ${term.cols}, ${term.rows}`);
|
||||
});
|
||||
|
||||
socket.on(
|
||||
'setTerminalOpts',
|
||||
(data: {
|
||||
cursorBlink: boolean;
|
||||
scrollback: number;
|
||||
tabStopWidth: number;
|
||||
bellStyle: 'none' | 'sound';
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
letterSpacing: number;
|
||||
lineHeight: number;
|
||||
}) => {
|
||||
term.options = data;
|
||||
}
|
||||
);
|
||||
|
||||
socket.on('title', (data: string) => {
|
||||
document.title = data;
|
||||
});
|
||||
|
||||
socket.on('menu', () => {
|
||||
drawMenu();
|
||||
});
|
||||
|
||||
socket.on('status', (data: string) => {
|
||||
status.innerHTML = data;
|
||||
});
|
||||
|
||||
socket.on('ssherror', (data: string) => {
|
||||
status.innerHTML = data;
|
||||
status.style.backgroundColor = 'red';
|
||||
errorExists = true;
|
||||
});
|
||||
|
||||
socket.on('headerBackground', (data: string) => {
|
||||
header.style.backgroundColor = data;
|
||||
});
|
||||
|
||||
socket.on('header', (data: string) => {
|
||||
if (data) {
|
||||
header.innerHTML = data;
|
||||
header.style.display = 'block';
|
||||
// header is 19px and footer is 19px, recaculate new terminal-container and resize
|
||||
terminalContainer.style.height = 'calc(100% - 38px)';
|
||||
resizeScreen();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('footer', (data: string) => {
|
||||
sessionFooter = data;
|
||||
footer.innerHTML = data;
|
||||
});
|
||||
|
||||
socket.on('statusBackground', (data: string) => {
|
||||
status.style.backgroundColor = data;
|
||||
});
|
||||
|
||||
socket.on('allowreplay', (data: boolean) => {
|
||||
if (data === true) {
|
||||
debug(`allowreplay: ${data}`);
|
||||
allowreplay = true;
|
||||
drawMenu();
|
||||
} else {
|
||||
allowreplay = false;
|
||||
debug(`allowreplay: ${data}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('allowreauth', (data: boolean) => {
|
||||
if (data === true) {
|
||||
debug(`allowreauth: ${data}`);
|
||||
allowreauth = true;
|
||||
drawMenu();
|
||||
} else {
|
||||
allowreauth = false;
|
||||
debug(`allowreauth: ${data}`);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', (err: any) => {
|
||||
if (!errorExists) {
|
||||
status.style.backgroundColor = 'red';
|
||||
status.innerHTML = `WEBSOCKET SERVER DISCONNECTED: ${err}`;
|
||||
}
|
||||
socket.io.reconnection(false);
|
||||
countdown.classList.remove('active');
|
||||
});
|
||||
|
||||
socket.on('error', (err: any) => {
|
||||
if (!errorExists) {
|
||||
status.style.backgroundColor = 'red';
|
||||
status.innerHTML = `ERROR: ${err}`;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('reauth', () => {
|
||||
if (allowreauth) {
|
||||
reauthSession();
|
||||
}
|
||||
});
|
||||
|
||||
// safe shutdown
|
||||
let hasCountdownStarted = false;
|
||||
|
||||
socket.on('shutdownCountdownUpdate', (remainingSeconds: any) => {
|
||||
if (!hasCountdownStarted) {
|
||||
countdown.classList.add('active');
|
||||
hasCountdownStarted = true;
|
||||
}
|
||||
countdown.innerText = `Shutting down in ${remainingSeconds}s`;
|
||||
});
|
||||
|
||||
term.onTitleChange((title) => {
|
||||
document.title = title;
|
||||
});
|
|
@ -1,9 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./built",
|
||||
"allowJs": true,
|
||||
"target": "es6",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": ["./src/**/*"],
|
||||
}
|
173
app/config.js
Normal file
|
@ -0,0 +1,173 @@
|
|||
// server
|
||||
// app/config.js
|
||||
|
||||
const path = require("path")
|
||||
const fs = require("fs")
|
||||
const readConfig = require("read-config-ng")
|
||||
const { deepMerge, validateConfig } = require("./utils")
|
||||
const { generateSecureSecret } = require("./crypto-utils")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { ConfigError, handleError } = require("./errors")
|
||||
const { DEFAULTS } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("config")
|
||||
|
||||
const defaultConfig = {
|
||||
listen: {
|
||||
ip: "0.0.0.0",
|
||||
port: DEFAULTS.LISTEN_PORT
|
||||
},
|
||||
http: {
|
||||
origins: ["*:*"]
|
||||
},
|
||||
user: {
|
||||
name: null,
|
||||
password: null,
|
||||
privateKey: null,
|
||||
passphrase: null
|
||||
},
|
||||
ssh: {
|
||||
host: null,
|
||||
port: DEFAULTS.SSH_PORT,
|
||||
term: DEFAULTS.SSH_TERM,
|
||||
readyTimeout: 20000,
|
||||
keepaliveInterval: 120000,
|
||||
keepaliveCountMax: 10,
|
||||
alwaysSendKeyboardInteractivePrompts: false,
|
||||
disableInteractiveAuth: false,
|
||||
algorithms: {
|
||||
cipher: [
|
||||
"aes128-ctr",
|
||||
"aes192-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-gcm",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes256-gcm",
|
||||
"aes256-gcm@openssh.com",
|
||||
"aes256-cbc"
|
||||
],
|
||||
compress: ["none", "zlib@openssh.com", "zlib"],
|
||||
hmac: ["hmac-sha2-256", "hmac-sha2-512", "hmac-sha1"],
|
||||
kex: [
|
||||
"ecdh-sha2-nistp256",
|
||||
"ecdh-sha2-nistp384",
|
||||
"ecdh-sha2-nistp521",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
"diffie-hellman-group14-sha1"
|
||||
],
|
||||
serverHostKey: [
|
||||
"ecdsa-sha2-nistp256",
|
||||
"ecdsa-sha2-nistp384",
|
||||
"ecdsa-sha2-nistp521",
|
||||
"ssh-rsa"
|
||||
]
|
||||
}
|
||||
},
|
||||
header: {
|
||||
text: null,
|
||||
background: "green"
|
||||
},
|
||||
options: {
|
||||
challengeButton: true,
|
||||
autoLog: false,
|
||||
allowReauth: true,
|
||||
allowReconnect: true,
|
||||
allowReplay: true
|
||||
},
|
||||
session: {
|
||||
secret: process.env.WEBSSH_SESSION_SECRET || generateSecureSecret(),
|
||||
name: "webssh2.sid"
|
||||
}
|
||||
}
|
||||
|
||||
function getConfigPath() {
|
||||
const nodeRoot = path.dirname(require.main.filename)
|
||||
return path.join(nodeRoot, "config.json")
|
||||
}
|
||||
|
||||
function loadConfig() {
|
||||
const configPath = getConfigPath()
|
||||
|
||||
try {
|
||||
if (fs.existsSync(configPath)) {
|
||||
const providedConfig = readConfig.sync(configPath)
|
||||
const mergedConfig = deepMerge(
|
||||
JSON.parse(JSON.stringify(defaultConfig)),
|
||||
providedConfig
|
||||
)
|
||||
|
||||
if (process.env.PORT) {
|
||||
mergedConfig.listen.port = parseInt(process.env.PORT, 10)
|
||||
debug("Using PORT from environment: %s", mergedConfig.listen.port)
|
||||
}
|
||||
|
||||
const validatedConfig = validateConfig(mergedConfig)
|
||||
debug("Merged and validated configuration")
|
||||
return validatedConfig
|
||||
}
|
||||
debug("Missing config.json for webssh. Using default config")
|
||||
return defaultConfig
|
||||
} catch (err) {
|
||||
const error = new ConfigError(
|
||||
`Problem loading config.json for webssh: ${err.message}`
|
||||
)
|
||||
handleError(error)
|
||||
return defaultConfig
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and validates the WebSSH2 configuration.
|
||||
* Merges the default configuration with user-provided config.json if it exists.
|
||||
* Falls back to default configuration if config.json is missing or invalid.
|
||||
* Overrides listen.port with PORT environment variable if provided.
|
||||
* @returns {Object} Configuration object with the following structure:
|
||||
* @returns {Object} .listen - Server listening settings
|
||||
* @returns {string} .listen.ip - IP address to listen on (default: "0.0.0.0")
|
||||
* @returns {number} .listen.port - Port number to listen on
|
||||
* @returns {Object} .http - HTTP server settings
|
||||
* @returns {string[]} .http.origins - Allowed CORS origins (default: ["*:*"])
|
||||
* @returns {Object} .user - Default user credentials
|
||||
* @returns {string|null} .user.name - Default username
|
||||
* @returns {string|null} .user.password - Default password
|
||||
* @returns {Object} .ssh - SSH connection settings
|
||||
* @returns {string|null} .ssh.host - SSH server hostname
|
||||
* @returns {number} .ssh.port - SSH server port
|
||||
* @returns {string} .ssh.term - Terminal type
|
||||
* @returns {number} .ssh.readyTimeout - Connection timeout in ms
|
||||
* @returns {number} .ssh.keepaliveInterval - Keepalive interval in ms
|
||||
* @returns {number} .ssh.keepaliveCountMax - Max keepalive count
|
||||
* @returns {boolean} .ssh.alwaysSendKeyboardInteractivePrompts - Force keyboard-interactive
|
||||
* @returns {Object} .ssh.algorithms - Supported SSH algorithms
|
||||
* @returns {string[]} .ssh.algorithms.cipher - Supported ciphers
|
||||
* @returns {string[]} .ssh.algorithms.compress - Supported compression
|
||||
* @returns {string[]} .ssh.algorithms.hmac - Supported HMAC algorithms
|
||||
* @returns {string[]} .ssh.algorithms.kex - Supported key exchange
|
||||
* @returns {string[]} .ssh.algorithms.serverHostKey - Supported host key types
|
||||
* @returns {Object} .header - UI header settings
|
||||
* @returns {string|null} .header.text - Header text
|
||||
* @returns {string} .header.background - Header background color
|
||||
* @returns {Object} .options - Feature flags and options
|
||||
* @returns {boolean} .options.challengeButton - Show challenge button
|
||||
* @returns {boolean} .options.autoLog - Enable automatic logging
|
||||
* @returns {boolean} .options.allowReauth - Allow reauthentication
|
||||
* @returns {boolean} .options.allowReconnect - Allow reconnection
|
||||
* @returns {boolean} .options.allowReplay - Allow session replay
|
||||
* @returns {Object} .session - Session configuration
|
||||
* @returns {string} .session.secret - Session secret key
|
||||
* @returns {string} .session.name - Session cookie name
|
||||
*/
|
||||
const config = loadConfig()
|
||||
|
||||
function getCorsConfig() {
|
||||
return {
|
||||
origin: config.http.origins,
|
||||
methods: ["GET", "POST"],
|
||||
credentials: true
|
||||
}
|
||||
}
|
||||
|
||||
// Extend the config object with the getCorsConfig function
|
||||
config.getCorsConfig = getCorsConfig
|
||||
|
||||
module.exports = config
|
|
@ -1,83 +0,0 @@
|
|||
{
|
||||
"listen": {
|
||||
"ip": "0.0.0.0",
|
||||
"port": 2224
|
||||
},
|
||||
"socketio": {
|
||||
"serveClient": false,
|
||||
"path": "/ssh/socket.io",
|
||||
"origins": ["localhost:2222"],
|
||||
},
|
||||
"user": {
|
||||
"name": null,
|
||||
"password": null,
|
||||
"privatekey": null,
|
||||
"overridebasic": false
|
||||
},
|
||||
"ssh": {
|
||||
"host": null,
|
||||
"port": 22,
|
||||
"localAddress": null,
|
||||
"localPort": null,
|
||||
"term": "xterm-color",
|
||||
"readyTimeout": 20000,
|
||||
"keepaliveInterval": 120000,
|
||||
"keepaliveCountMax": 10,
|
||||
"allowedSubnets": []
|
||||
},
|
||||
"terminal": {
|
||||
"cursorBlink": true,
|
||||
"scrollback": 10000,
|
||||
"tabStopWidth": 8,
|
||||
"bellStyle": "sound",
|
||||
"fontSize": 14
|
||||
},
|
||||
"header": {
|
||||
"text": null,
|
||||
"background": "green"
|
||||
},
|
||||
"session": {
|
||||
"name": "WebSSH2",
|
||||
"secret": "mysecret"
|
||||
},
|
||||
"options": {
|
||||
"challengeButton": true,
|
||||
"allowreauth": false
|
||||
},
|
||||
"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"
|
||||
],
|
||||
"hmac": [
|
||||
"hmac-sha2-256",
|
||||
"hmac-sha2-512",
|
||||
"hmac-sha1"
|
||||
],
|
||||
"compress": [
|
||||
"none",
|
||||
"zlib@openssh.com",
|
||||
"zlib"
|
||||
]
|
||||
},
|
||||
"serverlog": {
|
||||
"client": false,
|
||||
"server": false
|
||||
},
|
||||
"accesslog": false,
|
||||
"verify": false,
|
||||
"safeShutdownDuration": 300
|
||||
}
|
110
app/configSchema.js
Normal file
|
@ -0,0 +1,110 @@
|
|||
/**
|
||||
* Schema for validating the config
|
||||
*/
|
||||
const configSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
listen: {
|
||||
type: "object",
|
||||
properties: {
|
||||
ip: { type: "string", format: "ipv4" },
|
||||
port: { type: "integer", minimum: 1, maximum: 65535 }
|
||||
},
|
||||
required: ["ip", "port"]
|
||||
},
|
||||
http: {
|
||||
type: "object",
|
||||
properties: {
|
||||
origins: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
}
|
||||
},
|
||||
required: ["origins"]
|
||||
},
|
||||
user: {
|
||||
type: "object",
|
||||
properties: {
|
||||
name: { type: ["string", "null"] },
|
||||
password: { type: ["string", "null"] },
|
||||
privateKey: { type: ["string", "null"] },
|
||||
passphrase: { type: ["string", "null"] }
|
||||
},
|
||||
required: ["name", "password"]
|
||||
},
|
||||
ssh: {
|
||||
type: "object",
|
||||
properties: {
|
||||
host: { type: ["string", "null"] },
|
||||
port: { type: "integer", minimum: 1, maximum: 65535 },
|
||||
term: { type: "string" },
|
||||
readyTimeout: { type: "integer" },
|
||||
keepaliveInterval: { type: "integer" },
|
||||
keepaliveCountMax: { type: "integer" },
|
||||
algorithms: {
|
||||
type: "object",
|
||||
properties: {
|
||||
kex: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
},
|
||||
cipher: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
},
|
||||
hmac: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
},
|
||||
serverHostKey: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
},
|
||||
compress: {
|
||||
type: "array",
|
||||
items: { type: "string" }
|
||||
}
|
||||
},
|
||||
required: ["kex", "cipher", "hmac", "serverHostKey", "compress"]
|
||||
}
|
||||
},
|
||||
required: [
|
||||
"host",
|
||||
"port",
|
||||
"term",
|
||||
"readyTimeout",
|
||||
"keepaliveInterval",
|
||||
"keepaliveCountMax"
|
||||
]
|
||||
},
|
||||
header: {
|
||||
type: "object",
|
||||
properties: {
|
||||
text: { type: ["string", "null"] },
|
||||
background: { type: "string" }
|
||||
},
|
||||
required: ["text", "background"]
|
||||
},
|
||||
options: {
|
||||
type: "object",
|
||||
properties: {
|
||||
challengeButton: { type: "boolean" },
|
||||
autoLog: { type: "boolean" },
|
||||
allowReauth: { type: "boolean" },
|
||||
allowReconnect: { type: "boolean" },
|
||||
allowReplay: { type: "boolean" }
|
||||
},
|
||||
required: ["challengeButton", "allowReauth", "allowReplay"]
|
||||
},
|
||||
session: {
|
||||
type: "object",
|
||||
properties: {
|
||||
secret: { type: "string" },
|
||||
name: { type: "string" }
|
||||
},
|
||||
required: ["secret", "name"]
|
||||
}
|
||||
},
|
||||
required: ["listen", "http", "user", "ssh", "header", "options"]
|
||||
}
|
||||
module.exports = configSchema
|
61
app/connectionHandler.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
// server
|
||||
// app/connectionHandler.js
|
||||
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { HTTP, MESSAGES, DEFAULTS } = require("./constants")
|
||||
const { modifyHtml } = require("./utils")
|
||||
|
||||
const debug = createNamespacedDebug("connectionHandler")
|
||||
|
||||
/**
|
||||
* Handle reading the file and processing the response.
|
||||
* @param {string} filePath - The path to the HTML file.
|
||||
* @param {Object} config - The configuration object to inject into the HTML.
|
||||
* @param {Object} res - The Express response object.
|
||||
*/
|
||||
function handleFileRead(filePath, config, res) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
fs.readFile(filePath, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
return res
|
||||
.status(HTTP.INTERNAL_SERVER_ERROR)
|
||||
.send(MESSAGES.CLIENT_FILE_ERROR)
|
||||
}
|
||||
|
||||
const modifiedHtml = modifyHtml(data, config)
|
||||
res.send(modifiedHtml)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the connection request and send the modified client HTML.
|
||||
* @param {Object} req - The Express request object.
|
||||
* @param {Object} res - The Express response object.
|
||||
*/
|
||||
function handleConnection(req, res) {
|
||||
debug("Handling connection req.path:", req.path)
|
||||
|
||||
const clientPath = path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"node_modules",
|
||||
"webssh2_client",
|
||||
"client",
|
||||
"public"
|
||||
)
|
||||
|
||||
const tempConfig = {
|
||||
socket: {
|
||||
url: `${req.protocol}://${req.get("host")}`,
|
||||
path: "/ssh/socket.io"
|
||||
},
|
||||
autoConnect: req.path.startsWith("/host/") // Automatically connect if path starts with /host/
|
||||
}
|
||||
|
||||
const filePath = path.join(clientPath, DEFAULTS.CLIENT_FILE)
|
||||
handleFileRead(filePath, tempConfig, res)
|
||||
}
|
||||
|
||||
module.exports = handleConnection
|
64
app/constants.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
// server
|
||||
// app/constants.js
|
||||
|
||||
const path = require("path")
|
||||
|
||||
/**
|
||||
* Error messages
|
||||
*/
|
||||
const MESSAGES = {
|
||||
INVALID_CREDENTIALS: "Invalid credentials format",
|
||||
SSH_CONNECTION_ERROR: "SSH CONNECTION ERROR",
|
||||
SHELL_ERROR: "SHELL ERROR",
|
||||
CONFIG_ERROR: "CONFIG_ERROR",
|
||||
UNEXPECTED_ERROR: "An unexpected error occurred",
|
||||
EXPRESS_APP_CONFIG_ERROR: "Failed to configure Express app",
|
||||
CLIENT_FILE_ERROR: "Error loading client file",
|
||||
FAILED_SESSION_SAVE: "Failed to save session",
|
||||
CONFIG_VALIDATION_ERROR: "Config validation error"
|
||||
}
|
||||
|
||||
/**
|
||||
* Default values
|
||||
*/
|
||||
const DEFAULTS = {
|
||||
SSH_PORT: 22,
|
||||
LISTEN_PORT: 2222,
|
||||
SSH_TERM: "xterm-color",
|
||||
IO_PING_TIMEOUT: 60000, // 1 minute
|
||||
IO_PING_INTERVAL: 25000, // 25 seconds
|
||||
IO_PATH: "/ssh/socket.io",
|
||||
WEBSSH2_CLIENT_PATH: path.resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"node_modules",
|
||||
"webssh2_client",
|
||||
"client",
|
||||
"public"
|
||||
),
|
||||
CLIENT_FILE: "client.htm",
|
||||
MAX_AUTH_ATTEMPTS: 2
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP Related
|
||||
*/
|
||||
const HTTP = {
|
||||
OK: 200,
|
||||
UNAUTHORIZED: 401,
|
||||
INTERNAL_SERVER_ERROR: 500,
|
||||
AUTHENTICATE: "WWW-Authenticate",
|
||||
REALM: 'Basic realm="WebSSH2"',
|
||||
AUTH_REQUIRED: "Authentication required.",
|
||||
COOKIE: "basicauth",
|
||||
PATH: "/ssh/host/",
|
||||
SAMESITE: "Strict",
|
||||
SESSION_SID: "webssh2_sid",
|
||||
CREDS_CLEARED: "Credentials cleared."
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
MESSAGES,
|
||||
DEFAULTS,
|
||||
HTTP
|
||||
}
|
16
app/crypto-utils.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
// server
|
||||
// app/crypto-utils.js
|
||||
|
||||
const crypto = require("crypto")
|
||||
|
||||
/**
|
||||
* Generates a secure random session secret
|
||||
* @returns {string} A random 32-byte hex string
|
||||
*/
|
||||
function generateSecureSecret() {
|
||||
return crypto.randomBytes(32).toString("hex")
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
generateSecureSecret
|
||||
}
|
74
app/errors.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
// server
|
||||
// app/errors.js
|
||||
|
||||
const util = require("util")
|
||||
const { logError, createNamespacedDebug } = require("./logger")
|
||||
const { HTTP, MESSAGES } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("errors")
|
||||
|
||||
/**
|
||||
* Custom error for WebSSH2
|
||||
* @param {string} message - The error message
|
||||
* @param {string} code - The error code
|
||||
*/
|
||||
function WebSSH2Error(message, code) {
|
||||
Error.captureStackTrace(this, this.constructor)
|
||||
this.name = this.constructor.name
|
||||
this.message = message
|
||||
this.code = code
|
||||
}
|
||||
|
||||
util.inherits(WebSSH2Error, Error)
|
||||
|
||||
/**
|
||||
* Custom error for configuration issues
|
||||
* @param {string} message - The error message
|
||||
*/
|
||||
function ConfigError(message) {
|
||||
WebSSH2Error.call(this, message, MESSAGES.CONFIG_ERROR)
|
||||
}
|
||||
|
||||
util.inherits(ConfigError, WebSSH2Error)
|
||||
|
||||
/**
|
||||
* Custom error for SSH connection issues
|
||||
* @param {string} message - The error message
|
||||
*/
|
||||
function SSHConnectionError(message) {
|
||||
WebSSH2Error.call(this, message, MESSAGES.SSH_CONNECTION_ERROR)
|
||||
}
|
||||
|
||||
util.inherits(SSHConnectionError, WebSSH2Error)
|
||||
|
||||
/**
|
||||
* Handles an error by logging it and optionally sending a response
|
||||
* @param {Error} err - The error to handle
|
||||
* @param {Object} [res] - The response object (if in an Express route)
|
||||
*/
|
||||
function handleError(err, res) {
|
||||
if (err instanceof WebSSH2Error) {
|
||||
logError(err.message, err)
|
||||
debug(err.message)
|
||||
if (res) {
|
||||
res
|
||||
.status(HTTP.INTERNAL_SERVER_ERROR)
|
||||
.json({ error: err.message, code: err.code })
|
||||
}
|
||||
} else {
|
||||
logError(MESSAGES.UNEXPECTED_ERROR, err)
|
||||
debug(`handleError: ${MESSAGES.UNEXPECTED_ERROR}: %O`, err)
|
||||
if (res) {
|
||||
res
|
||||
.status(HTTP.INTERNAL_SERVER_ERROR)
|
||||
.json({ error: MESSAGES.UNEXPECTED_ERROR })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
WebSSH2Error: WebSSH2Error,
|
||||
ConfigError: ConfigError,
|
||||
SSHConnectionError: SSHConnectionError,
|
||||
handleError: handleError
|
||||
}
|
32
app/index.js
|
@ -1,32 +0,0 @@
|
|||
/* eslint no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
/*
|
||||
* index.js
|
||||
*
|
||||
* WebSSH2 - Web to SSH2 gateway
|
||||
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||
* See LICENSE file
|
||||
*
|
||||
* test change
|
||||
*/
|
||||
|
||||
const { config } = require('./server/app');
|
||||
const { server } = require('./server/app');
|
||||
|
||||
server.listen({ host: config.listen.ip, port: config.listen.port });
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`WebSSH2 service listening on ${config.listen.ip}:${config.listen.port}`);
|
||||
|
||||
server.on('error', (err) => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
config.listen.port += 1;
|
||||
console.warn(`WebSSH2 Address in use, retrying on port ${config.listen.port}`);
|
||||
setTimeout(() => {
|
||||
server.listen(config.listen.port);
|
||||
}, 250);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`WebSSH2 server.listen ERROR: ${err.code}`);
|
||||
}
|
||||
});
|
36
app/io.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
const socketIo = require("socket.io")
|
||||
const sharedsession = require("express-socket.io-session")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { DEFAULTS } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("app")
|
||||
|
||||
/**
|
||||
* Configures Socket.IO with the given server
|
||||
* @param {http.Server} server - The HTTP server instance
|
||||
* @param {Function} sessionMiddleware - The session middleware
|
||||
* @param {Object} config - The configuration object
|
||||
* @returns {import('socket.io').Server} The Socket.IO server instance
|
||||
*/
|
||||
function configureSocketIO(server, sessionMiddleware, config) {
|
||||
const io = socketIo(server, {
|
||||
serveClient: false,
|
||||
path: DEFAULTS.IO_PATH,
|
||||
pingTimeout: DEFAULTS.IO_PING_TIMEOUT,
|
||||
pingInterval: DEFAULTS.IO_PING_INTERVAL,
|
||||
cors: config.getCorsConfig()
|
||||
})
|
||||
|
||||
// Share session with io sockets
|
||||
io.use(
|
||||
sharedsession(sessionMiddleware, {
|
||||
autoSave: true
|
||||
})
|
||||
)
|
||||
|
||||
debug("IO configured")
|
||||
|
||||
return io
|
||||
}
|
||||
|
||||
module.exports = { configureSocketIO }
|
|
@ -1,17 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"target": "ES2020",
|
||||
"jsx": "preserve",
|
||||
"strictFunctionTypes": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/node_modules/*"
|
||||
],
|
||||
"include": [
|
||||
"server/*.js",
|
||||
"index.js"
|
||||
]
|
||||
}
|
30
app/logger.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// server
|
||||
// app/logger.js
|
||||
|
||||
const createDebug = require("debug")
|
||||
|
||||
/**
|
||||
* Creates a debug function for a specific namespace
|
||||
* @param {string} namespace - The debug namespace
|
||||
* @returns {Function} The debug function
|
||||
*/
|
||||
function createNamespacedDebug(namespace) {
|
||||
return createDebug(`webssh2:${namespace}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message
|
||||
* @param {string} message - The error message
|
||||
* @param {Error} [error] - The error object
|
||||
*/
|
||||
function logError(message, error) {
|
||||
console.error(message)
|
||||
if (error) {
|
||||
console.error(`ERROR: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createNamespacedDebug: createNamespacedDebug,
|
||||
logError: logError
|
||||
}
|
138
app/middleware.js
Normal file
|
@ -0,0 +1,138 @@
|
|||
// server
|
||||
// app/middleware.js
|
||||
|
||||
const createDebug = require("debug")
|
||||
const session = require("express-session")
|
||||
const bodyParser = require("body-parser")
|
||||
|
||||
const debug = createDebug("webssh2:middleware")
|
||||
const basicAuth = require("basic-auth")
|
||||
const validator = require("validator")
|
||||
const { HTTP } = require("./constants")
|
||||
|
||||
/**
|
||||
* Middleware function that handles HTTP Basic Authentication for the application.
|
||||
*
|
||||
* If the `config.user.name` and `config.user.password` are set, it will use those
|
||||
* credentials to authenticate the request and set the `req.session.sshCredentials`
|
||||
* object with the username and password.
|
||||
*
|
||||
* If the `config.user.name` and `config.user.password` are not set, it will attempt
|
||||
* to use HTTP Basic Authentication to authenticate the request. It will validate and
|
||||
* sanitize the credentials, and set the `req.session.sshCredentials` object with the
|
||||
* username and password.
|
||||
*
|
||||
* The function will also set the `req.session.usedBasicAuth` flag to indicate that
|
||||
* Basic Authentication was used.
|
||||
*
|
||||
* If the authentication fails, the function will send a 401 Unauthorized response
|
||||
* with the appropriate WWW-Authenticate header.
|
||||
*/
|
||||
function createAuthMiddleware(config) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
return (req, res, next) => {
|
||||
// Check if username and either password or private key is configured
|
||||
if (config.user.name && (config.user.password || config.user.privateKey)) {
|
||||
req.session.sshCredentials = {
|
||||
username: config.user.name
|
||||
}
|
||||
|
||||
// Add credentials based on what's available
|
||||
if (config.user.privateKey) {
|
||||
req.session.sshCredentials.privateKey = config.user.privateKey
|
||||
}
|
||||
if (config.user.password) {
|
||||
req.session.sshCredentials.password = config.user.password
|
||||
}
|
||||
|
||||
req.session.usedBasicAuth = true
|
||||
return next()
|
||||
}
|
||||
// Scenario 2: Basic Auth
|
||||
|
||||
// If no configured credentials, fall back to Basic Auth
|
||||
debug("auth: Basic Auth")
|
||||
const credentials = basicAuth(req)
|
||||
if (!credentials) {
|
||||
res.setHeader(HTTP.AUTHENTICATE, HTTP.REALM)
|
||||
return res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED)
|
||||
}
|
||||
|
||||
// Validate and sanitize credentials
|
||||
req.session.sshCredentials = {
|
||||
username: validator.escape(credentials.name),
|
||||
password: credentials.pass
|
||||
}
|
||||
req.session.usedBasicAuth = true
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures session middleware
|
||||
* @param {Object} config - The configuration object
|
||||
* @returns {Function} The session middleware
|
||||
*/
|
||||
function createSessionMiddleware(config) {
|
||||
return session({
|
||||
secret: config.session.secret,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
name: config.session.name
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates body parser middleware
|
||||
* @returns {Function[]} Array of body parser middleware
|
||||
*/
|
||||
function createBodyParserMiddleware() {
|
||||
return [bodyParser.urlencoded({ extended: true }), bodyParser.json()]
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates cookie-setting middleware
|
||||
* @returns {Function} The cookie-setting middleware
|
||||
*/
|
||||
function createCookieMiddleware() {
|
||||
return (req, res, next) => {
|
||||
if (req.session.sshCredentials) {
|
||||
const cookieData = {
|
||||
host: req.session.sshCredentials.host,
|
||||
port: req.session.sshCredentials.port
|
||||
}
|
||||
res.cookie(HTTP.COOKIE, JSON.stringify(cookieData), {
|
||||
httpOnly: false,
|
||||
path: HTTP.PATH,
|
||||
sameSite: HTTP.SAMESITE
|
||||
})
|
||||
}
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all middleware to the Express app
|
||||
* @param {express.Application} app - The Express application
|
||||
* @param {Object} config - The configuration object
|
||||
* @returns {Object} An object containing the session middleware
|
||||
*/
|
||||
function applyMiddleware(app, config) {
|
||||
const sessionMiddleware = createSessionMiddleware(config)
|
||||
app.use(sessionMiddleware)
|
||||
|
||||
app.use(createBodyParserMiddleware())
|
||||
app.use(createCookieMiddleware())
|
||||
|
||||
debug("applyMiddleware")
|
||||
|
||||
return { sessionMiddleware }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
applyMiddleware,
|
||||
createAuthMiddleware,
|
||||
createSessionMiddleware,
|
||||
createBodyParserMiddleware,
|
||||
createCookieMiddleware
|
||||
}
|
11335
app/package-lock.json
generated
|
@ -1,89 +0,0 @@
|
|||
{
|
||||
"name": "webssh2",
|
||||
"version": "0.6.0-pre-1",
|
||||
"ignore": [
|
||||
".gitignore"
|
||||
],
|
||||
"bin": "./index.js",
|
||||
"description": "A Websocket to SSH2 gateway using term.js, socket.io, ssh2, and express",
|
||||
"homepage": "https://github.com/billchurch/WebSSH2",
|
||||
"keywords": [
|
||||
"ssh",
|
||||
"webssh",
|
||||
"terminal",
|
||||
"webterminal"
|
||||
],
|
||||
"license": "SEE LICENSE IN FILE - LICENSE",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/billchurch/WebSSH2.git"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Bill Church",
|
||||
"email": "wmchurch@gmail.com"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/billchurch/WebSSH2/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"cidr-matcher": "^2.1.1",
|
||||
"debug": "^4.3.4",
|
||||
"express": "^4.19.2",
|
||||
"express-session": "^1.18.0",
|
||||
"morgan": "~1.10.0",
|
||||
"read-config-ng": "^3.0.7",
|
||||
"serve-favicon": "^2.5.0",
|
||||
"socket.io": "^4.7.5",
|
||||
"ssh2": "^1.15.0",
|
||||
"validator": "^13.11.0",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"build": "webpack --progress --config scripts/webpack.prod.js",
|
||||
"builddev": "webpack --progress --config scripts/webpack.dev.js",
|
||||
"analyze": "webpack --json --config scripts/webpack.prod.js | webpack-bundle-size-analyzer",
|
||||
"test": "snyk test",
|
||||
"watch": "nodemon index.js",
|
||||
"cleanmac": "find . -name '.DS_Store' -type f -delete",
|
||||
"release": "standard-version"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.5.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.1",
|
||||
"@typescript-eslint/parser": "^7.7.1",
|
||||
"@xterm/addon-fit": "^0.10.0",
|
||||
"@xterm/xterm": "^5.5.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"css-loader": "^7.1.1",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"mini-css-extract-plugin": "^2.9.0",
|
||||
"nodaemon": "0.0.5",
|
||||
"npm-check-updates": "^16.14.20",
|
||||
"prettier": "^3.2.5",
|
||||
"snazzy": "^9.0.0",
|
||||
"snyk": "^1.1290.0",
|
||||
"socket.io-client": "^4.7.5",
|
||||
"source-map-loader": "^5.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"terser-webpack-plugin": "^5.3.10",
|
||||
"ts-loader": "^9.5.1",
|
||||
"typescript": "^5.4.5",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"webpack-merge": "^5.10.0"
|
||||
}
|
||||
}
|
133
app/routes.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
// server
|
||||
// app/routes.js
|
||||
|
||||
const express = require("express")
|
||||
|
||||
const {
|
||||
getValidatedHost,
|
||||
getValidatedPort,
|
||||
maskSensitiveData,
|
||||
validateSshTerm
|
||||
} = require("./utils")
|
||||
const handleConnection = require("./connectionHandler")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { createAuthMiddleware } = require("./middleware")
|
||||
const { ConfigError, handleError } = require("./errors")
|
||||
const { HTTP } = require("./constants")
|
||||
const { parseEnvVars } = require("./utils")
|
||||
|
||||
const debug = createNamespacedDebug("routes")
|
||||
|
||||
module.exports = function(config) {
|
||||
const router = express.Router()
|
||||
const auth = createAuthMiddleware(config)
|
||||
|
||||
// Scenario 1: No auth required, uses websocket authentication instead
|
||||
router.get("/", (req, res) => {
|
||||
debug("router.get./: Accessed / route")
|
||||
handleConnection(req, res)
|
||||
})
|
||||
|
||||
/**
|
||||
* Handles the "/host/" route, which requires authentication and uses the
|
||||
* `auth` middleware function to handle HTTP Basic Authentication.
|
||||
*
|
||||
* This route validates the host and port parameters, sets the `sshCredentials`
|
||||
* object in the session, and calls the `handleConnection` function to handle
|
||||
* the connection.
|
||||
*
|
||||
* If the `config.ssh.host` is not set, it throws a `ConfigError` with the
|
||||
* appropriate error message.
|
||||
*
|
||||
* @param {Object} req - The Express request object
|
||||
* @param {Object} res - The Express response object
|
||||
*/
|
||||
router.get("/host/", auth, (req, res) => {
|
||||
debug(`router.get.host: /ssh/host/ route`)
|
||||
const envVars = parseEnvVars(req.query.env)
|
||||
if (envVars) {
|
||||
req.session.envVars = envVars
|
||||
debug("routes: Parsed environment variables: %O", envVars)
|
||||
}
|
||||
|
||||
try {
|
||||
if (!config.ssh.host) {
|
||||
throw new ConfigError(
|
||||
"Host parameter required when default host not configured"
|
||||
)
|
||||
}
|
||||
|
||||
const { host } = config.ssh
|
||||
const port = getValidatedPort(req.query.port)
|
||||
const sshterm = validateSshTerm(req.query.sshterm)
|
||||
|
||||
req.session.sshCredentials = req.session.sshCredentials || {}
|
||||
req.session.sshCredentials.host = host
|
||||
req.session.sshCredentials.port = port
|
||||
if (req.query.sshterm) {
|
||||
req.session.sshCredentials.term = sshterm
|
||||
}
|
||||
req.session.usedBasicAuth = true
|
||||
|
||||
const sanitizedCredentials = maskSensitiveData(
|
||||
JSON.parse(JSON.stringify(req.session.sshCredentials))
|
||||
)
|
||||
debug("/ssh/host/ Credentials: ", sanitizedCredentials)
|
||||
|
||||
handleConnection(req, res, { host: host })
|
||||
} catch (err) {
|
||||
const error = new ConfigError(`Invalid configuration: ${err.message}`)
|
||||
handleError(error, res)
|
||||
}
|
||||
})
|
||||
|
||||
// Scenario 2: Auth required, uses HTTP Basic Auth
|
||||
router.get("/host/:host?", auth, (req, res) => {
|
||||
debug(`router.get.host: /ssh/host/${req.params.host} route`)
|
||||
const envVars = parseEnvVars(req.query.env)
|
||||
if (envVars) {
|
||||
req.session.envVars = envVars
|
||||
debug("routes: Parsed environment variables: %O", envVars)
|
||||
}
|
||||
|
||||
try {
|
||||
const host = getValidatedHost(req.params.host)
|
||||
const port = getValidatedPort(req.query.port)
|
||||
|
||||
// Validate and sanitize sshterm parameter if it exists
|
||||
const sshterm = validateSshTerm(req.query.sshterm)
|
||||
|
||||
req.session.sshCredentials = req.session.sshCredentials || {}
|
||||
req.session.sshCredentials.host = host
|
||||
req.session.sshCredentials.port = port
|
||||
if (req.query.sshterm) {
|
||||
req.session.sshCredentials.term = sshterm
|
||||
}
|
||||
req.session.usedBasicAuth = true
|
||||
|
||||
// Sanitize and log the sshCredentials object
|
||||
const sanitizedCredentials = maskSensitiveData(
|
||||
JSON.parse(JSON.stringify(req.session.sshCredentials))
|
||||
)
|
||||
debug("/ssh/host/ Credentials: ", sanitizedCredentials)
|
||||
|
||||
handleConnection(req, res, { host: host })
|
||||
} catch (err) {
|
||||
const error = new ConfigError(`Invalid configuration: ${err.message}`)
|
||||
handleError(error, res)
|
||||
}
|
||||
})
|
||||
|
||||
// Clear credentials route
|
||||
router.get("/clear-credentials", (req, res) => {
|
||||
req.session.sshCredentials = null
|
||||
res.status(HTTP.OK).send(HTTP.CREDENTIALS_CLEARED)
|
||||
})
|
||||
|
||||
router.get("/force-reconnect", (req, res) => {
|
||||
req.session.sshCredentials = null
|
||||
res.status(HTTP.UNAUTHORIZED).send(HTTP.AUTH_REQUIRED)
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const path = require('path');
|
||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||
|
||||
module.exports = {
|
||||
context: path.resolve('__dirname', '../'),
|
||||
resolve: {
|
||||
// Add '.ts' and '.tsx' as resolvable extensions.
|
||||
extensions: ['', '.webpack.js', '.web.js', '.ts', '.tsx', '.js'],
|
||||
},
|
||||
entry: {
|
||||
webssh2: './client/src/js/index.ts',
|
||||
},
|
||||
plugins: [
|
||||
new CleanWebpackPlugin(),
|
||||
new CopyWebpackPlugin({
|
||||
patterns: ['./client/src/client.htm', './client/src/favicon.ico'],
|
||||
}),
|
||||
new MiniCssExtractPlugin(),
|
||||
],
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
path: path.resolve(__dirname, '../client/public'),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader'],
|
||||
},
|
||||
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
},
|
||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||
{
|
||||
test: /\.js$/,
|
||||
loader: 'source-map-loader',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const merge = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
devtool: 'inline-source-map',
|
||||
devServer: {
|
||||
contentBase: '../client/public',
|
||||
},
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
const { merge } = require('webpack-merge');
|
||||
const common = require('./webpack.common.js');
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
ie8: false,
|
||||
safari10: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
37
app/server.js
Normal file
|
@ -0,0 +1,37 @@
|
|||
const http = require("http")
|
||||
// const { createNamespacedDebug } = require("./logger")
|
||||
|
||||
// const debug = createNamespacedDebug("server")
|
||||
/**
|
||||
* Creates and configures the HTTP server
|
||||
* @param {express.Application} app - The Express application instance
|
||||
* @returns {http.Server} The HTTP server instance
|
||||
*/
|
||||
function createServer(app) {
|
||||
return http.createServer(app)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles server errors
|
||||
* @param {Error} err - The error object
|
||||
*/
|
||||
function handleServerError(err) {
|
||||
console.error("HTTP Server ERROR: %O", err)
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the server
|
||||
* @param {http.Server} server - The server instance
|
||||
* @param {Object} config - The configuration object
|
||||
*/
|
||||
function startServer(server, config) {
|
||||
server.listen(config.listen.port, config.listen.ip, () => {
|
||||
console.log(
|
||||
`startServer: listening on ${config.listen.ip}:${config.listen.port}`
|
||||
)
|
||||
})
|
||||
|
||||
server.on("error", handleServerError)
|
||||
}
|
||||
|
||||
module.exports = { createServer, startServer }
|
|
@ -1,111 +0,0 @@
|
|||
/* jshint esversion: 6, asi: true, node: true */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
|
||||
no-console: ["error", { allow: ["warn", "error", "info"] }] */
|
||||
// app.js
|
||||
|
||||
// eslint-disable-next-line import/order
|
||||
const config = require('./config');
|
||||
const path = require('path');
|
||||
|
||||
const nodeRoot = path.dirname(require.main.filename);
|
||||
const publicPath = path.join(nodeRoot, 'client', 'public');
|
||||
const express = require('express');
|
||||
const logger = require('morgan');
|
||||
|
||||
const app = express();
|
||||
const server = require('http').createServer(app);
|
||||
const favicon = require('serve-favicon');
|
||||
const io = require('socket.io')(server, config.socketio);
|
||||
const session = require('express-session')(config.express);
|
||||
|
||||
const appSocket = require('./socket');
|
||||
const { setDefaultCredentials, basicAuth } = require('./util');
|
||||
const { webssh2debug } = require('./logging');
|
||||
const { reauth, connect, notfound, handleErrors } = require('./routes');
|
||||
|
||||
setDefaultCredentials(config.user);
|
||||
|
||||
// safe shutdown
|
||||
let remainingSeconds = config.safeShutdownDuration;
|
||||
let shutdownMode = false;
|
||||
let shutdownInterval;
|
||||
let connectionCount = 0;
|
||||
// eslint-disable-next-line consistent-return
|
||||
function safeShutdownGuard(req, res, next) {
|
||||
if (!shutdownMode) return next();
|
||||
res.status(503).end('Service unavailable: Server shutting down');
|
||||
}
|
||||
// express
|
||||
app.use(safeShutdownGuard);
|
||||
app.use(session);
|
||||
if (config.accesslog) app.use(logger('common'));
|
||||
app.disable('x-powered-by');
|
||||
app.use(favicon(path.join(publicPath, 'favicon.ico')));
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.post('/ssh/host/:host?', connect);
|
||||
app.post('/ssh', express.static(publicPath, config.express.ssh));
|
||||
app.use('/ssh', express.static(publicPath, config.express.ssh));
|
||||
app.use(basicAuth);
|
||||
app.get('/ssh/reauth', reauth);
|
||||
app.get('/ssh/host/:host?', connect);
|
||||
app.use(notfound);
|
||||
app.use(handleErrors);
|
||||
|
||||
// clean stop
|
||||
function stopApp(reason) {
|
||||
shutdownMode = false;
|
||||
if (reason) console.info(`Stopping: ${reason}`);
|
||||
clearInterval(shutdownInterval);
|
||||
io.close();
|
||||
server.close();
|
||||
}
|
||||
|
||||
// bring up socket
|
||||
io.on('connection', appSocket);
|
||||
|
||||
// socket.io
|
||||
// expose express session with socket.request.session
|
||||
io.use((socket, next) => {
|
||||
socket.request.res ? session(socket.request, socket.request.res, next) : next(next); // eslint disable-line
|
||||
});
|
||||
|
||||
function countdownTimer() {
|
||||
if (!shutdownMode) clearInterval(shutdownInterval);
|
||||
remainingSeconds -= 1;
|
||||
if (remainingSeconds <= 0) {
|
||||
stopApp('Countdown is over');
|
||||
} else io.emit('shutdownCountdownUpdate', remainingSeconds);
|
||||
}
|
||||
|
||||
const signals = ['SIGTERM', 'SIGINT'];
|
||||
signals.forEach((signal) =>
|
||||
process.on(signal, () => {
|
||||
if (shutdownMode) stopApp('Safe shutdown aborted, force quitting');
|
||||
if (!connectionCount > 0) stopApp('All connections ended');
|
||||
shutdownMode = true;
|
||||
console.error(
|
||||
`\r\n${connectionCount} client(s) are still connected.\r\nStarting a ${remainingSeconds} seconds countdown.\r\nPress Ctrl+C again to force quit`
|
||||
);
|
||||
if (!shutdownInterval) shutdownInterval = setInterval(countdownTimer, 1000);
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = { server, config };
|
||||
|
||||
const onConnection = (socket) => {
|
||||
connectionCount += 1;
|
||||
socket.on('disconnect', () => {
|
||||
connectionCount -= 1;
|
||||
if (connectionCount <= 0 && shutdownMode) {
|
||||
stopApp('All clients disconnected');
|
||||
}
|
||||
});
|
||||
socket.on('geometry', (cols, rows) => {
|
||||
// TODO need to rework how we pass settings to ssh2, this is less than ideal
|
||||
socket.request.session.ssh.cols = cols;
|
||||
socket.request.session.ssh.rows = rows;
|
||||
webssh2debug(socket, `SOCKET GEOMETRY: termCols = ${cols}, termRows = ${rows}`);
|
||||
});
|
||||
};
|
||||
|
||||
io.on('connection', onConnection);
|
|
@ -1,136 +0,0 @@
|
|||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
|
||||
no-console: ["error", { allow: ["warn", "error", "info"] }] */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const debugWebSSH2 = require('debug')('WebSSH2');
|
||||
const crypto = require('crypto');
|
||||
const util = require('util');
|
||||
const readconfig = require('read-config-ng');
|
||||
|
||||
const nodeRoot = path.dirname(require.main.filename);
|
||||
const configPath = path.join(nodeRoot, 'config.json');
|
||||
|
||||
let myConfig;
|
||||
// establish defaults
|
||||
const configDefault = {
|
||||
listen: {
|
||||
ip: '0.0.0.0',
|
||||
port: 2222,
|
||||
},
|
||||
socketio: {
|
||||
serveClient: false,
|
||||
path: '/ssh/socket.io',
|
||||
origins: ['localhost:2222'],
|
||||
},
|
||||
express: {
|
||||
secret: crypto.randomBytes(20).toString('hex'),
|
||||
name: 'WebSSH2',
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
unset: 'destroy',
|
||||
ssh: {
|
||||
dotfiles: 'ignore',
|
||||
etag: false,
|
||||
extensions: ['htm', 'html'],
|
||||
index: false,
|
||||
maxAge: '1s',
|
||||
redirect: false,
|
||||
setHeaders(res) {
|
||||
res.set('x-timestamp', Date.now());
|
||||
},
|
||||
},
|
||||
},
|
||||
user: {
|
||||
name: null,
|
||||
password: null,
|
||||
privatekey: null,
|
||||
overridebasic: false,
|
||||
},
|
||||
ssh: {
|
||||
host: null,
|
||||
port: 22,
|
||||
term: 'xterm-color',
|
||||
readyTimeout: 20000,
|
||||
keepaliveInterval: 120000,
|
||||
keepaliveCountMax: 10,
|
||||
allowedSubnets: [],
|
||||
},
|
||||
terminal: {
|
||||
cursorBlink: true,
|
||||
scrollback: 10000,
|
||||
tabStopWidth: 8,
|
||||
bellStyle: 'sound',
|
||||
},
|
||||
header: {
|
||||
text: null,
|
||||
background: 'green',
|
||||
},
|
||||
options: {
|
||||
challengeButton: true,
|
||||
allowreauth: 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',
|
||||
],
|
||||
hmac: ['hmac-sha2-256', 'hmac-sha2-512', 'hmac-sha1'],
|
||||
compress: ['none', 'zlib@openssh.com', 'zlib'],
|
||||
},
|
||||
serverlog: {
|
||||
client: false,
|
||||
server: false,
|
||||
},
|
||||
accesslog: false,
|
||||
verify: false,
|
||||
safeShutdownDuration: 300,
|
||||
};
|
||||
|
||||
// test if config.json exists, if not provide error message but try to run anyway
|
||||
try {
|
||||
if (!fs.existsSync(configPath)) {
|
||||
console.error(
|
||||
`\n\nERROR: Missing config.json for WebSSH2. Current config: ${util.inspect(myConfig)}`
|
||||
);
|
||||
console.error('\n See config.json.sample for details\n\n');
|
||||
}
|
||||
console.info(`WebSSH2 service reading config from: ${configPath}`);
|
||||
const configFile = readconfig(configPath, { override: true });
|
||||
// myConfig = merger.mergeObjects([configDefault, configFile]);
|
||||
myConfig = { ...configDefault, ...configFile };
|
||||
debugWebSSH2(`\nCurrent config: ${util.inspect(myConfig)}`);
|
||||
} catch (err) {
|
||||
myConfig = configDefault;
|
||||
console.error(
|
||||
`\n\nERROR: Missing config.json for WebSSH2. Current config: ${util.inspect(myConfig)}`
|
||||
);
|
||||
console.error('\n See config.json.sample for details\n\n');
|
||||
console.error(`ERROR:\n\n ${err}`);
|
||||
}
|
||||
const config = myConfig;
|
||||
|
||||
if (process.env.LISTEN) config.listen.ip = process.env.LISTEN;
|
||||
|
||||
if (process.env.PORT) config.listen.port = process.env.PORT;
|
||||
|
||||
if (process.env.SOCKETIO_ORIGINS) config.socketio.origins = process.env.SOCKETIO_ORIGINS;
|
||||
|
||||
if (process.env.SOCKETIO_PATH) config.socketio.path = process.env.SOCKETIO_PATH;
|
||||
|
||||
if (process.env.SOCKETIO_SERVECLIENT)
|
||||
config.socketio.serveClient = process.env.SOCKETIO_SERVECLIENT;
|
||||
|
||||
module.exports = config;
|
|
@ -1,36 +0,0 @@
|
|||
<head><title>Post Test</title></head>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Credentials over HTTP POST test</h1>
|
||||
<p>This is a test to demonstrate sending credentials over POST instead of requiring HTTP Basic. If you use this, be sure to secure the app/site with HTTPS!</p>
|
||||
<form method="POST" action="http://localhost:2222/ssh/host/192.168.0.1">
|
||||
<p>
|
||||
<label for="username">Username</label>
|
||||
<input name="username">
|
||||
</p>
|
||||
<label for="userpassword">Password</label>
|
||||
<input name="userpassword" type="password">
|
||||
</p>
|
||||
<p>
|
||||
<label for="header">Header text</label>
|
||||
<input name="header" value="This is a test">
|
||||
<p>
|
||||
<label for="headerBackground">Header Background Color</label>
|
||||
<input name="headerBackground" value="red">
|
||||
</p>
|
||||
<fieldset>
|
||||
<legend>Cursor Blink:</legend>
|
||||
<div>
|
||||
<input type="radio" id="false" name="cursorBlink" value="false"
|
||||
checked>
|
||||
<label for="false">False</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="radio" id="true" name="cursorBlink" value="true">
|
||||
<label for="true">True</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button>Login</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
|
@ -1,47 +0,0 @@
|
|||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
|
||||
no-console: ["error", { allow: ["warn", "error", "info"] }] */
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// logging.js
|
||||
// private
|
||||
|
||||
const debug = require('debug');
|
||||
const util = require('util');
|
||||
|
||||
/**
|
||||
* generate consistent prefix for log messages
|
||||
* with epress session id and socket session id
|
||||
* @param {object} socket Socket information
|
||||
*/
|
||||
function prefix(socket) {
|
||||
return `(${socket.request.sessionID}/${socket.id})`;
|
||||
}
|
||||
|
||||
// public
|
||||
function webssh2debug(socket, msg) {
|
||||
debug('WebSSH2')(`${prefix(socket)} ${msg}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* audit log to console
|
||||
* @param {object} socket Socket information
|
||||
* @param {object} msg log message
|
||||
*/
|
||||
function auditLog(socket, msg) {
|
||||
console.info(`WebSSH2 ${prefix(socket)} AUDIT: ${msg}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* logs error to socket client (if connected)
|
||||
* and console.
|
||||
* @param {object} socket Socket information
|
||||
* @param {string} myFunc Function calling this function
|
||||
* @param {object} err error object or error message
|
||||
*/
|
||||
function logError(socket, myFunc, err) {
|
||||
console.error(`WebSSH2 ${prefix(socket)} ERROR: ${myFunc}: ${err}`);
|
||||
webssh2debug(socket, `logError: ${myFunc}: ${util.inspect(err)}`);
|
||||
if (!socket.request.session) return;
|
||||
socket.emit('ssherror', `SSH ${myFunc}: ${err}`);
|
||||
}
|
||||
|
||||
module.exports = { logError, auditLog, webssh2debug };
|
|
@ -1,203 +0,0 @@
|
|||
/* eslint-disable no-console */
|
||||
// ssh.js
|
||||
const validator = require('validator');
|
||||
const path = require('path');
|
||||
|
||||
const nodeRoot = path.dirname(require.main.filename);
|
||||
|
||||
const publicPath = path.join(nodeRoot, 'client', 'public');
|
||||
const { parseBool } = require('./util');
|
||||
const config = require('./config');
|
||||
|
||||
exports.reauth = function reauth(req, res) {
|
||||
let { referer } = req.headers;
|
||||
if (!validator.isURL(referer, { host_whitelist: ['localhost'] })) {
|
||||
console.error(
|
||||
`WebSSH2 (${req.sessionID}) ERROR: Referrer '${referer}' for '/reauth' invalid. Setting to '/' which will probably fail.`
|
||||
);
|
||||
referer = '/';
|
||||
}
|
||||
res
|
||||
.status(401)
|
||||
.send(
|
||||
`<!DOCTYPE html><html><head><meta http-equiv="refresh" content="0; url=${referer}"></head><body bgcolor="#000"></body></html>`
|
||||
);
|
||||
};
|
||||
|
||||
exports.connect = function connect(req, res) {
|
||||
res.sendFile(path.join(path.join(publicPath, 'client.htm')));
|
||||
|
||||
let { host, port } = config.ssh;
|
||||
let { text: header, background: headerBackground } = config.header;
|
||||
let { term: sshterm, readyTimeout } = config.ssh;
|
||||
let {
|
||||
cursorBlink,
|
||||
scrollback,
|
||||
tabStopWidth,
|
||||
bellStyle,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
letterSpacing,
|
||||
lineHeight,
|
||||
} = config.terminal;
|
||||
|
||||
// capture, assign, and validate variables
|
||||
|
||||
if (req.params?.host) {
|
||||
if (
|
||||
validator.isIP(`${req.params.host}`) ||
|
||||
validator.isFQDN(req.params.host) ||
|
||||
/^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.params.host)
|
||||
) {
|
||||
host = req.params.host;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.method === 'POST' && req.body.username && req.body.userpassword) {
|
||||
req.session.username = req.body.username;
|
||||
req.session.userpassword = req.body.userpassword;
|
||||
|
||||
if (req.body.port && validator.isInt(`${req.body.port}`, { min: 1, max: 65535 }))
|
||||
port = req.body.port;
|
||||
|
||||
if (req.body.header) header = req.body.header;
|
||||
|
||||
if (req.body.headerBackground) {
|
||||
headerBackground = req.body.headerBackground;
|
||||
console.log(`background: ${req.body.headerBackground}`);
|
||||
}
|
||||
|
||||
if (req.body.sshterm && /^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.body.sshterm))
|
||||
sshterm = req.body.sshterm;
|
||||
|
||||
if (req.body.cursorBlink && validator.isBoolean(`${req.body.cursorBlink}`))
|
||||
cursorBlink = parseBool(req.body.cursorBlink);
|
||||
|
||||
if (req.body.scrollback && validator.isInt(`${req.body.scrollback}`, { min: 1, max: 200000 }))
|
||||
scrollback = req.body.scrollback;
|
||||
|
||||
if (req.body.tabStopWidth && validator.isInt(`${req.body.tabStopWidth}`, { min: 1, max: 100 }))
|
||||
tabStopWidth = req.body.tabStopWidth;
|
||||
|
||||
if (req.body.bellStyle && ['sound', 'none'].indexOf(req.body.bellStyle) > -1)
|
||||
bellStyle = req.body.bellStyle;
|
||||
|
||||
if (
|
||||
req.body.readyTimeout &&
|
||||
validator.isInt(`${req.body.readyTimeout}`, { min: 1, max: 300000 })
|
||||
)
|
||||
readyTimeout = req.body.readyTimeout;
|
||||
|
||||
if (req.body.fontSize && validator.isNumeric(`${req.body.fontSize}`))
|
||||
fontSize = req.body.fontSize;
|
||||
|
||||
if (req.body.fontFamily) fontFamily = req.body.fontFamily;
|
||||
|
||||
if (req.body.letterSpacing && validator.isNumeric(`${req.body.letterSpacing}`))
|
||||
letterSpacing = req.body.letterSpacing;
|
||||
|
||||
if (req.body.lineHeight && validator.isNumeric(`${req.body.lineHeight}`))
|
||||
lineHeight = req.body.lineHeight;
|
||||
}
|
||||
|
||||
if (req.method === 'GET') {
|
||||
if (req.query?.port && validator.isInt(`${req.query.port}`, { min: 1, max: 65535 }))
|
||||
port = req.query.port;
|
||||
|
||||
if (req.query?.header) header = req.query.header;
|
||||
|
||||
if (req.query?.headerBackground) headerBackground = req.query.headerBackground;
|
||||
|
||||
if (req.query?.sshterm && /^(([a-z]|[A-Z]|\d|[!^(){}\-_~])+)?\w$/.test(req.query.sshterm))
|
||||
sshterm = req.query.sshterm;
|
||||
|
||||
if (req.query?.cursorBlink && validator.isBoolean(`${req.query.cursorBlink}`))
|
||||
cursorBlink = parseBool(req.query.cursorBlink);
|
||||
|
||||
if (
|
||||
req.query?.scrollback &&
|
||||
validator.isInt(`${req.query.scrollback}`, { min: 1, max: 200000 })
|
||||
)
|
||||
scrollback = req.query.scrollback;
|
||||
|
||||
if (
|
||||
req.query?.tabStopWidth &&
|
||||
validator.isInt(`${req.query.tabStopWidth}`, { min: 1, max: 100 })
|
||||
)
|
||||
tabStopWidth = req.query.tabStopWidth;
|
||||
|
||||
if (req.query?.bellStyle && ['sound', 'none'].indexOf(req.query.bellStyle) > -1)
|
||||
bellStyle = req.query.bellStyle;
|
||||
|
||||
if (
|
||||
req.query?.readyTimeout &&
|
||||
validator.isInt(`${req.query.readyTimeout}`, { min: 1, max: 300000 })
|
||||
)
|
||||
readyTimeout = req.query.readyTimeout;
|
||||
|
||||
if (req.query?.fontSize && validator.isNumeric(`${req.query.fontSize}`))
|
||||
fontSize = req.query.fontSize;
|
||||
|
||||
if (req.query?.fontFamily) fontFamily = req.query.fontFamily;
|
||||
|
||||
if (req.query?.lineHeight && validator.isNumeric(`${req.query.lineHeight}`))
|
||||
lineHeight = req.query.lineHeight;
|
||||
|
||||
if (req.query?.letterSpacing && validator.isNumeric(`${req.query.letterSpacing}`))
|
||||
letterSpacing = req.query.letterSpacing;
|
||||
}
|
||||
|
||||
req.session.ssh = {
|
||||
host,
|
||||
port,
|
||||
localAddress: config.ssh.localAddress,
|
||||
localPort: config.ssh.localPort,
|
||||
header: {
|
||||
name: header,
|
||||
background: headerBackground,
|
||||
},
|
||||
algorithms: config.algorithms,
|
||||
keepaliveInterval: config.ssh.keepaliveInterval,
|
||||
keepaliveCountMax: config.ssh.keepaliveCountMax,
|
||||
allowedSubnets: config.ssh.allowedSubnets,
|
||||
term: sshterm,
|
||||
terminal: {
|
||||
cursorBlink,
|
||||
scrollback,
|
||||
tabStopWidth,
|
||||
bellStyle,
|
||||
fontSize,
|
||||
fontFamily,
|
||||
letterSpacing,
|
||||
lineHeight,
|
||||
},
|
||||
cols: null,
|
||||
rows: null,
|
||||
allowreplay:
|
||||
config.options.challengeButton ||
|
||||
(validator.isBoolean(`${req.headers.allowreplay}`)
|
||||
? parseBool(req.headers.allowreplay)
|
||||
: false),
|
||||
allowreauth: config.options.allowreauth || false,
|
||||
mrhsession:
|
||||
validator.isAlphanumeric(`${req.headers.mrhsession}`) && req.headers.mrhsession
|
||||
? req.headers.mrhsession
|
||||
: 'none',
|
||||
serverlog: {
|
||||
client: config.serverlog.client || false,
|
||||
server: config.serverlog.server || false,
|
||||
},
|
||||
readyTimeout,
|
||||
};
|
||||
if (req.session.ssh.header.name) validator.escape(req.session.ssh.header.name);
|
||||
if (req.session.ssh.header.background) validator.escape(req.session.ssh.header.background);
|
||||
};
|
||||
|
||||
exports.notfound = function notfound(_req, res) {
|
||||
res.status(404).send("Sorry, can't find that!");
|
||||
};
|
||||
|
||||
exports.handleErrors = function handleErrors(err, _req, res) {
|
||||
console.error(err.stack);
|
||||
res.status(500).send('Something broke!');
|
||||
};
|
|
@ -1,241 +0,0 @@
|
|||
/* eslint-disable complexity */
|
||||
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true, "allowTernary": true }],
|
||||
no-console: ["error", { allow: ["warn", "error"] }] */
|
||||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// socket.js
|
||||
|
||||
// private
|
||||
const debug = require('debug');
|
||||
const SSH = require('ssh2').Client;
|
||||
const CIDRMatcher = require('cidr-matcher');
|
||||
const validator = require('validator');
|
||||
const dnsPromises = require('dns').promises;
|
||||
const util = require('util');
|
||||
const { webssh2debug, auditLog, logError } = require('./logging');
|
||||
|
||||
/**
|
||||
* parse conn errors
|
||||
* @param {object} socket Socket object
|
||||
* @param {object} err Error object
|
||||
*/
|
||||
function connError(socket, err) {
|
||||
let msg = util.inspect(err);
|
||||
const { session } = socket.request;
|
||||
if (err?.level === 'client-authentication') {
|
||||
msg = `Authentication failure user=${session.username} from=${socket.handshake.address}`;
|
||||
socket.emit('allowreauth', session.ssh.allowreauth);
|
||||
socket.emit('reauth');
|
||||
}
|
||||
if (err?.code === 'ENOTFOUND') {
|
||||
msg = `Host not found: ${err.hostname}`;
|
||||
}
|
||||
if (err?.level === 'client-timeout') {
|
||||
msg = `Connection Timeout: ${session.ssh.host}`;
|
||||
}
|
||||
logError(socket, 'CONN ERROR', msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* check ssh host is in allowed subnet
|
||||
* @param {object} socket Socket information
|
||||
*/
|
||||
async function checkSubnet(socket) {
|
||||
let ipaddress = socket.request.session.ssh.host;
|
||||
if (!validator.isIP(`${ipaddress}`)) {
|
||||
try {
|
||||
const result = await dnsPromises.lookup(socket.request.session.ssh.host);
|
||||
ipaddress = result.address;
|
||||
} catch (err) {
|
||||
logError(
|
||||
socket,
|
||||
'CHECK SUBNET',
|
||||
`${err.code}: ${err.hostname} user=${socket.request.session.username} from=${socket.handshake.address}`
|
||||
);
|
||||
socket.emit('ssherror', '404 HOST IP NOT FOUND');
|
||||
socket.disconnect(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const matcher = new CIDRMatcher(socket.request.session.ssh.allowedSubnets);
|
||||
if (!matcher.contains(ipaddress)) {
|
||||
logError(
|
||||
socket,
|
||||
'CHECK SUBNET',
|
||||
`Requested host ${ipaddress} outside configured subnets / REJECTED user=${socket.request.session.username} from=${socket.handshake.address}`
|
||||
);
|
||||
socket.emit('ssherror', '401 UNAUTHORIZED');
|
||||
socket.disconnect(true);
|
||||
}
|
||||
}
|
||||
|
||||
// public
|
||||
module.exports = function appSocket(socket) {
|
||||
let login = false;
|
||||
|
||||
socket.once('disconnecting', (reason) => {
|
||||
webssh2debug(socket, `SOCKET DISCONNECTING: ${reason}`);
|
||||
if (login === true) {
|
||||
auditLog(
|
||||
socket,
|
||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||
);
|
||||
login = false;
|
||||
}
|
||||
});
|
||||
|
||||
async function setupConnection() {
|
||||
// if websocket connection arrives without an express session, kill it
|
||||
if (!socket.request.session) {
|
||||
socket.emit('401 UNAUTHORIZED');
|
||||
webssh2debug(socket, 'SOCKET: No Express Session / REJECTED');
|
||||
socket.disconnect(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// If configured, check that requsted host is in a permitted subnet
|
||||
if (socket.request.session?.ssh?.allowedSubnets?.length > 0) {
|
||||
checkSubnet(socket);
|
||||
}
|
||||
|
||||
const conn = new SSH();
|
||||
|
||||
conn.on('banner', (data) => {
|
||||
// need to convert to cr/lf for proper formatting
|
||||
socket.emit('data', data.replace(/\r?\n/g, '\r\n').toString('utf-8'));
|
||||
});
|
||||
|
||||
conn.on('handshake', () => {
|
||||
socket.emit('setTerminalOpts', socket.request.session.ssh.terminal);
|
||||
socket.emit('menu');
|
||||
socket.emit('allowreauth', socket.request.session.ssh.allowreauth);
|
||||
socket.emit('title', `ssh://${socket.request.session.ssh.host}`);
|
||||
if (socket.request.session.ssh.header.background)
|
||||
socket.emit('headerBackground', socket.request.session.ssh.header.background);
|
||||
if (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}`
|
||||
);
|
||||
});
|
||||
|
||||
conn.on('ready', () => {
|
||||
webssh2debug(
|
||||
socket,
|
||||
`CONN READY: LOGIN: user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host} port=${socket.request.session.ssh.port} allowreplay=${socket.request.session.ssh.allowreplay} term=${socket.request.session.ssh.term}`
|
||||
);
|
||||
auditLog(
|
||||
socket,
|
||||
`LOGIN user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||
);
|
||||
login = true;
|
||||
socket.emit('status', 'SSH CONNECTION ESTABLISHED');
|
||||
socket.emit('statusBackground', 'green');
|
||||
socket.emit('allowreplay', socket.request.session.ssh.allowreplay);
|
||||
const { term, cols, rows } = socket.request.session.ssh;
|
||||
conn.shell({ term, cols, rows }, (err, stream) => {
|
||||
if (err) {
|
||||
logError(socket, `EXEC ERROR`, err);
|
||||
conn.end();
|
||||
socket.disconnect(true);
|
||||
return;
|
||||
}
|
||||
socket.once('disconnect', (reason) => {
|
||||
webssh2debug(socket, `CLIENT SOCKET DISCONNECT: ${util.inspect(reason)}`);
|
||||
conn.end();
|
||||
socket.request.session.destroy();
|
||||
});
|
||||
socket.on('error', (errMsg) => {
|
||||
webssh2debug(socket, `SOCKET ERROR: ${errMsg}`);
|
||||
logError(socket, 'SOCKET ERROR', errMsg);
|
||||
conn.end();
|
||||
socket.disconnect(true);
|
||||
});
|
||||
socket.on('control', (controlData) => {
|
||||
if (controlData === 'replayCredentials' && socket.request.session.ssh.allowreplay) {
|
||||
stream.write(`${socket.request.session.userpassword}\n`);
|
||||
}
|
||||
if (controlData === 'reauth' && socket.request.session.username && login === true) {
|
||||
auditLog(
|
||||
socket,
|
||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||
);
|
||||
login = false;
|
||||
conn.end();
|
||||
socket.disconnect(true);
|
||||
}
|
||||
webssh2debug(socket, `SOCKET CONTROL: ${controlData}`);
|
||||
});
|
||||
socket.on('resize', (data) => {
|
||||
stream.setWindow(data.rows, data.cols);
|
||||
webssh2debug(socket, `SOCKET RESIZE: ${JSON.stringify([data.rows, data.cols])}`);
|
||||
});
|
||||
socket.on('data', (data) => {
|
||||
stream.write(data);
|
||||
});
|
||||
stream.on('data', (data) => {
|
||||
socket.emit('data', data.toString('utf-8'));
|
||||
});
|
||||
stream.on('close', (code, signal) => {
|
||||
webssh2debug(socket, `STREAM CLOSE: ${util.inspect([code, signal])}`);
|
||||
if (socket.request.session?.username && login === true) {
|
||||
auditLog(
|
||||
socket,
|
||||
`LOGOUT user=${socket.request.session.username} from=${socket.handshake.address} host=${socket.request.session.ssh.host}:${socket.request.session.ssh.port}`
|
||||
);
|
||||
login = false;
|
||||
}
|
||||
if (code !== 0 && typeof code !== 'undefined')
|
||||
logError(socket, 'STREAM CLOSE', util.inspect({ message: [code, signal] }));
|
||||
socket.disconnect(true);
|
||||
conn.end();
|
||||
});
|
||||
stream.stderr.on('data', (data) => {
|
||||
console.error(`STDERR: ${data}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
conn.on('end', (err) => {
|
||||
if (err) logError(socket, 'CONN END BY HOST', err);
|
||||
webssh2debug(socket, 'CONN END BY HOST');
|
||||
socket.disconnect(true);
|
||||
});
|
||||
conn.on('close', (err) => {
|
||||
if (err) logError(socket, 'CONN CLOSE', err);
|
||||
webssh2debug(socket, 'CONN CLOSE');
|
||||
socket.disconnect(true);
|
||||
});
|
||||
conn.on('error', (err) => connError(socket, err));
|
||||
|
||||
conn.on('keyboard-interactive', (_name, _instructions, _instructionsLang, _prompts, finish) => {
|
||||
webssh2debug(socket, 'CONN keyboard-interactive');
|
||||
finish([socket.request.session.userpassword]);
|
||||
});
|
||||
if (
|
||||
socket.request.session.username &&
|
||||
(socket.request.session.userpassword || socket.request.session.privatekey) &&
|
||||
socket.request.session.ssh
|
||||
) {
|
||||
// console.log('hostkeys: ' + hostkeys[0].[0])
|
||||
const { ssh } = socket.request.session;
|
||||
ssh.username = socket.request.session.username;
|
||||
ssh.password = socket.request.session.userpassword;
|
||||
ssh.tryKeyboard = true;
|
||||
ssh.debug = debug('ssh2');
|
||||
conn.connect(ssh);
|
||||
} else {
|
||||
webssh2debug(
|
||||
socket,
|
||||
`CONN CONNECT: Attempt to connect without session.username/password or session varialbles defined, potentially previously abandoned client session. disconnecting websocket client.\r\nHandshake information: \r\n ${util.inspect(
|
||||
socket.handshake
|
||||
)}`
|
||||
);
|
||||
socket.emit('ssherror', 'WEBSOCKET ERROR - Refresh the browser and try again');
|
||||
socket.request.session.destroy();
|
||||
socket.disconnect(true);
|
||||
}
|
||||
}
|
||||
setupConnection();
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
/* jshint esversion: 6, asi: true, node: true */
|
||||
// util.js
|
||||
|
||||
// private
|
||||
const debug = require('debug')('WebSSH2');
|
||||
const Auth = require('basic-auth');
|
||||
|
||||
let defaultCredentials = { username: null, password: null, privatekey: null };
|
||||
|
||||
exports.setDefaultCredentials = function setDefaultCredentials({
|
||||
name: username,
|
||||
password,
|
||||
privatekey,
|
||||
overridebasic,
|
||||
}) {
|
||||
defaultCredentials = { username, password, privatekey, overridebasic };
|
||||
};
|
||||
|
||||
exports.basicAuth = function basicAuth(req, res, next) {
|
||||
const myAuth = Auth(req);
|
||||
// If Authorize: Basic header exists and the password isn't blank
|
||||
// AND config.user.overridebasic is false, extract basic credentials
|
||||
// from client]
|
||||
const { username, password, privatekey, overridebasic } = defaultCredentials;
|
||||
if (myAuth && myAuth.pass !== '' && !overridebasic) {
|
||||
req.session.username = myAuth.name;
|
||||
req.session.userpassword = myAuth.pass;
|
||||
debug(`myAuth.name: ${myAuth.name} and password ${myAuth.pass ? 'exists' : 'is blank'}`);
|
||||
} else {
|
||||
req.session.username = username;
|
||||
req.session.userpassword = password;
|
||||
req.session.privatekey = privatekey;
|
||||
}
|
||||
if (!req.session.userpassword && !req.session.privatekey) {
|
||||
res.statusCode = 401;
|
||||
debug('basicAuth credential request (401)');
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="WebSSH"');
|
||||
res.end('Username and password required for web SSH service.');
|
||||
return;
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// takes a string, makes it boolean (true if the string is true, false otherwise)
|
||||
exports.parseBool = function parseBool(str) {
|
||||
return str.toLowerCase() === 'true';
|
||||
};
|
372
app/socket.js
Normal file
|
@ -0,0 +1,372 @@
|
|||
// server
|
||||
// app/socket.js
|
||||
|
||||
const validator = require("validator")
|
||||
const EventEmitter = require("events")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { SSHConnectionError, handleError } = require("./errors")
|
||||
const {
|
||||
isValidCredentials,
|
||||
maskSensitiveData,
|
||||
validateSshTerm
|
||||
} = require("./utils")
|
||||
const { MESSAGES } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("socket")
|
||||
|
||||
class WebSSH2Socket extends EventEmitter {
|
||||
/**
|
||||
* Creates a new WebSSH2Socket instance
|
||||
* @param {Object} socket - The Socket.IO socket instance
|
||||
* @param {Object} config - The application configuration
|
||||
* @param {Function} SSHConnectionClass - The SSH connection class constructor
|
||||
*/
|
||||
constructor(socket, config, SSHConnectionClass) {
|
||||
super()
|
||||
this.socket = socket
|
||||
this.config = config
|
||||
this.SSHConnectionClass = SSHConnectionClass
|
||||
this.ssh = null
|
||||
this.sessionState = {
|
||||
authenticated: false,
|
||||
username: null,
|
||||
password: null,
|
||||
privateKey: null,
|
||||
passphrase: null,
|
||||
host: null,
|
||||
port: null,
|
||||
term: null,
|
||||
cols: null,
|
||||
rows: null
|
||||
}
|
||||
|
||||
this.initializeSocketEvents()
|
||||
}
|
||||
|
||||
initializeSocketEvents() {
|
||||
debug(`io.on connection: ${this.socket.id}`)
|
||||
|
||||
if (
|
||||
this.socket.handshake.session.usedBasicAuth &&
|
||||
this.socket.handshake.session.sshCredentials
|
||||
) {
|
||||
const creds = this.socket.handshake.session.sshCredentials
|
||||
debug(
|
||||
`handleConnection: ${this.socket.id}, Host: ${creds.host}: HTTP Basic Credentials Exist, creds: %O`,
|
||||
maskSensitiveData(creds)
|
||||
)
|
||||
this.handleAuthenticate(creds)
|
||||
} else if (!this.sessionState.authenticated) {
|
||||
// Check if interactive auth is disabled
|
||||
if (this.config.ssh.disableInteractiveAuth) {
|
||||
debug(`handleConnection: ${this.socket.id}, interactive auth disabled`)
|
||||
this.handleError("Interactive Auth Disabled")
|
||||
return
|
||||
}
|
||||
|
||||
debug(`handleConnection: ${this.socket.id}, emitting request_auth`)
|
||||
this.socket.emit("authentication", { action: "request_auth" })
|
||||
}
|
||||
|
||||
this.socket.on("authenticate", creds => {
|
||||
this.handleAuthenticate(creds)
|
||||
})
|
||||
this.socket.on("terminal", data => {
|
||||
this.handleTerminal(data)
|
||||
})
|
||||
this.socket.on("disconnect", reason => {
|
||||
this.handleConnectionClose(reason)
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyboardInteractive(data) {
|
||||
const self = this
|
||||
debug(`handleKeyboardInteractive: ${this.socket.id}, %O`, data)
|
||||
|
||||
// Send the keyboard-interactive request to the client
|
||||
this.socket.emit(
|
||||
"authentication",
|
||||
Object.assign(
|
||||
{
|
||||
action: "keyboard-interactive"
|
||||
},
|
||||
data
|
||||
)
|
||||
)
|
||||
|
||||
// Set up a one-time listener for the client's response
|
||||
this.socket.once("authentication", clientResponse => {
|
||||
const maskedclientResponse = maskSensitiveData(clientResponse, {
|
||||
properties: ["responses"]
|
||||
})
|
||||
debug(
|
||||
"handleKeyboardInteractive: Client response masked %O",
|
||||
maskedclientResponse
|
||||
)
|
||||
if (clientResponse.action === "keyboard-interactive") {
|
||||
// Forward the client's response to the SSH connection
|
||||
self.ssh.emit("keyboard-interactive-response", clientResponse.responses)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
handleAuthenticate(creds) {
|
||||
debug(`handleAuthenticate: ${this.socket.id}, %O`, maskSensitiveData(creds))
|
||||
|
||||
if (isValidCredentials(creds)) {
|
||||
this.sessionState.term = validateSshTerm(creds.term)
|
||||
? creds.term
|
||||
: this.config.ssh.term
|
||||
|
||||
this.initializeConnection(creds)
|
||||
} else {
|
||||
debug(`handleAuthenticate: ${this.socket.id}, CREDENTIALS INVALID`)
|
||||
this.socket.emit("authentication", {
|
||||
success: false,
|
||||
message: "Invalid credentials format"
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
initializeConnection(creds) {
|
||||
debug(
|
||||
`initializeConnection: ${this.socket.id}, INITIALIZING SSH CONNECTION: Host: ${creds.host}, creds: %O`,
|
||||
maskSensitiveData(creds)
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Create new SSH connection instance
|
||||
this.ssh = new this.SSHConnectionClass(this.config)
|
||||
|
||||
// Set up SSH event handlers
|
||||
this.ssh.on("keyboard-interactive", data => {
|
||||
this.handleKeyboardInteractive(data)
|
||||
})
|
||||
|
||||
this.ssh
|
||||
.connect(creds)
|
||||
.then(() => {
|
||||
this.sessionState = Object.assign({}, this.sessionState, {
|
||||
authenticated: true,
|
||||
username: creds.username,
|
||||
password: creds.password,
|
||||
privateKey: creds.privateKey,
|
||||
passphrase: creds.passphrase,
|
||||
host: creds.host,
|
||||
port: creds.port
|
||||
})
|
||||
|
||||
const authResult = { action: "auth_result", success: true }
|
||||
this.socket.emit("authentication", authResult)
|
||||
|
||||
const permissions = {
|
||||
autoLog: this.config.options.autoLog || false,
|
||||
allowReplay: this.config.options.allowReplay || false,
|
||||
allowReconnect: this.config.options.allowReconnect || false,
|
||||
allowReauth: this.config.options.allowReauth || false
|
||||
}
|
||||
this.socket.emit("permissions", permissions)
|
||||
|
||||
this.updateElement("footer", `ssh://${creds.host}:${creds.port}`)
|
||||
|
||||
if (this.config.header && this.config.header.text !== null) {
|
||||
this.updateElement("header", this.config.header.text)
|
||||
}
|
||||
|
||||
this.socket.emit("getTerminal", true)
|
||||
})
|
||||
.catch(err => {
|
||||
debug(
|
||||
`initializeConnection: SSH CONNECTION ERROR: ${this.socket.id}, Host: ${creds.host}, Error: ${err.message}`
|
||||
)
|
||||
const errorMessage =
|
||||
err instanceof SSHConnectionError
|
||||
? err.message
|
||||
: "SSH connection failed"
|
||||
this.socket.emit("authentication", {
|
||||
action: "auth_result",
|
||||
success: false,
|
||||
message: errorMessage
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles terminal data.
|
||||
* @param {Object} data - The terminal data.
|
||||
*/
|
||||
handleTerminal(data) {
|
||||
const { term, rows, cols } = data
|
||||
if (term && validateSshTerm(term)) this.sessionState.term = term
|
||||
if (rows && validator.isInt(rows.toString()))
|
||||
this.sessionState.rows = parseInt(rows, 10)
|
||||
if (cols && validator.isInt(cols.toString()))
|
||||
this.sessionState.cols = parseInt(cols, 10)
|
||||
|
||||
this.createShell()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new SSH shell session.
|
||||
*/
|
||||
createShell() {
|
||||
// Get envVars from socket session if they exist
|
||||
const envVars = this.socket.handshake.session.envVars || null
|
||||
|
||||
this.ssh
|
||||
.shell(
|
||||
{
|
||||
term: this.sessionState.term,
|
||||
cols: this.sessionState.cols,
|
||||
rows: this.sessionState.rows
|
||||
},
|
||||
envVars
|
||||
)
|
||||
.then(stream => {
|
||||
stream.on("data", data => {
|
||||
this.socket.emit("data", data.toString("utf-8"))
|
||||
})
|
||||
// stream.stderr.on("data", data => debug(`STDERR: ${data}`)) // needed for shell.exec
|
||||
stream.on("close", (code, signal) => {
|
||||
debug("close: SSH Stream closed")
|
||||
this.handleConnectionClose(code, signal)
|
||||
})
|
||||
|
||||
stream.on("end", () => {
|
||||
debug("end: SSH Stream ended")
|
||||
})
|
||||
|
||||
stream.on("error", err => {
|
||||
debug("error: SSH Stream error %O", err)
|
||||
})
|
||||
|
||||
this.socket.on("data", data => {
|
||||
stream.write(data)
|
||||
})
|
||||
this.socket.on("control", controlData => {
|
||||
this.handleControl(controlData)
|
||||
})
|
||||
this.socket.on("resize", data => {
|
||||
this.handleResize(data)
|
||||
})
|
||||
})
|
||||
.catch(err => this.handleError("createShell: ERROR", err))
|
||||
}
|
||||
|
||||
handleResize(data) {
|
||||
const { rows, cols } = data
|
||||
if (rows && validator.isInt(rows.toString()))
|
||||
this.sessionState.rows = parseInt(rows, 10)
|
||||
if (cols && validator.isInt(cols.toString()))
|
||||
this.sessionState.cols = parseInt(cols, 10)
|
||||
this.ssh.resizeTerminal(this.sessionState.rows, this.sessionState.cols)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles control commands.
|
||||
* @param {string} controlData - The control command received.
|
||||
*/
|
||||
handleControl(controlData) {
|
||||
if (
|
||||
validator.isIn(controlData, ["replayCredentials", "reauth"]) &&
|
||||
this.ssh.stream
|
||||
) {
|
||||
if (controlData === "replayCredentials") {
|
||||
this.replayCredentials()
|
||||
} else if (controlData === "reauth") {
|
||||
this.handleReauth()
|
||||
}
|
||||
} else {
|
||||
console.warn(
|
||||
`handleControl: Invalid control command received: ${controlData}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replays stored credentials.
|
||||
*/
|
||||
replayCredentials() {
|
||||
if (this.config.options.allowReplay && this.ssh.stream) {
|
||||
this.ssh.stream.write(`${this.sessionState.password}\n`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles reauthentication.
|
||||
*/
|
||||
handleReauth() {
|
||||
if (this.config.options.allowReauth) {
|
||||
this.clearSessionCredentials()
|
||||
this.socket.emit("authentication", { action: "reauth" })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors.
|
||||
* @param {string} context - The error context.
|
||||
* @param {Error} err - The error object.
|
||||
*/
|
||||
handleError(context, err) {
|
||||
const errorMessage = err ? `: ${err.message}` : ""
|
||||
handleError(new SSHConnectionError(`SSH ${context}${errorMessage}`))
|
||||
this.socket.emit("ssherror", `SSH ${context}${errorMessage}`)
|
||||
this.handleConnectionClose()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a UI element on the client side.
|
||||
* @param {string} element - The element to update.
|
||||
* @param {any} value - The new value for the element.
|
||||
*/
|
||||
updateElement(element, value) {
|
||||
this.socket.emit("updateUI", { element, value })
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the closure of the connection.
|
||||
* @param {string} reason - The reason for the closure.
|
||||
*/
|
||||
handleConnectionClose(code, signal) {
|
||||
this.ssh.end()
|
||||
debug(
|
||||
`handleConnectionClose: ${this.socket.id}, Code: ${code}, Signal: ${signal}`
|
||||
)
|
||||
this.socket.disconnect(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears session credentials.
|
||||
*/
|
||||
clearSessionCredentials() {
|
||||
if (this.socket.handshake.session.sshCredentials) {
|
||||
this.socket.handshake.session.sshCredentials.username = null
|
||||
this.socket.handshake.session.sshCredentials.password = null
|
||||
}
|
||||
this.socket.handshake.session.usedBasicAuth = false
|
||||
this.sessionState.authenticated = false
|
||||
this.sessionState.username = null
|
||||
this.sessionState.password = null
|
||||
|
||||
this.socket.handshake.session.save(err => {
|
||||
if (err)
|
||||
console.error(
|
||||
`clearSessionCredentials: ${MESSAGES.FAILED_SESSION_SAVE} ${this.socket.id}:`,
|
||||
err
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Modified export to include dependency injection
|
||||
module.exports = function(io, config, SSHConnectionClass) {
|
||||
io.on(
|
||||
"connection",
|
||||
socket => new WebSSH2Socket(socket, config, SSHConnectionClass)
|
||||
)
|
||||
}
|
334
app/ssh.js
Normal file
|
@ -0,0 +1,334 @@
|
|||
// server
|
||||
// app/ssh.js
|
||||
|
||||
const SSH = require("ssh2").Client
|
||||
const EventEmitter = require("events")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { SSHConnectionError, handleError } = require("./errors")
|
||||
const { maskSensitiveData } = require("./utils")
|
||||
const { DEFAULTS } = require("./constants")
|
||||
|
||||
const debug = createNamespacedDebug("ssh")
|
||||
|
||||
/**
|
||||
* SSHConnection class handles SSH connections and operations.
|
||||
* @extends EventEmitter
|
||||
*/
|
||||
class SSHConnection extends EventEmitter {
|
||||
constructor(config) {
|
||||
super()
|
||||
this.config = config
|
||||
this.conn = null
|
||||
this.stream = null
|
||||
this.creds = null
|
||||
this.authAttempts = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the format of an RSA private key, supporting both standard and encrypted keys
|
||||
* @param {string} key - The private key string to validate
|
||||
* @returns {boolean} - Whether the key appears to be valid
|
||||
*/
|
||||
validatePrivateKey(key) {
|
||||
// Pattern for standard RSA private key
|
||||
const standardKeyPattern = /^-----BEGIN (?:RSA )?PRIVATE KEY-----\r?\n([A-Za-z0-9+/=\r\n]+)\r?\n-----END (?:RSA )?PRIVATE KEY-----\r?\n?$/
|
||||
|
||||
// Pattern for encrypted RSA private key
|
||||
const encryptedKeyPattern = /^-----BEGIN RSA PRIVATE KEY-----\r?\n(?:Proc-Type: 4,ENCRYPTED\r?\nDEK-Info: ([^\r\n]+)\r?\n\r?\n)([A-Za-z0-9+/=\r\n]+)\r?\n-----END RSA PRIVATE KEY-----\r?\n?$/
|
||||
|
||||
// Test for either standard or encrypted key format
|
||||
return standardKeyPattern.test(key) || encryptedKeyPattern.test(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a private key is encrypted
|
||||
* @param {string} key - The private key to check
|
||||
* @returns {boolean} - Whether the key is encrypted
|
||||
*/
|
||||
isEncryptedKey(key) {
|
||||
return key.includes("Proc-Type: 4,ENCRYPTED")
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
debug("connect: %O", maskSensitiveData(creds))
|
||||
this.creds = creds
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.conn) {
|
||||
this.conn.end()
|
||||
}
|
||||
|
||||
this.conn = new SSH()
|
||||
this.authAttempts = 0
|
||||
|
||||
// First try with key authentication if available
|
||||
const sshConfig = this.getSSHConfig(creds, true)
|
||||
debug("Initial connection config: %O", maskSensitiveData(sshConfig))
|
||||
|
||||
this.setupConnectionHandlers(resolve, reject)
|
||||
|
||||
try {
|
||||
this.conn.connect(sshConfig)
|
||||
} catch (err) {
|
||||
reject(new SSHConnectionError(`Connection failed: ${err.message}`))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up SSH connection event handlers
|
||||
* @param {Function} resolve - Promise resolve function
|
||||
* @param {Function} reject - Promise reject function
|
||||
*/
|
||||
setupConnectionHandlers(resolve, reject) {
|
||||
this.conn.on("ready", () => {
|
||||
debug(`connect: ready: ${this.creds.host}`)
|
||||
resolve(this.conn)
|
||||
})
|
||||
|
||||
this.conn.on("error", err => {
|
||||
debug(`connect: error: ${err.message}`)
|
||||
|
||||
// Check if this is an authentication error and we haven't exceeded max attempts
|
||||
if (this.authAttempts < DEFAULTS.MAX_AUTH_ATTEMPTS) {
|
||||
this.authAttempts += 1
|
||||
debug(
|
||||
`Authentication attempt ${this.authAttempts} failed, trying password authentication`
|
||||
)
|
||||
|
||||
// Only try password auth if we have a password
|
||||
if (this.creds.password) {
|
||||
debug("Retrying with password authentication")
|
||||
|
||||
// Disconnect current connection
|
||||
if (this.conn) {
|
||||
this.conn.end()
|
||||
}
|
||||
|
||||
// Create new connection with password authentication
|
||||
this.conn = new SSH()
|
||||
const passwordConfig = this.getSSHConfig(this.creds, false)
|
||||
|
||||
this.setupConnectionHandlers(resolve, reject)
|
||||
this.conn.connect(passwordConfig)
|
||||
} else {
|
||||
debug("No password available, requesting password from client")
|
||||
this.emit("password-prompt", {
|
||||
host: this.creds.host,
|
||||
username: this.creds.username
|
||||
})
|
||||
|
||||
// Listen for password response one time
|
||||
this.once("password-response", password => {
|
||||
this.creds.password = password
|
||||
const newConfig = this.getSSHConfig(this.creds, false)
|
||||
this.setupConnectionHandlers(resolve, reject)
|
||||
this.conn.connect(newConfig)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// We've exhausted all authentication attempts
|
||||
const error = new SSHConnectionError(
|
||||
"All authentication methods failed"
|
||||
)
|
||||
handleError(error)
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
|
||||
this.conn.on(
|
||||
"keyboard-interactive",
|
||||
(name, instructions, lang, prompts, finish) => {
|
||||
this.handleKeyboardInteractive(
|
||||
name,
|
||||
instructions,
|
||||
lang,
|
||||
prompts,
|
||||
finish
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles keyboard-interactive authentication prompts.
|
||||
* @param {string} name - The name of the authentication request.
|
||||
* @param {string} instructions - The instructions for the keyboard-interactive prompt.
|
||||
* @param {string} lang - The language of the prompt.
|
||||
* @param {Array<Object>} prompts - The list of prompts provided by the server.
|
||||
* @param {Function} finish - The callback to complete the keyboard-interactive authentication.
|
||||
*/
|
||||
|
||||
handleKeyboardInteractive(name, instructions, lang, prompts, finish) {
|
||||
debug("handleKeyboardInteractive: Keyboard-interactive auth %O", prompts)
|
||||
|
||||
// Check if we should always send prompts to the client
|
||||
if (this.config.ssh.alwaysSendKeyboardInteractivePrompts) {
|
||||
this.sendPromptsToClient(name, instructions, prompts, finish)
|
||||
return
|
||||
}
|
||||
|
||||
const responses = []
|
||||
let shouldSendToClient = false
|
||||
|
||||
for (let i = 0; i < prompts.length; i += 1) {
|
||||
if (
|
||||
prompts[i].prompt.toLowerCase().includes("password") &&
|
||||
this.creds.password
|
||||
) {
|
||||
responses.push(this.creds.password)
|
||||
} else {
|
||||
shouldSendToClient = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldSendToClient) {
|
||||
this.sendPromptsToClient(name, instructions, prompts, finish)
|
||||
} else {
|
||||
finish(responses)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends prompts to the client for keyboard-interactive authentication.
|
||||
*
|
||||
* @param {string} name - The name of the authentication method.
|
||||
* @param {string} instructions - The instructions for the authentication.
|
||||
* @param {Array<{ prompt: string, echo: boolean }>} prompts - The prompts to be sent to the client.
|
||||
* @param {Function} finish - The callback function to be called when the client responds.
|
||||
*/
|
||||
sendPromptsToClient(name, instructions, prompts, finish) {
|
||||
this.emit("keyboard-interactive", {
|
||||
name: name,
|
||||
instructions: instructions,
|
||||
prompts: prompts.map(p => ({ prompt: p.prompt, echo: p.echo }))
|
||||
})
|
||||
|
||||
this.once("keyboard-interactive-response", responses => {
|
||||
finish(responses)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the SSH configuration object based on credentials.
|
||||
* @param {Object} creds - The credentials object
|
||||
* @param {boolean} useKey - Whether to attempt key authentication
|
||||
* @returns {Object} - The SSH configuration object
|
||||
*/
|
||||
getSSHConfig(creds, useKey) {
|
||||
const config = {
|
||||
host: creds.host,
|
||||
port: creds.port,
|
||||
username: creds.username,
|
||||
tryKeyboard: true,
|
||||
algorithms: this.config.ssh.algorithms,
|
||||
readyTimeout: this.config.ssh.readyTimeout,
|
||||
keepaliveInterval: this.config.ssh.keepaliveInterval,
|
||||
keepaliveCountMax: this.config.ssh.keepaliveCountMax,
|
||||
debug: createNamespacedDebug("ssh2")
|
||||
}
|
||||
|
||||
// Try private key first if available and useKey is true
|
||||
if (useKey && (creds.privateKey || this.config.user.privateKey)) {
|
||||
debug("Using private key authentication")
|
||||
const privateKey = creds.privateKey || this.config.user.privateKey
|
||||
|
||||
if (!this.validatePrivateKey(privateKey)) {
|
||||
throw new SSHConnectionError("Invalid private key format")
|
||||
}
|
||||
|
||||
config.privateKey = privateKey
|
||||
|
||||
// Check if key is encrypted and passphrase is needed
|
||||
if (this.isEncryptedKey(privateKey)) {
|
||||
const passphrase = creds.passphrase || this.config.user.passphrase
|
||||
if (!passphrase) {
|
||||
throw new SSHConnectionError(
|
||||
"Encrypted private key requires a passphrase"
|
||||
)
|
||||
}
|
||||
debug("Adding passphrase for encrypted private key")
|
||||
config.passphrase = passphrase
|
||||
}
|
||||
} else if (creds.password) {
|
||||
debug("Using password authentication")
|
||||
config.password = creds.password
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens an interactive shell session over the SSH connection.
|
||||
* @param {Object} options - Options for the shell
|
||||
* @param {Object} [envVars] - Environment variables to set
|
||||
* @returns {Promise<Object>} - A promise that resolves with the SSH shell stream
|
||||
*/
|
||||
shell(options, envVars) {
|
||||
const shellOptions = Object.assign({}, options, {
|
||||
env: this.getEnvironment(envVars)
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.shell(shellOptions, (err, stream) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
this.stream = stream
|
||||
resolve(stream)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Resizes the terminal window for the current SSH session.
|
||||
* @param {number} rows - The number of rows for the terminal.
|
||||
* @param {number} cols - The number of columns for the terminal.
|
||||
*/
|
||||
resizeTerminal(rows, cols) {
|
||||
if (this.stream) {
|
||||
this.stream.setWindow(rows, cols)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the SSH connection and stream.
|
||||
*/
|
||||
end() {
|
||||
if (this.stream) {
|
||||
this.stream.end()
|
||||
this.stream = null
|
||||
}
|
||||
if (this.conn) {
|
||||
this.conn.end()
|
||||
this.conn = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the environment variables for the SSH session
|
||||
* @param {Object} envVars - Environment variables from URL
|
||||
* @returns {Object} - Combined environment variables
|
||||
*/
|
||||
getEnvironment(envVars) {
|
||||
const env = {
|
||||
TERM: this.config.ssh.term
|
||||
}
|
||||
|
||||
if (envVars) {
|
||||
Object.keys(envVars).forEach(key => {
|
||||
env[key] = envVars[key]
|
||||
})
|
||||
}
|
||||
|
||||
return env
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SSHConnection
|
270
app/utils.js
Normal file
|
@ -0,0 +1,270 @@
|
|||
// server
|
||||
// /app/utils.js
|
||||
const validator = require("validator")
|
||||
const Ajv = require("ajv")
|
||||
const maskObject = require("jsmasker")
|
||||
const { createNamespacedDebug } = require("./logger")
|
||||
const { DEFAULTS, MESSAGES } = require("./constants")
|
||||
const configSchema = require("./configSchema")
|
||||
|
||||
const debug = createNamespacedDebug("utils")
|
||||
|
||||
/**
|
||||
* Deep merges two objects
|
||||
* @param {Object} target - The target object to merge into
|
||||
* @param {Object} source - The source object to merge from
|
||||
* @returns {Object} The merged object
|
||||
*/
|
||||
function deepMerge(target, source) {
|
||||
const output = Object.assign({}, target) // Avoid mutating target directly
|
||||
Object.keys(source).forEach(key => {
|
||||
if (Object.hasOwnProperty.call(source, key)) {
|
||||
if (
|
||||
source[key] instanceof Object &&
|
||||
!Array.isArray(source[key]) &&
|
||||
source[key] !== null
|
||||
) {
|
||||
output[key] = deepMerge(output[key] || {}, source[key])
|
||||
} else {
|
||||
output[key] = source[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given host is an IP address or a hostname.
|
||||
* If it's a hostname, it escapes it for safety.
|
||||
*
|
||||
* @param {string} host - The host string to validate and escape.
|
||||
* @returns {string} - The original IP or escaped hostname.
|
||||
*/
|
||||
function getValidatedHost(host) {
|
||||
let validatedHost
|
||||
|
||||
if (validator.isIP(host)) {
|
||||
validatedHost = host
|
||||
} else {
|
||||
validatedHost = validator.escape(host)
|
||||
}
|
||||
|
||||
return validatedHost
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sanitizes a port value.
|
||||
* If no port is provided, defaults to port 22.
|
||||
* If a port is provided, checks if it is a valid port number (1-65535).
|
||||
* If the port is invalid, defaults to port 22.
|
||||
*
|
||||
* @param {string} [portInput] - The port string to validate and parse.
|
||||
* @returns {number} - The validated port number.
|
||||
*/
|
||||
function getValidatedPort(portInput) {
|
||||
const defaultPort = DEFAULTS.SSH_PORT
|
||||
const port = defaultPort
|
||||
debug("getValidatedPort: input: %O", portInput)
|
||||
|
||||
if (portInput) {
|
||||
if (validator.isInt(portInput, { min: 1, max: 65535 })) {
|
||||
return parseInt(portInput, 10)
|
||||
}
|
||||
}
|
||||
debug(
|
||||
"getValidatedPort: port not specified or is invalid, setting port to: %O",
|
||||
port
|
||||
)
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided credentials object is valid.
|
||||
* Valid credentials must have:
|
||||
* - username (string)
|
||||
* - host (string)
|
||||
* - port (number)
|
||||
* AND either:
|
||||
* - password (string) OR
|
||||
* - privateKey (string) with optional passphrase (string)
|
||||
*
|
||||
* @param {Object} creds - The credentials object.
|
||||
* @returns {boolean} - Returns true if the credentials are valid, otherwise false.
|
||||
*/
|
||||
function isValidCredentials(creds) {
|
||||
const hasRequiredFields = !!(
|
||||
creds &&
|
||||
typeof creds.username === "string" &&
|
||||
typeof creds.host === "string" &&
|
||||
typeof creds.port === "number"
|
||||
)
|
||||
|
||||
if (!hasRequiredFields) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Must have either password or privateKey
|
||||
const hasPassword = typeof creds.password === "string"
|
||||
const hasPrivateKey = typeof creds.privateKey === "string"
|
||||
|
||||
// Passphrase is optional but must be string if provided
|
||||
const hasValidPassphrase =
|
||||
!creds.passphrase || typeof creds.passphrase === "string"
|
||||
|
||||
return (hasPassword || hasPrivateKey) && hasValidPassphrase
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sanitizes the SSH terminal name using validator functions.
|
||||
* Allows alphanumeric characters, hyphens, and periods.
|
||||
* Returns null if the terminal name is invalid or not provided.
|
||||
*
|
||||
* @param {string} [term] - The terminal name to validate.
|
||||
* @returns {string|null} - The sanitized terminal name if valid, null otherwise.
|
||||
*/
|
||||
function validateSshTerm(term) {
|
||||
debug(`validateSshTerm: %O`, term)
|
||||
|
||||
if (!term) {
|
||||
return null
|
||||
}
|
||||
|
||||
const validatedSshTerm =
|
||||
validator.isLength(term, { min: 1, max: 30 }) &&
|
||||
validator.matches(term, /^[a-zA-Z0-9.-]+$/)
|
||||
|
||||
return validatedSshTerm ? term : null
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the given configuration object.
|
||||
*
|
||||
* @param {Object} config - The configuration object to validate.
|
||||
* @throws {Error} If the configuration object fails validation.
|
||||
* @returns {Object} The validated configuration object.
|
||||
*/
|
||||
function validateConfig(config) {
|
||||
const ajv = new Ajv()
|
||||
const validate = ajv.compile(configSchema)
|
||||
const valid = validate(config)
|
||||
if (!valid) {
|
||||
throw new Error(
|
||||
`${MESSAGES.CONFIG_VALIDATION_ERROR}: ${ajv.errorsText(validate.errors)}`
|
||||
)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the HTML content by replacing certain placeholders with dynamic values.
|
||||
* @param {string} html - The original HTML content.
|
||||
* @param {Object} config - The configuration object to inject into the HTML.
|
||||
* @returns {string} - The modified HTML content.
|
||||
*/
|
||||
function modifyHtml(html, config) {
|
||||
debug("modifyHtml")
|
||||
const modifiedHtml = html.replace(
|
||||
/(src|href)="(?!http|\/\/)/g,
|
||||
'$1="/ssh/assets/'
|
||||
)
|
||||
|
||||
return modifiedHtml.replace(
|
||||
"window.webssh2Config = null;",
|
||||
`window.webssh2Config = ${JSON.stringify(config)};`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Masks sensitive information in an object
|
||||
* @param {Object} obj - The object to mask
|
||||
* @param {Object} [options] - Optional configuration for masking
|
||||
* @param {string[]} [options.properties=['password', 'key', 'secret', 'token']] - The properties to be masked
|
||||
* @param {number} [options.maskLength=8] - The length of the generated mask
|
||||
* @param {number} [options.minLength=5] - The minimum length of the generated mask
|
||||
* @param {number} [options.maxLength=15] - The maximum length of the generated mask
|
||||
* @param {string} [options.maskChar='*'] - The character used for masking
|
||||
* @param {boolean} [options.fullMask=false] - Whether to use a full mask for all properties
|
||||
* @returns {Object} The masked object
|
||||
*/
|
||||
function maskSensitiveData(obj, options) {
|
||||
const defaultOptions = {
|
||||
properties: [
|
||||
"password",
|
||||
"privateKey",
|
||||
"passphrase",
|
||||
"key",
|
||||
"secret",
|
||||
"token"
|
||||
]
|
||||
}
|
||||
debug("maskSensitiveData")
|
||||
|
||||
const maskingOptions = Object.assign({}, defaultOptions, options || {})
|
||||
const maskedObject = maskObject(obj, maskingOptions)
|
||||
|
||||
return maskedObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sanitizes environment variable key names
|
||||
* @param {string} key - The environment variable key to validate
|
||||
* @returns {boolean} - Whether the key is valid
|
||||
*/
|
||||
function isValidEnvKey(key) {
|
||||
// Only allow uppercase letters, numbers, and underscore
|
||||
return /^[A-Z][A-Z0-9_]*$/.test(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and sanitizes environment variable values
|
||||
* @param {string} value - The environment variable value to validate
|
||||
* @returns {boolean} - Whether the value is valid
|
||||
*/
|
||||
function isValidEnvValue(value) {
|
||||
// Disallow special characters that could be used for command injection
|
||||
return !/[;&|`$]/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses and validates environment variables from URL query string
|
||||
* @param {string} envString - The environment string from URL query
|
||||
* @returns {Object|null} - Object containing validated env vars or null if invalid
|
||||
*/
|
||||
function parseEnvVars(envString) {
|
||||
if (!envString) return null
|
||||
|
||||
const envVars = {}
|
||||
const pairs = envString.split(",")
|
||||
|
||||
for (let i = 0; i < pairs.length; i += 1) {
|
||||
const pair = pairs[i].split(":")
|
||||
// eslint-disable-next-line no-continue
|
||||
if (pair.length !== 2) continue
|
||||
|
||||
const key = pair[0].trim()
|
||||
const value = pair[1].trim()
|
||||
|
||||
if (isValidEnvKey(key) && isValidEnvValue(value)) {
|
||||
envVars[key] = value
|
||||
} else {
|
||||
debug(`parseEnvVars: Invalid env var pair: ${key}:${value}`)
|
||||
}
|
||||
}
|
||||
|
||||
return Object.keys(envVars).length > 0 ? envVars : null
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
deepMerge,
|
||||
getValidatedHost,
|
||||
getValidatedPort,
|
||||
isValidCredentials,
|
||||
isValidEnvKey,
|
||||
isValidEnvValue,
|
||||
maskSensitiveData,
|
||||
modifyHtml,
|
||||
parseEnvVars,
|
||||
validateConfig,
|
||||
validateSshTerm
|
||||
}
|
BIN
bin/BIG-IP-ILX-WebSSH2-current.tgz
Normal file
1
bin/BIG-IP-ILX-WebSSH2-current.tgz.sha256
Normal file
|
@ -0,0 +1 @@
|
|||
b512ae8f04eba0eab29e026542fab1063b1bb4ae6db04e3613a8939260fe031c Build/Release/BIG-IP-ILX-WebSSH2-0.2.9.tgz
|
BIN
bun.lockb
77
config.json.sample
Normal file
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"listen": {
|
||||
"ip": "0.0.0.0",
|
||||
"port": 2222
|
||||
},
|
||||
"http": {
|
||||
"origins": ["*.*"]
|
||||
},
|
||||
"user": {
|
||||
"name": null,
|
||||
"password": null,
|
||||
"privateKey": null
|
||||
},
|
||||
"session": {
|
||||
"secret": "secret",
|
||||
"name": "webssh2"
|
||||
},
|
||||
"ssh": {
|
||||
"host": null,
|
||||
"port": 22,
|
||||
"localAddress": null,
|
||||
"localPort": null,
|
||||
"term": "xterm-color",
|
||||
"readyTimeout": 20000,
|
||||
"keepaliveInterval": 120000,
|
||||
"keepaliveCountMax": 10,
|
||||
"allowedSubnets": [],
|
||||
"alwaysSendKeyboardInteractivePrompts": false,
|
||||
"disableInteractiveAuth": false,
|
||||
"algorithms": {
|
||||
"cipher": [
|
||||
"aes128-ctr",
|
||||
"aes192-ctr",
|
||||
"aes256-ctr",
|
||||
"aes128-gcm",
|
||||
"aes128-gcm@openssh.com",
|
||||
"aes256-gcm",
|
||||
"aes256-gcm@openssh.com",
|
||||
"aes256-cbc"
|
||||
],
|
||||
"compress": [
|
||||
"none",
|
||||
"zlib@openssh.com",
|
||||
"zlib"
|
||||
],
|
||||
"hmac": [
|
||||
"hmac-sha2-256",
|
||||
"hmac-sha2-512",
|
||||
"hmac-sha1"
|
||||
],
|
||||
"kex": [
|
||||
"ecdh-sha2-nistp256",
|
||||
"ecdh-sha2-nistp384",
|
||||
"ecdh-sha2-nistp521",
|
||||
"diffie-hellman-group-exchange-sha256",
|
||||
"diffie-hellman-group14-sha1"
|
||||
],
|
||||
"serverHostKey": [
|
||||
"ecdsa-sha2-nistp256",
|
||||
"ecdsa-sha2-nistp384",
|
||||
"ecdsa-sha2-nistp521",
|
||||
"ssh-rsa"
|
||||
]
|
||||
}
|
||||
},
|
||||
"header": {
|
||||
"text": null,
|
||||
"background": "green"
|
||||
},
|
||||
"options": {
|
||||
"challengeButton": true,
|
||||
"autoLog": false,
|
||||
"allowReauth": true,
|
||||
"allowReconnect": true,
|
||||
"allowReplay": true
|
||||
}
|
||||
}
|
BIN
images/Screenshot.png
Normal file
After Width: | Height: | Size: 1,019 KiB |
BIN
images/orthrus.png
Normal file
After Width: | Height: | Size: 103 KiB |
BIN
images/orthrus.webp
Normal file
After Width: | Height: | Size: 150 KiB |
BIN
images/orthrus2.png
Normal file
After Width: | Height: | Size: 118 KiB |
43
images/orthrus2.txt
Normal file
|
@ -0,0 +1,43 @@
|
|||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59m:[0m[38;5;95mr[0m[38;5;137ms[0m[38;5;137mX[0m[38;5;137mX[0m[38;5;95mr[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;137ms[0m[38;5;180mA[0m[38;5;137ms[0m[38;5;95mr[0m[38;5;59mi[0m[38;5;95mr[0m[38;5;137ms[0m[38;5;173mA[0m[38;5;180m2[0m[38;5;58m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;95mi[0m[38;5;180m5[0m[38;5;216m3[0m[38;5;216m5[0m[38;5;209m2[0m[38;5;173mX[0m[38;5;209mA[0m[38;5;216m5[0m[38;5;216m3[0m[38;5;216m5[0m[38;5;137mX[0m[38;5;59m;[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;52m,[0m[38;5;216m5[0m[38;5;215m2[0m[38;5;215m5[0m[38;5;215m5[0m[38;5;215m5[0m[38;5;215m5[0m[38;5;215m2[0m[38;5;209m2[0m[38;5;215m5[0m[38;5;95mr[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;101ms[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;101ms[0m[38;5;101ms[0m[38;5;58m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m.[0m[38;5;101ms[0m[38;5;216m3[0m[38;5;173mA[0m[38;5;131mr[0m[38;5;88m;[0m[38;5;52m,[0m[38;5;52m,[0m[38;5;52m,[0m[38;5;95mi[0m[38;5;131ms[0m[38;5;173mX[0m[38;5;180m2[0m[38;5;187m3[0m[38;5;181m3[0m[38;5;187m3[0m[38;5;181m3[0m[38;5;180m5[0m[38;5;180m5[0m[38;5;180m2[0m[38;5;58m;[0m[38;5;173mA[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;95m;[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;101ms[0m[38;5;137mX[0m[38;5;144mA[0m[38;5;145m2[0m[38;5;181m3[0m[38;5;145m5[0m[38;5;138mX[0m[38;5;95mr[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;53m:[0m[38;5;59m:[0m[38;5;59mi[0m[38;5;102ms[0m[38;5;59m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;59m;[0m[38;5;210m2[0m[38;5;167ms[0m[38;5;131mi[0m[38;5;95mi[0m[38;5;52m,[0m[38;5;52m:[0m[38;5;95mr[0m[38;5;145m2[0m[38;5;188mM[0m[38;5;230mS[0m[38;5;231m&[0m[38;5;230mB[0m[38;5;223mH[0m[38;5;216m3[0m[38;5;173mX[0m[38;5;215m2[0m[38;5;216m3[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;95mi[0m[38;5;131mr[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;173mX[0m[38;5;131ms[0m[38;5;52m:[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;138mX[0m[38;5;188mM[0m[38;5;231m@[0m[38;5;230mB[0m[38;5;230mS[0m[38;5;187m3[0m[38;5;102mX[0m[38;5;59m;[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;53m:[0m[38;5;59m:[0m[38;5;59m;[0m[38;5;60mr[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m.[0m[38;5;95mi[0m[38;5;167ms[0m[38;5;131mr[0m[38;5;89m;[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;95mr[0m[38;5;144mA[0m[38;5;144mA[0m[38;5;230mS[0m[38;5;230m9[0m[38;5;230m#[0m[38;5;216m3[0m[38;5;215m5[0m[38;5;215m2[0m[38;5;131mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;131ms[0m[38;5;173mA[0m[38;5;173mA[0m[38;5;52m:[0m[38;5;131ms[0m[38;5;167mX[0m[38;5;167mX[0m[38;5;173mX[0m[38;5;167mX[0m[38;5;167ms[0m[38;5;131mr[0m[38;5;59m;[0m[38;5;53m:[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;52m:[0m[38;5;52m:[0m[38;5;59m:[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;187m3[0m[38;5;230m9[0m[38;5;230m#[0m[38;5;187m3[0m[38;5;102ms[0m[38;5;59mi[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;53m:[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;17m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m:[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m.[0m[38;5;52m,[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;95mr[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;102mX[0m[38;5;230m9[0m[38;5;223mH[0m[38;5;215m5[0m[38;5;215m5[0m[38;5;95mr[0m[38;5;66ms[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;23m:[0m[38;5;66ms[0m[38;5;173mX[0m[38;5;173mX[0m[38;5;53m:[0m[38;5;95mi[0m[38;5;131ms[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;53m:[0m[38;5;53m:[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;95mi[0m[38;5;138mX[0m[38;5;230m#[0m[38;5;223mH[0m[38;5;16m,[0m[38;5;23m;[0m[38;5;60mi[0m[38;5;59m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m,[0m[38;5;101ms[0m[38;5;181m3[0m[38;5;224mG[0m[38;5;230m#[0m[38;5;230m#[0m[38;5;187m3[0m[38;5;101ms[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;95mr[0m[38;5;109m2[0m[38;5;16m.[0m[38;5;101ms[0m[38;5;230m9[0m[38;5;223mH[0m[38;5;215m5[0m[38;5;209mA[0m[38;5;66mr[0m[38;5;80m2[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;59mi[0m[38;5;123mM[0m[38;5;95mr[0m[38;5;215m5[0m[38;5;167mX[0m[38;5;95m;[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;52m,[0m[38;5;53m:[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;109mA[0m[38;5;187m3[0m[38;5;52m:[0m[38;5;16m.[0m[38;5;173mX[0m[38;5;138mA[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;144m2[0m[38;5;230m#[0m[38;5;187mh[0m[38;5;16m.[0m[38;5;60mr[0m[38;5;73mX[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m.[0m[38;5;101mr[0m[38;5;224mG[0m[38;5;230m9[0m[38;5;230m#[0m[38;5;230mS[0m[38;5;187mh[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;52m,[0m[38;5;59m:[0m[38;5;59mi[0m[38;5;138mX[0m[38;5;187mh[0m[38;5;181m5[0m[38;5;224mG[0m[38;5;230m9[0m[38;5;230m#[0m[38;5;223mh[0m[38;5;209m2[0m[38;5;137ms[0m[38;5;152mh[0m[38;5;109m2[0m[38;5;102mA[0m[38;5;188mH[0m[38;5;145m5[0m[38;5;173mX[0m[38;5;215m2[0m[38;5;209m2[0m[38;5;173mA[0m[38;5;131ms[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;101ms[0m[38;5;224mG[0m[38;5;223mM[0m[38;5;216m3[0m[38;5;223mH[0m[38;5;138mX[0m[38;5;59m;[0m[38;5;137mX[0m[38;5;230mS[0m[38;5;230m9[0m[38;5;230m9[0m[38;5;187m3[0m[38;5;188mh[0m[38;5;145m2[0m[38;5;95mr[0m[38;5;59m;[0m[38;5;52m:[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;95mr[0m[38;5;181m3[0m[38;5;16m,[0m[38;5;102mX[0m[38;5;230mB[0m[38;5;230mS[0m[38;5;230mG[0m[38;5;223mG[0m[38;5;216m3[0m[38;5;137mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m.[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;59mi[0m[38;5;95mr[0m[38;5;188mM[0m[38;5;230mB[0m[38;5;230m9[0m[38;5;230m#[0m[38;5;230m#[0m[38;5;223mH[0m[38;5;216m5[0m[38;5;173m2[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;173mA[0m[38;5;173mX[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;131mr[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;101ms[0m[38;5;138mX[0m[38;5;101ms[0m[38;5;101ms[0m[38;5;180m5[0m[38;5;230mS[0m[38;5;230m#[0m[38;5;230m#[0m[38;5;230m9[0m[38;5;187mM[0m[38;5;95ms[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;138mX[0m[38;5;230mS[0m[38;5;95mr[0m[38;5;224mG[0m[38;5;230mS[0m[38;5;230mG[0m[38;5;229mG[0m[38;5;216m5[0m[38;5;209m2[0m[38;5;180m2[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;53m:[0m[38;5;59m:[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;52m,[0m[38;5;181m3[0m[38;5;230m#[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;223mG[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;216m3[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;137ms[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;59mi[0m[38;5;95mi[0m[38;5;137ms[0m[38;5;180m2[0m[38;5;187m3[0m[38;5;223mM[0m[38;5;230mS[0m[38;5;230m#[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230m#[0m[38;5;230m#[0m[38;5;181m5[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;59m;[0m[38;5;230mS[0m[38;5;223mM[0m[38;5;223mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;223mM[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;216m2[0m[38;5;137ms[0m[38;5;52m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;52m:[0m[38;5;187mM[0m[38;5;138mX[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;101ms[0m[38;5;144m2[0m[38;5;223mH[0m[38;5;230mS[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mS[0m[38;5;187mM[0m[38;5;138mX[0m[38;5;187m3[0m[38;5;230mG[0m[38;5;223mM[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;174mA[0m[38;5;145m2[0m[38;5;66mr[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;180m5[0m[38;5;230mG[0m[38;5;181m3[0m[38;5;138mX[0m[38;5;187mh[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mG[0m[38;5;230mS[0m[38;5;223mM[0m[38;5;144m2[0m[38;5;101ms[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;101ms[0m[38;5;187m3[0m[38;5;102mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m.[0m[38;5;137mX[0m[38;5;230mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;216m5[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209m2[0m[38;5;216m2[0m[38;5;137mX[0m[38;5;59m;[0m[38;5;16m.[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m.[0m[38;5;59mi[0m[38;5;152mh[0m[38;5;152m3[0m[38;5;102ms[0m[38;5;145m2[0m[38;5;187mh[0m[38;5;187mM[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;144m2[0m[38;5;144mA[0m[38;5;187m3[0m[38;5;187mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;144mA[0m[38;5;144mA[0m[38;5;145m2[0m[38;5;145m5[0m[38;5;187mh[0m[38;5;151m5[0m[38;5;73mX[0m[38;5;66mX[0m[38;5;66mX[0m[38;5;109mA[0m[38;5;151m3[0m[38;5;151m3[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;138mA[0m[38;5;144m2[0m[38;5;187mh[0m[38;5;223mH[0m[38;5;224mG[0m[38;5;224mG[0m[38;5;224mG[0m[38;5;223mH[0m[38;5;187mM[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;102ms[0m[38;5;145m5[0m[38;5;152mh[0m[38;5;109m2[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59mi[0m[38;5;145m5[0m[38;5;187mh[0m[38;5;187mM[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;216m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;173mA[0m[38;5;95mr[0m[38;5;188mh[0m[38;5;59mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;23m:[0m[38;5;60mr[0m[38;5;59mi[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;67mX[0m[38;5;102mX[0m[38;5;102mX[0m[38;5;108mX[0m[38;5;102mX[0m[38;5;102mX[0m[38;5;109mA[0m[38;5;109m2[0m[38;5;115m2[0m[38;5;109m2[0m[38;5;73mX[0m[38;5;66ms[0m[38;5;66mr[0m[38;5;66ms[0m[38;5;109mA[0m[38;5;116m2[0m[38;5;116m5[0m[38;5;188mM[0m[38;5;230mG[0m[38;5;223mH[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;73mX[0m[38;5;66ms[0m[38;5;66ms[0m[38;5;67mX[0m[38;5;109mA[0m[38;5;110m2[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;109mX[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;108mX[0m[38;5;102mX[0m[38;5;66mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59m;[0m[38;5;66mr[0m[38;5;109mX[0m[38;5;109m2[0m[38;5;151m5[0m[38;5;151m5[0m[38;5;145m2[0m[38;5;173mX[0m[38;5;167mX[0m[38;5;167mX[0m[38;5;167mX[0m[38;5;173mX[0m[38;5;209mA[0m[38;5;173mA[0m[38;5;145m5[0m[38;5;116m5[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m,[0m[38;5;59m;[0m[38;5;66ms[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;66ms[0m[38;5;66ms[0m[38;5;60mr[0m[38;5;59m;[0m[38;5;59m:[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;59m:[0m[38;5;66mr[0m[38;5;109mA[0m[38;5;116m2[0m[38;5;115m5[0m[38;5;187mh[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;67mX[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;17m:[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;60mr[0m[38;5;66ms[0m[38;5;67mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;67ms[0m[38;5;66mr[0m[38;5;23m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59m;[0m[38;5;59mr[0m[38;5;66mr[0m[38;5;67ms[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;102mX[0m[38;5;131mr[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;138mA[0m[38;5;115m2[0m[38;5;59m;[0m[38;5;66ms[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m:[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;66mr[0m[38;5;67mX[0m[38;5;109m2[0m[38;5;116m5[0m[38;5;115m2[0m[38;5;115m5[0m[38;5;187mh[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;115m2[0m[38;5;116m2[0m[38;5;73mA[0m[38;5;66ms[0m[38;5;59mi[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;59m:[0m[38;5;73mX[0m[38;5;132ms[0m[38;5;131mr[0m[38;5;131ms[0m[38;5;131mr[0m[38;5;131mr[0m[38;5;102ms[0m[38;5;73mA[0m[38;5;60mr[0m[38;5;80m2[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;102mX[0m[38;5;116m5[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;116m5[0m[38;5;116m2[0m[38;5;115m2[0m[38;5;109m2[0m[38;5;115m5[0m[38;5;187mh[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;116m2[0m[38;5;116m5[0m[38;5;109mA[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;123mh[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;95mr[0m[38;5;131ms[0m[38;5;131mr[0m[38;5;131mr[0m[38;5;131mr[0m[38;5;131mr[0m[38;5;102mX[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;95mi[0m[38;5;152mh[0m[38;5;115m2[0m[38;5;115m5[0m[38;5;115m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;151m5[0m[38;5;187mM[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;151m3[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;115m2[0m[38;5;115m2[0m[38;5;116m2[0m[38;5;116m2[0m[38;5;109m2[0m[38;5;116m5[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m,[0m[38;5;95mi[0m[38;5;131ms[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;131mi[0m[38;5;95mr[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;74mA[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;145m5[0m[38;5;145m2[0m[38;5;115m2[0m[38;5;115m2[0m[38;5;115m2[0m[38;5;115m2[0m[38;5;115m5[0m[38;5;151m3[0m[38;5;223mM[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;187m3[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;115m2[0m[38;5;116m5[0m[38;5;59mi[0m[38;5;73mX[0m[38;5;59m;[0m[38;5;52m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;52m:[0m[38;5;59m:[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;52m:[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;52m:[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;137ms[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;23m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;95mr[0m[38;5;145m5[0m[38;5;115m5[0m[38;5;115m2[0m[38;5;115m5[0m[38;5;151m5[0m[38;5;187mh[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;187m3[0m[38;5;109m2[0m[38;5;115m2[0m[38;5;116m2[0m[38;5;66ms[0m[38;5;59m;[0m[38;5;95mi[0m[38;5;131mX[0m[38;5;174mA[0m[38;5;174mA[0m[38;5;180m2[0m[38;5;180m2[0m[38;5;180m2[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;215m5[0m[38;5;216m5[0m[38;5;223mM[0m[38;5;230mS[0m[38;5;224mH[0m[38;5;181m5[0m[38;5;138mA[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;103mX[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;66mr[0m[38;5;66ms[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;59m;[0m[38;5;96mr[0m[38;5;109mA[0m[38;5;115m5[0m[38;5;187m3[0m[38;5;223mM[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mH[0m[38;5;224mG[0m[38;5;145m5[0m[38;5;181m5[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;144mA[0m[38;5;102mX[0m[38;5;67mX[0m[38;5;96ms[0m[38;5;131ms[0m[38;5;132mX[0m[38;5;131mX[0m[38;5;131mX[0m[38;5;131ms[0m[38;5;167ms[0m[38;5;167mX[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209m2[0m[38;5;223mh[0m[38;5;230mG[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230mS[0m[38;5;230m#[0m[38;5;230mG[0m[38;5;144m2[0m[38;5;59mi[0m[38;5;66ms[0m[38;5;109mX[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;66ms[0m[38;5;60mi[0m[38;5;23m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59mi[0m[38;5;109m2[0m[38;5;151m3[0m[38;5;181m5[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mM[0m[38;5;187m3[0m[38;5;102ms[0m[38;5;102ms[0m[38;5;188mH[0m[38;5;187m3[0m[38;5;145m2[0m[38;5;108mX[0m[38;5;145m2[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;173mA[0m[38;5;137ms[0m[38;5;173mX[0m[38;5;173mA[0m[38;5;209m2[0m[38;5;173mA[0m[38;5;167mX[0m[38;5;131mX[0m[38;5;131mX[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;167ms[0m[38;5;167mX[0m[38;5;173mX[0m[38;5;173mX[0m[38;5;173mX[0m[38;5;209mX[0m[38;5;209mA[0m[38;5;223mh[0m[38;5;224mG[0m[38;5;223mG[0m[38;5;187mh[0m[38;5;144m2[0m[38;5;138mA[0m[38;5;144mA[0m[38;5;180m5[0m[38;5;223mH[0m[38;5;188mM[0m[38;5;60mr[0m[38;5;66ms[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;67mX[0m[38;5;60mr[0m[38;5;17m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mX[0m[38;5;145m5[0m[38;5;66ms[0m[38;5;66ms[0m[38;5;96mr[0m[38;5;181m5[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;181m5[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;102mX[0m[38;5;102ms[0m[38;5;103mX[0m[38;5;102mX[0m[38;5;145m2[0m[38;5;187mh[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;216m5[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;216m3[0m[38;5;223mh[0m[38;5;223mh[0m[38;5;223mh[0m[38;5;216m2[0m[38;5;167mX[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;167ms[0m[38;5;167ms[0m[38;5;173mX[0m[38;5;216m3[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;180m2[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;101ms[0m[38;5;138mX[0m[38;5;144mA[0m[38;5;187mh[0m[38;5;188mH[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;224mS[0m[38;5;230mG[0m[38;5;187mM[0m[38;5;151m3[0m[38;5;151m3[0m[38;5;145m2[0m[38;5;145mA[0m[38;5;187m3[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187m3[0m[38;5;187mh[0m[38;5;223mH[0m[38;5;230mG[0m[38;5;223mG[0m[38;5;230mG[0m[38;5;223mh[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;223mh[0m[38;5;223mM[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;216m5[0m[38;5;223mh[0m[38;5;173mX[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;167mX[0m[38;5;181m3[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;101mr[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;138mA[0m[38;5;187mh[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;144mA[0m[38;5;116m5[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;59mi[0m[38;5;230m#[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;181m3[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;224mG[0m[38;5;216m3[0m[38;5;209m2[0m[38;5;209m2[0m[38;5;223mM[0m[38;5;216m3[0m[38;5;209mA[0m[38;5;209mX[0m[38;5;216m5[0m[38;5;216m3[0m[38;5;167mX[0m[38;5;131mr[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;138mX[0m[38;5;145m2[0m[38;5;187m3[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;101ms[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;180m2[0m[38;5;223mM[0m[38;5;101ms[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;59mi[0m[38;5;66mX[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;59mi[0m[38;5;230mS[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;181m5[0m[38;5;181m5[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;181m3[0m[38;5;181m3[0m[38;5;187mM[0m[38;5;187mM[0m[38;5;187mM[0m[38;5;145m2[0m[38;5;138mX[0m[38;5;223mG[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mh[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;216m5[0m[38;5;223mh[0m[38;5;217m3[0m[38;5;180m3[0m[38;5;216m3[0m[38;5;173mA[0m[38;5;138mX[0m[38;5;95mi[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;131ms[0m[38;5;132mX[0m[38;5;138mX[0m[38;5;109mA[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;102mA[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mH[0m[38;5;144m2[0m[38;5;95mi[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;144m2[0m[38;5;181m3[0m[38;5;144m2[0m[38;5;138mX[0m[38;5;95mr[0m[38;5;59mr[0m[38;5;74mA[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;58m:[0m[38;5;224mG[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;144mA[0m[38;5;102mX[0m[38;5;145m2[0m[38;5;151m3[0m[38;5;187m3[0m[38;5;145m5[0m[38;5;102mX[0m[38;5;151m5[0m[38;5;151m5[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;59mi[0m[38;5;187m3[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;223mH[0m[38;5;216m3[0m[38;5;209m2[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;209mA[0m[38;5;173mX[0m[38;5;137mX[0m[38;5;109mA[0m[38;5;60mr[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;66ms[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;144m2[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;59mi[0m[38;5;95mr[0m[38;5;95mr[0m[38;5;95mi[0m[38;5;95mr[0m[38;5;108mX[0m[38;5;116m5[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mA[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;151m3[0m[38;5;66ms[0m[38;5;59mi[0m[38;5;66mr[0m[38;5;109m2[0m[38;5;116m2[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73m2[0m[38;5;73mX[0m[38;5;59mi[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;216m3[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;216m5[0m[38;5;180m5[0m[38;5;109m2[0m[38;5;66ms[0m[38;5;66ms[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;74m2[0m[38;5;80m2[0m[38;5;80m2[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;66mr[0m[38;5;101ms[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;180m2[0m[38;5;138mA[0m[38;5;138mX[0m[38;5;138mX[0m[38;5;144m2[0m[38;5;187m3[0m[38;5;151m5[0m[38;5;116m5[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;73mX[0m[38;5;66mr[0m[38;5;59mi[0m[38;5;66ms[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;73mA[0m[38;5;74mA[0m[38;5;74m2[0m[38;5;80m2[0m[38;5;73mX[0m[38;5;59mi[0m[38;5;187m3[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mM[0m[38;5;187m3[0m[38;5;109mA[0m[38;5;66ms[0m[38;5;73mX[0m[38;5;60mr[0m[38;5;66mr[0m[38;5;73m2[0m[38;5;73mX[0m[38;5;66mr[0m[38;5;59m;[0m[38;5;23m:[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;144m2[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;151m5[0m[38;5;116m5[0m[38;5;60mi[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;95ms[0m[38;5;223mH[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;145m2[0m[38;5;73m2[0m[38;5;67ms[0m[38;5;53m:[0m[38;5;16m,[0m[38;5;59m:[0m[38;5;59m:[0m[38;5;59m:[0m[38;5;59mi[0m[38;5;66mr[0m[38;5;67ms[0m[38;5;66mr[0m[38;5;59mi[0m[38;5;223mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;66ms[0m[38;5;60mi[0m[38;5;23m:[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;17m:[0m[38;5;23m;[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;144mA[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;80m2[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;59mi[0m[38;5;59m;[0m[38;5;59m:[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;138mX[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;151m3[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;52m,[0m[38;5;53m:[0m[38;5;66mr[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59mr[0m[38;5;109m2[0m[38;5;151m3[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;74mA[0m[38;5;59mi[0m[38;5;23m,[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;180m5[0m[38;5;223mM[0m[38;5;223mh[0m[38;5;223mh[0m[38;5;187m3[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;66mr[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;59m;[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;151m5[0m[38;5;109m2[0m[38;5;73mX[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59mi[0m[38;5;60mi[0m[38;5;60mr[0m[38;5;66mr[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59mi[0m[38;5;73mX[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;80m2[0m[38;5;80m2[0m[38;5;80m2[0m[38;5;17m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mX[0m[38;5;223mM[0m[38;5;223mh[0m[38;5;223mh[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73m2[0m[38;5;60mi[0m[38;5;23m;[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;187mh[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;223mM[0m[38;5;187mM[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;109m2[0m[38;5;67ms[0m[38;5;52m,[0m[38;5;59mi[0m[38;5;66ms[0m[38;5;67mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;66mr[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mX[0m[38;5;223mM[0m[38;5;223mh[0m[38;5;223mh[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;80m2[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;181m5[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;145m2[0m[38;5;116m2[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m:[0m[38;5;66ms[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;80m2[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;66ms[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mX[0m[38;5;223mM[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;180m5[0m[38;5;223mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;109m2[0m[38;5;80m2[0m[38;5;59mi[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;59m;[0m[38;5;109mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;74mA[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;102ms[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;144mA[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;145m2[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;181m5[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;80m2[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;95mi[0m[38;5;103mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;67ms[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;138mA[0m[38;5;181m5[0m[38;5;145m5[0m[38;5;145m5[0m[38;5;145m2[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;80m2[0m[38;5;23m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;187m3[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;80m2[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m,[0m[38;5;102mX[0m[38;5;109mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;74mA[0m[38;5;59m;[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;52m:[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;181m3[0m[38;5;181m3[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;74m2[0m[38;5;17m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;58m:[0m[38;5;52m,[0m[38;5;137ms[0m[38;5;180m5[0m[38;5;181m5[0m[38;5;187m3[0m[38;5;187m3[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;80m2[0m[38;5;23m:[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;95mr[0m[38;5;144mA[0m[38;5;180m2[0m[38;5;180m5[0m[38;5;187m3[0m[38;5;187mh[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;16m,[0m[38;5;52m,[0m[38;5;52m,[0m[38;5;52m,[0m[38;5;95mi[0m[38;5;102mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;16m,[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;52m,[0m[38;5;59m;[0m[38;5;59m:[0m[38;5;95mr[0m[38;5;180m2[0m[38;5;180m2[0m[38;5;181m5[0m[38;5;187m3[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;59mi[0m[38;5;138mX[0m[38;5;224mG[0m[38;5;180m5[0m[38;5;181m5[0m[38;5;223mH[0m[38;5;187mM[0m[38;5;181m3[0m[38;5;144m2[0m[38;5;145m2[0m[38;5;151m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;16m.[0m[38;5;16m [0m[38;5;52m:[0m[38;5;181m5[0m[38;5;138mX[0m[38;5;187mM[0m[38;5;230m#[0m[38;5;144m2[0m[38;5;181m5[0m[38;5;187mM[0m[38;5;187mh[0m[38;5;181m5[0m[38;5;144m2[0m[38;5;145m5[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;80mA[0m[38;5;66mr[0m[38;5;95mr[0m[38;5;131mr[0m[38;5;131ms[0m[38;5;102ms[0m[38;5;102ms[0m[38;5;102ms[0m[38;5;66ms[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;73mX[0m[38;5;74mA[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m,[0m[38;5;101ms[0m[38;5;138mX[0m[38;5;224mH[0m[38;5;187mh[0m[38;5;138mA[0m[38;5;187mh[0m[38;5;187mh[0m[38;5;180m5[0m[38;5;144mA[0m[38;5;145m2[0m[38;5;109m2[0m[38;5;73mX[0m[38;5;74mA[0m[38;5;59mi[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;53m:[0m[38;5;96ms[0m[38;5;109mA[0m[38;5;115m5[0m[38;5;59mi[0m[38;5;145m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;17m:[0m[38;5;16m [0m[38;5;16m.[0m[38;5;95mr[0m[38;5;145m5[0m[38;5;59mi[0m[38;5;151m5[0m[38;5;109m5[0m[38;5;59mi[0m[38;5;115m5[0m[38;5;115m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mX[0m[38;5;59m;[0m[38;5;132ms[0m[38;5;95mi[0m[38;5;102mX[0m[38;5;102mX[0m[38;5;67mX[0m[38;5;73mX[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;59m;[0m[38;5;102mX[0m[38;5;102ms[0m[38;5;116m5[0m[38;5;66mr[0m[38;5;102ms[0m[38;5;115m5[0m[38;5;109m2[0m[38;5;109m2[0m[38;5;109mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;73mA[0m[38;5;66mr[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m,[0m[38;5;17m,[0m[38;5;23m;[0m[38;5;17m:[0m[38;5;23m:[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;23m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;16m.[0m[38;5;23m:[0m[38;5;17m:[0m[38;5;23m:[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;23m:[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;23m:[0m[38;5;16m,[0m[38;5;52m:[0m[38;5;52m:[0m[38;5;52m:[0m[38;5;53m:[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;16m,[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m.[0m[38;5;53m:[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;23m:[0m[38;5;16m,[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;59m;[0m[38;5;23m;[0m[38;5;17m:[0m[38;5;16m.[0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
||||
[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m[38;5;16m [0m
|
BIN
images/orthrus2.webp
Normal file
After Width: | Height: | Size: 219 KiB |
BIN
images/orthrus2a.jpg
Normal file
After Width: | Height: | Size: 81 KiB |
27
index.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// server
|
||||
// index.js
|
||||
/**
|
||||
* index.js
|
||||
*
|
||||
* WebSSH2 - Web to SSH2 gateway
|
||||
* Bill Church - https://github.com/billchurch/WebSSH2 - May 2017
|
||||
*/
|
||||
|
||||
const { initializeServer } = require("./app/app")
|
||||
|
||||
/**
|
||||
* Main function to start the application
|
||||
*/
|
||||
function main() {
|
||||
initializeServer()
|
||||
}
|
||||
|
||||
// Run the application
|
||||
main()
|
||||
|
||||
// For testing purposes, export the functions
|
||||
module.exports = {
|
||||
initializeServer
|
||||
}
|
7
jsconfig.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"target": "ES6"
|
||||
},
|
||||
"exclude": ["node_modules"]
|
||||
}
|
10389
package-lock.json
generated
Normal file
97
package.json
|
@ -1,6 +1,95 @@
|
|||
{
|
||||
"dependencies": {},
|
||||
"name": "webssh2-server",
|
||||
"version": "0.2.24",
|
||||
"ignore": [
|
||||
".gitignore"
|
||||
],
|
||||
"bin": {
|
||||
"webssh2-server": "./index.js"
|
||||
},
|
||||
"description": "A Websocket to SSH2 gateway using xterm.js, socket.io, ssh2",
|
||||
"homepage": "https://github.com/billchurch/WebSSH2",
|
||||
"keywords": [
|
||||
"ssh",
|
||||
"webssh",
|
||||
"terminal",
|
||||
"webterminal"
|
||||
],
|
||||
"license": "SEE LICENSE IN FILE - LICENSE",
|
||||
"private": false,
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/billchurch/WebSSH2.git"
|
||||
},
|
||||
"contributors": [
|
||||
"Bill Church <wmchurch@gmail.com>"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/billchurch/WebSSH2/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": "^4.11.8",
|
||||
"basic-auth": "^2.0.1",
|
||||
"body-parser": "^1.15.2",
|
||||
"debug": "^3.2.7",
|
||||
"express": "^4.14.1",
|
||||
"express-session": "^1.18.0",
|
||||
"express-socket.io-session": "^1.3.5",
|
||||
"jsmasker": "^1.4.0",
|
||||
"read-config-ng": "~3.0.7",
|
||||
"socket.io": "~2.2.0",
|
||||
"ssh2": "~0.8.9",
|
||||
"validator": "^12.2.0",
|
||||
"webssh2_client": "^0.2.28"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"lint": "eslint app",
|
||||
"lint:fix": "eslint app --fix",
|
||||
"watch": "NODE_ENV=development DEBUG=webssh* nodemon index.js -w app/ -w index.js -w config.json -w package.json",
|
||||
"test": "jest",
|
||||
"release": "npm run lint:fix && npm run test && standard-version -a -s --release-as patch --commit-all --infile ChangeLog.md",
|
||||
"release:dry-run": "standard-version -a -s --release-as patch --dry-run",
|
||||
"publish:dry-run": "npm publish --dry-run",
|
||||
"publish:npm": "npm publish",
|
||||
"pretest": "npm run lint",
|
||||
"ci": "npm run test",
|
||||
"release:major": "npm run release -- --release-as major",
|
||||
"release:minor": "npm run release -- --release-as minor",
|
||||
"release:patch": "npm run release -- --release-as patch"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"**/tests/**/*.test.js"
|
||||
]
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"bin/*",
|
||||
"build/*"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"bun-types": "^1.0.1"
|
||||
}
|
||||
}
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-config-airbnb-base": "^13.2.0",
|
||||
"eslint-config-prettier": "^3.6.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jest": "^21.27.2",
|
||||
"eslint-plugin-node": "^8.0.1",
|
||||
"eslint-plugin-prettier": "^2.7.0",
|
||||
"jest": "^21.2.1",
|
||||
"nodemon": "^1.12.1",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier-eslint": "^7.1.0",
|
||||
"standard-version": "^4.4.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"directories": {
|
||||
"test": "tests"
|
||||
},
|
||||
"author": ""
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
{
|
||||
"changelogPath": "CHANGELOG.md",
|
||||
"include-v-in-tags": false,
|
||||
"prerelease": true,
|
||||
"packages": {
|
||||
"app": {
|
||||
"releaseType": "node",
|
||||
"draft": false,
|
||||
"bumpMinorPreMajor": false,
|
||||
"bumpPatchForMinorPreMajor": false,
|
||||
"versioning": "default"
|
||||
}
|
||||
}
|
||||
}
|
11
tests/crypto-utils.test.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
// server
|
||||
// tests/crypto-utils.test.js
|
||||
|
||||
const { generateSecureSecret } = require("../app/crypto-utils")
|
||||
|
||||
describe("generateSecureSecret", () => {
|
||||
it("should generate a 64-character hex string", () => {
|
||||
const secret = generateSecureSecret()
|
||||
expect(secret).toMatch(/^[0-9a-f]{64}$/)
|
||||
})
|
||||
})
|
88
tests/errors.test.js
Normal file
|
@ -0,0 +1,88 @@
|
|||
const {
|
||||
WebSSH2Error,
|
||||
ConfigError,
|
||||
SSHConnectionError,
|
||||
handleError
|
||||
} = require("../app/errors")
|
||||
const { logError } = require("../app/logger")
|
||||
const { HTTP, MESSAGES } = require("../app/constants")
|
||||
|
||||
jest.mock("../app/logger", () => ({
|
||||
logError: jest.fn(),
|
||||
createNamespacedDebug: jest.fn(() => jest.fn())
|
||||
}))
|
||||
|
||||
describe("errors", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("WebSSH2Error", () => {
|
||||
it("should create a WebSSH2Error with correct properties", () => {
|
||||
const error = new WebSSH2Error("Test error", "TEST_CODE")
|
||||
expect(error).toBeInstanceOf(Error)
|
||||
expect(error.name).toBe("WebSSH2Error")
|
||||
expect(error.message).toBe("Test error")
|
||||
expect(error.code).toBe("TEST_CODE")
|
||||
})
|
||||
})
|
||||
|
||||
describe("ConfigError", () => {
|
||||
it("should create a ConfigError with correct properties", () => {
|
||||
const error = new ConfigError("Config error")
|
||||
expect(error).toBeInstanceOf(WebSSH2Error)
|
||||
expect(error.name).toBe("ConfigError")
|
||||
expect(error.message).toBe("Config error")
|
||||
expect(error.code).toBe(MESSAGES.CONFIG_ERROR)
|
||||
})
|
||||
})
|
||||
|
||||
describe("SSHConnectionError", () => {
|
||||
it("should create a SSHConnectionError with correct properties", () => {
|
||||
const error = new SSHConnectionError("SSH connection error")
|
||||
expect(error).toBeInstanceOf(WebSSH2Error)
|
||||
expect(error.name).toBe("SSHConnectionError")
|
||||
expect(error.message).toBe("SSH connection error")
|
||||
expect(error.code).toBe(MESSAGES.SSH_CONNECTION_ERROR)
|
||||
})
|
||||
})
|
||||
|
||||
describe("handleError", () => {
|
||||
const mockRes = {
|
||||
status: jest.fn(() => mockRes),
|
||||
json: jest.fn()
|
||||
}
|
||||
|
||||
it("should handle WebSSH2Error correctly", () => {
|
||||
const error = new WebSSH2Error("Test error", "TEST_CODE")
|
||||
handleError(error, mockRes)
|
||||
|
||||
expect(logError).toHaveBeenCalledWith("Test error", error)
|
||||
expect(mockRes.status).toHaveBeenCalledWith(HTTP.INTERNAL_SERVER_ERROR)
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: "Test error",
|
||||
code: "TEST_CODE"
|
||||
})
|
||||
})
|
||||
|
||||
it("should handle generic Error correctly", () => {
|
||||
const error = new Error("Generic error")
|
||||
handleError(error, mockRes)
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(MESSAGES.UNEXPECTED_ERROR, error)
|
||||
expect(mockRes.status).toHaveBeenCalledWith(HTTP.INTERNAL_SERVER_ERROR)
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: MESSAGES.UNEXPECTED_ERROR
|
||||
})
|
||||
})
|
||||
|
||||
it("should not send response if res is not provided", () => {
|
||||
const error = new Error("No response error")
|
||||
handleError(error)
|
||||
|
||||
expect(logError).toHaveBeenCalledWith(MESSAGES.UNEXPECTED_ERROR, error)
|
||||
expect(mockRes.status).not.toHaveBeenCalled()
|
||||
expect(mockRes.json).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
48
tests/logger.test.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
// server
|
||||
// tests/logger.test.js
|
||||
|
||||
const createDebug = require("debug")
|
||||
const { createNamespacedDebug, logError } = require("../app/logger")
|
||||
|
||||
jest.mock("debug")
|
||||
|
||||
describe("logger", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
console.error = jest.fn()
|
||||
})
|
||||
|
||||
describe("createNamespacedDebug", () => {
|
||||
it("should create a debug function with the correct namespace", () => {
|
||||
const mockDebug = jest.fn()
|
||||
createDebug.mockReturnValue(mockDebug)
|
||||
|
||||
const result = createNamespacedDebug("test")
|
||||
|
||||
expect(createDebug).toHaveBeenCalledWith("webssh2:test")
|
||||
expect(result).toBe(mockDebug)
|
||||
})
|
||||
})
|
||||
|
||||
describe("logError", () => {
|
||||
it("should log an error message without an error object", () => {
|
||||
const message = "Test error message"
|
||||
|
||||
logError(message)
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith(message)
|
||||
expect(console.error).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("should log an error message with an error object", () => {
|
||||
const message = "Test error message"
|
||||
const error = new Error("Test error")
|
||||
|
||||
logError(message, error)
|
||||
|
||||
expect(console.error).toHaveBeenCalledWith(message)
|
||||
expect(console.error).toHaveBeenCalledWith("ERROR: Error: Test error")
|
||||
expect(console.error).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
})
|
||||
})
|