moved all files to code directory
This commit is contained in:
9
nodejs/LICENSE
Executable file
9
nodejs/LICENSE
Executable file
@ -0,0 +1,9 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) <year> <copyright holders>
|
||||
|
||||
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.
|
230
nodejs/README.md
Executable file
230
nodejs/README.md
Executable file
@ -0,0 +1,230 @@
|
||||
# proxy
|
||||
|
||||
## API docs
|
||||
[API docs](api.md)
|
||||
|
||||
## Server set up
|
||||
|
||||
The server requires:
|
||||
* NodeJS 8.x
|
||||
* open ssh server(any modern version will do)
|
||||
* inbound Internet access
|
||||
* OpenResty
|
||||
* redis
|
||||
* lua rocks
|
||||
|
||||
This has been tested on ubuntu 16.04, but should work on any modern Linux distro. It used the Linux users for its user management, so this will **ONLY** work on Linux, no macOS, BSD or Windows.
|
||||
|
||||
The steps below are for a new ubuntu server, they should be mostly the same for other distros, but the paths and availability of packages may vary. A dedicated server is highly recommended (since it will make ever user a system user), a VPS like Digital Ocean will do just fine.
|
||||
|
||||
* Install other
|
||||
These packages are needed for the PAM node package
|
||||
```bash
|
||||
apt install libpam0g-dev build-essential
|
||||
```
|
||||
|
||||
* Install open ssh server
|
||||
```bash
|
||||
apt install ssh
|
||||
```
|
||||
|
||||
* Install openresty
|
||||
|
||||
[OpenResty® Linux Packages](https://openresty.org/en/linux-packages.html)
|
||||
|
||||
* Install redis
|
||||
```bash
|
||||
apt install redis-server
|
||||
```
|
||||
|
||||
* install lua plugin
|
||||
```bash
|
||||
apt install luarocks
|
||||
sudo luarocks install lua-resty-auto-ssl
|
||||
```
|
||||
|
||||
* Configure sshd for tunneling
|
||||
|
||||
|
||||
* openresty config
|
||||
|
||||
Set up fail back SSL certs
|
||||
```bash
|
||||
mkdir /etc/ssl/
|
||||
|
||||
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj '/CN=sni-support-required-for-valid-ssl' -keyout /etc/ssl/resty-auto-ssl-fallback.key -out /etc/ssl/resty-auto-ssl-fallback.crt
|
||||
|
||||
openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj '/CN=sni-support-required-for-valid-ssl' -keyout /etc/ssl/resty-auto-ssl-fallback.key -out /etc/ssl/resty-auto-ssl-fallback.crt
|
||||
|
||||
```
|
||||
|
||||
|
||||
change the `/etc/openresty/nginx.conf to have this config`
|
||||
|
||||
```
|
||||
#user nobody;
|
||||
worker_processes 4;
|
||||
|
||||
#error_log logs/error.log;
|
||||
#error_log logs/error.log notice;
|
||||
#error_log logs/error.log info;
|
||||
|
||||
#pid logs/nginx.pid;
|
||||
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
|
||||
http {
|
||||
client_max_body_size 4g;
|
||||
|
||||
|
||||
lua_shared_dict auto_ssl 100m;
|
||||
lua_shared_dict auto_ssl_settings 64k;
|
||||
|
||||
resolver 8.8.4.4 8.8.8.8;
|
||||
|
||||
init_by_lua_block {
|
||||
auto_ssl = (require "resty.auto-ssl").new()
|
||||
auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")
|
||||
auto_ssl:set("allow_domain", function(domain)
|
||||
return true
|
||||
end)
|
||||
auto_ssl:init()
|
||||
}
|
||||
|
||||
init_worker_by_lua_block {
|
||||
auto_ssl:init_worker()
|
||||
}
|
||||
|
||||
ssl_session_cache shared:SSL:10m;
|
||||
ssl_session_timeout 10m;
|
||||
|
||||
server {
|
||||
listen 127.0.0.1:8999;
|
||||
|
||||
# Increase the body buffer size, to ensure the internal POSTs can always
|
||||
# parse the full POST contents into memory.
|
||||
client_body_buffer_size 128k;
|
||||
client_max_body_size 128k;
|
||||
|
||||
location / {
|
||||
content_by_lua_block {
|
||||
auto_ssl:hook_server()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
# '$status $body_bytes_sent "$http_referer" '
|
||||
# '"$http_user_agent" "$http_x_forwarded_for"';
|
||||
|
||||
access_log /var/log/nginx/access.log;
|
||||
error_log /var/log/nginx/error.log;
|
||||
|
||||
sendfile on;
|
||||
#tcp_nopush on;
|
||||
|
||||
#keepalive_timeout 0;
|
||||
keepalive_timeout 65;
|
||||
|
||||
#gzip on;
|
||||
include sites-enabled/*;
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
add the SSL config file `/etc/openresty/autossl.conf`
|
||||
|
||||
```
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
|
||||
|
||||
ssl_certificate_by_lua_block {
|
||||
auto_ssl:ssl_certificate()
|
||||
}
|
||||
|
||||
location /.well-known/acme-challenge/ {
|
||||
content_by_lua_block {
|
||||
auto_ssl:challenge_server()
|
||||
}
|
||||
}
|
||||
|
||||
ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
|
||||
ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;
|
||||
|
||||
```
|
||||
|
||||
|
||||
Add the proxy config `/etc/openresty/sites-enabled/000-proxy`
|
||||
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
listen 443 ssl;
|
||||
|
||||
include autossl.conf;
|
||||
|
||||
location / {
|
||||
resolver 10.0.3.1; #8.8.4.4; # use Google's open DNS server
|
||||
|
||||
set $target '';
|
||||
access_by_lua '
|
||||
local key = ngx.var.host
|
||||
if not key then
|
||||
ngx.log(ngx.ERR, "no user-agent found")
|
||||
return ngx.exit(400)
|
||||
end
|
||||
|
||||
local redis = require "resty.redis"
|
||||
local red = redis:new()
|
||||
|
||||
red:set_timeout(1000) -- 1 second
|
||||
|
||||
local ok, err = red:connect("127.0.0.1", 6379)
|
||||
if not ok then
|
||||
ngx.log(ngx.ERR, "failed to connect to redis: ", err)
|
||||
return ngx.exit(500)
|
||||
end
|
||||
|
||||
local host, err = red:hget("proxy_host_"..key, "ip")
|
||||
if not host then
|
||||
ngx.log(ngx.ERR, "failed to get redis key: ", err)
|
||||
return ngx.exit(500)
|
||||
end
|
||||
|
||||
if host == ngx.null then
|
||||
ngx.log(ngx.ERR, "no host found for key ", key)
|
||||
return ngx.exit(400)
|
||||
end
|
||||
ngx.log(ngx.WARN, "==Found match!!! ", key, host)
|
||||
ngx.var.target = host
|
||||
';
|
||||
|
||||
|
||||
proxy_pass http://$target;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header Host $host;
|
||||
add_header X-Target-Host $target;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## ref
|
||||
|
||||
https://blog.trackets.com/2014/05/17/ssh-tunnel-local-and-remote-port-forwarding-explained-with-examples.html
|
||||
https://github.com/GUI/lua-resty-auto-ssl
|
106
nodejs/api.md
Executable file
106
nodejs/api.md
Executable file
@ -0,0 +1,106 @@
|
||||
## get host info
|
||||
|
||||
**get** `/api/<HOST>`
|
||||
|
||||
```bash
|
||||
curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://admin.rubyisforpussys.com/api/mine.com
|
||||
```
|
||||
|
||||
* 200 {"host":"yours.com","results":{"ip":"127.0.0.1:4000","updated":"1518595297563","username":"test10"}}
|
||||
* 404 {"host":"mine.comf","results":null}
|
||||
|
||||
|
||||
## view all hosts
|
||||
|
||||
**get** `/api/`
|
||||
|
||||
```bash
|
||||
curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://admin.rubyisforpussys.com/api/
|
||||
```
|
||||
|
||||
* 200 {"hosts":["mine.com","mine2.com"]}
|
||||
|
||||
|
||||
## add/edit host
|
||||
|
||||
**post** `/api/`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d "{\"host\": \"yours.com\", \"ip\": \"127.0.0.1:4000\"}" https://admin.rubyisforpussys.com/api/
|
||||
```
|
||||
|
||||
* 200 {"message":"Host yours.com Added"}
|
||||
* 400 {"message":"Missing fields: ip"}
|
||||
|
||||
|
||||
## delete host
|
||||
|
||||
**delete** /`api`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X DELETE -d "{\"host\": \"yours.com\"}" https://admin.rubyisforpussys.com/api/
|
||||
```
|
||||
|
||||
* 200 {"message":"Host yours.com deleted"}
|
||||
|
||||
|
||||
## create invite token
|
||||
|
||||
**post** `/users/invite`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -H "auth-token: 0b06eb2e-4ca4-4881-9a0f-b8df55431cd1" -X POST https://admin.rubyisforpussys.com/users/invite
|
||||
```
|
||||
|
||||
* 200 {"token":"5caf94d2-2c91-4010-8df7-968d10802b9d"}
|
||||
|
||||
|
||||
## sing up
|
||||
|
||||
**post** `/auth/invite/<INVITE TOKEN>`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -X POST -d "{\"username\": \"test9\", \"password\": \"palm7\"}" https://admin.rubyisforpussys.com/auth/invite/b33d8819-ec64-4cf4-a6ec-77562d738fa4
|
||||
|
||||
```
|
||||
|
||||
* 200 {"user":"test9","token":"af662d8b-3d44-4110-8ad9-047dc752d97f"}
|
||||
* 400 {"message":"Missing fields"}
|
||||
* 401 {"message":"Token not valid"}
|
||||
* 409 {"message":"username taken"}
|
||||
|
||||
|
||||
## login
|
||||
|
||||
**post** `/auth/login`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -X POST -d "{\"username\": \"test8\", \"password\": \"palm7\"}" https://admin.rubyisforpussys.com/auth/login
|
||||
```
|
||||
|
||||
* 200 {"login":true,"token":"027d3964-7d81-4462-a6f9-2c1f9b40b4be"}
|
||||
* 401 {"login":false}
|
||||
|
||||
|
||||
## verify SSH key
|
||||
|
||||
**post** `/auth/verifykey`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -X POST -d "{\"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDM9vboz5YGgESsrR2e4JOeP2qtmQo2S8BjI+Y/VxPQ6WbNFzAkXxDniHcnPCrhkeX36SKINvMjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.com\"}" https://admin.rubyisforpussys.com/auth/verifykey
|
||||
```
|
||||
|
||||
* 200 {"info":"4096 SHA256:dfdCYzt0atMBXVZTJzUxsu99IjXXFXpocSox5q+jOs8 wmantly@gmail.com (RSA)\n"}
|
||||
* 400 {"message":"Key is not a public key file!"}
|
||||
|
||||
|
||||
## add ssh key to current user
|
||||
|
||||
**post** `/users/key`
|
||||
|
||||
```bash
|
||||
curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d "{\"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDM9vboz5YGgESsrR2e4JOeP2qtmQo2S8BjI+Y/VxPQ6WbNFzAkXxDniHcnPCrhkeX36SKINvMjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.co\"}" https://admin.rubyisforpussys.com/users/key
|
||||
```
|
||||
|
||||
* 200 {"message":true}
|
||||
* 400 {"message":"Bad SSH key"}
|
32
nodejs/app.js
Executable file
32
nodejs/app.js
Executable file
@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
|
||||
const middleware = require('./middleware/auth');
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.use('/auth', require('./routes/auth'));
|
||||
app.use('/users', middleware.auth, require('./routes/users'));
|
||||
app.use('/api', middleware.auth, require('./routes/routes'));
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.json({message: 'error!'});
|
||||
});
|
||||
|
||||
module.exports = app;
|
90
nodejs/bin/www
Executable file
90
nodejs/bin/www
Executable file
@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('proxy-api:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
25
nodejs/hostcontrol.js
Executable file
25
nodejs/hostcontrol.js
Executable file
@ -0,0 +1,25 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
//you must add the absolute path to hosts.js on line 4, or this file will NOT work
|
||||
const hosts = require("./models/hosts.js")
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
(async function(command){
|
||||
if (command == "--list"){
|
||||
console.log(await hosts.listAll())
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
})(command)
|
||||
/*
|
||||
if process.argv[2] == "--info"
|
||||
|
||||
if process.agrv[2] == "--add"
|
||||
|
||||
if process.argv[2] == "--remove"
|
||||
|
||||
else{
|
||||
console.log("help text")
|
||||
}
|
||||
*/
|
19
nodejs/middleware/auth.js
Executable file
19
nodejs/middleware/auth.js
Executable file
@ -0,0 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
const Users = require('../models/users');
|
||||
|
||||
async function auth(req, res, next){
|
||||
if(req.header('auth-token')){
|
||||
let user = await Users.checkToken({token: req.header('auth-token')});
|
||||
if(user.username){
|
||||
req.user = user;
|
||||
return next();
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(401).json({
|
||||
message: 'Login failed'
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {auth};
|
0
nodejs/models/auth.js
Executable file
0
nodejs/models/auth.js
Executable file
48
nodejs/models/hosts.js
Executable file
48
nodejs/models/hosts.js
Executable file
@ -0,0 +1,48 @@
|
||||
'use strict';
|
||||
|
||||
const {promisify} = require('util');
|
||||
const client = require('../redis');
|
||||
|
||||
async function getInfo(data){
|
||||
let info = await client.HGETALL('host_' + data.host);
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
|
||||
async function listAll(){
|
||||
try{
|
||||
let hosts = await client.SMEMBERS('hosts');
|
||||
return hosts;
|
||||
}catch(error){
|
||||
return new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function add(data){
|
||||
try{
|
||||
await client.SADD('hosts', data.host);
|
||||
await client.HSET('host_' + data.host, 'ip', data.ip);
|
||||
await client.HSET('host_' + data.host, 'updated', (new Date).getTime());
|
||||
await client.HSET('host_' + data.host, 'username', data.username);
|
||||
} catch (error){
|
||||
|
||||
return new Error(error);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function remove(data){
|
||||
try{
|
||||
await client.SREM('hosts', data.host);
|
||||
let count = await client.DEL('host_' + data.host);
|
||||
return count;
|
||||
} catch(error) {
|
||||
return new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {getInfo, listAll, add, remove};
|
107
nodejs/models/users.js
Executable file
107
nodejs/models/users.js
Executable file
@ -0,0 +1,107 @@
|
||||
'use strict';
|
||||
|
||||
const {promisify} = require('util');
|
||||
const client = require('../redis');
|
||||
const linuxUser = require('linux-user');
|
||||
const pam = require('authenticate-pam');
|
||||
|
||||
const UUID = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)};
|
||||
|
||||
const authenticate = promisify(pam.authenticate);
|
||||
|
||||
const addSSHtoUser = promisify(linuxUser.addSSHtoUser)
|
||||
const getUserGroups = promisify(linuxUser.getUserGroups);
|
||||
const verifySSHKey = promisify(linuxUser.verifySSHKey);
|
||||
const addUser = promisify(linuxUser.addUser);
|
||||
const setPassword = promisify(linuxUser.setPassword);
|
||||
|
||||
/*
|
||||
Invite
|
||||
*/
|
||||
async function makeInviteToken(data){
|
||||
let token = UUID();
|
||||
await client.HSET('users_tokens', token, JSON.stringify({
|
||||
created_by: data.username,
|
||||
isAdmin: data.isAdmin,
|
||||
invited: false
|
||||
}));
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async function checkInvite(data){
|
||||
let token = await client.HGET('users_tokens', data.token);
|
||||
|
||||
return JSON.parse(token);
|
||||
}
|
||||
|
||||
async function consumeInvite(data){
|
||||
let invite = await checkInvite(data);
|
||||
|
||||
invite.invited = data.username;
|
||||
|
||||
await client.HSET('users_tokens', data.token, JSON.stringify(invite));
|
||||
}
|
||||
|
||||
/*
|
||||
Auth/ Auth token
|
||||
*/
|
||||
|
||||
async function login(data){
|
||||
try{
|
||||
await authenticate(data.username, data.password);
|
||||
|
||||
return await getUserGroups(data.username);
|
||||
}catch(error){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function addToken(data){
|
||||
let token = UUID();
|
||||
await client.HSET('users_tokens', token, data.username);
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async function checkToken(data){
|
||||
let user = await client.HGET('users_tokens', data.token);
|
||||
|
||||
return {
|
||||
username: user,
|
||||
groups: (user && await getUserGroups(user))
|
||||
}
|
||||
}
|
||||
|
||||
async function addSSHkey(data){
|
||||
|
||||
try{
|
||||
let user = await addSSHtoUser(data.username, data.key);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Users
|
||||
*/
|
||||
|
||||
async function add(data) {
|
||||
|
||||
let systemUser = await addUser(data.username);
|
||||
let systemUserPassword = await setPassword(data.username, data.password);
|
||||
|
||||
}
|
||||
|
||||
async function verifyKey(data){
|
||||
return await verifySSHKey(key)
|
||||
}
|
||||
|
||||
async function ifUserExists(data){
|
||||
const getUserInfo = promisify(linuxUser.getUserInfo);
|
||||
return await getUserInfo(data.username);
|
||||
}
|
||||
|
||||
module.exports = {login, add, addToken, checkToken, ifUserExists,
|
||||
makeInviteToken, checkInvite, consumeInvite, addSSHkey, verifyKey};
|
2906
nodejs/package-lock.json
generated
Executable file
2906
nodejs/package-lock.json
generated
Executable file
File diff suppressed because it is too large
Load Diff
27
nodejs/package.json
Executable file
27
nodejs/package.json
Executable file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "proxy-api",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"author": [
|
||||
{
|
||||
"name": "William Mantly",
|
||||
"email": "wmantly@gmail.com"
|
||||
}
|
||||
],
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"authenticate-pam": "github:WeiAnAn/node-authenticate-pam",
|
||||
"express": "~4.16.1",
|
||||
"forever": "^1.0.0",
|
||||
"linux-sys-user": "github:wmantly/linux-user",
|
||||
"linux-user": "github:wmantly/linux-user",
|
||||
"redis": "^2.8.0"
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.theta42.com/wmantly/proxy.git"
|
||||
}
|
||||
}
|
23
nodejs/redis.js
Executable file
23
nodejs/redis.js
Executable file
@ -0,0 +1,23 @@
|
||||
const {createClient} = require('redis');
|
||||
const {promisify} = require('util');
|
||||
|
||||
const config = {
|
||||
prefix: 'proxy_'
|
||||
}
|
||||
|
||||
function client() {
|
||||
return createClient(config);
|
||||
}
|
||||
|
||||
const _client = client();
|
||||
|
||||
module.exports = {
|
||||
client: client,
|
||||
HGET: promisify(_client.HGET).bind(_client),
|
||||
SADD: promisify(_client.SADD).bind(_client),
|
||||
SREM: promisify(_client.SREM).bind(_client),
|
||||
DEL: promisify(_client.DEL).bind(_client),
|
||||
HSET: promisify(_client.HSET).bind(_client),
|
||||
HGETALL: promisify(_client.HGETALL).bind(_client),
|
||||
SMEMBERS: promisify(_client.SMEMBERS).bind(_client),
|
||||
};
|
90
nodejs/routes/auth.js
Executable file
90
nodejs/routes/auth.js
Executable file
@ -0,0 +1,90 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const Users = require('../models/users');
|
||||
|
||||
/*
|
||||
Password login
|
||||
*/
|
||||
router.post('/login', async function(req, res){
|
||||
let username = req.body.username;
|
||||
let password = req.body.password;
|
||||
|
||||
let groups = await Users.login({username, password});
|
||||
|
||||
if(groups){
|
||||
return res.json({
|
||||
login: true,
|
||||
token: await Users.addToken({username}),
|
||||
groups: groups,
|
||||
});
|
||||
}else{
|
||||
return res.status(401).json({
|
||||
login: false
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
verify public ssh key
|
||||
*/
|
||||
router.post('/verifykey', async function(req, res){
|
||||
let key = req.body.key;
|
||||
|
||||
try{
|
||||
return res.json({
|
||||
info: await Users.verifyKey(key)
|
||||
});
|
||||
}catch(error){
|
||||
return res.status(400).json({
|
||||
message: 'Key is not a public key file!'
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
router.post('/invite/:token', async function(req, res, next) {
|
||||
let username = req.body.username;
|
||||
let password = req.body.password;
|
||||
let token = req.params.token;
|
||||
|
||||
// make sure invite token is valid
|
||||
let invite = await Users.checkInvite({token});
|
||||
|
||||
if(!invite || invite.invited){
|
||||
return res.status(401).json({
|
||||
message: 'Token not valid'
|
||||
});
|
||||
}
|
||||
|
||||
// make sure requires fields are in
|
||||
if(!username || !password){
|
||||
return res.status(400).json({
|
||||
message: 'Missing fields'
|
||||
});
|
||||
}
|
||||
|
||||
// make sure the requested user name can be used
|
||||
if(await Users.ifUserExists({username})){
|
||||
return res.status(409).json({
|
||||
message: 'Username taken'
|
||||
});
|
||||
}
|
||||
|
||||
// create the new user
|
||||
await Users.add({username, password, isAdmin: invite.isAdmin});
|
||||
|
||||
// consume the invite token
|
||||
await Users.consumeInvite({token, username});
|
||||
|
||||
// send back API token for the new user
|
||||
return res.json({
|
||||
user: username,
|
||||
token: await Users.addToken({username})
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = router;
|
9
nodejs/routes/index.js
Executable file
9
nodejs/routes/index.js
Executable file
@ -0,0 +1,9 @@
|
||||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.render('index', { title: 'Express' });
|
||||
});
|
||||
|
||||
module.exports = router;
|
76
nodejs/routes/routes.js
Executable file
76
nodejs/routes/routes.js
Executable file
@ -0,0 +1,76 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
|
||||
const Host = require('../models/hosts');
|
||||
|
||||
router.get('/:host', async function(req, res){
|
||||
let host = req.params.host;
|
||||
|
||||
let info = await Host.getInfo({host});
|
||||
|
||||
return res.status(info ? 200 : 404).json({
|
||||
host: req.params.host,
|
||||
results: info
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/', async function(req, res){
|
||||
try{
|
||||
let hosts = await Host.listAll();
|
||||
return res.json({hosts: hosts});
|
||||
}catch(error){
|
||||
return res.status(500).json({message: `ERROR ${error}`});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async function(req, res){
|
||||
let ip = req.body.ip;
|
||||
let host = req.body.host;
|
||||
|
||||
if(!host || !ip){
|
||||
return res.status(400).json({
|
||||
message: `Missing fields: ${!host ? 'host' : ''} ${!ip ? 'ip' : ''}`
|
||||
});
|
||||
}
|
||||
|
||||
try{
|
||||
Host.add({host, ip, username: req.user.username});
|
||||
|
||||
return res.json({
|
||||
message: `Host ${host} Added`
|
||||
});
|
||||
} catch (error){
|
||||
|
||||
return res.status(500).json({
|
||||
message: `ERROR: ${error}`
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
router.delete('/', async function(req, res){
|
||||
let host = req.body.host;
|
||||
let count;
|
||||
|
||||
if(!host){
|
||||
return res.status(400).json({
|
||||
message: `Missing fields: ${!host ? 'host' : ''}`
|
||||
});
|
||||
}
|
||||
|
||||
try{
|
||||
count = await Host.remove({host});
|
||||
|
||||
}catch(error){
|
||||
return res.status(500).json({
|
||||
message: `ERROR: ${error}`
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
message: `Host ${host} deleted`,
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
26
nodejs/routes/users.js
Executable file
26
nodejs/routes/users.js
Executable file
@ -0,0 +1,26 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const Users = require('../models/users');
|
||||
|
||||
router.post('/invite', async function(req, res){
|
||||
let token = await Users.makeInviteToken({
|
||||
username: res.user
|
||||
});
|
||||
|
||||
return res.json({token:token});
|
||||
});
|
||||
|
||||
router.post('/key', async function(req, res){
|
||||
let added = await Users.addSSHkey({
|
||||
username: req.user.username,
|
||||
key: req.body.key
|
||||
});
|
||||
|
||||
return res.status(added === true ? 200 : 400).json({
|
||||
message: added
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
module.exports = router;
|
0
nodejs/ssh-keygen
Executable file
0
nodejs/ssh-keygen
Executable file
Reference in New Issue
Block a user