simple proxmox
This commit is contained in:
		| @ -3,16 +3,42 @@ | |||||||
| const path = require('path'); | const path = require('path'); | ||||||
| const ejs = require('ejs') | const ejs = require('ejs') | ||||||
| const express = require('express'); | const express = require('express'); | ||||||
|  | const {P2PSub} = require('p2psub'); | ||||||
|  | const pvejs = require('./utils/pvejs'); | ||||||
|  |  | ||||||
|  |  | ||||||
| // Set up the express app. | // Set up the express app. | ||||||
| const app = express(); | const app = express(); | ||||||
|  |  | ||||||
|  | app.onListen = []; | ||||||
|  |  | ||||||
| // Allow the express app to be exported into other files.  | // Allow the express app to be exported into other files.  | ||||||
| module.exports = app; | module.exports = app; | ||||||
|  |  | ||||||
| // Build the conf object from the conf files. | // Build the conf object from the conf files. | ||||||
| app.conf = require('./conf/conf'); | app.conf = require('./conf/conf'); | ||||||
|  |  | ||||||
|  | app.p2p = new P2PSub(app.conf.p2p); | ||||||
|  |  | ||||||
|  | app.onListen.push(function(){ | ||||||
|  |   app.p2p.subscribe(/./g, function(data, topic){ | ||||||
|  |     if(data.__noSocket) return; | ||||||
|  |  | ||||||
|  |     app.io.emit('P2PSub', { topic, data }) | ||||||
|  |   });                                  | ||||||
|  |  | ||||||
|  |   app.io.on('connection', (socket) => { | ||||||
|  |     // console.log('connection io', socket) | ||||||
|  |     socket.on('P2PSub', (msg) => { | ||||||
|  |       msg.data.__noSocket = true; | ||||||
|  |       app.p2p.publish(msg.topic, msg.data); | ||||||
|  |       socket.broadcast.emit('P2PSub', msg) | ||||||
|  |  | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  | }); | ||||||
|  |  | ||||||
| // Hold onto the auth middleware  | // Hold onto the auth middleware  | ||||||
| const middleware = require('./middleware/auth'); | const middleware = require('./middleware/auth'); | ||||||
|  |  | ||||||
| @ -39,8 +65,29 @@ app.use('/api/user', middleware.auth, require('./routes/user')); | |||||||
|  |  | ||||||
| app.use('/api/token', middleware.auth, require('./routes/token')); | app.use('/api/token', middleware.auth, require('./routes/token')); | ||||||
|  |  | ||||||
| app.use('/api/group', middleware.auth, require('./routes/group')); | (async function(){ | ||||||
|  |   app.api = await pvejs(app.conf.proxmox); | ||||||
|  | })() | ||||||
|  |  | ||||||
|  | setInterval(async function(){ | ||||||
|  |   try{ | ||||||
|  |     let res = await app.api.GET({path: '/cluster/resources'}); | ||||||
|  |     let types = {}; | ||||||
|  |  | ||||||
|  |     for(let item of res.json){ | ||||||
|  |       if(!types[item.type]){ | ||||||
|  |          types[item.type] = []; | ||||||
|  |       } | ||||||
|  |       types[item.type].push(item); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     app.p2p.publish('proxmox-cluster', {vpnSite: app.conf.vpnSite, data: types}); | ||||||
|  |  | ||||||
|  |   }catch(error){ | ||||||
|  |     console.error('proxmox pub', error) | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | }, 10000); | ||||||
|  |  | ||||||
| // Catch 404 and forward to error handler. If none of the above routes are | // Catch 404 and forward to error handler. If none of the above routes are | ||||||
| // used, this is what will be called. | // used, this is what will be called. | ||||||
|  | |||||||
| @ -21,6 +21,10 @@ app.set('port', port); | |||||||
|  |  | ||||||
| var server = http.createServer(app); | var server = http.createServer(app); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | var io = require('socket.io')(server); | ||||||
|  | app.io = io; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Listen on provided port, on all network interfaces. |  * Listen on provided port, on all network interfaces. | ||||||
|  */ |  */ | ||||||
| @ -87,4 +91,8 @@ function onListening() { | |||||||
|     ? 'pipe ' + addr |     ? 'pipe ' + addr | ||||||
|     : 'port ' + addr.port; |     : 'port ' + addr.port; | ||||||
|   debug('Listening on ' + bind); |   debug('Listening on ' + bind); | ||||||
|  |  | ||||||
|  |   for(let listener of app.onListen){ | ||||||
|  |     listener() | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,13 +3,27 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
| 	userModel: 'ldap', // pam, redis, ldap | 	userModel: 'ldap', // pam, redis, ldap | ||||||
| 	ldap: { | 	ldap: { | ||||||
| 		url: 'ldap://192.168.1.54:389', | 		url: 'ldap://192.168.1.55:389', | ||||||
| 		bindDN: 'cn=admin,dc=theta42,dc=com', | 		bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com', | ||||||
| 		bindPassword: '__IN SRECREST FILE__', | 		bindPassword: '__IN SRECREST FILE__', | ||||||
| 		userBase: 'ou=people,dc=theta42,dc=com', | 		userBase: 'ou=people,dc=theta42,dc=com', | ||||||
| 		groupBase: 'ou=groups,dc=theta42,dc=com',		 | 		groupBase: 'ou=groups,dc=theta42,dc=com',		 | ||||||
| 		userFilter: '(objectClass=posixAccount)', | 		userFilter: '(objectClass=posixAccount)', | ||||||
| 		userNameAttribute: 'uid' | 		userNameAttribute: 'uid' | ||||||
| 	}, | 	}, | ||||||
| 	SENDGRID_API_KEy: '__IN SRECREST FILE__', | 	p2p: { | ||||||
|  | 		listenPort: 7575 | ||||||
|  | 	}, | ||||||
|  | 	proxmox: { | ||||||
|  | 		host: "__IN SRECREST FILE__",//this can be an ip or FQDN | ||||||
|  | 		authInfo: { | ||||||
|  | 			username: "__IN SRECREST FILE__",//this must include the username@realm | ||||||
|  | 			apiToken: "__IN SRECREST FILE__"//In the future, i would like this to be encrypted | ||||||
|  | 		},  | ||||||
|  | 	}, | ||||||
|  | 	vpnSite: { | ||||||
|  | 		id: "__IN SRECREST FILE__", | ||||||
|  | 		name: "__IN SRECREST FILE__", | ||||||
|  | 		admin: "__IN SRECREST FILE__" | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -1,32 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| const sgMail = require('@sendgrid/mail'); |  | ||||||
| const mustache = require('mustache'); |  | ||||||
| const conf = require('../app').conf; |  | ||||||
|  |  | ||||||
| sgMail.setApiKey(conf.SENDGRID_API_KEY); |  | ||||||
|  |  | ||||||
| var Mail = {}; |  | ||||||
|  |  | ||||||
| Mail.send = async function(to, subject, message, from){ |  | ||||||
| 	await sgMail.send({ |  | ||||||
| 		to: to, |  | ||||||
| 		from: from || 'Theta 42 Accounts <accounts@no-reply.theta42.com>', |  | ||||||
| 		subject: subject, |  | ||||||
| 		text: message, |  | ||||||
| 		html: message, |  | ||||||
| 	}); |  | ||||||
| }; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| Mail.sendTemplate = async function(to, template, context, from){ |  | ||||||
| 	template = require(`../views/email_templates/${template}`); |  | ||||||
| 	await Mail.send( |  | ||||||
| 		to, |  | ||||||
| 		mustache.render(template.subject, context), |  | ||||||
| 		mustache.render(template.message, context), |  | ||||||
| 		from || (template.from && mustache.render(template.message, context)) |  | ||||||
| 	) |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| module.exports = {Mail}; |  | ||||||
| @ -27,61 +27,6 @@ async function getGroups(client, member){ | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| var Group = {}; | var Group = {}; | ||||||
|  |  | ||||||
| Group.list = async function(member){ | Group.list = async function(member){ | ||||||
| @ -151,63 +96,4 @@ Group.get = async function(data){ | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| 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.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}; | module.exports = {Group}; | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ | |||||||
| const { Client, Attribute, Change } = require('ldapts'); | const { Client, Attribute, Change } = require('ldapts'); | ||||||
| const crypto = require('crypto'); | const crypto = require('crypto'); | ||||||
|  |  | ||||||
| const {Mail} = require('./email'); |  | ||||||
| const {Token, InviteToken, PasswordResetToken} = require('./token'); | const {Token, InviteToken, PasswordResetToken} = require('./token'); | ||||||
| const conf = require('../app').conf.ldap; | const conf = require('../app').conf.ldap; | ||||||
|  |  | ||||||
| @ -11,106 +10,6 @@ const client = new Client({ | |||||||
|   url: conf.url, |   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}`; |  | ||||||
| 	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){ | const user_parse = function(data){ | ||||||
| 	if(data[conf.userNameAttribute]){ | 	if(data[conf.userNameAttribute]){ | ||||||
| 		data.username = data[conf.userNameAttribute] | 		data.username = data[conf.userNameAttribute] | ||||||
| @ -225,195 +124,6 @@ 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){ | User.login = async function(data){ | ||||||
| 	try{ | 	try{ | ||||||
| 		let user = await this.get(data.uid); | 		let user = await this.get(data.uid); | ||||||
|  | |||||||
							
								
								
									
										1214
									
								
								nodejs/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1214
									
								
								nodejs/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -12,16 +12,16 @@ | |||||||
|     "start": "node ./bin/www" |     "start": "node ./bin/www" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@sendgrid/mail": "^7.1.0", |  | ||||||
|     "ejs": "^3.0.1", |     "ejs": "^3.0.1", | ||||||
|     "express": "~4.16.1", |     "express": "~4.16.1", | ||||||
|     "extend": "^3.0.2", |     "extend": "^3.0.2", | ||||||
|     "ldapts": "^2.2.1", |     "ldapts": "^2.2.1", | ||||||
|     "moment": "^2.25.3", |     "moment": "^2.25.3", | ||||||
|     "mustache": "^4.0.1", |     "mustache": "^4.0.1", | ||||||
|     "nodemon": "^2.0.4", |     "node-fetch": "^2.6.0", | ||||||
|  |     "p2psub": "^0.1.6", | ||||||
|     "redis": "^2.8.0", |     "redis": "^2.8.0", | ||||||
|     "smtpc": "^0.1.2" |     "socket.io": "^2.3.0" | ||||||
|   }, |   }, | ||||||
|   "license": "MIT", |   "license": "MIT", | ||||||
|   "repository": { |   "repository": { | ||||||
|  | |||||||
| @ -1,5 +1,68 @@ | |||||||
| var app = {}; | var app = {}; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | app.pubsub = (function(){ | ||||||
|  | 	app.topics = {}; | ||||||
|  |  | ||||||
|  | 	app.subscribe = function(topic, listener) { | ||||||
|  | 		if(topic instanceof RegExp){ | ||||||
|  | 			listener.match = topic; | ||||||
|  | 			topic = "__REGEX__"; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		// create the topic if not yet created | ||||||
|  | 		if(!app.topics[topic]) app.topics[topic] = []; | ||||||
|  |  | ||||||
|  | 		// add the listener | ||||||
|  | 		app.topics[topic].push(listener); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	app.matchTopics = function(topic){ | ||||||
|  | 		topic = topic || ''; | ||||||
|  | 		var matches = [... app.topics[topic] ? app.topics[topic] : []]; | ||||||
|  |  | ||||||
|  | 		if(!app.topics['__REGEX__']) return matches; | ||||||
|  |  | ||||||
|  | 		for(var listener of app.topics['__REGEX__']){ | ||||||
|  | 			if(topic.match(listener.match)) matches.push(listener); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return matches; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	app.publish = function(topic, data) { | ||||||
|  |  | ||||||
|  | 		// send the event to all listeners | ||||||
|  | 		app.matchTopics(topic).forEach(function(listener) { | ||||||
|  | 			setTimeout(function(data, topic){ | ||||||
|  | 					listener(data || {}, topic); | ||||||
|  | 				}, 0, data, topic); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return this; | ||||||
|  | })(app); | ||||||
|  |  | ||||||
|  | app.io = (function(app){ | ||||||
|  | 	var socket = io(); | ||||||
|  | 	// socket.emit('chat message', $('#m').val()); | ||||||
|  | 	socket.on('P2PSub', function(msg){ | ||||||
|  | 		msg.data.__noSocket	= true; | ||||||
|  | 		app.publish(msg.topic, msg.data); | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	app.subscribe(/./g, function(data, topic){ | ||||||
|  | 	  console.log('local_pubs', data, topic) | ||||||
|  | 	  if(data.__noSocket) return; | ||||||
|  | 	  // console.log('local_pubs 2', data, topic) | ||||||
|  |  | ||||||
|  | 	  socket.emit('P2PSub', { topic, data }) | ||||||
|  | 	}); | ||||||
|  |  | ||||||
|  | 	return io; | ||||||
|  |  | ||||||
|  | })(app); | ||||||
|  |  | ||||||
| app.api = (function(app){ | app.api = (function(app){ | ||||||
| 	var baseURL = '/api/' | 	var baseURL = '/api/' | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,9 +1,7 @@ | |||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| const router = require('express').Router(); | const router = require('express').Router(); | ||||||
| const {User} = require('../models/user'); |  | ||||||
| const {Auth, AuthToken} = require('../models/auth'); | const {Auth, AuthToken} = require('../models/auth'); | ||||||
| const {PasswordResetToken} = require('../models/token'); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| router.post('/login', async function(req, res, next){ | router.post('/login', async function(req, res, next){ | ||||||
| @ -31,86 +29,4 @@ 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) { |  | ||||||
| 	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, |  | ||||||
| 			token: token.token |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| 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; | 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!' |  | ||||||
| // 		}); |  | ||||||
| // 	} |  | ||||||
| 	 |  | ||||||
| // }); |  | ||||||
| @ -1,82 +0,0 @@ | |||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| const router = require('express').Router(); |  | ||||||
| const {User} = require('../models/user_ldap'); |  | ||||||
| const {Group} = require('../models/group_ldap');  |  | ||||||
|  |  | ||||||
| router.get('/', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		let member = req.query.member ? await User.get(req.query.member) : {} |  | ||||||
|  |  | ||||||
| 		console.log('member', 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{ |  | ||||||
| 		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('/:name/:uid', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		var group = await Group.get(req.params.name); |  | ||||||
| 		var user = await User.get(req.params.uid); |  | ||||||
| 		return res.json({ |  | ||||||
| 			results: group.addMember(user), |  | ||||||
| 			message: `Added user ${req.params.uid} to ${req.params.name} group.` |  | ||||||
| 		}); |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.delete('/:name/:uid', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		var group = await Group.get(req.params.name); |  | ||||||
| 		var user = await User.get(req.params.uid); |  | ||||||
| 		return res.json({ |  | ||||||
| 			results: group.removeMember(user), |  | ||||||
| 			message: `Removed user ${req.params.uid} from ${req.params.name} group.` |  | ||||||
| 		}); |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.delete('/:name', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		var group = await Group.get(req.params.name); |  | ||||||
| 		return res.json({ |  | ||||||
| 			removed: await group.remove(), |  | ||||||
| 			results: group, |  | ||||||
| 			message: `Group ${req.params.name} Deleted` |  | ||||||
| 		}); |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| module.exports = router; |  | ||||||
| @ -12,64 +12,21 @@ router.get('/', async function(req, res, next) { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| /* GET home page. */ | /* GET home page. */ | ||||||
| router.get('/users', function(req, res, next) { |  | ||||||
|   res.render('users', { title: 'Express' }); | router.get('/topics', function(req, res, next) { | ||||||
|  |   res.render('topics', { title: 'Express' }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.get('/users/:uid', function(req, res, next) { | router.get('/chat', function(req, res, next) { | ||||||
|   res.render('home', { title: 'Express' }); |   res.render('chat', { title: 'Express' }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.get('/groups', function(req, res, next) { |  | ||||||
|   res.render('groups', { title: 'Express' }); |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|              |  | ||||||
| router.get('/login/resetpassword/:token', async function(req, res, next){ |  | ||||||
| 	let token = await PasswordResetToken.get(req.params.token); |  | ||||||
|  |  | ||||||
| 	if(token.is_valid && 86400000+Number(token.created_on) > (new Date).getTime()){ |  | ||||||
| 		res.render('reset_password', {token:token}); |  | ||||||
| 	}else{ |  | ||||||
| 		next({message: 'token not found', status: 404}); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.get('/login/invite/:token/:mailToken', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		 |  | ||||||
| 		let token = await InviteToken.get(req.params.token); |  | ||||||
|  |  | ||||||
| 		if(token.is_valid && token.mail !== '__NONE__' && token.mail_token === req.params.mailToken){ |  | ||||||
| 			token.created_on = moment(token.created_on, 'x').fromNow(); |  | ||||||
|   			res.render('invite', { title: 'Express', invite: token }); |  | ||||||
| 		}else{ |  | ||||||
| 			next({message: 'token not found', status: 404}); |  | ||||||
| 		} |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.get('/login/invite/:token', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		let token = await InviteToken.get(req.params.token); |  | ||||||
| 		token.created_on = moment(token.created_on, 'x').fromNow(); |  | ||||||
|  |  | ||||||
| 		if(token.is_valid){ |  | ||||||
|   			res.render('invite_email', { title: 'Express', invite: token }); |  | ||||||
| 		}else{ |  | ||||||
| 			next({message: 'token not found', status: 404}); |  | ||||||
| 		} |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
|  |  | ||||||
| /* GET home page. */ |  | ||||||
| router.get('/login', function(req, res, next) { | router.get('/login', function(req, res, next) { | ||||||
|   res.render('login', {redirect: req.query.redirect}); |   res.render('login', {redirect: req.query.redirect}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | router.get('/proxmox', function(req, res, next) { | ||||||
|  |   res.render('proxmox', {}); | ||||||
|  | }); | ||||||
|  |  | ||||||
| module.exports = router; | module.exports = router; | ||||||
|  | |||||||
							
								
								
									
										83
									
								
								nodejs/routes/user.js
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							
							
						
						
									
										83
									
								
								nodejs/routes/user.js
									
									
									
									
									
										
										
										Executable file → Normal file
									
								
							| @ -13,42 +13,6 @@ router.get('/', async function(req, res, next){ | |||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  |  | ||||||
| router.post('/', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		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 = await User.get(req.params.uid); |  | ||||||
|  |  | ||||||
| 		return res.json({uid: req.params.uid, results: await user.remove()}) |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.put('/:uid', async function(req, res, next){ |  | ||||||
| 	try{ |  | ||||||
| 		let user = await User.get(req.params.uid); |  | ||||||
|  |  | ||||||
| 		// console.log('update user', 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){ | router.get('/me', async function(req, res, next){ | ||||||
| 	try{ | 	try{ | ||||||
|  |  | ||||||
| @ -58,53 +22,6 @@ router.get('/me', async function(req, res, next){ | |||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  |  | ||||||
| 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 = await User.get(req.params.uid); |  | ||||||
| 		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 |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 	}catch(error){ |  | ||||||
| 		next(error); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| }); |  | ||||||
|  |  | ||||||
| router.get('/:uid', async function(req, res, next){ | router.get('/:uid', async function(req, res, next){ | ||||||
| 	try{ | 	try{ | ||||||
| 		return res.json({ | 		return res.json({ | ||||||
|  | |||||||
							
								
								
									
										214
									
								
								nodejs/utils/pvejs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								nodejs/utils/pvejs.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,214 @@ | |||||||
|  | 'use strict';  | ||||||
|  |  | ||||||
|  | const fetch = require('node-fetch'); | ||||||
|  | const { URLSearchParams } = require('url'); | ||||||
|  |  | ||||||
|  | var apiProto = {}; | ||||||
|  |  | ||||||
|  | apiProto.__getApiDoc = async function(host){ | ||||||
|  |    try{ | ||||||
|  |       let setupRes = await fetch(host || this.host+'/pve-docs/api-viewer/apidoc.js', { method: 'GET'}); | ||||||
|  |       let text = await setupRes.text() | ||||||
|  |  | ||||||
|  |       text = text.split('// avoid errors when running without development tools')[0]; | ||||||
|  |  | ||||||
|  |       return eval(text += '; pveapi;'); | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | apiProto.__makeMap = async function(APItree){ | ||||||
|  |    APItree = APItree || await this.__getApiDoc(); | ||||||
|  |    let apiMap = {}; | ||||||
|  |  | ||||||
|  |    function __rec(obj){ | ||||||
|  |       for(let item of obj){ | ||||||
|  |          if(item.children){ | ||||||
|  |             __rec(item.children); | ||||||
|  |          } | ||||||
|  |          if(item.path){ | ||||||
|  |             delete item.children; | ||||||
|  |             apiMap[item.path] = item; | ||||||
|  |             item.methods = item.info; | ||||||
|  |             delete item.info; | ||||||
|  |          } | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |  | ||||||
|  |    __rec(APItree); | ||||||
|  |  | ||||||
|  |    return apiMap | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | //these helper functions are to build the fetch call | ||||||
|  | apiProto.__buildHeaders = function(){ | ||||||
|  |    let headers = {}; | ||||||
|  |    if(this.authInfo.apiToken){ | ||||||
|  |       headers = { | ||||||
|  |          "Authorization": `PVEAPIToken=${this.authInfo.username}=${this.authInfo.apiToken}` | ||||||
|  |       } | ||||||
|  |    }else{    | ||||||
|  |       headers = { | ||||||
|  |          'Cookie': 'PVEAuthCookie='+this.user.ticket, | ||||||
|  |          'CSRFPreventionToken': this.user.CSRFPreventionToken | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |  | ||||||
|  |    return headers; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // this function takes an iterable of 'key':'value' pairs | ||||||
|  | apiProto.__buildBody = function(props, data){ | ||||||
|  |    const params = new URLSearchParams(); | ||||||
|  |    for (let key in props){ | ||||||
|  |       if(data[key]){ | ||||||
|  |          params.append(key, data[key]) | ||||||
|  |          continue; | ||||||
|  |       } | ||||||
|  |       if(props[key].optional !==1){ | ||||||
|  |          // console.log(props[key]) | ||||||
|  |          throw new Error('MissingKey'); | ||||||
|  |       } | ||||||
|  |    } | ||||||
|  |    return params; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.__getEndPoint = function(args){ | ||||||
|  |    let endpoint = this.apiMap[args.path] | ||||||
|  |    if(!args || !endpoint) throw new Error('endpointNotFound'); | ||||||
|  |    if(!endpoint.methods[args.method]) throw new Error('methodNotFound'); | ||||||
|  |  | ||||||
|  |    return endpoint; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.__fetch = async function(method, path, params, data){ | ||||||
|  |    try{ | ||||||
|  |       let endpoint = this.__getEndPoint({path, method}); | ||||||
|  |  | ||||||
|  |       let fetchOptions = { | ||||||
|  |          method: method, | ||||||
|  |          headers: this.__buildHeaders(), | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if(['PUT', 'POST'].includes(method)){ | ||||||
|  |          fetchOptions.body = this.__buildBody( | ||||||
|  |             endpoint.methods[method].parameters.properties, | ||||||
|  |             data | ||||||
|  |          ) | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       let HTTPres = await fetch(this.BASEURL+path, fetchOptions); | ||||||
|  |  | ||||||
|  |       // console.log(HTTPres) | ||||||
|  |  | ||||||
|  |       return { | ||||||
|  |          statusCode: HTTPres.status, | ||||||
|  |          statusText: HTTPres.statusText, | ||||||
|  |          json: (await HTTPres.json()).data, | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |  | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.GET = async function(args){ | ||||||
|  |    try{ | ||||||
|  |  | ||||||
|  |       return await this.__fetch( | ||||||
|  |          'GET', | ||||||
|  |          args.path, | ||||||
|  |          args.parama || {}, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.DELETE = async function(args){ | ||||||
|  |    try{ | ||||||
|  |  | ||||||
|  |       return await this.__fetch( | ||||||
|  |          'DELETE', | ||||||
|  |          args.path, | ||||||
|  |          args.parama || {}, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.POST = async function(args){ | ||||||
|  |    try{ | ||||||
|  |  | ||||||
|  |       return await this.__fetch( | ||||||
|  |          'POST', | ||||||
|  |          args.path, | ||||||
|  |          args.parama || {}, | ||||||
|  |          args.data || {} | ||||||
|  |       ); | ||||||
|  |        | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.PUT = async function(args){ | ||||||
|  |    try{ | ||||||
|  |  | ||||||
|  |       return await this.__fetch( | ||||||
|  |          'PUT', | ||||||
|  |          args.path, | ||||||
|  |          args.parama || {}, | ||||||
|  |          args.data || {} | ||||||
|  |       ); | ||||||
|  |        | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.auth = async function(authInfo){ | ||||||
|  |    try{ | ||||||
|  |       this.user = (await this.POST({ | ||||||
|  |          path: '/access/ticket', | ||||||
|  |          data: authInfo || this.authInfo | ||||||
|  |       })).json | ||||||
|  |  | ||||||
|  |       return authInfo; | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |       throw error; | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | apiProto.__init = async function(conf){ | ||||||
|  |    try{ | ||||||
|  |       this.user = {}; | ||||||
|  |       this.BASEURL = conf.host+'/api2/json'; | ||||||
|  |       this.apiMap = await this.__makeMap(); | ||||||
|  |  | ||||||
|  |       if(!this.authInfo.apiToken) await this.auth();  | ||||||
|  |  | ||||||
|  |       return this; | ||||||
|  |  | ||||||
|  |    }catch(error){ | ||||||
|  |       throw(error); | ||||||
|  |    } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | async function API(conf){ | ||||||
|  |    let instance = Object.create(apiProto); | ||||||
|  |    Object.assign(instance, conf); | ||||||
|  |  | ||||||
|  |    return await instance.__init(conf); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | module.exports = API; | ||||||
|  |  | ||||||
							
								
								
									
										79
									
								
								nodejs/views/chat.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								nodejs/views/chat.ejs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | |||||||
|  | <%- include('top') %> | ||||||
|  | <script id="rowTemplate" type="text/html"> | ||||||
|  | 	<p> | ||||||
|  | 		<b>{{ uid }}:</b> {{message}} | ||||||
|  | 	</p> | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <script type="text/javascript"> | ||||||
|  |  | ||||||
|  | 	var publishTopic = function(btn){ | ||||||
|  | 		event.preventDefault(); // avoid to execute the actual submit of the form. | ||||||
|  | 		var $form = $(btn).closest( '[action]' ); // gets the 'form' parent | ||||||
|  | 		var formData = $form.find( '[name]' ).serializeObject();  | ||||||
|  |  | ||||||
|  | 		if( !$form.validate()) { | ||||||
|  | 			app.util.actionMessage('Please fix the form errors.', $form, 'danger') | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		console.log('formData', formData) | ||||||
|  |  | ||||||
|  | 		app.publish('p2p-chat', { | ||||||
|  | 			uid: app.auth.user.uid, | ||||||
|  | 			message: formData.message | ||||||
|  | 		}) | ||||||
|  | 		$form.trigger("reset"); | ||||||
|  |  | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	$(document).ready(function(){ | ||||||
|  | 		app.subscribe("p2p-chat", function(data, topic){ | ||||||
|  | 			var rowTemplate = $('#rowTemplate').html(); | ||||||
|  | 			var $target = $('#tableAJAX'); | ||||||
|  |  | ||||||
|  | 			user_row = Mustache.render(rowTemplate, data); | ||||||
|  | 			$target.append(user_row); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 	}); | ||||||
|  | </script> | ||||||
|  | <div class="row" style="display:none"> | ||||||
|  | 	<div class="col-md-4"> | ||||||
|  | 		<div class="card shadow-lg"> | ||||||
|  | 			<div class="card-header"> | ||||||
|  | 				<i class="fas fa-layer-plus"></i> | ||||||
|  | 				Chat | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-header actionMessage" style="display:none"></div> | ||||||
|  | 			<div class="card-body"> | ||||||
|  | 				<form action='__internal__' onsubmit="publishTopic(this)"> | ||||||
|  |  | ||||||
|  | 					<div class="form-group"> | ||||||
|  | 						<label class="control-label">message</label> | ||||||
|  | 						<textarea class="form-control shadow" name="message" placeholder="{...}" validate=":1"></textarea> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
|  | 					<button type="submit" class="btn btn-outline-dark">Send</button> | ||||||
|  | 				</form> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="col-md-8"> | ||||||
|  | 		<div class="card shadow-lg"> | ||||||
|  | 			<div class="card-header"> | ||||||
|  | 				<i class="fad fa-users-class"></i> | ||||||
|  | 				Incoming Chats | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-header actionMessage" style="display:none"> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-body"> | ||||||
|  | 				<div class="" id="tableAJAX"> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | <%- include('bottom') %> | ||||||
| @ -1,25 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
| 	subject: 'Password reset for Theta 42 account', |  | ||||||
| 	message: ` |  | ||||||
| <h2> Theta 42 account</h2> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	Hello {{ user.givenName }}, |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	You have asked to reset the password for user name <b>{{ user.uid }}</b> . Please |  | ||||||
| 	click the link below to complete this request. If this was done in errror, |  | ||||||
| 	please ignore this email. |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	{{ link }} |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| </p> |  | ||||||
| 	Thank you,<br /> |  | ||||||
| 	Theta 42 |  | ||||||
| </p> |  | ||||||
| ` |  | ||||||
| }; |  | ||||||
| @ -1,24 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
| 	subject: 'Validate email for Theta 42 account', |  | ||||||
| 	message: ` |  | ||||||
| <h2> Theta 42 account</h2> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	Welcome, |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	We need to verify the provided email address in order to continue. Please |  | ||||||
| 	follow the link below to verify this email address: |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	{{ link }} |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| </p> |  | ||||||
| 	Thank you,<br /> |  | ||||||
| 	Theta 42 |  | ||||||
| </p> |  | ||||||
| ` |  | ||||||
| }; |  | ||||||
| @ -1,34 +0,0 @@ | |||||||
| module.exports = { |  | ||||||
| 	subject: 'Welcome to Theta 42!', |  | ||||||
| 	message: ` |  | ||||||
| <p> |  | ||||||
| 	Welcome {{user.givenName}}, |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	Your new Theta 42 Single sign-on account is ready to use. Here is some |  | ||||||
| 	information to get you started. |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	Your username is <b>{{user.uid}}</b> |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	You can manage your account at https://sso.theta42.com |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	You account is ready to be used now, test it by SSHing into the Theta 42 |  | ||||||
| 	jump host \`ssh {{user.uid}}@718it.biz\` |  | ||||||
| </p> |  | ||||||
|  |  | ||||||
| <p> |  | ||||||
| 	The SSO service is still in beta, so please report any bugs you may find! |  | ||||||
| 	You will be notified of new features and services as they become available. |  | ||||||
| </p> |  | ||||||
| 	Thank you,<br /> |  | ||||||
| 	Theta 42 |  | ||||||
| </p> |  | ||||||
| ` |  | ||||||
| }; |  | ||||||
| @ -1,134 +0,0 @@ | |||||||
| <%- include('top') %> |  | ||||||
| <script id="rowTemplate" type="text/html"> |  | ||||||
| 	<p> |  | ||||||
| 	<div class="card shadow"> |  | ||||||
| 		<div class="card-header"> |  | ||||||
| 			<i class="fad fa-users-class"></i> |  | ||||||
| 			Group: {{ cn }} |  | ||||||
| 		</div> |  | ||||||
| 		<div class="card-body"> |  | ||||||
| 			<p> |  | ||||||
| 				{{ description }} |  | ||||||
| 			</p> |  | ||||||
| 			<p> |  | ||||||
| 				<ul class="list-group"> |  | ||||||
| 				{{ #member }} |  | ||||||
| 					<li class="list-group-item shadow"> |  | ||||||
| 						<i class="fad fa-user"></i> {{ uid }} |  | ||||||
| 						<button type="button" action="group/{{groupCN}}/{{uid}}" method="delete" onclick="formAJAX(this)" evalAJAX="tableAJAX(data.message)" class="btn btn-sm btn-danger float-right"> |  | ||||||
| 							<i class="fad fa-user-slash"></i> |  | ||||||
| 						</button> |  | ||||||
| 					</li> |  | ||||||
| 				{{ /member }} |  | ||||||
| 				</ul> |  | ||||||
| 			</p> |  | ||||||
|  |  | ||||||
| 			<button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> |  | ||||||
| 				<i class="fad fa-user-plus"></i> |  | ||||||
| 			</button> |  | ||||||
| 			<div class="dropdown-menu shadow-lg" aria-labelledby="dropdownMenuButton"> |  | ||||||
| 			{{ #toAdd }}{{#.}} |  | ||||||
| 				<a class="dropdown-item" action="group/{{groupCN}}/{{uid}}" method="put" onclick="formAJAX(this)" evalAJAX="tableAJAX(data.message)"> |  | ||||||
| 					<i class="fad fa-user"></i> {{uid}} |  | ||||||
| 				</a> |  | ||||||
| 			{{/.}}{{ /toAdd }} |  | ||||||
| 			 |  | ||||||
| 			</div> |  | ||||||
|  |  | ||||||
| 			<button type="button" onclick="app.group.remove({cn: '{{cn}}'}, function(){tableAJAX('Group {{cn}} deleted.')})" class="btn btn-danger float-right"> |  | ||||||
| 				<i class="fad fa-trash"></i> |  | ||||||
| 			</button> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| 	</p> |  | ||||||
| </script> |  | ||||||
| <script type="text/javascript"> |  | ||||||
|  |  | ||||||
| 	var userlist; |  | ||||||
|  |  | ||||||
| 	function getUserList(callback){ |  | ||||||
| 		app.user.list(function(error, data){ |  | ||||||
| 			userlist = data.results; |  | ||||||
| 			callback() |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	function tableAJAX(actionMessage){ |  | ||||||
| 		var rowTemplate = $('#rowTemplate').html(); |  | ||||||
| 		var $target = $('#tableAJAX'); |  | ||||||
|  |  | ||||||
| 		$target.html('').hide(); |  | ||||||
| 		app.util.actionMessage('Refreshing user list...', $target); |  | ||||||
|  |  | ||||||
| 		app.group.list(function(error, data){ |  | ||||||
| 			$.each( data.results, function(key, value) { |  | ||||||
|  |  | ||||||
| 				// console.log(value.member) |  | ||||||
|  |  | ||||||
| 				value.toAdd = userlist.map(function(user){ |  | ||||||
| 					if(!value.member.includes(user.dn)) return user; |  | ||||||
| 				}) |  | ||||||
| 				value.member = value.member.map(function(user){ |  | ||||||
| 					return { |  | ||||||
| 						dn: user, |  | ||||||
| 						uid: user.match(/cn=[a-zA-Z0-9\_\-\@\.]+/)[0].replace('cn=', '') |  | ||||||
| 					} |  | ||||||
| 				}) |  | ||||||
| 				value.groupCN = value.cn; |  | ||||||
| 				user_row = Mustache.render(rowTemplate, value); |  | ||||||
| 				$target.append(user_row); |  | ||||||
| 			}); |  | ||||||
|  |  | ||||||
| 			$target.fadeIn('slow'); |  | ||||||
|  |  | ||||||
| 			app.util.actionMessage(actionMessage || '', $target, 'info'); |  | ||||||
|  |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	$(document).ready(function(){ |  | ||||||
| 		getUserList(tableAJAX); |  | ||||||
| 	}); |  | ||||||
| </script> |  | ||||||
| <div class="row" style="display:none"> |  | ||||||
| 	<div class="col-md-4"> |  | ||||||
| 		<div class="card shadow-lg"> |  | ||||||
| 			<div class="card-header"> |  | ||||||
| 				<i class="fas fa-layer-plus"></i> |  | ||||||
| 				Add new group |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header actionMessage" style="display:none"></div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<form action="group/" method="post" onsubmit="formAJAX(this)" evalAJAX="tableAJAX('')"> |  | ||||||
| 					<div class="form-group"> |  | ||||||
| 						<label class="control-label">Name</label> |  | ||||||
| 						<input type="text" class="form-control shadow" name="name" placeholder="app_gitea_admin" validate=":3" /> |  | ||||||
| 					</div> |  | ||||||
|  |  | ||||||
| 					<div class="form-group"> |  | ||||||
| 						<label class="control-label">Description</label> |  | ||||||
| 						<textarea class="form-control shadow" name="description" placeholder="Admin group for gitea app" validate=":3"></textarea> |  | ||||||
| 					</div> |  | ||||||
|  |  | ||||||
| 					<button type="submit" class="btn btn-outline-dark">Add</button> |  | ||||||
| 				</form> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| 	<div class="col-md-8"> |  | ||||||
| 		<div class="card shadow-lg"> |  | ||||||
| 			<div class="card-header"> |  | ||||||
| 				<i class="fad fa-users-class"></i> |  | ||||||
| 				Group list |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header actionMessage" style="display:none"> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<div class="" id="tableAJAX"> |  | ||||||
| 				</div> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| <%- include('bottom') %> |  | ||||||
| @ -1,39 +0,0 @@ | |||||||
| <%- include('top') %> |  | ||||||
| <script type="text/javascript"> |  | ||||||
| 	function tableAJAX(message){ |  | ||||||
| 		app.util.actionMessage(message); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	$(document).ready(function(){ |  | ||||||
| 		$('form').attr('action', 'auth/invite/<%= invite.token %>/<%= invite.mail_token %>').attr('evalAJAX', 'location.replace("/login");') |  | ||||||
| 		$('[name="mail"').val('<%= invite.mail %>').prop("disabled", true); |  | ||||||
| 	}); |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style type="text/css"> |  | ||||||
| 	div.form-group:hover { |  | ||||||
| 	    -ms-transform: scale(1.02); |  | ||||||
| 	    -webkit-transform: scale(1.02); |  | ||||||
| 	    transform: scale(1.02); |  | ||||||
| 	} |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
| <div class="row" style="display:none"> |  | ||||||
| 		<div class="col-md-12"> |  | ||||||
| 		<div class="card shadow-lg"> |  | ||||||
| 			<div class="card-header"> |  | ||||||
| 				Add new user |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header actionMessage" style="display:none"> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<p> |  | ||||||
| 					Invited By: <b><%= invite.created_by %></b>, <%= invite.created_on %> |  | ||||||
| 				</p> |  | ||||||
| 				<%- include('user_form') %> |  | ||||||
|  |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| <%- include('bottom') %> |  | ||||||
| @ -1,59 +0,0 @@ | |||||||
| <%- include('top') %> |  | ||||||
| <script type="text/javascript"> |  | ||||||
|  |  | ||||||
|  |  | ||||||
| 	var emailSent = function(){ |  | ||||||
| 		$('#email_card .card-body').html("<h1>Thank you!</h1><p>Check your mail</p>") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	$(document).ready(function(){ |  | ||||||
| 	}); |  | ||||||
| </script> |  | ||||||
|  |  | ||||||
| <style type="text/css"> |  | ||||||
| 	div.form-group:hover { |  | ||||||
| 		-ms-transform: scale(1.02); |  | ||||||
| 		-webkit-transform: scale(1.02); |  | ||||||
| 		transform: scale(1.02); |  | ||||||
| 	} |  | ||||||
| </style> |  | ||||||
|  |  | ||||||
| <div class="row"> |  | ||||||
| 	<div id="email_card" class="card-deck"> |  | ||||||
| 		<div class="shadow-lg card mb-3"> |  | ||||||
| 			<div class="card-header"> |  | ||||||
| 				Validate Email |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header actionMessage" style="display:none"> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<p> |  | ||||||
| 					Invited By: <b><%= invite.created_by %></b>, <%= invite.created_on %>. |  | ||||||
| 				</p> |  | ||||||
| 				<p> |  | ||||||
| 					Please enter a valid email address. A link will be sent to |  | ||||||
| 					the supplied address to complete the registration process. |  | ||||||
| 				</p> |  | ||||||
| 				<p> |  | ||||||
| 					The supplied email will also be used as the linked email for |  | ||||||
| 					the new user. |  | ||||||
| 				</p> |  | ||||||
| 				<form action="auth/invite/<%= invite.token %>" onsubmit="formAJAX(this)" evalAJAX="emailSent()"> |  | ||||||
| 					<div class="form-group"> |  | ||||||
| 						<label class="control-label">Email</label> |  | ||||||
| 						<div class="input-group mb-3"> |  | ||||||
| 						  <div class="input-group-prepend"> |  | ||||||
| 							<span class="input-group-text" ><i class="fad fa-at"></i></span> |  | ||||||
| 						  </div> |  | ||||||
| 							<input type="email" name="mail" class="form-control" placeholder="jsmith@gmail.com" validate="email:3" /> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
|  |  | ||||||
| 					<button type="submit" class="btn btn-outline-dark"><i class="fad fa-paper-plane"></i> Send It!</button> |  | ||||||
| 				</form> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> |  | ||||||
| 	</div> |  | ||||||
| </div> |  | ||||||
| <%- include('bottom') %> |  | ||||||
| @ -3,7 +3,7 @@ | |||||||
|  |  | ||||||
| 	app.auth.isLoggedIn(function(error, isLoggedIn){ | 	app.auth.isLoggedIn(function(error, isLoggedIn){ | ||||||
| 		if(isLoggedIn){ | 		if(isLoggedIn){ | ||||||
| 			window.location.href = app.util.getUrlParameter('redirect') || '/'; | 			window.location.href = app.util.getUrlParameter('redirect') || '/topics'; | ||||||
| 		} | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| @ -48,53 +48,6 @@ | |||||||
| 				</form> | 				</form> | ||||||
| 			</div> | 			</div> | ||||||
| 		</div> | 		</div> | ||||||
| 		<div class="shadow-lg card border-danger mb-3"> |  | ||||||
| 			<div class="card-header shadow"> |  | ||||||
| 				Social Login |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header shadow actionMessage" style="display:none"> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<h3>Coming soon!</h3> |  | ||||||
| 				<p> |  | ||||||
| 					<ul class="list-group"> |  | ||||||
| 						<li class="list-group-item"><i class="fab fa-google"></i> Login with google OATH</li> |  | ||||||
| 						<li class="list-group-item"><i class="fab fa-github"></i> Login with github OATH</li> |  | ||||||
| 						<li class="list-group-item"><i class="fab fa-facebook"></i> Login with facebook OATH</li> |  | ||||||
| 					</ul> |  | ||||||
| 				</p> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 		<div class="shadow-lg card mb-3"> |  | ||||||
| 			<div class="card-header shadow"> |  | ||||||
| 				Password Reset |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-header shadow actionMessage" style="display:none"> |  | ||||||
| 			</div> |  | ||||||
| 			<div class="card-body"> |  | ||||||
| 				<p> |  | ||||||
| 					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! |  | ||||||
| 				</p> |  | ||||||
| 				<form action="auth/resetpassword" onsubmit="formAJAX(this)"> |  | ||||||
| 					<input type="hidden" name="redirect" value="<%= redirect %>"> |  | ||||||
|  |  | ||||||
| 					<div class="form-group"> |  | ||||||
| 						<label class="control-label">Email</label> |  | ||||||
| 						<div class="input-group mb-3 shadow"> |  | ||||||
| 						  <div class="input-group-prepend"> |  | ||||||
| 						    <span class="input-group-text" ><i class="fad fa-at"></i></span> |  | ||||||
| 						  </div> |  | ||||||
| 							<input type="email" name="mail" class="form-control" placeholder="jsmith@gmail.com" validate="email:3" /> |  | ||||||
| 						</div> |  | ||||||
| 					</div> |  | ||||||
|  |  | ||||||
| 					<button type="submit" class="btn btn-outline-dark"><i class="fad fa-question"></i> Help me!</button> |  | ||||||
| 				</form> |  | ||||||
| 			</div> |  | ||||||
| 		</div> |  | ||||||
| 	</div> | 	</div> | ||||||
| </div> | </div> | ||||||
| <%- include('bottom') %> | <%- include('bottom') %> | ||||||
|  | |||||||
							
								
								
									
										114
									
								
								nodejs/views/proxmox.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								nodejs/views/proxmox.ejs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,114 @@ | |||||||
|  | <%- include('top') %> | ||||||
|  | <script type="text/javascript"> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	var proxmoxSites = {}; | ||||||
|  |  | ||||||
|  | 	function formatBytes(a,b=2){if(0===a)return"0 Bytes";const c=0>b?0:b,d=Math.floor(Math.log(a)/Math.log(1024));return parseFloat((a/Math.pow(1024,d)).toFixed(c))+" "+["Bytes","KB","MB","GB","TB","PB","EB","ZB","YB"][d]} | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 	var parseNodeData = function(data){ | ||||||
|  | 		cluster = data.data | ||||||
|  | 		cluster.onlineCount = 0; | ||||||
|  | 		cluster.cpu = 0 | ||||||
|  | 		cluster.maxcpu = 0 | ||||||
|  | 		cluster.maxmem = 0 | ||||||
|  | 		cluster.mem = 0 | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		for(let node of data.data.node){ | ||||||
|  | 			// console.log('parse', node) | ||||||
|  | 			if(node.status === "offline") continue; | ||||||
|  | 			cluster.onlineCount++ | ||||||
|  | 			cluster.cpu += node.cpu | ||||||
|  | 			cluster.maxcpu += node.maxcpu | ||||||
|  | 			cluster.maxmem += node.maxmem | ||||||
|  | 			cluster.mem += node.mem | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		cluster.cpu = parseFloat(((cluster.cpu/cluster.onlineCount)*100).toFixed(2)); | ||||||
|  | 		cluster.maxmem = formatBytes(cluster.maxmem) | ||||||
|  | 		cluster.mem = formatBytes(cluster.mem) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		$.extend(data.data, cluster); | ||||||
|  |  | ||||||
|  | 		return data; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	$(document).ready(function(){ | ||||||
|  | 		app.subscribe('proxmox-cluster', function(data){ | ||||||
|  | 			if(!proxmoxSites[data.vpnSite.name]){ | ||||||
|  | 				let index = $.scope.proxmox.push(parseNodeData(data)); | ||||||
|  | 				proxmoxSites[data.vpnSite.name] = { | ||||||
|  | 					data:data, | ||||||
|  | 					index: index | ||||||
|  | 				} | ||||||
|  | 			}else{ | ||||||
|  |  | ||||||
|  | 				$.scope.proxmox.update(proxmoxSites[data.vpnSite.name].index, parseNodeData(data)); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <div class="row"> | ||||||
|  | 	<div class="shadow-lg card mb-3" jq-repeat="proxmox"> | ||||||
|  | 		<div class="card-header"> | ||||||
|  | 				{{ vpnSite.name }} | ||||||
|  | 		</div> | ||||||
|  | 		<div class="card-header actionMessage" style="display:none"> | ||||||
|  | 		</div> | ||||||
|  | 		<div class="card-body"> | ||||||
|  | 			Nodes online {{data.onlineCount}} of {{data.node.length}} <br /> | ||||||
|  | 			CPU: {{data.cpu}}% of {{data.maxcpu}} cores <br /> | ||||||
|  | 			Ram: {{data.mem}} of {{data.maxmem}} | ||||||
|  | 			<ul> | ||||||
|  | 			{{#data.lxc}} | ||||||
|  | 				<li>{{name}} -- {{status}}</li>  | ||||||
|  | 			{{/data.lxc}} | ||||||
|  | 			</ul> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | <!-- 	<div id="email_card" class="card-deck"> | ||||||
|  | 		<div class="shadow-lg card mb-3"> | ||||||
|  | 			<div class="card-header"> | ||||||
|  | 				Validate Email | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-header actionMessage" style="display:none"> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-body"> | ||||||
|  | 				<p> | ||||||
|  | 					Invited By: <b></b>,. | ||||||
|  | 				</p> | ||||||
|  | 				<p> | ||||||
|  | 					Please enter a valid email address. A link will be sent to | ||||||
|  | 					the supplied address to complete the registration process. | ||||||
|  | 				</p> | ||||||
|  | 				<p> | ||||||
|  | 					The supplied email will also be used as the linked email for | ||||||
|  | 					the new user. | ||||||
|  | 				</p> | ||||||
|  | 				<form action="auth/invite/" onsubmit="formAJAX(this)" evalAJAX="emailSent()"> | ||||||
|  | 					<div class="form-group"> | ||||||
|  | 						<label class="control-label">Email</label> | ||||||
|  | 						<div class="input-group mb-3"> | ||||||
|  | 						  <div class="input-group-prepend"> | ||||||
|  | 							<span class="input-group-text" ><i class="fad fa-at"></i></span> | ||||||
|  | 						  </div> | ||||||
|  | 							<input type="email" name="mail" class="form-control" placeholder="jsmith@gmail.com" validate="email:3" /> | ||||||
|  | 						</div> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
|  | 					<button type="submit" class="btn btn-outline-dark"><i class="fad fa-paper-plane"></i> Send It!</button> | ||||||
|  | 				</form> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> --> | ||||||
|  | <%- include('bottom') %> | ||||||
| @ -3,19 +3,22 @@ | |||||||
|   <head> |   <head> | ||||||
|     <meta charset="utf-8"> |     <meta charset="utf-8"> | ||||||
|     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |     <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||||
| 		<title>SSO Manager - Theta 42</title> | 		<title>VPN-p2p - Theta 42</title> | ||||||
| 		<!-- CSS are placed here --> | 		<!-- CSS are placed here --> | ||||||
| 		<!-- <link rel='stylesheet' href='/static/css/bootstrap.min.css' /> --> | 		<!-- <link rel='stylesheet' href='/static/css/bootstrap.min.css' /> --> | ||||||
| 		<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> | 		<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous"> | ||||||
| 		<link rel='stylesheet' href='/static/css/styles.css' /> | 		<link rel='stylesheet' href='/static/css/styles.css' /> | ||||||
| 		<!-- Scripts are placed here --> | 		<!-- Scripts are placed here --> | ||||||
|  | 		<script src="/socket.io/socket.io.js"></script> | ||||||
| 		<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script> | 		<script src="https://code.jquery.com/jquery-3.5.0.min.js"></script> | ||||||
|  |  | ||||||
| 		<!-- <script type="text/javascript" src='/static/js/jquery.min.js'></script> --> | 		<!-- <script type="text/javascript" src='/static/js/jquery.min.js'></script> --> | ||||||
| 		<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> | 		<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"></script> | ||||||
| 		<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> | 		<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script> | ||||||
| 		<!-- <script type="text/javascript" src='/static/js/bootstrap.min.js'></script> --> | 		<!-- <script type="text/javascript" src='/static/js/bootstrap.min.js'></script> --> | ||||||
| 		<script src="https://kit.fontawesome.com/4625ee80a2.js" crossorigin="anonymous"></script> | 		<script src="https://kit.fontawesome.com/4625ee80a2.js" crossorigin="anonymous"></script> | ||||||
| 		<script type="text/javascript" src='/static/js/mustache.min.js'></script> | 		<script type="text/javascript" src='/static/js/mustache.min.js'></script> | ||||||
|  | 		<script src="//stuff.718it.biz/jq-repeat.js"></script> | ||||||
| 		<script type="text/javascript" src="/static/js/app.js"></script> | 		<script type="text/javascript" src="/static/js/app.js"></script> | ||||||
| 		<script type="text/javascript" src="/static/js/val.js"></script> | 		<script type="text/javascript" src="/static/js/val.js"></script> | ||||||
| 		<script type="text/javascript" src="/static/js/moment.js"></script> | 		<script type="text/javascript" src="/static/js/moment.js"></script> | ||||||
| @ -28,11 +31,10 @@ | |||||||
| 	</head> | 	</head> | ||||||
| 	<body> | 	<body> | ||||||
| 		<header class="shadow d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm"> | 		<header class="shadow d-flex flex-column flex-md-row align-items-center p-3 px-md-4 mb-3 bg-white border-bottom shadow-sm"> | ||||||
| 			<h5 class="hover-effect my-0 mr-md-auto font-weight-normal">SSO Manager - Theta 42</h5> | 			<h5 class="hover-effect my-0 mr-md-auto font-weight-normal">VPN p2p - Theta 42</h5> | ||||||
| 			<nav class="my-2 my-md-0 mr-md-3"> | 			<nav class="my-2 my-md-0 mr-md-3"> | ||||||
| 				<a class="text-dark hover-effect" href="/"><i class="fad fa-tachometer-alt-fastest"></i> Home</a> | 				<a class="text-dark hover-effect" href="/topics"><i class="fad fa-tachometer-alt-fastest"></i> Topics</a> | ||||||
| 				<a class="text-dark hover-effect" href="/users"><i class="fad fa-users"></i> Users</a> | 				<a class="text-dark hover-effect" href="/chat"><i class="fad fa-tachometer-alt-fastest"></i> Chat</a> | ||||||
| 				<a class="text-dark hover-effect" href="/groups"><i class="fad fa-users-class"></i> Groups</a> |  | ||||||
| 			</nav> | 			</nav> | ||||||
| 			<a class="hover-effect btn btn-outline-primary" onclick="app.auth.logOut(e => window.location.href='/')"><i class="fas fa-sign-out"></i> Log Out</a> | 			<a class="hover-effect btn btn-outline-primary" onclick="app.auth.logOut(e => window.location.href='/')"><i class="fas fa-sign-out"></i> Log Out</a> | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										88
									
								
								nodejs/views/topics.ejs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								nodejs/views/topics.ejs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,88 @@ | |||||||
|  | <%- include('top') %> | ||||||
|  | <script id="rowTemplate" type="text/html"> | ||||||
|  | 	<p> | ||||||
|  | 	<div class="card shadow"> | ||||||
|  | 		<div class="card-header"> | ||||||
|  | 			<i class="fad fa-users-class"></i> | ||||||
|  | 			<b>Topic:</b> {{ topic }} | ||||||
|  | 		</div> | ||||||
|  | 		<div class="card-body"> | ||||||
|  | 			{{ data }} | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | 	</p> | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <script type="text/javascript"> | ||||||
|  |  | ||||||
|  | 	var publishTopic = function(btn){ | ||||||
|  | 		event.preventDefault(); // avoid to execute the actual submit of the form. | ||||||
|  | 		var $form = $(btn).closest( '[action]' ); // gets the 'form' parent | ||||||
|  | 		var formData = $form.find( '[name]' ).serializeObject();  | ||||||
|  |  | ||||||
|  | 		if( !$form.validate()) { | ||||||
|  | 			app.util.actionMessage('Please fix the form errors.', $form, 'danger') | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		app.publish(formData.topic, JSON.parse(formData.data)) | ||||||
|  | 		$form.trigger("reset"); | ||||||
|  | 		app.util.actionMessage('Topic '+formData.topic+' published!', $form, 'success'); //re-populate table | ||||||
|  |  | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	$(document).ready(function(){ | ||||||
|  | 		app.subscribe(/./g, function(data, topic){ | ||||||
|  | 			var rowTemplate = $('#rowTemplate').html(); | ||||||
|  | 			var $target = $('#tableAJAX'); | ||||||
|  |  | ||||||
|  | 			user_row = Mustache.render(rowTemplate, {topic, data: JSON.stringify(data)}); | ||||||
|  | 			$target.append(user_row); | ||||||
|  | 			$target.fadeIn('slow'); | ||||||
|  |  | ||||||
|  |  | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 	}); | ||||||
|  | </script> | ||||||
|  | <div class="row" style="display:none"> | ||||||
|  | 	<div class="col-md-4"> | ||||||
|  | 		<div class="card shadow-lg"> | ||||||
|  | 			<div class="card-header"> | ||||||
|  | 				<i class="fas fa-layer-plus"></i> | ||||||
|  | 				Publish | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-header actionMessage" style="display:none"></div> | ||||||
|  | 			<div class="card-body"> | ||||||
|  | 				<form action="group/" method="post" onsubmit="publishTopic(this)"> | ||||||
|  | 					<div class="form-group"> | ||||||
|  | 						<label class="control-label">Topic</label> | ||||||
|  | 						<input type="text" class="form-control shadow" name="topic" placeholder="app_gitea_admin" validate=":3" /> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
|  | 					<div class="form-group"> | ||||||
|  | 						<label class="control-label">JSON</label> | ||||||
|  | 						<textarea class="form-control shadow" name="data" placeholder="{...}" validate=":3"></textarea> | ||||||
|  | 					</div> | ||||||
|  |  | ||||||
|  | 					<button type="submit" class="btn btn-outline-dark">Publish</button> | ||||||
|  | 				</form> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | 	<div class="col-md-8"> | ||||||
|  | 		<div class="card shadow-lg"> | ||||||
|  | 			<div class="card-header"> | ||||||
|  | 				<i class="fad fa-users-class"></i> | ||||||
|  | 				Incoming Topics | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-header actionMessage" style="display:none"> | ||||||
|  | 			</div> | ||||||
|  | 			<div class="card-body"> | ||||||
|  | 				<div class="" id="tableAJAX"> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 	</div> | ||||||
|  | </div> | ||||||
|  | <%- include('bottom') %> | ||||||
							
								
								
									
										491
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										491
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,491 +0,0 @@ | |||||||
| { |  | ||||||
|   "requires": true, |  | ||||||
|   "lockfileVersion": 1, |  | ||||||
|   "dependencies": { |  | ||||||
|     "abbrev": { |  | ||||||
|       "version": "1.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", |  | ||||||
|       "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" |  | ||||||
|     }, |  | ||||||
|     "ansi-regex": { |  | ||||||
|       "version": "2.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", |  | ||||||
|       "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" |  | ||||||
|     }, |  | ||||||
|     "aproba": { |  | ||||||
|       "version": "1.2.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", |  | ||||||
|       "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" |  | ||||||
|     }, |  | ||||||
|     "are-we-there-yet": { |  | ||||||
|       "version": "1.1.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", |  | ||||||
|       "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", |  | ||||||
|       "requires": { |  | ||||||
|         "delegates": "^1.0.0", |  | ||||||
|         "readable-stream": "^2.0.6" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "balanced-match": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" |  | ||||||
|     }, |  | ||||||
|     "bcrypt": { |  | ||||||
|       "version": "4.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-4.0.1.tgz", |  | ||||||
|       "integrity": "sha512-hSIZHkUxIDS5zA2o00Kf2O5RfVbQ888n54xQoF/eIaquU4uaLxK8vhhBdktd0B3n2MjkcAWzv4mnhogykBKOUQ==", |  | ||||||
|       "requires": { |  | ||||||
|         "node-addon-api": "^2.0.0", |  | ||||||
|         "node-pre-gyp": "0.14.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "brace-expansion": { |  | ||||||
|       "version": "1.1.11", |  | ||||||
|       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", |  | ||||||
|       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", |  | ||||||
|       "requires": { |  | ||||||
|         "balanced-match": "^1.0.0", |  | ||||||
|         "concat-map": "0.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "chownr": { |  | ||||||
|       "version": "1.1.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", |  | ||||||
|       "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" |  | ||||||
|     }, |  | ||||||
|     "code-point-at": { |  | ||||||
|       "version": "1.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", |  | ||||||
|       "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" |  | ||||||
|     }, |  | ||||||
|     "concat-map": { |  | ||||||
|       "version": "0.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", |  | ||||||
|       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" |  | ||||||
|     }, |  | ||||||
|     "console-control-strings": { |  | ||||||
|       "version": "1.1.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", |  | ||||||
|       "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" |  | ||||||
|     }, |  | ||||||
|     "core-util-is": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" |  | ||||||
|     }, |  | ||||||
|     "debug": { |  | ||||||
|       "version": "3.2.6", |  | ||||||
|       "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", |  | ||||||
|       "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", |  | ||||||
|       "requires": { |  | ||||||
|         "ms": "^2.1.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "deep-extend": { |  | ||||||
|       "version": "0.6.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", |  | ||||||
|       "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" |  | ||||||
|     }, |  | ||||||
|     "delegates": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" |  | ||||||
|     }, |  | ||||||
|     "detect-libc": { |  | ||||||
|       "version": "1.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", |  | ||||||
|       "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" |  | ||||||
|     }, |  | ||||||
|     "fs-minipass": { |  | ||||||
|       "version": "1.2.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", |  | ||||||
|       "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", |  | ||||||
|       "requires": { |  | ||||||
|         "minipass": "^2.6.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "fs.realpath": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" |  | ||||||
|     }, |  | ||||||
|     "gauge": { |  | ||||||
|       "version": "2.7.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", |  | ||||||
|       "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", |  | ||||||
|       "requires": { |  | ||||||
|         "aproba": "^1.0.3", |  | ||||||
|         "console-control-strings": "^1.0.0", |  | ||||||
|         "has-unicode": "^2.0.0", |  | ||||||
|         "object-assign": "^4.1.0", |  | ||||||
|         "signal-exit": "^3.0.0", |  | ||||||
|         "string-width": "^1.0.1", |  | ||||||
|         "strip-ansi": "^3.0.1", |  | ||||||
|         "wide-align": "^1.1.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "glob": { |  | ||||||
|       "version": "7.1.6", |  | ||||||
|       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", |  | ||||||
|       "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", |  | ||||||
|       "requires": { |  | ||||||
|         "fs.realpath": "^1.0.0", |  | ||||||
|         "inflight": "^1.0.4", |  | ||||||
|         "inherits": "2", |  | ||||||
|         "minimatch": "^3.0.4", |  | ||||||
|         "once": "^1.3.0", |  | ||||||
|         "path-is-absolute": "^1.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "has-unicode": { |  | ||||||
|       "version": "2.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", |  | ||||||
|       "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" |  | ||||||
|     }, |  | ||||||
|     "iconv-lite": { |  | ||||||
|       "version": "0.4.24", |  | ||||||
|       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", |  | ||||||
|       "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", |  | ||||||
|       "requires": { |  | ||||||
|         "safer-buffer": ">= 2.1.2 < 3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "ignore-walk": { |  | ||||||
|       "version": "3.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", |  | ||||||
|       "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", |  | ||||||
|       "requires": { |  | ||||||
|         "minimatch": "^3.0.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "inflight": { |  | ||||||
|       "version": "1.0.6", |  | ||||||
|       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", |  | ||||||
|       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", |  | ||||||
|       "requires": { |  | ||||||
|         "once": "^1.3.0", |  | ||||||
|         "wrappy": "1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "inherits": { |  | ||||||
|       "version": "2.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", |  | ||||||
|       "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" |  | ||||||
|     }, |  | ||||||
|     "ini": { |  | ||||||
|       "version": "1.3.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", |  | ||||||
|       "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" |  | ||||||
|     }, |  | ||||||
|     "is-fullwidth-code-point": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", |  | ||||||
|       "requires": { |  | ||||||
|         "number-is-nan": "^1.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "isarray": { |  | ||||||
|       "version": "1.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", |  | ||||||
|       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" |  | ||||||
|     }, |  | ||||||
|     "minimatch": { |  | ||||||
|       "version": "3.0.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", |  | ||||||
|       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", |  | ||||||
|       "requires": { |  | ||||||
|         "brace-expansion": "^1.1.7" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "minimist": { |  | ||||||
|       "version": "1.2.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", |  | ||||||
|       "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" |  | ||||||
|     }, |  | ||||||
|     "minipass": { |  | ||||||
|       "version": "2.9.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", |  | ||||||
|       "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", |  | ||||||
|       "requires": { |  | ||||||
|         "safe-buffer": "^5.1.2", |  | ||||||
|         "yallist": "^3.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "minizlib": { |  | ||||||
|       "version": "1.3.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", |  | ||||||
|       "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", |  | ||||||
|       "requires": { |  | ||||||
|         "minipass": "^2.9.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "mkdirp": { |  | ||||||
|       "version": "0.5.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", |  | ||||||
|       "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", |  | ||||||
|       "requires": { |  | ||||||
|         "minimist": "^1.2.5" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "ms": { |  | ||||||
|       "version": "2.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", |  | ||||||
|       "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" |  | ||||||
|     }, |  | ||||||
|     "needle": { |  | ||||||
|       "version": "2.4.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.1.tgz", |  | ||||||
|       "integrity": "sha512-x/gi6ijr4B7fwl6WYL9FwlCvRQKGlUNvnceho8wxkwXqN8jvVmmmATTmZPRRG7b/yC1eode26C2HO9jl78Du9g==", |  | ||||||
|       "requires": { |  | ||||||
|         "debug": "^3.2.6", |  | ||||||
|         "iconv-lite": "^0.4.4", |  | ||||||
|         "sax": "^1.2.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "node-addon-api": { |  | ||||||
|       "version": "2.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.0.tgz", |  | ||||||
|       "integrity": "sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA==" |  | ||||||
|     }, |  | ||||||
|     "node-pre-gyp": { |  | ||||||
|       "version": "0.14.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", |  | ||||||
|       "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", |  | ||||||
|       "requires": { |  | ||||||
|         "detect-libc": "^1.0.2", |  | ||||||
|         "mkdirp": "^0.5.1", |  | ||||||
|         "needle": "^2.2.1", |  | ||||||
|         "nopt": "^4.0.1", |  | ||||||
|         "npm-packlist": "^1.1.6", |  | ||||||
|         "npmlog": "^4.0.2", |  | ||||||
|         "rc": "^1.2.7", |  | ||||||
|         "rimraf": "^2.6.1", |  | ||||||
|         "semver": "^5.3.0", |  | ||||||
|         "tar": "^4.4.2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "nopt": { |  | ||||||
|       "version": "4.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", |  | ||||||
|       "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", |  | ||||||
|       "requires": { |  | ||||||
|         "abbrev": "1", |  | ||||||
|         "osenv": "^0.1.4" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "npm-bundled": { |  | ||||||
|       "version": "1.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", |  | ||||||
|       "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", |  | ||||||
|       "requires": { |  | ||||||
|         "npm-normalize-package-bin": "^1.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "npm-normalize-package-bin": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", |  | ||||||
|       "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" |  | ||||||
|     }, |  | ||||||
|     "npm-packlist": { |  | ||||||
|       "version": "1.4.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", |  | ||||||
|       "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", |  | ||||||
|       "requires": { |  | ||||||
|         "ignore-walk": "^3.0.1", |  | ||||||
|         "npm-bundled": "^1.0.1", |  | ||||||
|         "npm-normalize-package-bin": "^1.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "npmlog": { |  | ||||||
|       "version": "4.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", |  | ||||||
|       "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", |  | ||||||
|       "requires": { |  | ||||||
|         "are-we-there-yet": "~1.1.2", |  | ||||||
|         "console-control-strings": "~1.1.0", |  | ||||||
|         "gauge": "~2.7.3", |  | ||||||
|         "set-blocking": "~2.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "number-is-nan": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", |  | ||||||
|       "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" |  | ||||||
|     }, |  | ||||||
|     "object-assign": { |  | ||||||
|       "version": "4.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", |  | ||||||
|       "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" |  | ||||||
|     }, |  | ||||||
|     "once": { |  | ||||||
|       "version": "1.4.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", |  | ||||||
|       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", |  | ||||||
|       "requires": { |  | ||||||
|         "wrappy": "1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "os-homedir": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" |  | ||||||
|     }, |  | ||||||
|     "os-tmpdir": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" |  | ||||||
|     }, |  | ||||||
|     "osenv": { |  | ||||||
|       "version": "0.1.5", |  | ||||||
|       "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", |  | ||||||
|       "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", |  | ||||||
|       "requires": { |  | ||||||
|         "os-homedir": "^1.0.0", |  | ||||||
|         "os-tmpdir": "^1.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "path-is-absolute": { |  | ||||||
|       "version": "1.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", |  | ||||||
|       "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" |  | ||||||
|     }, |  | ||||||
|     "process-nextick-args": { |  | ||||||
|       "version": "2.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", |  | ||||||
|       "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" |  | ||||||
|     }, |  | ||||||
|     "rc": { |  | ||||||
|       "version": "1.2.8", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", |  | ||||||
|       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", |  | ||||||
|       "requires": { |  | ||||||
|         "deep-extend": "^0.6.0", |  | ||||||
|         "ini": "~1.3.0", |  | ||||||
|         "minimist": "^1.2.0", |  | ||||||
|         "strip-json-comments": "~2.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "readable-stream": { |  | ||||||
|       "version": "2.3.7", |  | ||||||
|       "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", |  | ||||||
|       "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", |  | ||||||
|       "requires": { |  | ||||||
|         "core-util-is": "~1.0.0", |  | ||||||
|         "inherits": "~2.0.3", |  | ||||||
|         "isarray": "~1.0.0", |  | ||||||
|         "process-nextick-args": "~2.0.0", |  | ||||||
|         "safe-buffer": "~5.1.1", |  | ||||||
|         "string_decoder": "~1.1.1", |  | ||||||
|         "util-deprecate": "~1.0.1" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "rimraf": { |  | ||||||
|       "version": "2.7.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", |  | ||||||
|       "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", |  | ||||||
|       "requires": { |  | ||||||
|         "glob": "^7.1.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "safe-buffer": { |  | ||||||
|       "version": "5.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", |  | ||||||
|       "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" |  | ||||||
|     }, |  | ||||||
|     "safer-buffer": { |  | ||||||
|       "version": "2.1.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", |  | ||||||
|       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" |  | ||||||
|     }, |  | ||||||
|     "sax": { |  | ||||||
|       "version": "1.2.4", |  | ||||||
|       "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", |  | ||||||
|       "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" |  | ||||||
|     }, |  | ||||||
|     "semver": { |  | ||||||
|       "version": "5.7.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", |  | ||||||
|       "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" |  | ||||||
|     }, |  | ||||||
|     "set-blocking": { |  | ||||||
|       "version": "2.0.0", |  | ||||||
|       "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", |  | ||||||
|       "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" |  | ||||||
|     }, |  | ||||||
|     "signal-exit": { |  | ||||||
|       "version": "3.0.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", |  | ||||||
|       "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" |  | ||||||
|     }, |  | ||||||
|     "string-width": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", |  | ||||||
|       "requires": { |  | ||||||
|         "code-point-at": "^1.0.0", |  | ||||||
|         "is-fullwidth-code-point": "^1.0.0", |  | ||||||
|         "strip-ansi": "^3.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "string_decoder": { |  | ||||||
|       "version": "1.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", |  | ||||||
|       "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", |  | ||||||
|       "requires": { |  | ||||||
|         "safe-buffer": "~5.1.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "strip-ansi": { |  | ||||||
|       "version": "3.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", |  | ||||||
|       "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", |  | ||||||
|       "requires": { |  | ||||||
|         "ansi-regex": "^2.0.0" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "strip-json-comments": { |  | ||||||
|       "version": "2.0.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", |  | ||||||
|       "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" |  | ||||||
|     }, |  | ||||||
|     "tar": { |  | ||||||
|       "version": "4.4.13", |  | ||||||
|       "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", |  | ||||||
|       "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", |  | ||||||
|       "requires": { |  | ||||||
|         "chownr": "^1.1.1", |  | ||||||
|         "fs-minipass": "^1.2.5", |  | ||||||
|         "minipass": "^2.8.6", |  | ||||||
|         "minizlib": "^1.2.1", |  | ||||||
|         "mkdirp": "^0.5.0", |  | ||||||
|         "safe-buffer": "^5.1.2", |  | ||||||
|         "yallist": "^3.0.3" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "util-deprecate": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" |  | ||||||
|     }, |  | ||||||
|     "wide-align": { |  | ||||||
|       "version": "1.1.3", |  | ||||||
|       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", |  | ||||||
|       "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", |  | ||||||
|       "requires": { |  | ||||||
|         "string-width": "^1.0.2 || 2" |  | ||||||
|       } |  | ||||||
|     }, |  | ||||||
|     "wrappy": { |  | ||||||
|       "version": "1.0.2", |  | ||||||
|       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", |  | ||||||
|       "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" |  | ||||||
|     }, |  | ||||||
|     "yallist": { |  | ||||||
|       "version": "3.1.1", |  | ||||||
|       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", |  | ||||||
|       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user