diff --git a/.gitignore b/.gitignore index 2d49c1f..e901d22 100755 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,7 @@ Berksfile.lock .zero-knife.rb Policyfile.lock.json -ops/cookbooks/vendor \ No newline at end of file +ops/cookbooks/vendor + +secrets.json +secrets.js diff --git a/nodejs/app.js b/nodejs/app.js index 08fddb7..1cf6f5f 100755 --- a/nodejs/app.js +++ b/nodejs/app.js @@ -3,11 +3,19 @@ const path = require('path'); const ejs = require('ejs') const express = require('express'); -const middleware = require('./middleware/auth'); // Set up the express app. const app = express(); +// Allow the express app to be exported into other files. +module.exports = app; + +// Build the conf object from the conf files. +app.conf = require('./conf/conf'); + +// Hold onto the auth middleware +const middleware = require('./middleware/auth'); + // 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()); @@ -51,6 +59,3 @@ app.use(function(err, req, res, next) { res.status(err.status || 500); res.json({name: err.name, message: err.message}); }); - -// Allow the express app to be exported into other files. -module.exports = app; \ No newline at end of file diff --git a/nodejs/conf/base.js b/nodejs/conf/base.js new file mode 100644 index 0000000..73ef0c9 --- /dev/null +++ b/nodejs/conf/base.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = { + userModel: 'redis', // pam, redis, ldap + ldap: { + url: 'ldap://192.168.1.55:389', + bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com', + bindPassword: '__IN SRECREST FILE__', + searchBase: 'ou=people,dc=theta42,dc=com', + userFilter: '(objectClass=inetOrgPerson)', + userNameAttribute: 'uid' + } +}; diff --git a/nodejs/conf/conf.js b/nodejs/conf/conf.js new file mode 100644 index 0000000..ce09580 --- /dev/null +++ b/nodejs/conf/conf.js @@ -0,0 +1,32 @@ +'use strict'; + +const extend = require('extend'); + +const environment = process.env.NODE_ENV || 'development'; + +function load(filePath, required){ + try { + return require(filePath); + } catch(error){ + if(error.name === 'SyntaxError'){ + console.error(`Loading ${filePath} file failed!\n`, error); + process.exit(1); + } else if (error.code === 'MODULE_NOT_FOUND'){ + console.warn(`No config file ${filePath} FOUND! This may cause issues...`); + if (required){ + process.exit(1); + } + return {}; + }else{ + console.dir(`Unknown error in loading ${filePath} config file.\n`, error); + } + } +}; + +module.exports = extend( + true, // enable deep copy + load('./base', true), + load(`./${environment}`), + load('./secrets'), + {environment} +); diff --git a/nodejs/models/auth.js b/nodejs/models/auth.js old mode 100755 new mode 100644 index 4c1eb80..e5d68af --- a/nodejs/models/auth.js +++ b/nodejs/models/auth.js @@ -1,7 +1,3 @@ -const {promisify} = require('util'); -const pam = require('authenticate-pam'); -const authenticate = promisify(pam.authenticate); - const {User} = require('./user'); const {Token, AuthToken} = require('./token'); @@ -19,19 +15,16 @@ Auth.errors.login = function(){ Auth.login = async function(data){ try{ - let auth = await authenticate(data.username, data.password); - let user = await User.get(data); + let user = await User.login(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); diff --git a/nodejs/models/user.js b/nodejs/models/user.js old mode 100755 new mode 100644 index b68e768..67338a0 --- a/nodejs/models/user.js +++ b/nodejs/models/user.js @@ -1,142 +1,7 @@ 'use strict'; -const linuxUser = require('linux-sys-user').promise(); -const objValidate = require('../utils/object_validate'); -const {Token, InviteToken} = require('./token'); +const conf = require('../app').conf; -var User = {} +const User = require(`./user_${conf.userModel}`) -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.remove = async function(data){ - try{ - return await linuxUser.removeUser(this.username); - }catch(error){ - throw error; - } -}; - -User.setPassword = async function(data){ - try{ - await linuxUser.setPassword(this.username, data.password); - - 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}; +module.exports = User; diff --git a/nodejs/models/user_ldap.js b/nodejs/models/user_ldap.js new file mode 100644 index 0000000..7d8f993 --- /dev/null +++ b/nodejs/models/user_ldap.js @@ -0,0 +1,235 @@ +'use strict'; + +const { Client, Attribute, Change } = require('ldapts'); +const {Token, InviteToken} = require('./token'); +const conf = require('../app').conf.ldap; + +const client = new Client({ + url: conf.url, +}); + + +const user_parse = function(data){ + if(data[conf.userNameAttribute]){ + data.username = data[conf.userNameAttribute] + delete data[conf.userNameAttribute]; + } + + if(data.uidNumber){ + data.uid = data.uidNumber; + delete data.uidNumber; + } + + return data; +} + +var User = {} + +User.backing = "LDAP"; + +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{ + await client.bind(conf.bindDN, conf.bindPassword); + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: conf.userFilter, + }); + + await client.unbind(); + + return res.searchEntries.map(function(user){return user.uid}); + }catch(error){ + throw error; + } +}; + +User.listDetail = async function(){ + try{ + await client.bind(conf.bindDN, conf.bindPassword); + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: conf.userFilter, + }); + + await client.unbind(); + + let users = [] + + for(let user of res.searchEntries){ + let obj = Object.create(this); + Object.assign(obj, user_parse(user)); + + users.push(obj) + + } + + return users; + + }catch(error){ + throw error; + } +}; + +User.get = async function(data){ + try{ + if(typeof data !== 'object'){ + let username = data; + data = {}; + data.username = username; + } + + await client.bind(conf.bindDN, conf.bindPassword); + + let filter = `(&${conf.userFilter}(${conf.userNameAttribute}=${data.username}))`; + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: filter, + }); + + await client.unbind(); + + let user = res.searchEntries[0] + + if(user){ + let obj = Object.create(this); + Object.assign(obj, user_parse(user)); + + return obj; + }else{ + let error = new Error('UserNotFound'); + error.name = 'UserNotFound'; + error.message = `LDAP:${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.remove = async function(data){ +// try{ +// return await linuxUser.removeUser(this.username); +// }catch(error){ +// throw error; +// } +// }; + +// User.setPassword = async function(data){ +// try{ +// await linuxUser.setPassword(this.username, data.password); + +// 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; + } +}; + +User.login = async function(data){ + try{ + let user = await this.get(data.username); + + await client.bind(user.dn, data.password); + + await client.unbind(); + + return user; + + }catch(error){ + throw error; + } +}; + + +module.exports = {User}; + + +// (async function(){ +// try{ +// console.log(await User.list()); + +// console.log(await User.listDetail()); + +// console.log(await User.get('wmantly')) + +// }catch(error){ +// console.error(error) +// } +// })() \ No newline at end of file diff --git a/nodejs/models/user_pam.js b/nodejs/models/user_pam.js new file mode 100644 index 0000000..998e940 --- /dev/null +++ b/nodejs/models/user_pam.js @@ -0,0 +1,161 @@ +'use strict'; + +const linuxUser = require('linux-sys-user').promise(); +const objValidate = require('../utils/object_validate'); +const {Token, InviteToken} = require('./token'); +const {promisify} = require('util'); +const pam = require('authenticate-pam'); +const authenticate = promisify(pam.authenticate); + +var User = {} + +User.keyMap = { + 'username': {isRequired: true, type: 'string', min: 3, max: 500}, + 'password': {isRequired: true, type: 'string', min: 3, max: 500}, +} + +User.backing = "PAM"; + +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.remove = async function(data){ + try{ + return await linuxUser.removeUser(this.username); + }catch(error){ + throw error; + } +}; + +User.setPassword = async function(data){ + try{ + await linuxUser.setPassword(this.username, data.password); + + 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; + } +}; + +User.login = async function(data){ + try{ + let auth = await authenticate(data.username, data.password); + let user = await User.get(data); + + return user; + }catch(error){ + if (error == 'Authentication failure'){ + throw this.errors.login() + } + throw error; + } +}; + +module.exports = {User}; diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js new file mode 100644 index 0000000..f76fc3c --- /dev/null +++ b/nodejs/models/user_redis.js @@ -0,0 +1,122 @@ +'use strict'; + +const objValidate = require('../utils/object_validate'); +const {Token, InviteToken} = require('./token'); +const bcrypt = require('bcrypt'); +const saltRounds = 10; + +const User = require('../utils/redis_model')({ + _name: 'user', + _key: 'username', + _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}, + 'username': {isRequired: true, type: 'string', min: 3, max: 500}, + 'password': {isRequired: true, type: 'string', min: 3, max: 500}, + 'backing': {default:"redis", isRequired: false, type: 'string',}, + } +}); + +User.backing = "redis"; + + +User.add = async function(data) { + try{ + data['password'] = await bcrypt.hash(data['password'], saltRounds); + data['backing'] = data['backing'] || 'redis'; + + + return this.__proto__.add(data); + + }catch(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{ + 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){ + if (error == 'Authentication failure'){ + throw this.errors.login() + } + throw error; + } +}; + +module.exports = {User}; + + +(async function(){ + try{ + await User.get('proxyadmin1'); + console.info('proxyadmin1 user exists'); + }catch(error){ + try{ + let user = await User.add({ + username:'proxyadmin1', + password: 'proxyadmin1', + created_by:'proxyadmin1' + }); + console.log('proxyadmin1 created', user); + }catch(error){ + console.error(error) + } + } +})(); \ No newline at end of file diff --git a/nodejs/package.json b/nodejs/package.json index fac02f4..9dae9e2 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -13,6 +13,8 @@ }, "dependencies": { "authenticate-pam": "github:WeiAnAn/node-authenticate-pam", + "ldapts": "^2.2.1", + "extend": "^3.0.2", "ejs": "^3.0.1", "express": "~4.16.1", "linux-sys-user": "^1.1.0", diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js index 7a09a4b..121a61f 100755 --- a/nodejs/routes/user.js +++ b/nodejs/routes/user.js @@ -5,7 +5,9 @@ const {User} = require('../models/user'); router.get('/', async function(req, res, next){ try{ - return res.json({results: await User.list()}); + return res.json({ + results: await User[req.query.detail ? "listDetail" : "list"]() + }); }catch(error){ next(error); } @@ -13,6 +15,8 @@ router.get('/', async function(req, res, next){ router.post('/', async function(req, res, next){ try{ + req.body.created_by = req.user.username + return res.json({results: await User.add(req.body)}); }catch(error){ next(error); diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index 792b102..dffe9d0 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -88,6 +88,7 @@ table.add = async function(data){ 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, @@ -178,13 +179,11 @@ table.remove = async function(data){ function Table(data){ // Create a table instance. - let obj = Object.create(table); - - // Insert the user assigned options - Object.assign(obj, data); + let instance = Object.create(data); + Object.assign(instance, table); // Return the table instance to the caller. - return obj; + return Object.create(instance); }; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..9ff046a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,491 @@ +{ + "requires": true, + "lockfileVersion": 1, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "bcrypt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", + "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", + "requires": { + "node-addon-api": "^2.0.0", + "node-pre-gyp": "0.14.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "requires": { + "minipass": "^2.6.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "requires": { + "minipass": "^2.9.0" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "needle": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", + "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-addon-api": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", + "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" + }, + "node-pre-gyp": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", + "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + } + }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + } + } +}