diff --git a/Vagrantfile b/Vagrantfile index b76b6ca..b532461 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -132,8 +132,8 @@ Vagrant.configure("2") do |config| end config.vm.provision "shell", inline: <<~SHELL - cp -a /etc/openresty/. /vagrant/openresty - rm -rf /etc/openresty - ln -s /vagrant/openresty /etc/openresty + # cp -a /etc/openresty/. /vagrant/openresty + # rm -rf /etc/openresty + # ln -s /vagrant/openresty /etc/openresty SHELL end diff --git a/nodejs/models/auth.js b/nodejs/models/auth.js index e5d68af..834f98f 100644 --- a/nodejs/models/auth.js +++ b/nodejs/models/auth.js @@ -5,8 +5,8 @@ Auth = {} Auth.errors = {} Auth.errors.login = function(){ - let error = new Error('PamLoginFailed'); - error.name = 'PamLoginFailed'; + let error = new Error('LoginFailed'); + error.name = 'LoginFailed'; error.message = `Invalid Credentials, login failed.`; error.status = 401; @@ -20,7 +20,7 @@ Auth.login = async function(data){ return {user, token} }catch(error){ - throw error; + throw this.errors.login(); } }; diff --git a/nodejs/models/host.js b/nodejs/models/host.js index ea5c61f..3261b0e 100755 --- a/nodejs/models/host.js +++ b/nodejs/models/host.js @@ -231,4 +231,4 @@ module.exports = {Host}; // console.log(count++, Host.lookUp('718it.biz').host === '718it.biz') -// })() +// })() \ No newline at end of file diff --git a/nodejs/models/token.js b/nodejs/models/token.js index 496e26c..bfa6229 100644 --- a/nodejs/models/token.js +++ b/nodejs/models/token.js @@ -1,60 +1,65 @@ 'use strict'; -const redis_model = require('../utils/redis_model') +const Table = 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 +class Token extends Table{ + static _key = 'token'; + static _keyMap = { + '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'} } -} -var InviteToken = Object.create(Token({ - name: 'invite', - keyMap:{ - claimed_by: {default:"__NONE__", isRequired: false, type: 'string',} + constructor(...args){ + super(...args); } -})); -InviteToken.consume = async function(data){ - try{ - if(this.is_valid){ - data['is_valid'] = false; - - await this.update(data); - return true; + async check(){ + try{ + return this.is_valid; + }catch(error){ + return false } - return false; - - }catch(error){ - throw error; } } -var AuthToken = Object.create(Token({ - name: 'auth', -})); +class AuthToken extends Token{ + constructor(...args){ + super(...args); + } -AuthToken.add = async function(data){ - data.created_by = data.username; - return AuthToken.__proto__.add(data); -}; + static async add(data){ + data.created_by = data.username; + return super.add(data) -module.exports = {Token, InviteToken, AuthToken} + } + +} + +class InviteToken extends Token{ + static _keyMap = { + ...super._keyMap, + claimed_by: {default:"__NONE__", isRequired: false, type: 'string',}, + } + + async consume(data){ + try{ + if(this.is_valid){ + data['is_valid'] = false; + + await this.update(data); + return true; + } + return false; + + }catch(error){ + throw error; + } + } +} + +module.exports = {Token, InviteToken, AuthToken}; diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js index f921ddb..e3a8e7b 100644 --- a/nodejs/models/user_redis.js +++ b/nodejs/models/user_redis.js @@ -1,14 +1,13 @@ 'use strict'; -const objValidate = require('../utils/object_validate'); +const Table = require('../utils/redis_model'); const {Token, InviteToken} = require('./token'); const bcrypt = require('bcrypt'); const saltRounds = 10; -const User = require('../utils/redis_model')({ - _name: 'user', - _key: 'username', - _keyMap: { +class User extends Table{ + static _key = 'username'; + static _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',}, @@ -17,91 +16,91 @@ const User = require('../utils/redis_model')({ 'password': {isRequired: true, type: 'string', min: 3, max: 500}, 'backing': {default:"redis", isRequired: false, type: 'string',}, } -}); -User.backing = "redis"; + static backing = 'redis' + + static async add(data) { + try{ + data['password'] = await bcrypt.hash(data['password'], saltRounds); + data['backing'] = data['backing'] || 'redis'; -User.add = async function(data) { - try{ - data['password'] = await bcrypt.hash(data['password'], saltRounds); - data['backing'] = data['backing'] || 'redis'; + return await super.add(data) - - console.log('set password', data) - - return this.__proto__.add(data); - - }catch(error){ - throw error; + }catch(error){ + throw error; + } } -}; -User.addByInvite = async function(data){ - try{ - let token = await InviteToken.get(data.token); + static async addByInvite(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; + 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; } - let user = await this.add(data); + }; - if(user){ - await token.consume({claimed_by: user.username}); - return user; + async setPassword(data){ + try{ + data['password'] = await bcrypt.hash(data['password'], saltRounds); + + return this.update(data); + }catch(error){ + throw error; } - - }catch(error){ - throw error; } -}; + async invite(){ + try{ + let token = await InviteToken.add({created_by: this.username}); + + return token; -User.setPassword = async function(data){ - try{ - data['password'] = await bcrypt.hash(data['password'], saltRounds); - - return this.__proto__.update(data); - }catch(error){ - throw error; - } -}; - -User.invite = async function(){ - try{ - let token = await InviteToken.add({created_by: this.username}); - - return token; - - }catch(error){ - throw error; - } -}; - -User.login = async function(data){ - try{ - let user = await User.get(data); - - let auth = await bcrypt.compare(data.password, user.password); - - if(auth){ - return user - }else{ - throw this.errors.login(); + }catch(error){ + throw error; } - }catch(error){ - if (error == 'Authentication failure'){ - throw this.errors.login() - } - throw error; } -}; + static async login(data){ + try{ + + let user = await User.get(data); + let auth = await bcrypt.compare(data.password, user.password); + + if(auth){ + return user + }else{ + throw this.errors.login(); + } + }catch(error){ + if (error == 'Authentication failure'){ + throw this.errors.login() + } + throw error; + } + }; + + +} + +module.exports = {User}; module.exports = {User}; diff --git a/nodejs/routes/host.js b/nodejs/routes/host.js index aa6b046..803c010 100755 --- a/nodejs/routes/host.js +++ b/nodejs/routes/host.js @@ -4,10 +4,12 @@ const router = require('express').Router(); const {Host} = require('../models/host'); +const Model = Host; + router.get('/', async function(req, res, next){ try{ return res.json({ - hosts: await Host[req.query.detail ? "listDetail" : "list"]() + hosts: await Model[req.query.detail ? "listDetail" : "list"]() }); }catch(error){ return next(error); @@ -17,36 +19,36 @@ router.get('/', async function(req, res, next){ router.post('/', async function(req, res, next){ try{ req.body.created_by = req.user.username; - await Host.add(req.body); + let item = await Model.add(req.body); return res.json({ - message: `Host "${req.body.host}" added.` + message: `"${item[Model._key]}" added.` }); } catch (error){ return next(error); } }); -router.get('/:host(*)', async function(req, res, next){ +router.get('/:item(*)', async function(req, res, next){ try{ return res.json({ - host: req.params.host, - results: await Host.get({host: req.params.host}) + item: req.params.item, + results: await Model.get(req.params.item) }); }catch(error){ return next(error); } }); -router.put('/:host(*)', async function(req, res, next){ +router.put('/:item(*)', async function(req, res, next){ try{ req.body.updated_by = req.user.username; - let host = await Host.get(req.params.host); - await host.update.call(host, req.body); + let item = await Model.get(req.params.item); + await item.update(req.body); return res.json({ - message: `Host "${req.params.host}" updated.` + message: `"${req.params.item}" updated.` }); }catch(error){ @@ -55,13 +57,13 @@ router.put('/:host(*)', async function(req, res, next){ } }); -router.delete('/:host(*)', async function(req, res, next){ +router.delete('/:item(*)', async function(req, res, next){ try{ - let host = await Host.get(req.params); - let count = await host.remove.call(host, host); + let item = await Model.get(req.params.item); + let count = await item.remove(); return res.json({ - message: `Host ${req.params.host} deleted`, + message: `${req.params.item} deleted`, }); }catch(error){ @@ -69,11 +71,11 @@ router.delete('/:host(*)', async function(req, res, next){ } }); -router.get('/lookup/:host(*)', async function(req, res, next){ +router.get('/lookup/:item(*)', async function(req, res, next){ try{ return res.json({ - string: req.params.host, - results: await Host.lookUp(req.params.host), + string: req.params.item, + results: await Model.lookUp(req.params.item), }); }catch(error){ diff --git a/nodejs/utils/object_validate.js b/nodejs/utils/object_validate.js index 6295c48..42f53e3 100644 --- a/nodejs/utils/object_validate.js +++ b/nodejs/utils/object_validate.js @@ -8,7 +8,7 @@ const process_type = { 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){ @@ -20,6 +20,7 @@ function processKeys(map, data, partial){ 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)){ @@ -57,6 +58,7 @@ function parseFromString(map, data){ boolean: function(value){ return value === 'false' ? false : true }, number: Number, string: String, + object: JSON.parse }; for(let key of Object.keys(data)){ @@ -68,6 +70,14 @@ function parseFromString(map, data){ return data; } +function parseToString(data){ + let types = { + object: JSON.stringify + } + + return (types[typeof(data)] || String)(data); +} + function ObjectValidateError(message) { this.name = 'ObjectValidateError'; this.message = (message || {}); @@ -77,4 +87,4 @@ function ObjectValidateError(message) { ObjectValidateError.prototype = Error.prototype; -module.exports = {processKeys, parseFromString, ObjectValidateError}; +module.exports = {processKeys, parseFromString, ObjectValidateError, parseToString}; \ No newline at end of file diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index 27eb2f1..f0bdd36 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -1,194 +1,165 @@ 'use strict'; -const {createClient} = require('redis'); +const client = require('../utils/redis'); const objValidate = require('../utils/object_validate'); -var client = createClient({}); -client.connect() - -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; +class Table{ + constructor(data){ + for(let key in data){ + this[key] = data[key]; } + } - // Get all the hash keys for the passed index key. - let res = await client.HGETALL(`${this._name}_${data[this._key]}`); + static async get(index){ + try{ - // If the redis query resolved to something, prepare the data. - if(Object.keys(res).length){ + if(typeof index === 'object'){ + index = index[this._key] + } + + let result = await client.HGETALL(`${this.prototype.constructor.name}_${index}`); + + if(!result){ + let error = new Error('EntryNotFound'); + error.name = 'EntryNotFound'; + error.message = `${this.prototype.constructor.name}:${index} does not exists`; + error.status = 404; + throw error; + } // Redis always returns strings, use the keyMap schema to turn them // back to native values. - res = objValidate.parseFromString(this._keyMap, res); + result = objValidate.parseFromString(this._keyMap, result); - // 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, noMemberAdd){ - // 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; + return new this.prototype.constructor(result) + }catch(error){ throw error; } - // Add the key to the members for this redis table - if(!noMemberAdd) 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, String(data[key])); + static async exists(index){ + try{ + await this.get(data); + + return true + }catch(error){ + return false; + } + } + + static async list(){ + // return a list of all the index keys for this table. + try{ + return await client.SMEMBERS(this.prototype.constructor.name); + + }catch(error){ + throw error; + } + } + + static async listDetail(){ + // Return a list of the entries as instances. + let out = []; + + for(let entry of await this.list()){ + out.push(await this.get(entry)); } - // return the created redis entry as entry instance. - return await this.get(data[this._key]); - } catch(error){ - console.error('redis model | table.add:', error) - throw error; + return out } -}; -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); + static async add(data){ + // Add a entry to this redis table. + try{ + // Validate the passed data by the keyMap schema. - // Check to see if entry name changed. - if(data[this._key] && data[this._key] !== this[this._key]){ + data = objValidate.processKeys(this._keyMap, data); - // Merge the current data into with the updated data - let newData = Object.assign({}, this, 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.prototype.constructor.name}:${data[this._key]} already exists`; + error.status = 409; - // 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]); + throw error; } + + // Add the key to the members for this redis table + await client.SADD(this.prototype.constructor.name, data[this._key]); + + // Add the values for this entry. + for(let key of Object.keys(data)){ + await client.HSET(`${this.prototype.constructor.name}_${data[this._key]}`, key, objValidate.parseToString(data[key])); + } + + // return the created redis entry as entry instance. + return await this.get(data[this._key]); + } catch(error){ + throw error; } - - return this; - - } catch(error){ - // Pass any error to the calling function - throw error; } -}; -table.remove = async function(data){ - // Remove an entry from this table. + async update(data, key){ + // Update an existing entry. + try{ + // Check to see if entry name changed. + if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){ - data = data || this; - try{ - // Remove the index key from the tables members list. - await client.SREM(this._name, data[this._key]); + // Merge the current data into with the updated data + let newData = Object.assign({}, this, data); - // Remove the entries hash values. - let count = await client.DEL(`${this._name}_${data[this._key]}`); + // Remove the updated failed so it doesnt keep it + delete newData.updated; - // Return the number of removed values to the caller. - return count; + // Create a new record for the updated entry. If that succeeds, + // delete the old recored + if(await this.add(newData)) await this.remove(); - } catch(error) { - throw error; + }else{ + // Update what ever fields that where passed. + + // Validate the passed data, ignoring required fields. + data = objValidate.processKeys(this.constructor._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.constructor.name}_${this[this.constructor._key]}`, key, data[key]); + } + } + + return this; + + } catch(error){ + // Pass any error to the calling function + throw error; + } } -}; -function Table(data){ - // Create a table instance. - let instance = Object.create(data); - Object.assign(instance, table); + async remove(data){ + // Remove an entry from this table. - // Return the table instance to the caller. - return Object.create(instance); + try{ + // Remove the index key from the tables members list. -}; + await client.SREM(this.constructor.name, this[this.constructor._key]); -module.exports = Table; + // Remove the entries hash values. + let count = await client.DEL(`${this.constructor.name}_${this[this.constructor._key]}`); + + // Return the number of removed values to the caller. + return count; + + } catch(error) { + throw error; + } + }; + +} + + +module.exports = Table; \ No newline at end of file