diff --git a/nodejs/app.js b/nodejs/app.js index 4f9c9e5..2a677ba 100755 --- a/nodejs/app.js +++ b/nodejs/app.js @@ -33,13 +33,12 @@ app.use('/', require('./routes/index')); // API routes for authentication. app.use('/api/auth', require('./routes/auth')); +app.use('/api/git', require('./routes/git_webhook.js')); + // API routes for working with users. All endpoints need to be have valid user. app.use('/api/user', middleware.auth, require('./routes/user')); -app.use('/api/token', middleware.auth, require('./routes/token')); - -app.use('/api/group', middleware.auth, require('./routes/group')); // Catch 404 and forward to error handler. If none of the above routes are @@ -54,7 +53,7 @@ app.use(function(req, res, next) { // Error handler. This is where `next()` will go on error app.use(function(err, req, res, next) { console.error(err.status || res.status, err.name, req.method, req.url); - if(![401, 404].includes(err.status || res.status)){ + if(![ 404].includes(err.status || res.status)){ console.error(err.message); console.error(err.stack); console.error('========================================='); diff --git a/nodejs/bin/www b/nodejs/bin/www index 0b61fb7..f9e8a58 100755 --- a/nodejs/bin/www +++ b/nodejs/bin/www @@ -7,12 +7,13 @@ var app = require('../app'); var debug = require('debug')('proxy-api:server'); var http = require('http'); +const conf = require('../conf/conf'); /** * Get port from environment and store in Express. */ -var port = normalizePort(process.env.NODE_PORT || app.conf.port || '3000'); +var port = normalizePort(process.env.NODE_PORT || conf.port || '3000'); app.set('port', port); /** diff --git a/nodejs/conf/base.js b/nodejs/conf/base.js index a7b2f42..996424c 100644 --- a/nodejs/conf/base.js +++ b/nodejs/conf/base.js @@ -3,13 +3,16 @@ module.exports = { userModel: 'ldap', // pam, redis, ldap ldap: { - url: 'ldap://192.168.1.54:389', - bindDN: 'cn=admin,dc=theta42,dc=com', + url: 'ldap://192.168.1.55:389', + bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com', bindPassword: '__IN SRECREST FILE__', userBase: 'ou=people,dc=theta42,dc=com', groupBase: 'ou=groups,dc=theta42,dc=com', userFilter: '(objectClass=posixAccount)', userNameAttribute: 'uid' }, - SENDGRID_API_KEy: '__IN SRECREST FILE__', + httpProxyAPI:{ + host: 'http://10.1.0.51:3000', + key: '__IN SRECREST FILE__' + } }; diff --git a/nodejs/lib/deploy.js b/nodejs/lib/deploy.js new file mode 100644 index 0000000..ba8d169 --- /dev/null +++ b/nodejs/lib/deploy.js @@ -0,0 +1,252 @@ +'user strict'; +const extend = require('extend'); +const axios = require('axios') +const {Repo, Environment, Deployment, Target} = require('../models/repo'); +const deployTargets = require('./lxc'); +const conf = require('../conf/conf') + +async function doDeploy(action, repo, branch, repoSshurl, commit){ + var deployment; + try{ + + console.log(action, repo, branch, repoSshurl, commit) + repo = await Repo.get(repo); + + let deployments = await repo.getDeploymentsbyBranch(branch, true) + + console.log('deployments', deployments) + + if(deployments.length && action === 'delete'){ + deployment = deployments[0] + }if(deployments.length){ + deployment = deployments[0] + action = 'update'; + }else{ + var environment = await repo.getEnvironmentsbyBranch(branch) + deployment = await environment.addDeployment() + } + + deployment.environment.settings.repoSshurl = repoSshurl + deployment.environment.settings.branch = branch + + + }catch(error){ + console.error('create start', error) + throw new Error('Failed to make new Deployment') + } + + try{ + deployment = new Depoy(deployment); + setImmediate(async function(deployment, action) { + try{ + await deployment[action]() + }catch(error){ + console.log('set error', error) + } + }, deployment, action) + + return {id: deployment.id}; + }catch(error){ + console.error('create remote', error) + } +} + +function event(deployment, message){ + console.info('event:', message) +} + +class Depoy{ + constructor(deployment){ + this.deployment = deployment + this.environment = deployment.environment; + this.settings = pasrseSetings(deployment); + this.secrets = pasrseSecrets(deployment); + this.id = deployment.repo_env_id + + this.target = new deployTargets[deployment.target.type](this.settings) + } + + async exec(code, info){ + await this.event(`exec-start`, {info}) + code = ` + sudo su + ${exportBashVars(this.secrets)} + echo 'nameserver 8.8.8.8' > /etc/resolv.conf + export DEBIAN_FRONTEND=noninteractive + ${code} + ` + let res = await this.target.exec(code); + + await this.event(`exec-finish`, {info, ...res}) + + return res; + } + + async event(name, data){ + console.log(`EVENT: ${name}`, data) + } + + async log(type, message){ + console.log('LOG:', type, message) + } + + async setinfo(){ + let info = await this.target.info(); + if(!info.ip){ + return await this.setinfo(); + } + let id = info.ip.slice(-2); + let settings = { + sshURL: `${this.settings.host}:22${id}`, + httpURL: `${this.settings.host}:80${id}`, + } + + this.settings = {...this.settings, ...settings}; + + await this.deployment.update('settings', {settings: this.settings, state:'deployed'}) + } + + async create(){ + + this.event('deployment-started', {info: `Creating deployment ${this.settings.appName}`}) + await this.target.create('bionic-base') + await this.target.start() + await this.setinfo(); + + console.log(this.settings) + + try{ + await this.exec(` + while [ ! -f /opt/datacom/firstboot ]; do sleep 1; done + sleep 2 + `, 'Wait for target to be ready') + }catch(error){} + await this.init(); + await this.updateProxy(); + } + + async init(){ + await this.exec(deployInitScript(this.settings), 'Initializing deployment') + + await this.exec(` + cd ${this.settings.workingPath}; + ./${this.settings.scriptsPath}/${this.environment.environment}/deploy.sh + `, 'Running repo deploy script') + } + + async update(){ + await this.exec(` + cd ${this.settings.workingPath}; + export GIT_SSH_COMMAND="/usr/bin/ssh -o StrictHostKeyChecking=no -i $HOME/.ssh/id_rsa_deploy_key" + git pull origin master; + ./${this.settings.scriptsPath}/${this.environment.environment}/update.sh + `, 'Running repo update script') + } + + async updateProxy(){ + let target = this.settings.httpURL.split(':'); + + let res = await axios.post(`${conf.httpProxyAPI.host}/api/host/`, { + forcessl: true, + host: this.settings.domain.replace('*', this.settings.branch), + ip: target[0], + targetPort: Number(target[1] || 80), + targetssl: false + }, { + headers: { "auth-token": conf.httpProxyAPI.key } + }) + } + + async delete(){ + await this.target.destroy() + await this.deployment.update({state: 'deleted', isActive: false}) + } + +} + + +function deployUpdateScript(argument) { + // body... +} + +function deployInitScript(args){ + return ` + mkdir -p "${args.workingPath}"; + mkdir "$HOME/.ssh"; + chmod 700 "$HOME/.ssh" + echo "${args.privateKey}" > $HOME/.ssh/id_rsa_deploy_key + chmod 600 $HOME/.ssh/id_rsa_deploy_key + wget https://raw.githubusercontent.com/tests-always-included/mo/master/mo -O /usr/local/bin/mo + chmod +x /usr/local/bin/mo + export GIT_SSH_COMMAND="/usr/bin/ssh -o StrictHostKeyChecking=no -i $HOME/.ssh/id_rsa_deploy_key" + git clone ${args.repoSshurl} ${args.workingPath}; + ` +} + +function exportBashVars(map){ + let out = ''; + for (const [key, value] of Object.entries(map)){ + out += `export ${key}="${value}";` + } + + return out +} + +function pasrseBase(deployment){ + let appName = deployment.repo_env_id.replace('/', '_') + + return { + appName: appName, + scriptsPath: deployment.environment.repo.scriptsPath, + privateKey: deployment.environment.repo.privateKey, + environment: deployment.environment.environment, + workingPath: `${deployment.environment.workingPath}/${appName}`, + domain: deployment.environment.domain, + name: appName, + } +} + +function pasrseSecrets(deployment){ + return { + ...deployment.environment.repo.secrets, + ...deployment.environment.secrets, + ...pasrseBase(deployment), + + } +} + +function pasrseSetings(deployment){ + return { + ...deployment.target.settings, + ...deployment.environment.repo.settings, + ...deployment.environment.settings, + ...pasrseBase(deployment), + } +} + + + +module.exports = {doDeploy}; + +(async function(){try{ + // console.log(await doDeploy('create', 'wmantly/static-test', 'master', 'ssh://gitea@git.theta42.com:2222/wmantly/static-test.git')) + + // let repo = await Repo.get('wmantly/static-test'); + // let deployments = await repo.getDeploymentsbyBranch('master') + + // for(let d of deployments){ + // try{ + // let lxc = new deployTargets.LXC({...{name: d.repo_env_id.replace('/', '_')}, ...d.target.settings}) + // await lxc.destroy(); + // await d.remove() + + // }catch(error){ + // console.log('err', error) + // }finally{ + // await d.remove() + // } + // } + +}catch(error){ + console.error('IIFE error:', error) +}})() diff --git a/nodejs/lib/lxc.js b/nodejs/lib/lxc.js new file mode 100644 index 0000000..a5f607a --- /dev/null +++ b/nodejs/lib/lxc.js @@ -0,0 +1,174 @@ +'use strict'; +const util = require('util'); +const exec = util.promisify(require('child_process').exec) + + +class Local{ + async sysExec(command){ + try{ + return await exec(command) + }catch(error){ + throw(error); + } + } + + async exec(...args){ + await this.sysExec() + } +} + +class SSH extends Local{ + constructor(args){ + super() + this.user = args.user; + this.host = args.host; + this.keyPath = args.keyPath; + } + + async sysExec(command){try{ + // console.log('command', command) + command = new Buffer.from(command).toString('base64'); + command = `ssh -i "${this.keyPath}" -o StrictHostKeyChecking=no ${this.user}@${this.host} "echo ${command} | base64 --decode | bash"`; + return await super.sysExec(command); + }catch(error){ + throw error; + }} +} + +class LXC{ + constructor(args){ + // console.log('lxc args', args) + this.name = args.name + if(args.host){ + this.sysExec = (new SSH(args)).sysExec.bind(args) + }else{ + this.sysExec = (new Local()).sysExec + } + } + + static async list(){ + try{ + let res = await this.prototype.sysExec(`lxc-ls --fancy`); + let output = res.stdout.split("\n").slice(0).slice(0,-1); + let keys = output.splice(0,1)[0].split(/\s+/).slice(0,-1).map(function(v){return v.toLowerCase()}); + let info = []; + + for(let line of output){ + if(line.match(/^-/)) continue; + + line = line.split(/\s+/).slice(0,-1); + + let mapOut = {}; + line.map(function(value,idx){ + mapOut[keys[idx]] = value; + }); + info.push(mapOut); + } + + return info; + + }catch(error){ + throw error; + } + } + + async create(from){ + try{ + return await this.sysExec(`lxc-copy --name "${from}" --newname "${this.name}" --daemon`); + }catch(error){ + throw error; + } + } + + async start(){ + try{ + return await this.sysExec(`lxc-start --name "${this.name}" --daemon`); + }catch(error){ + throw error; + } + } + + async destroy(){ + try{ + let res = await this.sysExec(`lxc-destroy --force --name ${this.name}`) + + return !!res.stdout.match(/Destroyed container/); + }catch(error){ + throw error; + } + } + + async stop(){ + try{ + return await this.sysExec(`lxc-stop --name "${this.name}"`); + }catch(error){ + throw error; + } + } + + async exec(code){ + try{ + code = new Buffer.from(code).toString('base64') + return await this.sysExec(`lxc-attach -n "${this.name}" --clear-env -- bash -c 'echo "${code}" | base64 --decode | bash'`) + }catch(error){ + throw error; + } + } + + async info(){ + try{ + let info = {}; + + let res = await this.sysExec(`lxc-info --name "${this.name}"`); + res = res.stdout; + + if(res.match("doesn't exist")){ + throw new Error('ContainerDoesntExist') + } + + res = res.replace(/\suse/ig, '').replace(/\sbytes/ig, '').split("\n").slice(0,-1); + for(var i in res){ + var temp = res[i].split(/\:\s+/); + info[temp[0].toLowerCase().trim()] = temp[1].trim(); + } + var args = [info].concat(Array.prototype.slice.call(arguments, 1)); + + return info; + }catch(error){ + throw error; + } + } + + async setAutoStart(name){ + await this.sysExec(`echo "lxc.start.auto = 1" >> "$HOME/.local/share/lxc/${this.name}/config"`) + } +} + + + +module.exports = {Local, SSH, LXC}; + +(async function(){try{ + +// let lxc = new LXC(); + +// // console.log(await lxc.copy('hass', 'hass2')) + + // console.log(await lxc.destroy('hass2')) + // console.log(await LXC.list()) + + // let lxc = new LXC({name:'hass'}) + // console.log(await hass.start()) + + + + // let lxc = new LXC({user:'virt-service', host:'142.93.30.52', keyPath:'/home/william/.ssh/id_rsa_virt-service', name: 'test2'}) + // console.log(await lxc.exec('hostname')) +// // console.log(await lxc.exec('test2', 'sleep 50')) + // console.log(await lxc.info()) +// + + +}catch(error){ + console.error('IIFE error', error); +}})() diff --git a/nodejs/models/auth.js b/nodejs/models/auth.js index bfe079a..690529e 100644 --- a/nodejs/models/auth.js +++ b/nodejs/models/auth.js @@ -22,6 +22,7 @@ Auth.login = async function(data){ return {user, token} }catch(error){ + console.log('login error', error); throw this.errors.login(); } }; diff --git a/nodejs/models/group_ldap.js b/nodejs/models/group_ldap.js deleted file mode 100644 index 282863a..0000000 --- a/nodejs/models/group_ldap.js +++ /dev/null @@ -1,281 +0,0 @@ -'use strict'; - -const { Client, Attribute, Change } = require('ldapts'); -const conf = require('../app').conf.ldap; - -const client = new Client({ - url: conf.url, -}); - -async function getGroups(client, member){ - try{ - - let memberFilter = member ? `(member=${member})`: '' - - let groups = (await client.search(conf.groupBase, { - scope: 'sub', - filter: `(&(objectClass=groupOfNames)${memberFilter})`, - attributes: ['*', 'createTimestamp', 'modifyTimestamp'], - })).searchEntries; - - return groups.map(function(group){ - if(!Array.isArray(group.member)) group.member = [group.member]; - if(!Array.isArray(group.owner)) group.owner = [group.owner]; - return group - }); - }catch(error){ - throw error; - } -} - -async function addGroup(client, data){ - try{ - - await client.add(`cn=${data.name},${conf.groupBase}`, { - cn: data.name, - member: data.owner, - description: data.description, - owner: data.owner, - objectclass: [ 'groupOfNames', 'top' ] - }); - - return data; - - }catch(error){ - throw error; - } -} - -async function addMember(client, group, user){ - try{ - await client.modify(group.dn, [ - new Change({ - operation: 'add', - modification: new Attribute({ - type: 'member', - values: [user.dn] - }) - }), - ]); - }catch(error){ - // if(error = "TypeOrValueExistsError"){ - // console.error('addMember error skipped', error) - // return ; - // } - throw error; - } -} - -async function removeMember(client, group, user){ - try{ - await client.modify(group.dn, [ - new Change({ - operation: 'delete', - modification: new Attribute({ - type: 'member', - values: [user.dn] - })}), - ]); - }catch(error){ - if(error = "TypeOrValueExistsError")return ; - throw error; - } -} - -async function addOwner(client, group, user){ - try{ - await client.modify(group.dn, [ - new Change({ - operation: 'add', - modification: new Attribute({ - type: 'owner', - values: [user.dn] - }) - }), - ]); - }catch(error){ - // if(error = "TypeOrValueExistsError"){ - // console.error('addMember error skipped', error) - // return ; - // } - throw error; - } -} - -async function removeOwner(client, group, user){ - try{ - await client.modify(group.dn, [ - new Change({ - operation: 'delete', - modification: new Attribute({ - type: 'owner', - values: [user.dn] - })}), - ]); - }catch(error){ - if(error = "TypeOrValueExistsError")return ; - throw error; - } -} - -var Group = {}; - -Group.list = async function(member){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - let groups = await getGroups(client, member) - - await client.unbind(); - - return groups.map(group => group.cn); - }catch(error){ - throw error; - } -} - -Group.listDetail = async function(member){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - let groups = await getGroups(client, member) - - await client.unbind(); - - - return groups; - }catch(error){ - throw error; - } -} - -Group.get = async function(data){ - try{ - - if(typeof data !== 'object'){ - let name = data; - data = {}; - data.name = name; - } - - await client.bind(conf.bindDN, conf.bindPassword); - - let group = (await client.search(conf.groupBase, { - scope: 'sub', - filter: `(&(objectClass=groupOfNames)(cn=${data.name}))`, - attributes: ['*', 'createTimestamp', 'modifyTimestamp'], - })).searchEntries[0]; - - await client.unbind(); - - if(!Array.isArray(group.member)) group.member = [group.member]; - if(!Array.isArray(group.owner)) group.owner = [group.owner]; - - if(group){ - let obj = Object.create(this); - Object.assign(obj, group); - - return obj; - }else{ - let error = new Error('GroupNotFound'); - error.name = 'GroupNotFound'; - error.message = `LDAP:${data.cn} does not exists`; - error.status = 404; - throw error; - } - }catch(error){ - throw error; - } -} - -Group.add = async function(data){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await addGroup(client, data); - - await client.unbind(); - - return this.get(data); - - }catch(error){ - throw error; - } -} - -Group.addMember = async function(user){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await addMember(client, this, user); - - await client.unbind(); - - return this; - - }catch(error){ - throw error; - } -}; - -Group.removeMember = async function(user){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await removeMember(client, this, user); - - await client.unbind(); - - return this; - - }catch(error){ - throw error; - } -}; - - -Group.addOwner = async function(user){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await addOwner(client, this, user); - - await client.unbind(); - - return this; - - }catch(error){ - throw error; - } -}; - -Group.removeOwner = async function(user){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await removeOwner(client, this, user); - - await client.unbind(); - - return this; - - }catch(error){ - throw error; - } -}; - -Group.remove = async function(){ - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await client.del(this.dn); - - await client.unbind(); - - return true; - }catch(error){ - throw error; - } -} - -module.exports = {Group}; diff --git a/nodejs/models/repo.js b/nodejs/models/repo.js new file mode 100644 index 0000000..a205bfe --- /dev/null +++ b/nodejs/models/repo.js @@ -0,0 +1,258 @@ +'use strict'; + +const {promisify} = require('util'); +const forge = require('node-forge'); + +const Table = require('../utils/redis_model'); + +var rasGenerate = promisify(forge.pki.rsa.generateKeyPair); + +async function generateOpenSshPair(keySize){ + keySize = keySize || 2048; + let keyPair = await rasGenerate({bits: keySize}); + + return { + publicKey: forge.ssh.publicKeyToOpenSSH(keyPair.publicKey), + privateKey: forge.ssh.privateKeyToOpenSSH(keyPair.privateKey) + + }; +}; + +const UUID = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)}; + +class Repo extends Table{ + static _key = 'repo' + 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',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'repo': {isRequired: true, type: 'string', min: 3, max: 500}, + 'hookCallCount': {default: 0, type: 'number'}, + 'scriptsPath': {default:'scripts', type: 'string'}, + 'settings': {default: {}, type:'object'}, + 'secrets': {default: {}, type: 'object', min: 3, max: 500}, + 'privateKey': {type: 'string'}, + 'publicKey': {type: 'string'}, + } + + constructor(...args){ + super(...args); + } + + static async add(data){ + return super.add({...data, ...(await generateOpenSshPair(2048))}) + } + + async getEnvironments(){ + let environments = await Environment.list(); + let out = []; + + for(let environment of environments){ + if(environment.startsWith(this.repo)){ + environment = await Environment.get(environment); + environment.repo = this; + out.push(environment) + } + } + + return out; + } + + async getEnvironmentsbyBranch(branch){ + let list = await this.getEnvironments(); + let any; + + for(let key of list){ + if(branch === key.branchMatch) return key; + if(key.branchMatch === '*') any = key; + } + + return any; + } + + async getDeploymentsbyBranch(branch, state){ + let environment = await this.getEnvironmentsbyBranch(branch); + let deployments = await Deployment.list(); + let out = [] + + for(let deployment of deployments){ + if(deployment.startsWith(`${this.repo}_${environment.environment}`)){ + deployment = await Deployment.get(deployment); + deployment.environment = environment; + deployment.target = await Target.get(environment.target); + out.push(deployment) + if(state && deployment.state === state){ + } + } + } + + return out; + } +} + +class Environment extends Table{ + static _key = 'repo_env' + 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',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'repo_env': {isRequired: true, type: 'string', min: 3, max: 500}, + 'repo': {type: 'string', min: 3, max: 500}, + 'environment': {isRequired: true, type: 'string', min: 3, max: 500}, + 'branchMatch': {isRequired: true, type: 'string', min: 1, max: 500}, + 'target': {isRequired: true, type: 'string', min: 3, max: 500}, + 'settings': {default: {}, type: 'object', min: 3, max: 500}, + 'secrets': {default: {}, type: 'object', min: 3, max: 500}, + 'hookCallCount': {default: 0, type: 'number'}, + 'lastCommit': {default:"__NONE__", isRequired: false, type: 'string'}, + 'workingPath': {default: '/opt/datacom', type: 'string'}, + 'domain': {isRequired: true, type: 'string'}, + + } + + static async add(data){ + try{ + await Repo.get(data.repo); + await Target.get(data.target); + + data.repo_env = `${data.repo}_${data.environment}` + return await super.add(data); + + }catch(error){ + throw error; + } + }; + + async addDeployment(data){ + try{ + data = data || {} + data.created_by = data.uid || this.created_by; + data.repo = this.repo.repo || this.repo; + data.environment = this.environment; + data.id = UUID().split('-').reverse()[0] + data.repo_env_id = `${data.repo}_${data.environment}_${data.id}` + let deployment = await Deployment.add(data); + deployment.target = await Target.get(this.target) + deployment.environment = this; + + return deployment; + }catch(error){ + throw error; + } + }; +} + +class Deployment extends Table{ + static _key = 'repo_env_id' + 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',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'id': {type: 'string', min: 12, max: 12}, + 'repo_env_id': {isRequired: true, type: 'string', min: 3, max: 500}, + 'repo': {type: 'string', min: 3, max: 500}, + 'environment': {isRequired: true, type: 'string', min: 3, max: 500}, + 'state': {default: 'new', type: 'string', min: 3, max: 500}, + 'isActive': {default: true, type: 'boolean',}, + 'target_url': {default:"__NONE__", isRequired: false, type: 'string'}, + } +} + +class Target extends Table{ + static _key = 'name' + 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',}, + 'updated_on': {default: function(){return (new Date).getTime()}, always: true}, + 'name': {isRequired: true, type: 'string', min: 2, max: 500}, + 'type': {isRequired: true, type: 'string', min: 1, max: 36}, + 'settings': {default: {}, type: 'object', min: 3, max: 500}, + + } +} + +module.exports = {Repo, Environment, Deployment, Target}; + +(async function(){try{ + +// // console.log(await Repo.list()) + +// // To ssh://git.theta42.com:2222/wmantly/static-test.git + +// let lxc_starting = await Target.add({ +// created_by: 'wmantly', +// name: 'lxc_starting', +// type: 'LXC', +// settings: { +// user:'virt-service', +// host:'142.93.30.52', +// keyPath:'/home/william/.ssh/id_rsa_virt-service' +// } +// }); + +// var repo = await Repo.add({ +// created_by: 'wmantly', +// repo: 'wmantly/static-test', +// }) + +// var environment = await Environment.add({ +// created_by: 'wmantly', +// environment: 'staging', +// branchMatch: '*', +// repo: 'wmantly/static-test', +// domain: 'test.dc.vm42.us', +// target: 'lxc_starting' +// }) + + + + let environment = await Environment.get('wmantly/static-test_staging') + await environment.update({'domain': '*.dc.vm42.us'}) + + +// // console.log(test) + + +// // console.log(await Environment.listDetail()) +// // let repo = await Repo.get('wmantly/test2') +// // console.log(repo) +// // repo.update({hookCallCount: 5}); +// // let envs = await repo.getEnvironments(); +// // let env = await repo.getEnvironmentsbyBranch('staging'); +// // let deployment = await env.addDeployment() +// // console.log('deployment', deployment) +// // let deployments = await repo.getDeploymentsbyBranch('staging') +// // console.log('deployments', deployments) +// // console.log('deployments', await Deployment.listDetail()) + + + +// console.log('repo', await Repo.listDetail()) +// console.log('environment', await Environment.listDetail()) + + // for(let d of await Deployment.listDetail()){ + // console.log('to remove', d) + // await d.remove() + // } + + // console.log('deployment', await Deployment.listDetail()) + + + // console.log('blah') + // let repo = await Repo.get('wmantly/static-test'); + // // let environment = await repo.getEnvironmentsbyBranch('master') + // // console.log('environment', environment) + + // let deployment = await repo.getDeploymentsbyBranch('master') + + // console.log('deployments', deployment) + + +// return 0; +}catch(error){ + console.error('IIFE error', error, error.message); +}})() diff --git a/nodejs/models/test b/nodejs/models/test new file mode 100644 index 0000000..6eba994 --- /dev/null +++ b/nodejs/models/test @@ -0,0 +1,55 @@ +var that +class Base{ + static add(){ + + } + constructor(){ + + } + + blah(){ + that = this + } +} + +class Ex extends Base{ + static thingy = {a:1, b:2} + constructor(){ + super() + } +} + + + + + +Repo.byBranch = async function(repo){ + let list = await Environment.list(); + let out = []; + for(let key of list){ + if(key.startsWith((repo || this.repo))) out.push(await Environment.get(key)) + } + + return out; +} + + + +Environment.addDeployment = async function(data){ + try{ + data.repo = this.repo; + data.environment = this.environment; + data.id = UUID().split('-').reverse()[0] + data.repo_env = `${data.repo}_${data.environment}_${data.id}` + return await Deployment.__proto__.add.call(Environment, data); + + }catch(error){ + throw error; + } +}; + + + + +module.exports = {Repo, Environment, Deployment, Target}; +*/ diff --git a/nodejs/models/token.js b/nodejs/models/token.js index 3b4b5bd..aef9c1b 100644 --- a/nodejs/models/token.js +++ b/nodejs/models/token.js @@ -1,71 +1,28 @@ '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)}; +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'} + } -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 + async check(){ + return this.is_valid } } -var InviteToken = Object.create(Token({ - name: 'invite', - keyMap:{ - claimed_by: {default:"__NONE__", isRequired: false, type: 'string',}, - mail: {default:"__NONE__", isRequired: false, type: 'string',}, - mail_token: {default: UUID, type: 'string', min: 36, max: 36}, - } -})); - -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; +class AuthToken extends Token{ + static async add(data){ + data.created_by = data.created_by || data.uid; + return await super.add(data) } } -var AuthToken = Object.create(Token({ - name: 'auth', -})); - -AuthToken.add = async function(data){ - data.created_by = data.uid; - return AuthToken.__proto__.add(data); -}; - -var PasswordResetToken = Object.create(Token({ - name: 'auth', -})); - -PasswordResetToken.add = async function(data){ - data.created_by = data.uid; - return PasswordResetToken.__proto__.add(data); -}; - -module.exports = {Token, InviteToken, AuthToken, PasswordResetToken}; +module.exports = {Token, AuthToken}; diff --git a/nodejs/models/user_ldap.js b/nodejs/models/user_ldap.js index 6a84f0c..9fb1d65 100644 --- a/nodejs/models/user_ldap.js +++ b/nodejs/models/user_ldap.js @@ -1,116 +1,12 @@ 'use strict'; -const { Client, Attribute, Change } = require('ldapts'); -const crypto = require('crypto'); - -const {Mail} = require('./email'); -const {Token, InviteToken, PasswordResetToken} = require('./token'); +const {Client, Attribute} = require('ldapts'); const conf = require('../app').conf.ldap; const client = new Client({ url: conf.url, }); -async function addPosixGroup(client, data){ - - try{ - const groups = (await client.search(conf.groupBase, { - scope: 'sub', - filter: '(&(objectClass=posixGroup))', - })).searchEntries; - - data.gidNumber = (Math.max(...groups.map(i => i.gidNumber))+1)+''; - - await client.add(`cn=${data.cn},${conf.groupBase}`, { - cn: data.cn, - gidNumber: data.gidNumber, - objectclass: [ 'posixGroup', 'top' ] - }); - - return data; - - }catch(error){ - throw error; - } -} - -async function addPosixAccount(client, data){ - try{ - const people = (await client.search(conf.userBase, { - scope: 'sub', - filter: conf.userFilter, - })).searchEntries; - - data.uidNumber = (Math.max(...people.map(i => i.uidNumber))+1)+''; - - await client.add(`cn=${data.cn},${conf.userBase}`, { - cn: data.cn, - sn: data.sn, - uid: data.uid, - uidNumber: data.uidNumber, - gidNumber: data.gidNumber, - givenName: data.givenName, - mail: data.mail, - mobile: data.mobile, - loginShell: data.loginShell, - homeDirectory: data.homeDirectory, - userPassword: data.userPassword, - description: data.description || ' ', - sudoHost: 'ALL', - sudoCommand: 'ALL', - sudoUser: data.uid, - sshPublicKey: data.sshPublicKey, - objectclass: ['inetOrgPerson', 'sudoRole', 'ldapPublicKey', 'posixAccount', 'top' ] - }); - - return data - - }catch(error){ - throw error; - } - -} - -async function addLdapUser(client, data){ - - var group; - - try{ - data.uid = `${data.givenName[0]}${data.sn}`.toLowerCase(); - data.cn = data.uid; - data.loginShell = '/bin/bash'; - data.homeDirectory= `/home/${data.uid}`; - data.userPassword = '{MD5}'+crypto.createHash('md5').update(data.userPassword, "binary").digest('base64'); - - group = await addPosixGroup(client, data); - data = await addPosixAccount(client, group); - - return data; - - }catch(error){ - await deleteLdapDN(client, `cn=${data.uid},${conf.groupBase}`, true); - throw error; - } -} - -async function deleteLdapUser(client, data){ - try{ - await client.del(`cn=${data.cn},${conf.groupBase}`); - await client.del(data.dn); - }catch(error){ - throw error; - } -} - -async function deleteLdapDN(client, dn, ignoreError){ - try{ - client.del(dn) - }catch(error){ - if(!ignoreError) throw error; - console.error('ERROR: deleteLdapDN', error) - } -} - const user_parse = function(data){ if(data[conf.userNameAttribute]){ data.username = data[conf.userNameAttribute] @@ -225,200 +121,11 @@ User.exists = async function(data, key){ } }; -User.add = async function(data) { - try{ - await client.bind(conf.bindDN, conf.bindPassword); - - await addLdapUser(client, data); - - await client.unbind(); - - let user = await this.get(data.uid); - - - await Mail.sendTemplate( - user.mail, - 'welcome', - { - user: user - } - ) - - return user; - - }catch(error){ - if(error.message.includes('exists')){ - let error = new Error('UserNameUsed'); - error.name = 'UserNameUsed'; - error.message = `LDAP:${data.uid} already exists`; - error.status = 409; - - throw error; - } - throw error; - } -}; - -User.update = async function(data){ - try{ - let editableFeilds = ['mobile', 'sshPublicKey', 'description']; - - await client.bind(conf.bindDN, conf.bindPassword); - - for(let field of editableFeilds){ - if(data[field]){ - await client.modify(this.dn, [ - new Change({ - operation: 'replace', - modification: new Attribute({ - type: field, - values: [data[field]] - }) - }), - ]); - } - } - - await client.unbind() - - return this; - - }catch(error){ - throw error; - } -}; - -User.addByInvite = async function(data){ - try{ - let token = await InviteToken.get(data.token); - - if(!token.is_valid && data.mailToken !== token.mail_token){ - 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; - } - - data.mail = token.mail; - - let user = await this.add(data); - - if(user){ - await token.consume({claimed_by: user.uid}); - return user; - } - - }catch(error){ - throw error; - } - -}; - -User.verifyEmail = async function(data){ - try{ - - let exists = await this.exists(data.mail, 'mail'); - - if(exists) throw new Error('EmailInUse'); - - let token = await InviteToken.get(data.token); - await token.update({mail: data.mail}) - - await Mail.sendTemplate( - data.mail, - 'validate_link', - { - link:`${data.url}/login/invite/${token.token}/${token.mail_token}` - } - ) - - return this; - }catch(error){ - throw error; - } -}; - -User.passwordReset = async function(url, mail){ - try{ - - let user = await User.get({ - searchKey: 'mail', - searchValue: mail - }); - - let token = await PasswordResetToken.add(user); - - await Mail.sendTemplate( - user.mail, - 'reset_link', - { - user: user, - link:`${url}/login/resetpassword/${token.token}` - } - ) - - return true; - }catch(error){ - // if(error.name === 'UserNotFound') return false; - throw error; - } -}; - - -User.remove = async function(data){ - try{ - - await client.bind(conf.bindDN, conf.bindPassword); - - await deleteLdapUser(client, this); - - await client.unbind(); - - return true; - - }catch(error){ - throw error; - } -}; - -User.setPassword = async function(data){ - try{ - - await client.bind(conf.bindDN, conf.bindPassword); - - await client.modify(this.dn, [ - new Change({ - operation: 'replace', - modification: new Attribute({ - type: 'userPassword', - values: ['{MD5}'+crypto.createHash('md5').update(data.userPassword, "binary").digest('base64')] - })}), - ]); - - await client.unbind(); - - return this; - }catch(error){ - throw error; - } -}; - -User.invite = async function(){ - try{ - let token = await InviteToken.add({created_by: this.uid}); - - return token; - - }catch(error){ - throw error; - } -}; - User.login = async function(data){ try{ + let user = await this.get(data.uid); - + await client.bind(user.dn, data.password); await client.unbind(); diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index d4f41d5..2ba1b36 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -1,35 +1,2050 @@ { "name": "t42-ldap-manager", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "t42-ldap-manager", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "axios": "^0.21.1", + "ejs": "^3.1.5", + "express": "~4.16.1", + "extend": "^3.0.2", + "ldapts": "^2.10.1", + "moment": "^2.29.1", + "mustache": "^4.1.0", + "redis": "^2.8.0" + }, + "devDependencies": { + "nodemon": "^2.0.6" + } + }, + "node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/asn1": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@types/asn1/-/asn1-0.2.0.tgz", + "integrity": "sha512-5TMxIpYbIA9c1J0hYQjQDX3wr+rTgQEAXaW2BI8ECM8FO53wSW4HFZplTalrKSHuZUc76NtXcePRhwuOHqGD5g==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" + }, + "node_modules/@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==" + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-align": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", + "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "dev": true, + "dependencies": { + "string-width": "^3.0.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/ansi-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" + }, + "node_modules/axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "dependencies": { + "follow-redirects": "^1.10.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dependencies": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/boxen": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", + "integrity": "sha512-eB4uT9RGzg2odpER62bBwSLvUeGC+WbRjjyyFhGsKnc8wp/m0+hQsMUvUe3H2V0D5vw0nBdO1hCJoZo5mKeuIQ==", + "dev": true, + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "cli-boxes": "^2.2.0", + "string-width": "^4.1.0", + "term-size": "^2.1.0", + "type-fest": "^0.8.1", + "widest-line": "^3.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/boxen/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/boxen/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacheable-request/node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cacheable-request/node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.3.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.1" + } + }, + "node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "dev": true, + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "node_modules/duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "dependencies": { + "jake": "^10.6.1" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "dependencies": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "dependencies": { + "minimatch": "^3.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/glob-parent": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", + "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/global-dirs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-2.1.0.tgz", + "integrity": "sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ==", + "dev": true, + "dependencies": { + "ini": "1.3.7" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", + "dev": true + }, + "node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ini": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.7.tgz", + "integrity": "sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-installed-globally": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.3.2.tgz", + "integrity": "sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g==", + "dev": true, + "dependencies": { + "global-dirs": "^2.0.1", + "is-path-inside": "^3.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-4.0.0.tgz", + "integrity": "sha512-96ECIfh9xtDDlPylNPXhzjsykHsMJZ18ASpaWzQyBr4YRTcVjUvzaHayDAES2oU/3KpljhHUjtSRNiDwi0F0ig==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.2.tgz", + "integrity": "sha512-/2UGPSgmtqwo1ktx8NDHjuPwZWmHhO+gj0f93EkhLB5RgW9RZevWYYlIkS6zePc6U2WpOdQYIwHe9YC4DWEBVg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true + }, + "node_modules/jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dependencies": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=", + "dev": true + }, + "node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ldapts": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-2.11.0.tgz", + "integrity": "sha512-9QJmn4XQx15FMe8cGBiSfDvyAwVH8zT7FEbCw3aWyiP1778mGd/POIrsLMw6X6K3EmPY8Bf4VYbwiH04+NpM8g==", + "dependencies": { + "@types/asn1": "~0.2.0", + "@types/node": "~14.14.20", + "@types/uuid": "~8.3.0", + "asn1": "~0.2.4", + "debug": "~4.3.1", + "strict-event-emitter-types": "~2.0.0", + "uuid": "~8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ldapts/node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/ldapts/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "bin": { + "mime": "cli.js" + } + }, + "node_modules/mime-db": { + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "dependencies": { + "mime-db": "1.45.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/mustache": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.1.0.tgz", + "integrity": "sha512-0FsgP/WVq4mKyjolIyX+Z9Bd+3WS8GOwoUTyKXT5cTYMGeauNTi2HPCwERqseC1IHAy0Z7MDZnJBfjabd4O8GQ==", + "bin": { + "mustache": "bin/mustache" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "chokidar": "^3.2.2", + "debug": "^3.2.6", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.7", + "semver": "^5.7.1", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.3", + "update-notifier": "^4.1.0" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", + "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "dependencies": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readdirp": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", + "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "dependencies": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redis-commands": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", + "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" + }, + "node_modules/redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.1.tgz", + "integrity": "sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "dev": true, + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/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==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/semver-diff/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, + "node_modules/string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/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=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/term-size": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz", + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "dependencies": { + "nopt": "~1.0.10" + }, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undefsafe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.3.tgz", + "integrity": "sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==", + "dev": true, + "dependencies": { + "debug": "^2.2.0" + } + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-notifier": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-4.1.3.tgz", + "integrity": "sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==", + "dev": true, + "dependencies": { + "boxen": "^4.2.0", + "chalk": "^3.0.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.3.1", + "is-npm": "^4.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.0.0", + "pupa": "^2.0.1", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/update-notifier/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/update-notifier/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "dev": true, + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "engines": { + "node": ">=8" + } + } + }, "dependencies": { - "@sendgrid/client": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.4.0.tgz", - "integrity": "sha512-KAZlEb1P8sATgBN+7hXgzaRF94nF9KQgDxQ6zUT1BV0kEsNtJQ2cs35sCtWt6AKKJrL0xPI/MsfcAJqom4YQBg==", - "requires": { - "@sendgrid/helpers": "^7.4.0", - "axios": "^0.19.2" - } - }, - "@sendgrid/helpers": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.4.0.tgz", - "integrity": "sha512-IQI2vemiJB0+X6bEp4HRG+0/wrzR2RDGnB5rwfq1CsPDrUFdJfxbE2zbGx//1GnlNwAtbHyc93ejU1m0KZr86w==", - "requires": { - "deepmerge": "^4.2.2" - } - }, - "@sendgrid/mail": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.4.0.tgz", - "integrity": "sha512-SAARsfbl50OEJ99LYGKfgrYiV5O6+23aeGJuEBTHHSwRZ6KhD3n1BjPeIejbqgbqYLZJfNLxyU3o5xRdJPp3zg==", - "requires": { - "@sendgrid/client": "^7.4.0", - "@sendgrid/helpers": "^7.4.0" - } - }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -54,9 +2069,9 @@ } }, "@types/node": { - "version": "14.14.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.17.tgz", - "integrity": "sha512-G0lD1/7qD60TJ/mZmhog76k7NcpLWkPVGgzkRy3CTlnFu4LUQh5v2Wa661z6vnXmD8EQrnALUyf0VRtrACYztw==" + "version": "14.14.22", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.22.tgz", + "integrity": "sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==" }, "@types/uuid": { "version": "8.3.0", @@ -87,6 +2102,24 @@ "string-width": "^3.0.0" }, "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "string-width": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", @@ -97,13 +2130,22 @@ "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } } } }, "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -143,11 +2185,11 @@ "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=" }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "balanced-match": { @@ -156,9 +2198,9 @@ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, "body-parser": { @@ -317,14 +2359,14 @@ } }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -434,11 +2476,6 @@ "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" - }, "defer-to-connect": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", @@ -489,9 +2526,9 @@ } }, "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, "encodeurl": { @@ -603,22 +2640,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } - } + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==" }, "forwarded": { "version": "0.1.2", @@ -631,9 +2655,9 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "dev": true, "optional": true }, @@ -784,9 +2808,9 @@ "dev": true }, "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, "is-glob": { @@ -880,17 +2904,17 @@ } }, "ldapts": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-2.10.1.tgz", - "integrity": "sha512-uUueatw1+GdfxQ0jA3O75jHZXWClx9QpspHxI5q2CGQecCQVm5bcPKI0iPXgLVNWMDDwVOwmPCku5piRyXs6mg==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/ldapts/-/ldapts-2.11.0.tgz", + "integrity": "sha512-9QJmn4XQx15FMe8cGBiSfDvyAwVH8zT7FEbCw3aWyiP1778mGd/POIrsLMw6X6K3EmPY8Bf4VYbwiH04+NpM8g==", "requires": { "@types/asn1": "~0.2.0", - "@types/node": "~14.14.10", + "@types/node": "~14.14.20", "@types/uuid": "~8.3.0", "asn1": "~0.2.4", "debug": "~4.3.1", "strict-event-emitter-types": "~2.0.0", - "uuid": "~8.3.1" + "uuid": "~8.3.2" }, "dependencies": { "debug": { @@ -952,16 +2976,16 @@ "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==" }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-response": { @@ -1005,9 +3029,9 @@ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "nodemon": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.6.tgz", - "integrity": "sha512-4I3YDSKXg6ltYpcnZeHompqac4E6JeAMpGm8tJnB9Y3T0ehasLa4139dJOcCrB93HHrUMsCrKtoAlXTqT5n4AQ==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz", + "integrity": "sha512-XHzK69Awgnec9UzHr1kc8EomQh4sjTQ8oRf8TsGrSmHDx9/UmiGG9E/mM3BuTfNeFwdNBvrqQq/RHL0xIeyFOA==", "dev": true, "requires": { "chokidar": "^3.2.2", @@ -1212,9 +3236,9 @@ } }, "redis-commands": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.5.0.tgz", - "integrity": "sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==" + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.6.0.tgz", + "integrity": "sha512-2jnZ0IkjZxvguITjFTrGiLyzQZcTvaw8DAaCXxZq/dsHXz7KfMQ3OUJy7Tz9vnRtZRVz6VRCPDvruvU8Ts44wQ==" }, "redis-parser": { "version": "2.6.0", @@ -1323,11 +3347,6 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, - "smtpc": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/smtpc/-/smtpc-0.1.2.tgz", - "integrity": "sha1-ew76q8soHi1FR2X6/uu92MOCN3Q=" - }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1347,44 +3366,15 @@ "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } } }, "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "dev": true, "requires": { - "ansi-regex": "^4.1.0" + "ansi-regex": "^5.0.0" } }, "strip-json-comments": { diff --git a/nodejs/package.json b/nodejs/package.json index 4c02e50..f6b6e91 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -12,15 +12,14 @@ "start": "node ./bin/www" }, "dependencies": { - "@sendgrid/mail": "^7.4.0", + "axios": "^0.21.1", "ejs": "^3.1.5", "express": "~4.16.1", "extend": "^3.0.2", "ldapts": "^2.10.1", "moment": "^2.29.1", "mustache": "^4.1.0", - "redis": "^2.8.0", - "smtpc": "^0.1.2" + "redis": "^2.8.0" }, "license": "MIT", "repository": { diff --git a/nodejs/routes/auth.js b/nodejs/routes/auth.js index cead2fc..41746b7 100755 --- a/nodejs/routes/auth.js +++ b/nodejs/routes/auth.js @@ -2,19 +2,19 @@ const router = require('express').Router(); const {User} = require('../models/user'); -const {Auth, AuthToken} = require('../models/auth'); -const {PasswordResetToken} = require('../models/token'); +const {Auth, AuthToken} = require('../models/auth'); router.post('/login', async function(req, res, next){ try{ let auth = await Auth.login(req.body); + console.log('auth route', auth) return res.json({ login: true, token: auth.token.token, - message:`${req.body.uid} logged in!`, }); }catch(error){ + console.log('error route', error) next(error); } }); @@ -31,46 +31,14 @@ router.all('/logout', async function(req, res, next){ } }); -router.post('/resetpassword', async function(req, res, next){ - try{ - let sent = await User.passwordReset(`${req.protocol}://${req.hostname}`, req.body.mail); - - console.info('resetpassword for', req.body.mail, 'sent') - - return res.json({ - message: 'If the emaill address is in our system, you will receive a message.' - }); - }catch(error){ - next(error); - } -}); - -router.post('/resetpassword/:token', async function(req, res, next){ - try{ - let token = await PasswordResetToken.get(req.params.token); - - if(token.is_valid && 86400000+Number(token.created_on) > (new Date).getTime()){ - let user = await User.get(token.created_by); - await user.setPassword(req.body); - token.update({is_valid: false}); - return res.json({ - message: 'Password has been changed.' - }); - } - }catch(error){ - next(error); - } -}); - -router.post('/invite/:token/:mailToken', async function(req, res, next) { +router.post('/invite/:token', async function(req, res, next) { try{ req.body.token = req.params.token; - req.body.mailToken = req.params.mailToken; let user = await User.addByInvite(req.body); let token = await AuthToken.add(user); return res.json({ - user: user.uid, + user: user.username, token: token.token }); @@ -80,21 +48,6 @@ router.post('/invite/:token/:mailToken', async function(req, res, next) { }); -router.post('/invite/:token', async function(req, res, next){ - try{ - let data = { - token: req.params.token, - url: `${req.protocol}://${req.hostname}`, - mail: req.body.mail, - } - - await User.verifyEmail(data); - return res.send({message: 'sent'}); - }catch(error){ - next(error) - } -}); - module.exports = router; /* diff --git a/nodejs/routes/git_webhook.js b/nodejs/routes/git_webhook.js new file mode 100644 index 0000000..38d7bc6 --- /dev/null +++ b/nodejs/routes/git_webhook.js @@ -0,0 +1,28 @@ +'use strict'; + +const router = require('express').Router(); +const {doDeploy} = require('../lib/deploy'); + +router.all('/', async function(req, res, next) { + try{ + var event = req.headers['x-github-event']; + var call = (req.body.created && 'create') || + (req.body.deleted && 'delete') || + 'update'; + + var branch = req.body.ref.replace('refs/heads/', ''); + var sshURL = req.body.repository.ssh_url; + var commit = req.body.after; + let repo = req.body.repository.full_name; + + let id = await doDeploy('create', repo, branch, sshURL, commit); + + res.json({id}); + + }catch(error){ + next(error) + } +}); + + +module.exports = router; \ No newline at end of file diff --git a/nodejs/routes/group.js b/nodejs/routes/group.js deleted file mode 100644 index a7f0f49..0000000 --- a/nodejs/routes/group.js +++ /dev/null @@ -1,125 +0,0 @@ -'use strict'; - -const router = require('express').Router(); -const {User} = require('../models/user_ldap'); -const {Group} = require('../models/group_ldap'); -const permission = require('../utils/permission'); - -router.get('/', async function(req, res, next){ - try{ - let member = req.query.member ? await User.get(req.query.member) : {} - - return res.json({ - results: await Group[req.query.detail ? "listDetail" : "list"](member.dn) - }); - }catch(error){ - next(error); - } -}); - -router.post('/', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin']); - - req.body.owner = req.user.dn; - return res.json({ - results: await Group.add(req.body), - message: `${req.body.name} was added!` - }) - }catch(error){ - next(error); - } -}); - -router.get('/:name', async function(req, res, next){ - try{ - return res.json({ - results: await Group.get(req.params.name) - }); - }catch(error){ - next(error); - } -}); - -router.put('/owner/:group/:uid', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]); - - var group = await Group.get(req.params.group); - var user = await User.get(req.params.uid); - return res.json({ - results: group.addOwner(user), - message: `Added owner ${req.params.uid} to ${req.params.group} group.` - }); - }catch(error){ - next(error); - } -}); - -router.delete('/owner/:group/:uid', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]); - - var group = await Group.get(req.params.group); - var user = await User.get(req.params.uid); - return res.json({ - results: group.removeOwner(user), - message: `Removed Owner ${req.params.uid} from ${req.params.group} group.` - }); - }catch(error){ - next(error); - } -}); - -router.put('/:group/:uid', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]); - - var group = await Group.get(req.params.group); - var user = await User.get(req.params.uid); - return res.json({ - results: group.addMember(user), - message: `Added user ${req.params.uid} to ${req.params.group} group.` - }); - }catch(error){ - next(error); - } -}); - -router.delete('/:group/:uid', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]); - - var group = await Group.get(req.params.group); - var user = await User.get(req.params.uid); - return res.json({ - results: group.removeMember(user), - message: `Removed user ${req.params.uid} from ${req.params.group} group.` - }); - }catch(error){ - next(error); - } -}); - -router.delete('/:group', async function(req, res, next){ - try{ - - await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]); - - var group = await Group.get(req.params.group); - return res.json({ - removed: await group.remove(), - results: group, - message: `Group ${req.params.group} Deleted` - }); - }catch(error){ - next(error); - } -}); - -module.exports = router; diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js index a6a17ba..3c5aa0d 100755 --- a/nodejs/routes/user.js +++ b/nodejs/routes/user.js @@ -1,125 +1,43 @@ 'use strict'; const router = require('express').Router(); -const {User} = require('../models/user'); -const permission = require('../utils/permission'); +const {User} = require('../models/user'); +const {Auth, AuthToken} = require('../models/auth'); -router.get('/', async function(req, res, next){ + +router.post('/login', async function(req, res, next){ try{ + let auth = await Auth.login(req.body); return res.json({ - results: await User[req.query.detail ? "listDetail" : "list"]() + login: true, + token: auth.token.token, }); }catch(error){ next(error); } }); -router.post('/', async function(req, res, next){ +router.all('/logout', async function(req, res, next){ try{ - await permission.byGroup(req.user, ['app_sso_admin']) - - req.body.created_by = req.user.uid - - return res.json({results: await User.add(req.body)}); - }catch(error){ - next(error); - } -}); - -router.delete('/:uid', async function(req, res, next){ - try{ - let user; - - if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){ - user = req.user; - }else{ - user = await User.get(req.params.uid); - await permission.byGroup(req.user, ['app_sso_admin']) + if(req.user){ + await req.user.logout(); } - return res.json({uid: req.params.uid, results: await user.remove()}) + res.json({message: 'Bye'}) }catch(error){ next(error); } }); -router.put('/:uid', async function(req, res, next){ +router.post('/invite/:token', async function(req, res, next) { try{ - let user; - - if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){ - user = req.user; - }else{ - user = await User.get(req.params.uid); - await permission.byGroup(req.user, ['app_sso_admin']) - } + req.body.token = req.params.token; + let user = await User.addByInvite(req.body); + let token = await AuthToken.add(user); return res.json({ - results: await user.update(req.body), - message: `Updated ${req.params.uid} user` - - }); - }catch(error){ - next(error); - } -}); - -router.get('/me', async function(req, res, next){ - try{ - - return res.json(await User.get({uid: req.user.uid})); - }catch(error){ - next(error); - } -}); - -router.put('/password', async function(req, res, next){ - try{ - return res.json({results: await req.user.setPassword(req.body)}) - }catch(error){ - next(error); - } -}); - -router.put('/:uid/password', async function(req, res, next){ - try{ - let user; - - if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){ - user = req.user; - }else{ - user = await User.get(req.params.uid); - await permission.byGroup(req.user, ['app_sso_admin']) - } - - return res.json({ - results: await user.setPassword(req.body), - message: `User ${user.uid} password changed.` - }); - }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({ - uid: req.user.uid, - key: req.body.key - }); - - return res.status(added === true ? 200 : 400).json({ - message: added + user: user.username, + token: token.token }); }catch(error){ @@ -128,14 +46,22 @@ router.post('/key', async function(req, res, next){ }); -router.get('/:uid', async function(req, res, next){ - try{ - return res.json({ - results: await User.get(req.params.uid), - }); - }catch(error){ - next(error); - } -}); - 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!' +// }); +// } + +// }); \ No newline at end of file diff --git a/nodejs/utils/object_validate.js b/nodejs/utils/object_validate.js index 6295c48..ee762c5 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}; diff --git a/nodejs/utils/redis.js b/nodejs/utils/redis.js index e032540..960c8f5 100755 --- a/nodejs/utils/redis.js +++ b/nodejs/utils/redis.js @@ -4,7 +4,7 @@ const {createClient} = require('redis'); const {promisify} = require('util'); const config = { - prefix: 'sso_' + prefix: 'deploy_' } function client() { @@ -13,6 +13,9 @@ function client() { const _client = client(); +const SCAN = promisify(_client.SCAN).bind(_client); + + module.exports = { client: client, HGET: promisify(_client.HGET).bind(_client), @@ -24,4 +27,18 @@ module.exports = { HGETALL: promisify(_client.HGETALL).bind(_client), SMEMBERS: promisify(_client.SMEMBERS).bind(_client), RENAME: promisify(_client.RENAME).bind(_client), + HSCAN: promisify(_client.HSCAN).bind(_client), + SCAN: async function(match){ + let coursor = 0; + let results = []; + do{ + let res = await SCAN(coursor, 'MATCH', config.prefix+match); + coursor = Number(res[0]); + + results.push(...res[1].map(e => e.replace(config.prefix, ''))) + } while(coursor); + + return results + } + }; diff --git a/nodejs/utils/redis_model.js b/nodejs/utils/redis_model.js index dffe9d0..30cb36d 100644 --- a/nodejs/utils/redis_model.js +++ b/nodejs/utils/redis_model.js @@ -4,187 +4,158 @@ 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; +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(res){ + 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){ - // 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 - 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]); + 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){ - 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]); + + // 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; diff --git a/nodejs/views/login.ejs b/nodejs/views/login.ejs index 3ef2163..8ccd414 100755 --- a/nodejs/views/login.ejs +++ b/nodejs/views/login.ejs @@ -16,7 +16,7 @@
- Password Log in + SSO Log in
@@ -48,53 +48,6 @@
-
-
- Social Login -
- -
-

Coming soon!

-

-

-

-
-
-
-
- Password Reset -
- -
-

- Forgot your password? Or your user name? No problem! Just - enter you email address below and if you are in our system, - we will email with the required information to get back up - and running! -

-
- - -
- -
-
- -
- -
-
- - -
-
-
<%- include('bottom') %> diff --git a/nodejs/views/top.ejs b/nodejs/views/top.ejs index f6f2c53..8eca685 100755 --- a/nodejs/views/top.ejs +++ b/nodejs/views/top.ejs @@ -3,7 +3,7 @@ - SSO Manager - Theta 42 + Deployment Manager - Theta 42 @@ -25,7 +25,7 @@
-
SSO Manager - Theta 42
+
Deployment Manager - Theta 42