| @ -1,29 +1,29 @@ | |||||||
| ## get host info  | ## get host info  | ||||||
|  |  | ||||||
| **get** `/api/<HOST>` | **GET** `/api/hosts<HOST>` | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://admin.rubyisforpussys.com/api/mine.com | curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://proxy-host.com/api/hostsmine.com | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"host":"yours.com","results":{"ip":"127.0.0.1:4000","updated":"1518595297563","username":"test10","forceSSL": false, "targetSSL": true, "targetPort": "443"}} | * 200 {"host":"yours.com","results":{"ip":"127.0.0.1:4000","updated":"1518595297563","username":"test10","forceSSL": false, "targetSSL": true, "targetPort": "443"}} | ||||||
| * 404 {"host":"mine.comf","results":null} | * 404 {"name": "HostNotFound", "message": "Host does not exists"} | ||||||
|  |  | ||||||
|  |  | ||||||
| ## view all hosts  | ## view all hosts  | ||||||
|  |  | ||||||
| **get** `/api/` | **GET** `/api/hosts` | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://admin.rubyisforpussys.com/api/ | curl -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" https://proxy-host.com/api/hosts | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"hosts":["mine.com","mine2.com"]} | * 200 {"hosts":["mine.com","mine2.com"]} | ||||||
|  |  | ||||||
|  |  | ||||||
| ## add/edit host  | ## Add host  | ||||||
|  |  | ||||||
| **post** `/api/` | **POST** `/api/hosts` | ||||||
|  |  | ||||||
| Params | Params | ||||||
| * **host** -- Required, The domain name for the new record. | * **host** -- Required, The domain name for the new record. | ||||||
| @ -37,22 +37,37 @@ the proxy. The default is false and this is HIGHLY recommended. | |||||||
| * ** | * ** | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d '{"host": "test.vm42.com", "ip": "192.168.1.21", "targetSSL": false, "targetPort": "443", "forceSSL": true} https://admin.rubyisforpussys.com/api/ | curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d '{"host": "test.vm42.com", "ip": "192.168.1.21", "targetSSL": false, "targetPort": "443", "forceSSL": true} https://proxy-host.com/api/hosts | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"message":"Host yours.com Added"} | * 200 {"message":"Host yours.com added."} | ||||||
| * 400 {"message":"Missing fields:  ip"} | * 409 {"name":"HostNameUsed", "message":"Host already exists"}  | ||||||
|  | * 422 {"name":"ObjectValidateError","message":[{"key":"ip","message":"ip is required."}]} Missing or incorrect keys/values. Returns a list with a message per key error. | ||||||
|  |  | ||||||
|  | ## Edit | ||||||
|  |  | ||||||
|  | **PUT** `/api/hosts<host>` | ||||||
|  |  | ||||||
|  | Takes the same params as add, but none are required | ||||||
|  |  | ||||||
|  | curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d '{"host": "test.vm42.com", "ip": "192.168.1.21", "targetSSL": false, "targetPort": "443", "forceSSL": true} https://proxy-host.com/api/hosts | ||||||
|  |  | ||||||
|  | * 200 {"message":"Host yours.com updated."} | ||||||
|  | * 404 {"name": "HostNotFound", "message": "Host does not exists"} | ||||||
|  | * 409 {"name":"HostNameUsed", "message":"Host already exists"}  | ||||||
|  | * 422 {"name":"ObjectValidateError","message":[{"key":"ip","message":"ip is required."}]} Missing or incorrect keys/values. Returns a list with a message per key error. | ||||||
|  |  | ||||||
|  |  | ||||||
| ## delete host | ## delete host | ||||||
|  |  | ||||||
| **delete** /`api` | **DELETE** /`api/hosts/<host>` | ||||||
|  |  | ||||||
| ```bash | ```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/ | curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X DELETE https://proxy-host.com/api/hosts | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"message":"Host yours.com deleted"} | * 200 {"message":"Host yours.com deleted"} | ||||||
|  | * 404 {"name": "HostNotFound", "message": "Host does not exists"} | ||||||
|  |  | ||||||
|  |  | ||||||
| ## create invite token | ## create invite token | ||||||
| @ -60,7 +75,7 @@ curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd | |||||||
| **post** `/users/invite` | **post** `/users/invite` | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "Content-Type: application/json" -H "auth-token: 0b06eb2e-4ca4-4881-9a0f-b8df55431cd1" -X POST https://admin.rubyisforpussys.com/users/invite | curl -H "Content-Type: application/json" -H "auth-token: 0b06eb2e-4ca4-4881-9a0f-b8df55431cd1" -X POST https://proxy-host.com/users/invite | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"token":"5caf94d2-2c91-4010-8df7-968d10802b9d"} | * 200 {"token":"5caf94d2-2c91-4010-8df7-968d10802b9d"} | ||||||
| @ -71,7 +86,7 @@ curl -H "Content-Type: application/json" -H "auth-token: 0b06eb2e-4ca4-4881-9a0f | |||||||
| **post** `/auth/invite/<INVITE TOKEN>` | **post** `/auth/invite/<INVITE TOKEN>` | ||||||
|  |  | ||||||
| ```bash | ```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 | curl -H "Content-Type: application/json" -X POST -d "{\"username\": \"test9\", \"password\": \"palm7\"}" https://proxy-host.com/auth/invite/b33d8819-ec64-4cf4-a6ec-77562d738fa4 | ||||||
|  |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| @ -86,7 +101,7 @@ curl -H "Content-Type: application/json" -X POST -d "{\"username\": \"test9\", \ | |||||||
| **post** `/auth/login` | **post** `/auth/login` | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "Content-Type: application/json" -X POST -d '{"username": "test8", "password": "mypassword"}' https://admin.rubyisforpussys.com/auth/login | curl -H "Content-Type: application/json" -X POST -d '{"username": "test8", "password": "mypassword"}' https://proxy-host.com/auth/login | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"login":true,"token":"027d3964-7d81-4462-a6f9-2c1f9b40b4be"} | * 200 {"login":true,"token":"027d3964-7d81-4462-a6f9-2c1f9b40b4be"} | ||||||
| @ -98,7 +113,7 @@ curl -H "Content-Type: application/json" -X POST -d '{"username": "test8", "pass | |||||||
| **post** `/auth/verifykey` | **post** `/auth/verifykey` | ||||||
|  |  | ||||||
| ```bash | ```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 | 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://proxy-host.com/auth/verifykey | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"info":"4096 SHA256:dfdCYzt0atMBXVZTJzUxsu99IjXXFXpocSox5q+jOs8 wmantly@gmail.com (RSA)\n"} | * 200 {"info":"4096 SHA256:dfdCYzt0atMBXVZTJzUxsu99IjXXFXpocSox5q+jOs8 wmantly@gmail.com (RSA)\n"} | ||||||
| @ -110,7 +125,7 @@ curl -H "Content-Type: application/json" -X POST -d "{\"key\":\"ssh-rsa AAAAB3Nz | |||||||
| **post** `/users/key` | **post** `/users/key` | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d "{\"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.co\"}" https://admin.rubyisforpussys.com/users/key | curl -H "Content-Type: application/json" -H "auth-token: 8eff4f16-086d-40fd-acbd-7634b9a36117" -X POST -d "{\"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAjWnt4XOK2S+X+1tCoXJzqtcKKyK0gx8ijBxcWVPxsMWjMYTGSVSKiKnt6CyQzrbVGJMh3iAQ8Yv1JwH+6SAtMgT8it7iLyntNFJCesh4I/znEG58A5VBbdUle1Ztz9afjj1CZns17jk7KPm9ig5DmuvdvnMEfhFjfKv1Rp6S5nxacMoTP4tJNSEUh55IicoWk94ii5GwUVLYgyMmzdlA32TqVLFpU2yAvdA9WSnBaI/ZyktlfI7YAmK2wFBsagr9Pq1TcUAY6rZ/GTMjDxExgdYn/FxlufcuqeNJsJXs2A+0xDS/9mv/yGQzNZrL8DrVhY2OKKLoH4Q7enDbhSgEFmJUJMqPxuPEgLEvKfzcURSvIwRj1iCEw6S4dhdaLJl2RRBb1ZWBQbE5ogIbvAl7GFJUAhj3pqYJnd30VENv1MkK+IoCS7EEP0caqL9RNAId0Plud7q2XElHqzkYUE+z+Q/LvGgclXK1ZmZejNaMnV53wfhAevfwVyNGK9i5gbwc1P2lplIa5laXCcVWezqELEkTpdjp4AeKmMuCr8rY8EnLKIcKWEOsX5UumztCow6e1E55v3VeHvRZLpw4DZP7EE0Q8B/jPFWqbCw== wmantly@gmail.co\"}" https://proxy-host.com/users/key | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| * 200 {"message":true} | * 200 {"message":true} | ||||||
|  | |||||||
| @ -1,32 +1,56 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
|  | const path = require('path'); | ||||||
|  | const ejs = require('ejs') | ||||||
| const express = require('express'); | const express = require('express'); | ||||||
| const app = express(); |  | ||||||
|  |  | ||||||
| const middleware = require('./middleware/auth'); | const middleware = require('./middleware/auth'); | ||||||
|  |  | ||||||
|  | // Set up the express app. | ||||||
|  | const app = express(); | ||||||
|  |  | ||||||
|  | // load the JSON parser middleware. Express will parse JSON into native objects | ||||||
|  | // for any request that has JSON in its content type.  | ||||||
| app.use(express.json()); | app.use(express.json()); | ||||||
|  |  | ||||||
| app.use('/auth',  require('./routes/auth')); | // Set up the templating engine to build HTML for the front end. | ||||||
| app.use('/users', middleware.auth, require('./routes/users')); | app.set('views', path.join(__dirname, 'views')); | ||||||
| app.use('/api', middleware.auth, require('./routes/routes')); | app.set('view engine', 'ejs'); | ||||||
|  |  | ||||||
| // catch 404 and forward to error handler | // Have express server static content( images, CSS, browser JS) from the public | ||||||
|  | // local folder. | ||||||
|  | app.use('/static', express.static(path.join(__dirname, 'public'))) | ||||||
|  |  | ||||||
|  | // Routes for front end content. | ||||||
|  | app.use('/',  require('./routes/index')); | ||||||
|  |  | ||||||
|  | // API routes for authentication.  | ||||||
|  | app.use('/api/auth',  require('./routes/auth')); | ||||||
|  |  | ||||||
|  | // API routes for working with users. All endpoints need to be have valid user. | ||||||
|  | app.use('/api/user', middleware.auth, require('./routes/user')); | ||||||
|  |  | ||||||
|  | // API routes for working with hosts. All endpoints need to be have valid user. | ||||||
|  | app.use('/api/host', middleware.auth, require('./routes/host')); | ||||||
|  |  | ||||||
|  | // Catch 404 and forward to error handler. If none of the above routes are | ||||||
|  | // used, this is what will be called. | ||||||
| app.use(function(req, res, next) { | app.use(function(req, res, next) { | ||||||
|   var err = new Error('Not Found'); |   var err = new Error('Not Found'); | ||||||
|  |   err.message = 'Page not found' | ||||||
|   err.status = 404; |   err.status = 404; | ||||||
|   next(err); |   next(err); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // error handler | // Error handler. This is where `next()` will go on error | ||||||
| app.use(function(err, req, res, next) { | app.use(function(err, req, res, next) { | ||||||
|   // set locals, only providing error in development |   console.error(err.status || res.status, err.name, req.method, req.url); | ||||||
|   res.locals.message = err.message; |   console.error(err.message); | ||||||
|   res.locals.error = req.app.get('env') === 'development' ? err : {}; |   console.error(err.stack); | ||||||
|  |   console.error('========================================='); | ||||||
|  |  | ||||||
|   // render the error page |  | ||||||
|   res.status(err.status || 500); |   res.status(err.status || 500); | ||||||
|   res.json({message: 'error!'}); |   res.json({name: err.name, message: err.message}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | // Allow the express app to be exported into other files.  | ||||||
| module.exports = app; | module.exports = app; | ||||||
|  | |||||||
| @ -1,19 +1,17 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| const Users = require('../models/users');  | const {Auth} = require('../models/auth');  | ||||||
|  |  | ||||||
| async function auth(req, res, next){ | async function auth(req, res, next){ | ||||||
| 	if(req.header('auth-token')){ | 	try{ | ||||||
| 		let user = await Users.checkToken({token: req.header('auth-token')}); | 		let user = await Auth.checkToken({token: req.header('auth-token')}); | ||||||
| 		if(user.username){ | 		if(user.username){ | ||||||
| 			req.user = user; | 			req.user = user; | ||||||
| 			return next(); | 			return next(); | ||||||
| 		} | 		} | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	return res.status(401).json({ |  | ||||||
| 		message: 'Login failed' |  | ||||||
| 	}); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = {auth}; | module.exports = {auth}; | ||||||
|  | |||||||
| @ -0,0 +1,55 @@ | |||||||
|  | const {promisify} = require('util'); | ||||||
|  | const pam = require('authenticate-pam'); | ||||||
|  | const authenticate = promisify(pam.authenticate); | ||||||
|  |  | ||||||
|  | const {User} = require('./user'); | ||||||
|  | const {Token, AuthToken} = require('./token'); | ||||||
|  |  | ||||||
|  | Auth = {} | ||||||
|  | Auth.errors = {} | ||||||
|  |  | ||||||
|  | Auth.errors.login = function(){ | ||||||
|  | 	let error = new Error('PamLoginFailed'); | ||||||
|  | 	error.name = 'PamLoginFailed'; | ||||||
|  | 	error.message = `Invalid Credentials, login failed.`; | ||||||
|  | 	error.status = 401; | ||||||
|  |  | ||||||
|  | 	return error; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Auth.login = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		let auth = await authenticate(data.username, data.password); | ||||||
|  | 		let user = await User.get(data); | ||||||
|  | 		let token = await AuthToken.add(user); | ||||||
|  |  | ||||||
|  | 		return {user, token} | ||||||
|  | 	}catch(error){ | ||||||
|  | 		if (error == 'Authentication failure'){ | ||||||
|  | 			throw this.errors.login() | ||||||
|  | 		} | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Auth.checkToken = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		let token = await AuthToken.get(data); | ||||||
|  | 		if(token.is_valid){ | ||||||
|  | 			return await User.get(token.created_by); | ||||||
|  | 		} | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw this.errors.login(); | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Auth.logOut = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		let token = await AuthToken.get(data); | ||||||
|  | 		await token.remove(); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | module.exports = {Auth, AuthToken}; | ||||||
|  | |||||||
							
								
								
									
										19
									
								
								nodejs/models/host.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								nodejs/models/host.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const Host = require('../utils/redis_model')({ | ||||||
|  | 	_name: 'host', | ||||||
|  | 	_key: 'host', | ||||||
|  | 	_keyMap: { | ||||||
|  | 		'created_by': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | 		'created_on': {default: function(){return (new Date).getTime()}}, | ||||||
|  | 		'updated_by': {default:"__NONE__", isRequired: false, type: 'string',}, | ||||||
|  | 		'updated_on': {default: function(){return (new Date).getTime()}, always: true}, | ||||||
|  | 		'host': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | 		'ip': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | 		'targetport': {isRequired: true, type: 'number', min:0, max:65535}, | ||||||
|  | 		'forcessl': {isRequired: false, default: true, type: 'boolean'}, | ||||||
|  | 		'targetssl': {isRequired: false, default: false, type: 'boolean'}, | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = {Host}; | ||||||
| @ -1,56 +0,0 @@ | |||||||
| '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); |  | ||||||
| 		await client.HSET('host_' + data.host, 'targetPort', data.targetPort); |  | ||||||
| 		if(data.forceSSL !== undefined){ |  | ||||||
| 			await client.HSET('host_' + data.host, 'forcessl', !!data.forceSSL); |  | ||||||
| 		} |  | ||||||
| 		if(data.targetSSL !== undefined){ |  | ||||||
| 			await client.HSET('host_' + data.host, 'targetssl', !!data.targetSSL); |  | ||||||
| 		} |  | ||||||
| 	} 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}; |  | ||||||
							
								
								
									
										60
									
								
								nodejs/models/token.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								nodejs/models/token.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,60 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const redis_model = require('../utils/redis_model') | ||||||
|  | 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 Token = function(data){ | ||||||
|  | 	return redis_model({ | ||||||
|  | 		_name: `token_${data.name}`, | ||||||
|  | 		_key: 'token', | ||||||
|  | 		_keyMap: Object.assign({}, { | ||||||
|  | 			'created_by': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | 			'created_on': {default: function(){return (new Date).getTime()}}, | ||||||
|  | 			'updated_on': {default: function(){return (new Date).getTime()}, always: true}, | ||||||
|  | 			'token': {default: UUID, type: 'string', min: 36, max: 36}, | ||||||
|  | 			'is_valid': {default: true, type: 'boolean'} | ||||||
|  | 		}, data.keyMap || {}) | ||||||
|  | 	}); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Token.check = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		return this.is_valid; | ||||||
|  | 	}catch(error){ | ||||||
|  | 		return false | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var InviteToken = Object.create(Token({ | ||||||
|  | 	name: 'invite_test1', | ||||||
|  | 	keyMap:{ | ||||||
|  | 		claimed_by: {default:"__NONE__", isRequired: false, type: 'string',} | ||||||
|  | 	} | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | InviteToken.consume = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		if(this.is_valid){ | ||||||
|  | 			data['is_valid'] = false; | ||||||
|  |  | ||||||
|  | 			await this.update(data); | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | var AuthToken = Object.create(Token({ | ||||||
|  | 	name: 'auth_test1', | ||||||
|  | })); | ||||||
|  |  | ||||||
|  | AuthToken.add = async function(data){ | ||||||
|  | 	data.created_by = data.username; | ||||||
|  | 	return AuthToken.__proto__.add(data); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = {Token, InviteToken, AuthToken} | ||||||
							
								
								
									
										138
									
								
								nodejs/models/user.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										138
									
								
								nodejs/models/user.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,138 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const linuxUser = require('linux-sys-user').promise(); | ||||||
|  | const objValidate = require('../utils/object_validate'); | ||||||
|  | const {Token, InviteToken} = require('./token'); | ||||||
|  |  | ||||||
|  | var User = {} | ||||||
|  |  | ||||||
|  | User.keyMap = { | ||||||
|  | 	'username': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | 	'password': {isRequired: true, type: 'string', min: 3, max: 500}, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | User.list = async function(){ | ||||||
|  | 	try{ | ||||||
|  | 		let users = await linuxUser.getUsers(); | ||||||
|  |  | ||||||
|  | 		for(let user of users){ | ||||||
|  | 			delete user.password | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return users; | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.get = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		if(typeof data !== 'object'){ | ||||||
|  | 			let username = data; | ||||||
|  | 			data = {}; | ||||||
|  | 			data.username = username; | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		let user = await linuxUser.getUserInfo(data.username); | ||||||
|  |  | ||||||
|  | 		if(user){ | ||||||
|  | 			let obj = Object.create(this); | ||||||
|  | 			Object.assign(obj, user); | ||||||
|  | 			 | ||||||
|  | 			return obj; | ||||||
|  | 		}else{ | ||||||
|  | 			let error = new Error('UserNotFound'); | ||||||
|  | 			error.name = 'UserNotFound'; | ||||||
|  | 			error.message = `PAM:${data.username} does not exists`; | ||||||
|  | 			error.status = 404; | ||||||
|  | 			throw error; | ||||||
|  | 		} | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.exists = async function(data){ | ||||||
|  | 	// Return true or false if the requested entry exists ignoring error's. | ||||||
|  | 	try{ | ||||||
|  | 		await this.get(data); | ||||||
|  |  | ||||||
|  | 		return true | ||||||
|  | 	}catch(error){ | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.add = async function(data) { | ||||||
|  | 	try{ | ||||||
|  | 		data = objValidate.processKeys(this.keyMap, data); | ||||||
|  | 		let systemUser = await linuxUser.addUser(data.username); | ||||||
|  | 		await require('util').promisify(setTimeout)(500) | ||||||
|  | 		let systemUserPassword = await linuxUser.setPassword(data.username, data.password); | ||||||
|  |  | ||||||
|  | 		return this.get(data.username); | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		if(error.message.includes('exists')){ | ||||||
|  | 			let error = new Error('UserNameUsed'); | ||||||
|  | 			error.name = 'UserNameUsed'; | ||||||
|  | 			error.message = `PAM:${data.username} already exists`; | ||||||
|  | 			error.status = 409; | ||||||
|  |  | ||||||
|  | 			throw error; | ||||||
|  | 		} | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.addByInvite = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		let token = await InviteToken.get(data.token); | ||||||
|  |  | ||||||
|  | 		if(!token.is_valid){ | ||||||
|  | 			let error = new Error('Token Invalid'); | ||||||
|  | 			error.name = 'Token Invalid'; | ||||||
|  | 			error.message = `Token is not valid or as allready been used. ${data.token}`; | ||||||
|  | 			error.status = 401; | ||||||
|  | 			throw error; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		let user = await this.add(data); | ||||||
|  |  | ||||||
|  | 		if(user){ | ||||||
|  | 			await token.consume({claimed_by: user.username}); | ||||||
|  | 			return user; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.setPassword = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		if(!data.password1 || data.password1 !== data.password2){ | ||||||
|  | 			throw new Error('PasswordMismatch'); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		await linuxUser.setPassword(this.username, data.password1); | ||||||
|  |  | ||||||
|  | 		return this; | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | User.invite = async function(){ | ||||||
|  | 	try{ | ||||||
|  | 		let token = await InviteToken.add({created_by: this.username}); | ||||||
|  | 		 | ||||||
|  | 		return token; | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = {User}; | ||||||
| @ -1,107 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| const {promisify} = require('util'); |  | ||||||
| const client = require('../redis'); |  | ||||||
| const linuxUser = require('linux-sys-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}; |  | ||||||
							
								
								
									
										2487
									
								
								nodejs/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2487
									
								
								nodejs/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -13,10 +13,9 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "authenticate-pam": "github:WeiAnAn/node-authenticate-pam", |     "authenticate-pam": "github:WeiAnAn/node-authenticate-pam", | ||||||
|  |     "ejs": "^3.0.1", | ||||||
|     "express": "~4.16.1", |     "express": "~4.16.1", | ||||||
|     "forever": "^1.0.0", |     "linux-sys-user": "^1.1.0", | ||||||
|     "linux-sys-user": "github:wmantly/linux-user", |  | ||||||
|     "linux-user": "github:wmantly/linux-user", |  | ||||||
|     "redis": "^2.8.0" |     "redis": "^2.8.0" | ||||||
|   }, |   }, | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|  | |||||||
| @ -1,90 +1,55 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| const router = require('express').Router(); | const router = require('express').Router(); | ||||||
| const Users = require('../models/users');  | const {User} = require('../models/user'); | ||||||
|  | const {Auth, AuthToken} = require('../models/auth');  | ||||||
|  |  | ||||||
| /* |  | ||||||
| 	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}); | router.post('/login', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
| 	if(groups){ | 		let auth = await Auth.login(req.body); | ||||||
| 		return res.json({ | 		return res.json({ | ||||||
| 			login: true, | 			login: true, | ||||||
| 			token: await Users.addToken({username}), | 			token: auth.token.token, | ||||||
| 			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){ | 	}catch(error){ | ||||||
| 		return res.status(400).json({ | 		next(error); | ||||||
| 			message: 'Key is not a public key file!' |  | ||||||
| 		}); |  | ||||||
| 	} | 	} | ||||||
| 	 |  | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  |  | ||||||
| router.post('/invite/:token', async function(req, res, next) { | router.post('/invite/:token', async function(req, res, next) { | ||||||
| 	let username = req.body.username; | 	try{ | ||||||
| 	let password = req.body.password; | 		req.body.token = req.params.token; | ||||||
| 	let token = req.params.token; | 		let user = await User.addByInvite(req.body); | ||||||
|  | 		let token = await AuthToken.add(user); | ||||||
|  |  | ||||||
| 	// make sure invite token is valid | 		return res.json({ | ||||||
| 	let invite = await Users.checkInvite({token}); | 			user: user.username, | ||||||
|  | 			token: token.token | ||||||
| 	if(!invite || invite.invited){ |  | ||||||
| 		return res.status(401).json({ |  | ||||||
| 			message: 'Token not valid' |  | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// 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; | module.exports = router; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  | 	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!' | ||||||
|  | // 		}); | ||||||
|  | // 	} | ||||||
|  | 	 | ||||||
|  | // }); | ||||||
							
								
								
									
										72
									
								
								nodejs/routes/host.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										72
									
								
								nodejs/routes/host.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const router = require('express').Router(); | ||||||
|  | const {Host} = require('../models/host'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | router.get('/:host', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  |  | ||||||
|  | 		return res.json({ | ||||||
|  | 			host: req.params.host, | ||||||
|  | 			results: await Host.get({host: req.params.host}) | ||||||
|  | 		}); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		return next(error); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.get('/', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  | 		return res.json({ | ||||||
|  | 			hosts:  await Host[req.query.detail ? "listDetail" : "list"]() | ||||||
|  | 		}); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error) | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.put('/:host', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  | 		req.body.updated_by = req.user.username; | ||||||
|  | 		await Host.update(req.body, req.params.host); | ||||||
|  |  | ||||||
|  | 		return res.json({ | ||||||
|  | 			message: `Host "${req.params.host}" updated.` | ||||||
|  | 		}); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		return next(error); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.post('/', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  | 		req.body.created_by = req.user.username; | ||||||
|  | 		await Host.add(req.body); | ||||||
|  |  | ||||||
|  | 		return res.json({ | ||||||
|  | 			message: `Host "${req.body.host}" added.` | ||||||
|  | 		}); | ||||||
|  | 	} catch (error){ | ||||||
|  | 		next(error); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.delete('/:host', async function(req, res, next){ | ||||||
|  | 	 | ||||||
|  | 	try{ | ||||||
|  | 		let host = await Host.get(req.params); | ||||||
|  | 		let count = await host.remove(host); | ||||||
|  |  | ||||||
|  | 		return res.json({ | ||||||
|  | 			message: `Host ${req.params.host} deleted`, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
| @ -2,8 +2,23 @@ var express = require('express'); | |||||||
| var router = express.Router(); | var router = express.Router(); | ||||||
|  |  | ||||||
| /* GET home page. */ | /* GET home page. */ | ||||||
| router.get('/', function(req, res, next) { | router.get('/', async function(req, res, next) { | ||||||
|   res.render('index', { title: 'Express' }); |   res.render('hosts', { title: 'Express' }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /* GET home page. */ | ||||||
|  | router.get('/hosts', function(req, res, next) { | ||||||
|  |   res.render('hosts', { title: 'Express' }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /* GET home page. */ | ||||||
|  | router.get('/users', function(req, res, next) { | ||||||
|  |   res.render('users', { title: 'Express' }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | /* GET home page. */ | ||||||
|  | router.get('/login', function(req, res, next) { | ||||||
|  |   res.render('login', {redirect: req.query.redirect}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
|  | |||||||
| @ -1,81 +0,0 @@ | |||||||
| '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; |  | ||||||
| 	let targetPort = req.body.targetPort; |  | ||||||
|  |  | ||||||
| 	if(!host || !ip || !targetPort ){ |  | ||||||
| 		return res.status(400).json({ |  | ||||||
| 			message: `Missing fields: ${!host ? 'host' : ''} ${!ip ? 'ip' : ''} ${!targetPort ? 'targetPort' : ''}`  |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	try{ |  | ||||||
| 		await Host.add({host, ip, targetPort, |  | ||||||
| 			  username: req.user.username, |  | ||||||
| 			  forceSSL: req.body.forceSSL, |  | ||||||
| 			  targetSSL: req.body.targetSSL, |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		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; |  | ||||||
							
								
								
									
										42
									
								
								nodejs/routes/user.js
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										42
									
								
								nodejs/routes/user.js
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const router = require('express').Router(); | ||||||
|  | const {User} = require('../models/user');  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | router.get('/me', async function(req, res){ | ||||||
|  | 	try{ | ||||||
|  | 		return res.json({username: req.user.username}); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.post('/invite', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  | 		let token = await req.user.invite(); | ||||||
|  |  | ||||||
|  | 		return res.json({token: token.token}); | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | router.post('/key', async function(req, res, next){ | ||||||
|  | 	try{ | ||||||
|  | 		let added = await User.addSSHkey({ | ||||||
|  | 			username: req.user.username, | ||||||
|  | 			key: req.body.key | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		return res.status(added === true ? 200 : 400).json({ | ||||||
|  | 			message: added | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		next(error); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = router; | ||||||
| @ -1,26 +0,0 @@ | |||||||
| '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; |  | ||||||
							
								
								
									
										80
									
								
								nodejs/utils/object_validate.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								nodejs/utils/object_validate.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const process_type = { | ||||||
|  | 	number: function(key, value){ | ||||||
|  | 		if(key.min && value < key.min) return `is to small, min ${key.min}.` | ||||||
|  | 		if(key.max && value > key.max) return `is to large, max ${key.max}.` | ||||||
|  | 	}, | ||||||
|  | 	string: function(key, value){ | ||||||
|  | 		if(key.min && value.length < key.min) return `is too short, min ${key.min}.` | ||||||
|  | 		if(key.max && value.length > key.max) return `is too short, max ${key.max}.` | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function returnOrCall(value){ | ||||||
|  | 	return typeof(value) === 'function' ? value() : value; | ||||||
|  | }  | ||||||
|  |  | ||||||
|  | function processKeys(map, data, partial){ | ||||||
|  | 	let errors = []; | ||||||
|  | 	let out = {}; | ||||||
|  |  | ||||||
|  | 	for(let key of Object.keys(map)){ | ||||||
|  | 		if(!map[key].always && partial && !data.hasOwnProperty(key)) continue; | ||||||
|  |  | ||||||
|  | 		if(!partial && map[key].isRequired && !data.hasOwnProperty(key)){ | ||||||
|  | 			errors.push({key, message:`${key} is required.`}); | ||||||
|  | 			continue; | ||||||
|  | 		}  | ||||||
|  |  | ||||||
|  | 		if(data.hasOwnProperty(key) && typeof(data[key]) !== map[key].type){ | ||||||
|  | 			errors.push({key, message:`${key} is not ${map[key].type} type.`}); | ||||||
|  | 			continue; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		out[key] = data.hasOwnProperty(key) && data[key] !== undefined ? data[key] : returnOrCall(map[key].default); | ||||||
|  |  | ||||||
|  | 		if(data.hasOwnProperty(key) && process_type[map[key].type]){ | ||||||
|  | 			let typeError = process_type[map[key].type](map[key], data[key]); | ||||||
|  | 			if(typeError){ | ||||||
|  | 				errors.push({key, message:`${key} ${typeError}`}); | ||||||
|  | 				 | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if(errors.length !== 0){ | ||||||
|  | 		throw new ObjectValidateError(errors); | ||||||
|  | 		return {__errors__: errors}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function parseFromString(map, data){ | ||||||
|  | 	let types = { | ||||||
|  | 		boolean: function(value){ return value === 'false' ? false : true }, | ||||||
|  | 		number: Number, | ||||||
|  | 		string: String, | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	for(let key of Object.keys(data)){ | ||||||
|  | 		if(map[key] && map[key].type){ | ||||||
|  | 			data[key] = types[map[key].type](data[key]); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return data; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function ObjectValidateError(message) { | ||||||
|  | 	this.name = 'ObjectValidateError'; | ||||||
|  | 	this.message = (message || {}); | ||||||
|  | 	this.status = 422; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | ObjectValidateError.prototype = Error.prototype; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = {processKeys, parseFromString, ObjectValidateError}; | ||||||
| @ -14,6 +14,7 @@ const _client = client(); | |||||||
| module.exports = { | module.exports = { | ||||||
| 	client: client, | 	client: client, | ||||||
| 	HGET: promisify(_client.HGET).bind(_client), | 	HGET: promisify(_client.HGET).bind(_client), | ||||||
|  | 	HDEL: promisify(_client.HDEL).bind(_client), | ||||||
| 	SADD: promisify(_client.SADD).bind(_client), | 	SADD: promisify(_client.SADD).bind(_client), | ||||||
| 	SREM: promisify(_client.SREM).bind(_client), | 	SREM: promisify(_client.SREM).bind(_client), | ||||||
| 	DEL: promisify(_client.DEL).bind(_client), | 	DEL: promisify(_client.DEL).bind(_client), | ||||||
							
								
								
									
										191
									
								
								nodejs/utils/redis_model.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										191
									
								
								nodejs/utils/redis_model.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,191 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | const client = require('../utils/redis'); | ||||||
|  | const objValidate = require('../utils/object_validate'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | let table = {}; | ||||||
|  |  | ||||||
|  | table.get = async function(data){ | ||||||
|  | 	try{ | ||||||
|  | 		// if the data argument was passed as the index key value, make a data | ||||||
|  | 		// object and add the index key to it. | ||||||
|  | 		if(typeof data !== 'object'){ | ||||||
|  | 			let key = data; | ||||||
|  | 			data = {}; | ||||||
|  | 			data[this._key] = key; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Get all the hash keys for the passed index key. | ||||||
|  | 		let res = await client.HGETALL(`${this._name}_${data[this._key]}`); | ||||||
|  |  | ||||||
|  | 		// If the redis query resolved to something, prepare the data. | ||||||
|  | 		if(res){ | ||||||
|  |  | ||||||
|  | 			// Redis always returns strings, use the keyMap schema to turn them | ||||||
|  | 			// back to native values. | ||||||
|  | 			res = objValidate.parseFromString(this._keyMap, res); | ||||||
|  |  | ||||||
|  | 			// Make sure the index key in in the returned object. | ||||||
|  | 			res[this._key] = data[this._key]; | ||||||
|  |  | ||||||
|  | 			// Create a instance for this redis entry. | ||||||
|  | 			var entry = Object.create(this); | ||||||
|  |  | ||||||
|  | 			// Insert the redis response into the instance. | ||||||
|  | 			Object.assign(entry, res); | ||||||
|  |  | ||||||
|  | 			// Return the instance to the caller. | ||||||
|  | 			return entry; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	let error = new Error('EntryNotFound'); | ||||||
|  | 	error.name = 'EntryNotFound'; | ||||||
|  | 	error.message = `${this._name}:${data[this._key]} does not exists`; | ||||||
|  | 	error.status = 404; | ||||||
|  | 	throw error; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.exists = async function(data){ | ||||||
|  | 	// Return true or false if the requested entry exists ignoring error's. | ||||||
|  | 	try{ | ||||||
|  | 		await this.get(data); | ||||||
|  |  | ||||||
|  | 		return true | ||||||
|  | 	}catch(error){ | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.list = async function(){ | ||||||
|  | 	// return a list of all the index keys for this table. | ||||||
|  | 	try{ | ||||||
|  |  | ||||||
|  | 		return await client.SMEMBERS(this._name); | ||||||
|  |  | ||||||
|  | 	}catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.listDetail = async function(){ | ||||||
|  | 	// Return a list of the entries as instances. | ||||||
|  | 	let out = []; | ||||||
|  |  | ||||||
|  | 	for(let entry of await this.list()){ | ||||||
|  | 		out.push(await this.get(entry)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return out | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.add = async function(data){ | ||||||
|  | 	// Add a entry to this redis table. | ||||||
|  | 	try{ | ||||||
|  |  | ||||||
|  | 		// Validate the passed data by the keyMap schema. | ||||||
|  | 		data = objValidate.processKeys(this._keyMap, data); | ||||||
|  |  | ||||||
|  | 		// Do not allow the caller to overwrite an existing index key, | ||||||
|  | 		if(data[this._key] && await this.exists(data)){ | ||||||
|  | 			let error = new Error('EntryNameUsed'); | ||||||
|  | 			error.name = 'EntryNameUsed'; | ||||||
|  | 			error.message = `${this._name}:${data[this._key]} already exists`; | ||||||
|  | 			error.status = 409; | ||||||
|  |  | ||||||
|  | 			throw error; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// Add the key to the members for this redis table | ||||||
|  | 		await client.SADD(this._name, data[this._key]); | ||||||
|  |  | ||||||
|  | 		// Add the values for this entry. | ||||||
|  | 		for(let key of Object.keys(data)){ | ||||||
|  | 			await client.HSET(`${this._name}_${data[this._key]}`, key, data[key]); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// return the created redis entry as entry instance. | ||||||
|  | 		return await this.get(data[this._key]); | ||||||
|  | 	} catch(error){ | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.update = async function(data, key){ | ||||||
|  | 	// Update an existing entry. | ||||||
|  | 	try{ | ||||||
|  | 		// If an index key is passed, we assume is passed, assume we are not | ||||||
|  | 		// part of an entry instance. Make one and recall this from from a entry | ||||||
|  | 		// instance, | ||||||
|  | 		if(key) return await (await this.get(key)).update(data); | ||||||
|  |  | ||||||
|  | 		// Check to see if entry name changed. | ||||||
|  | 		if(data[this._key] && data[this._key] !== this[this._key]){ | ||||||
|  |  | ||||||
|  | 			// Merge the current data into with the updated data  | ||||||
|  | 			let newData = Object.assign({}, this, data); | ||||||
|  |  | ||||||
|  | 			// Remove the updated failed so it doesnt keep it | ||||||
|  | 			delete newData.updated; | ||||||
|  |  | ||||||
|  | 			// Create a new record for the updated entry. If that succeeds, | ||||||
|  | 			// delete the old recored | ||||||
|  | 			if(await this.add(newData)) await this.remove(); | ||||||
|  |  | ||||||
|  | 		}else{ | ||||||
|  | 			// Update what ever fields that where passed. | ||||||
|  |  | ||||||
|  | 			// Validate the passed data, ignoring required fields. | ||||||
|  | 			data = objValidate.processKeys(this._keyMap, data, true); | ||||||
|  | 			 | ||||||
|  | 			// Loop over the data fields and apply them to redis | ||||||
|  | 			for(let key of Object.keys(data)){ | ||||||
|  | 				this[key] = data[key]; | ||||||
|  | 				await client.HSET(`${this._name}_${this[this._key]}`, key, data[key]); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return this; | ||||||
|  | 	 | ||||||
|  | 	} catch(error){ | ||||||
|  | 		// Pass any error to the calling function | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | table.remove = async function(data){ | ||||||
|  | 	// Remove an entry from this table. | ||||||
|  |  | ||||||
|  | 	data = data || this; | ||||||
|  | 	try{ | ||||||
|  | 		// Remove the index key from the tables members list. | ||||||
|  | 		await client.SREM(this._name, data[this._key]); | ||||||
|  |  | ||||||
|  | 		// Remove the entries hash values. | ||||||
|  | 		let count = await client.DEL(`${this._name}_${data[this._key]}`); | ||||||
|  |  | ||||||
|  | 		// Return the number of removed values to the caller. | ||||||
|  | 		return count; | ||||||
|  |  | ||||||
|  | 	} catch(error) { | ||||||
|  | 		throw error; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | function Table(data){ | ||||||
|  | 	// Create a table instance. | ||||||
|  | 	let obj = Object.create(table); | ||||||
|  |  | ||||||
|  | 	// Insert the user assigned options | ||||||
|  | 	Object.assign(obj, data); | ||||||
|  |  | ||||||
|  | 	// Return the table instance to the caller. | ||||||
|  | 	return obj; | ||||||
|  |  | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | module.exports = Table; | ||||||
		Reference in New Issue
	
	Block a user