This commit is contained in:
William Mantly 2021-01-25 23:42:47 -05:00
parent 10d10079aa
commit e585683664
Signed by: wmantly
GPG Key ID: 186A8370EFF937CA
22 changed files with 3116 additions and 1268 deletions

View File

@ -33,13 +33,12 @@ app.use('/', require('./routes/index'));
// API routes for authentication.
app.use('/api/auth', require('./routes/auth'));
app.use('/api/git', require('./routes/git_webhook.js'));
// API routes for working with users. All endpoints need to be have valid user.
app.use('/api/user', middleware.auth, require('./routes/user'));
app.use('/api/token', middleware.auth, require('./routes/token'));
app.use('/api/group', middleware.auth, require('./routes/group'));
// Catch 404 and forward to error handler. If none of the above routes are
@ -54,7 +53,7 @@ app.use(function(req, res, next) {
// Error handler. This is where `next()` will go on error
app.use(function(err, req, res, next) {
console.error(err.status || res.status, err.name, req.method, req.url);
if(![401, 404].includes(err.status || res.status)){
if(![ 404].includes(err.status || res.status)){
console.error(err.message);
console.error(err.stack);
console.error('=========================================');

View File

@ -7,12 +7,13 @@
var app = require('../app');
var debug = require('debug')('proxy-api:server');
var http = require('http');
const conf = require('../conf/conf');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.NODE_PORT || app.conf.port || '3000');
var port = normalizePort(process.env.NODE_PORT || conf.port || '3000');
app.set('port', port);
/**

View File

@ -3,13 +3,16 @@
module.exports = {
userModel: 'ldap', // pam, redis, ldap
ldap: {
url: 'ldap://192.168.1.54:389',
bindDN: 'cn=admin,dc=theta42,dc=com',
url: 'ldap://192.168.1.55:389',
bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com',
bindPassword: '__IN SRECREST FILE__',
userBase: 'ou=people,dc=theta42,dc=com',
groupBase: 'ou=groups,dc=theta42,dc=com',
userFilter: '(objectClass=posixAccount)',
userNameAttribute: 'uid'
},
SENDGRID_API_KEy: '__IN SRECREST FILE__',
httpProxyAPI:{
host: 'http://10.1.0.51:3000',
key: '__IN SRECREST FILE__'
}
};

252
nodejs/lib/deploy.js Normal file
View File

@ -0,0 +1,252 @@
'user strict';
const extend = require('extend');
const axios = require('axios')
const {Repo, Environment, Deployment, Target} = require('../models/repo');
const deployTargets = require('./lxc');
const conf = require('../conf/conf')
async function doDeploy(action, repo, branch, repoSshurl, commit){
var deployment;
try{
console.log(action, repo, branch, repoSshurl, commit)
repo = await Repo.get(repo);
let deployments = await repo.getDeploymentsbyBranch(branch, true)
console.log('deployments', deployments)
if(deployments.length && action === 'delete'){
deployment = deployments[0]
}if(deployments.length){
deployment = deployments[0]
action = 'update';
}else{
var environment = await repo.getEnvironmentsbyBranch(branch)
deployment = await environment.addDeployment()
}
deployment.environment.settings.repoSshurl = repoSshurl
deployment.environment.settings.branch = branch
}catch(error){
console.error('create start', error)
throw new Error('Failed to make new Deployment')
}
try{
deployment = new Depoy(deployment);
setImmediate(async function(deployment, action) {
try{
await deployment[action]()
}catch(error){
console.log('set error', error)
}
}, deployment, action)
return {id: deployment.id};
}catch(error){
console.error('create remote', error)
}
}
function event(deployment, message){
console.info('event:', message)
}
class Depoy{
constructor(deployment){
this.deployment = deployment
this.environment = deployment.environment;
this.settings = pasrseSetings(deployment);
this.secrets = pasrseSecrets(deployment);
this.id = deployment.repo_env_id
this.target = new deployTargets[deployment.target.type](this.settings)
}
async exec(code, info){
await this.event(`exec-start`, {info})
code = `
sudo su
${exportBashVars(this.secrets)}
echo 'nameserver 8.8.8.8' > /etc/resolv.conf
export DEBIAN_FRONTEND=noninteractive
${code}
`
let res = await this.target.exec(code);
await this.event(`exec-finish`, {info, ...res})
return res;
}
async event(name, data){
console.log(`EVENT: ${name}`, data)
}
async log(type, message){
console.log('LOG:', type, message)
}
async setinfo(){
let info = await this.target.info();
if(!info.ip){
return await this.setinfo();
}
let id = info.ip.slice(-2);
let settings = {
sshURL: `${this.settings.host}:22${id}`,
httpURL: `${this.settings.host}:80${id}`,
}
this.settings = {...this.settings, ...settings};
await this.deployment.update('settings', {settings: this.settings, state:'deployed'})
}
async create(){
this.event('deployment-started', {info: `Creating deployment ${this.settings.appName}`})
await this.target.create('bionic-base')
await this.target.start()
await this.setinfo();
console.log(this.settings)
try{
await this.exec(`
while [ ! -f /opt/datacom/firstboot ]; do sleep 1; done
sleep 2
`, 'Wait for target to be ready')
}catch(error){}
await this.init();
await this.updateProxy();
}
async init(){
await this.exec(deployInitScript(this.settings), 'Initializing deployment')
await this.exec(`
cd ${this.settings.workingPath};
./${this.settings.scriptsPath}/${this.environment.environment}/deploy.sh
`, 'Running repo deploy script')
}
async update(){
await this.exec(`
cd ${this.settings.workingPath};
export GIT_SSH_COMMAND="/usr/bin/ssh -o StrictHostKeyChecking=no -i $HOME/.ssh/id_rsa_deploy_key"
git pull origin master;
./${this.settings.scriptsPath}/${this.environment.environment}/update.sh
`, 'Running repo update script')
}
async updateProxy(){
let target = this.settings.httpURL.split(':');
let res = await axios.post(`${conf.httpProxyAPI.host}/api/host/`, {
forcessl: true,
host: this.settings.domain.replace('*', this.settings.branch),
ip: target[0],
targetPort: Number(target[1] || 80),
targetssl: false
}, {
headers: { "auth-token": conf.httpProxyAPI.key }
})
}
async delete(){
await this.target.destroy()
await this.deployment.update({state: 'deleted', isActive: false})
}
}
function deployUpdateScript(argument) {
// body...
}
function deployInitScript(args){
return `
mkdir -p "${args.workingPath}";
mkdir "$HOME/.ssh";
chmod 700 "$HOME/.ssh"
echo "${args.privateKey}" > $HOME/.ssh/id_rsa_deploy_key
chmod 600 $HOME/.ssh/id_rsa_deploy_key
wget https://raw.githubusercontent.com/tests-always-included/mo/master/mo -O /usr/local/bin/mo
chmod +x /usr/local/bin/mo
export GIT_SSH_COMMAND="/usr/bin/ssh -o StrictHostKeyChecking=no -i $HOME/.ssh/id_rsa_deploy_key"
git clone ${args.repoSshurl} ${args.workingPath};
`
}
function exportBashVars(map){
let out = '';
for (const [key, value] of Object.entries(map)){
out += `export ${key}="${value}";`
}
return out
}
function pasrseBase(deployment){
let appName = deployment.repo_env_id.replace('/', '_')
return {
appName: appName,
scriptsPath: deployment.environment.repo.scriptsPath,
privateKey: deployment.environment.repo.privateKey,
environment: deployment.environment.environment,
workingPath: `${deployment.environment.workingPath}/${appName}`,
domain: deployment.environment.domain,
name: appName,
}
}
function pasrseSecrets(deployment){
return {
...deployment.environment.repo.secrets,
...deployment.environment.secrets,
...pasrseBase(deployment),
}
}
function pasrseSetings(deployment){
return {
...deployment.target.settings,
...deployment.environment.repo.settings,
...deployment.environment.settings,
...pasrseBase(deployment),
}
}
module.exports = {doDeploy};
(async function(){try{
// console.log(await doDeploy('create', 'wmantly/static-test', 'master', 'ssh://gitea@git.theta42.com:2222/wmantly/static-test.git'))
// let repo = await Repo.get('wmantly/static-test');
// let deployments = await repo.getDeploymentsbyBranch('master')
// for(let d of deployments){
// try{
// let lxc = new deployTargets.LXC({...{name: d.repo_env_id.replace('/', '_')}, ...d.target.settings})
// await lxc.destroy();
// await d.remove()
// }catch(error){
// console.log('err', error)
// }finally{
// await d.remove()
// }
// }
}catch(error){
console.error('IIFE error:', error)
}})()

174
nodejs/lib/lxc.js Normal file
View File

@ -0,0 +1,174 @@
'use strict';
const util = require('util');
const exec = util.promisify(require('child_process').exec)
class Local{
async sysExec(command){
try{
return await exec(command)
}catch(error){
throw(error);
}
}
async exec(...args){
await this.sysExec()
}
}
class SSH extends Local{
constructor(args){
super()
this.user = args.user;
this.host = args.host;
this.keyPath = args.keyPath;
}
async sysExec(command){try{
// console.log('command', command)
command = new Buffer.from(command).toString('base64');
command = `ssh -i "${this.keyPath}" -o StrictHostKeyChecking=no ${this.user}@${this.host} "echo ${command} | base64 --decode | bash"`;
return await super.sysExec(command);
}catch(error){
throw error;
}}
}
class LXC{
constructor(args){
// console.log('lxc args', args)
this.name = args.name
if(args.host){
this.sysExec = (new SSH(args)).sysExec.bind(args)
}else{
this.sysExec = (new Local()).sysExec
}
}
static async list(){
try{
let res = await this.prototype.sysExec(`lxc-ls --fancy`);
let output = res.stdout.split("\n").slice(0).slice(0,-1);
let keys = output.splice(0,1)[0].split(/\s+/).slice(0,-1).map(function(v){return v.toLowerCase()});
let info = [];
for(let line of output){
if(line.match(/^-/)) continue;
line = line.split(/\s+/).slice(0,-1);
let mapOut = {};
line.map(function(value,idx){
mapOut[keys[idx]] = value;
});
info.push(mapOut);
}
return info;
}catch(error){
throw error;
}
}
async create(from){
try{
return await this.sysExec(`lxc-copy --name "${from}" --newname "${this.name}" --daemon`);
}catch(error){
throw error;
}
}
async start(){
try{
return await this.sysExec(`lxc-start --name "${this.name}" --daemon`);
}catch(error){
throw error;
}
}
async destroy(){
try{
let res = await this.sysExec(`lxc-destroy --force --name ${this.name}`)
return !!res.stdout.match(/Destroyed container/);
}catch(error){
throw error;
}
}
async stop(){
try{
return await this.sysExec(`lxc-stop --name "${this.name}"`);
}catch(error){
throw error;
}
}
async exec(code){
try{
code = new Buffer.from(code).toString('base64')
return await this.sysExec(`lxc-attach -n "${this.name}" --clear-env -- bash -c 'echo "${code}" | base64 --decode | bash'`)
}catch(error){
throw error;
}
}
async info(){
try{
let info = {};
let res = await this.sysExec(`lxc-info --name "${this.name}"`);
res = res.stdout;
if(res.match("doesn't exist")){
throw new Error('ContainerDoesntExist')
}
res = res.replace(/\suse/ig, '').replace(/\sbytes/ig, '').split("\n").slice(0,-1);
for(var i in res){
var temp = res[i].split(/\:\s+/);
info[temp[0].toLowerCase().trim()] = temp[1].trim();
}
var args = [info].concat(Array.prototype.slice.call(arguments, 1));
return info;
}catch(error){
throw error;
}
}
async setAutoStart(name){
await this.sysExec(`echo "lxc.start.auto = 1" >> "$HOME/.local/share/lxc/${this.name}/config"`)
}
}
module.exports = {Local, SSH, LXC};
(async function(){try{
// let lxc = new LXC();
// // console.log(await lxc.copy('hass', 'hass2'))
// console.log(await lxc.destroy('hass2'))
// console.log(await LXC.list())
// let lxc = new LXC({name:'hass'})
// console.log(await hass.start())
// let lxc = new LXC({user:'virt-service', host:'142.93.30.52', keyPath:'/home/william/.ssh/id_rsa_virt-service', name: 'test2'})
// console.log(await lxc.exec('hostname'))
// // console.log(await lxc.exec('test2', 'sleep 50'))
// console.log(await lxc.info())
//
}catch(error){
console.error('IIFE error', error);
}})()

View File

@ -22,6 +22,7 @@ Auth.login = async function(data){
return {user, token}
}catch(error){
console.log('login error', error);
throw this.errors.login();
}
};

View File

@ -1,281 +0,0 @@
'use strict';
const { Client, Attribute, Change } = require('ldapts');
const conf = require('../app').conf.ldap;
const client = new Client({
url: conf.url,
});
async function getGroups(client, member){
try{
let memberFilter = member ? `(member=${member})`: ''
let groups = (await client.search(conf.groupBase, {
scope: 'sub',
filter: `(&(objectClass=groupOfNames)${memberFilter})`,
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
})).searchEntries;
return groups.map(function(group){
if(!Array.isArray(group.member)) group.member = [group.member];
if(!Array.isArray(group.owner)) group.owner = [group.owner];
return group
});
}catch(error){
throw error;
}
}
async function addGroup(client, data){
try{
await client.add(`cn=${data.name},${conf.groupBase}`, {
cn: data.name,
member: data.owner,
description: data.description,
owner: data.owner,
objectclass: [ 'groupOfNames', 'top' ]
});
return data;
}catch(error){
throw error;
}
}
async function addMember(client, group, user){
try{
await client.modify(group.dn, [
new Change({
operation: 'add',
modification: new Attribute({
type: 'member',
values: [user.dn]
})
}),
]);
}catch(error){
// if(error = "TypeOrValueExistsError"){
// console.error('addMember error skipped', error)
// return ;
// }
throw error;
}
}
async function removeMember(client, group, user){
try{
await client.modify(group.dn, [
new Change({
operation: 'delete',
modification: new Attribute({
type: 'member',
values: [user.dn]
})}),
]);
}catch(error){
if(error = "TypeOrValueExistsError")return ;
throw error;
}
}
async function addOwner(client, group, user){
try{
await client.modify(group.dn, [
new Change({
operation: 'add',
modification: new Attribute({
type: 'owner',
values: [user.dn]
})
}),
]);
}catch(error){
// if(error = "TypeOrValueExistsError"){
// console.error('addMember error skipped', error)
// return ;
// }
throw error;
}
}
async function removeOwner(client, group, user){
try{
await client.modify(group.dn, [
new Change({
operation: 'delete',
modification: new Attribute({
type: 'owner',
values: [user.dn]
})}),
]);
}catch(error){
if(error = "TypeOrValueExistsError")return ;
throw error;
}
}
var Group = {};
Group.list = async function(member){
try{
await client.bind(conf.bindDN, conf.bindPassword);
let groups = await getGroups(client, member)
await client.unbind();
return groups.map(group => group.cn);
}catch(error){
throw error;
}
}
Group.listDetail = async function(member){
try{
await client.bind(conf.bindDN, conf.bindPassword);
let groups = await getGroups(client, member)
await client.unbind();
return groups;
}catch(error){
throw error;
}
}
Group.get = async function(data){
try{
if(typeof data !== 'object'){
let name = data;
data = {};
data.name = name;
}
await client.bind(conf.bindDN, conf.bindPassword);
let group = (await client.search(conf.groupBase, {
scope: 'sub',
filter: `(&(objectClass=groupOfNames)(cn=${data.name}))`,
attributes: ['*', 'createTimestamp', 'modifyTimestamp'],
})).searchEntries[0];
await client.unbind();
if(!Array.isArray(group.member)) group.member = [group.member];
if(!Array.isArray(group.owner)) group.owner = [group.owner];
if(group){
let obj = Object.create(this);
Object.assign(obj, group);
return obj;
}else{
let error = new Error('GroupNotFound');
error.name = 'GroupNotFound';
error.message = `LDAP:${data.cn} does not exists`;
error.status = 404;
throw error;
}
}catch(error){
throw error;
}
}
Group.add = async function(data){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await addGroup(client, data);
await client.unbind();
return this.get(data);
}catch(error){
throw error;
}
}
Group.addMember = async function(user){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await addMember(client, this, user);
await client.unbind();
return this;
}catch(error){
throw error;
}
};
Group.removeMember = async function(user){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await removeMember(client, this, user);
await client.unbind();
return this;
}catch(error){
throw error;
}
};
Group.addOwner = async function(user){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await addOwner(client, this, user);
await client.unbind();
return this;
}catch(error){
throw error;
}
};
Group.removeOwner = async function(user){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await removeOwner(client, this, user);
await client.unbind();
return this;
}catch(error){
throw error;
}
};
Group.remove = async function(){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await client.del(this.dn);
await client.unbind();
return true;
}catch(error){
throw error;
}
}
module.exports = {Group};

258
nodejs/models/repo.js Normal file
View File

@ -0,0 +1,258 @@
'use strict';
const {promisify} = require('util');
const forge = require('node-forge');
const Table = require('../utils/redis_model');
var rasGenerate = promisify(forge.pki.rsa.generateKeyPair);
async function generateOpenSshPair(keySize){
keySize = keySize || 2048;
let keyPair = await rasGenerate({bits: keySize});
return {
publicKey: forge.ssh.publicKeyToOpenSSH(keyPair.publicKey),
privateKey: forge.ssh.privateKeyToOpenSSH(keyPair.privateKey)
};
};
const UUID = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)};
class Repo extends Table{
static _key = 'repo'
static _keyMap = {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'repo': {isRequired: true, type: 'string', min: 3, max: 500},
'hookCallCount': {default: 0, type: 'number'},
'scriptsPath': {default:'scripts', type: 'string'},
'settings': {default: {}, type:'object'},
'secrets': {default: {}, type: 'object', min: 3, max: 500},
'privateKey': {type: 'string'},
'publicKey': {type: 'string'},
}
constructor(...args){
super(...args);
}
static async add(data){
return super.add({...data, ...(await generateOpenSshPair(2048))})
}
async getEnvironments(){
let environments = await Environment.list();
let out = [];
for(let environment of environments){
if(environment.startsWith(this.repo)){
environment = await Environment.get(environment);
environment.repo = this;
out.push(environment)
}
}
return out;
}
async getEnvironmentsbyBranch(branch){
let list = await this.getEnvironments();
let any;
for(let key of list){
if(branch === key.branchMatch) return key;
if(key.branchMatch === '*') any = key;
}
return any;
}
async getDeploymentsbyBranch(branch, state){
let environment = await this.getEnvironmentsbyBranch(branch);
let deployments = await Deployment.list();
let out = []
for(let deployment of deployments){
if(deployment.startsWith(`${this.repo}_${environment.environment}`)){
deployment = await Deployment.get(deployment);
deployment.environment = environment;
deployment.target = await Target.get(environment.target);
out.push(deployment)
if(state && deployment.state === state){
}
}
}
return out;
}
}
class Environment extends Table{
static _key = 'repo_env'
static _keyMap = {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'repo_env': {isRequired: true, type: 'string', min: 3, max: 500},
'repo': {type: 'string', min: 3, max: 500},
'environment': {isRequired: true, type: 'string', min: 3, max: 500},
'branchMatch': {isRequired: true, type: 'string', min: 1, max: 500},
'target': {isRequired: true, type: 'string', min: 3, max: 500},
'settings': {default: {}, type: 'object', min: 3, max: 500},
'secrets': {default: {}, type: 'object', min: 3, max: 500},
'hookCallCount': {default: 0, type: 'number'},
'lastCommit': {default:"__NONE__", isRequired: false, type: 'string'},
'workingPath': {default: '/opt/datacom', type: 'string'},
'domain': {isRequired: true, type: 'string'},
}
static async add(data){
try{
await Repo.get(data.repo);
await Target.get(data.target);
data.repo_env = `${data.repo}_${data.environment}`
return await super.add(data);
}catch(error){
throw error;
}
};
async addDeployment(data){
try{
data = data || {}
data.created_by = data.uid || this.created_by;
data.repo = this.repo.repo || this.repo;
data.environment = this.environment;
data.id = UUID().split('-').reverse()[0]
data.repo_env_id = `${data.repo}_${data.environment}_${data.id}`
let deployment = await Deployment.add(data);
deployment.target = await Target.get(this.target)
deployment.environment = this;
return deployment;
}catch(error){
throw error;
}
};
}
class Deployment extends Table{
static _key = 'repo_env_id'
static _keyMap = {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'id': {type: 'string', min: 12, max: 12},
'repo_env_id': {isRequired: true, type: 'string', min: 3, max: 500},
'repo': {type: 'string', min: 3, max: 500},
'environment': {isRequired: true, type: 'string', min: 3, max: 500},
'state': {default: 'new', type: 'string', min: 3, max: 500},
'isActive': {default: true, type: 'boolean',},
'target_url': {default:"__NONE__", isRequired: false, type: 'string'},
}
}
class Target extends Table{
static _key = 'name'
static _keyMap = {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_by': {default:"__NONE__", isRequired: false, type: 'string',},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'name': {isRequired: true, type: 'string', min: 2, max: 500},
'type': {isRequired: true, type: 'string', min: 1, max: 36},
'settings': {default: {}, type: 'object', min: 3, max: 500},
}
}
module.exports = {Repo, Environment, Deployment, Target};
(async function(){try{
// // console.log(await Repo.list())
// // To ssh://git.theta42.com:2222/wmantly/static-test.git
// let lxc_starting = await Target.add({
// created_by: 'wmantly',
// name: 'lxc_starting',
// type: 'LXC',
// settings: {
// user:'virt-service',
// host:'142.93.30.52',
// keyPath:'/home/william/.ssh/id_rsa_virt-service'
// }
// });
// var repo = await Repo.add({
// created_by: 'wmantly',
// repo: 'wmantly/static-test',
// })
// var environment = await Environment.add({
// created_by: 'wmantly',
// environment: 'staging',
// branchMatch: '*',
// repo: 'wmantly/static-test',
// domain: 'test.dc.vm42.us',
// target: 'lxc_starting'
// })
let environment = await Environment.get('wmantly/static-test_staging')
await environment.update({'domain': '*.dc.vm42.us'})
// // console.log(test)
// // console.log(await Environment.listDetail())
// // let repo = await Repo.get('wmantly/test2')
// // console.log(repo)
// // repo.update({hookCallCount: 5});
// // let envs = await repo.getEnvironments();
// // let env = await repo.getEnvironmentsbyBranch('staging');
// // let deployment = await env.addDeployment()
// // console.log('deployment', deployment)
// // let deployments = await repo.getDeploymentsbyBranch('staging')
// // console.log('deployments', deployments)
// // console.log('deployments', await Deployment.listDetail())
// console.log('repo', await Repo.listDetail())
// console.log('environment', await Environment.listDetail())
// for(let d of await Deployment.listDetail()){
// console.log('to remove', d)
// await d.remove()
// }
// console.log('deployment', await Deployment.listDetail())
// console.log('blah')
// let repo = await Repo.get('wmantly/static-test');
// // let environment = await repo.getEnvironmentsbyBranch('master')
// // console.log('environment', environment)
// let deployment = await repo.getDeploymentsbyBranch('master')
// console.log('deployments', deployment)
// return 0;
}catch(error){
console.error('IIFE error', error, error.message);
}})()

55
nodejs/models/test Normal file
View File

@ -0,0 +1,55 @@
var that
class Base{
static add(){
}
constructor(){
}
blah(){
that = this
}
}
class Ex extends Base{
static thingy = {a:1, b:2}
constructor(){
super()
}
}
Repo.byBranch = async function(repo){
let list = await Environment.list();
let out = [];
for(let key of list){
if(key.startsWith((repo || this.repo))) out.push(await Environment.get(key))
}
return out;
}
Environment.addDeployment = async function(data){
try{
data.repo = this.repo;
data.environment = this.environment;
data.id = UUID().split('-').reverse()[0]
data.repo_env = `${data.repo}_${data.environment}_${data.id}`
return await Deployment.__proto__.add.call(Environment, data);
}catch(error){
throw error;
}
};
module.exports = {Repo, Environment, Deployment, Target};
*/

View File

@ -1,71 +1,28 @@
'use strict';
const redis_model = require('../utils/redis_model')
const Table = require('../utils/redis_model')
const UUID = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)};
class Token extends Table{
static _key = 'token'
static _keyMap = {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'token': {default: UUID, type: 'string', min: 36, max: 36},
'is_valid': {default: true, type: 'boolean'}
}
const Token = function(data){
return redis_model({
_name: `token_${data.name}`,
_key: 'token',
_keyMap: Object.assign({}, {
'created_by': {isRequired: true, type: 'string', min: 3, max: 500},
'created_on': {default: function(){return (new Date).getTime()}},
'updated_on': {default: function(){return (new Date).getTime()}, always: true},
'token': {default: UUID, type: 'string', min: 36, max: 36},
'is_valid': {default: true, type: 'boolean'}
}, data.keyMap || {})
});
};
Token.check = async function(data){
try{
return this.is_valid;
}catch(error){
return false
async check(){
return this.is_valid
}
}
var InviteToken = Object.create(Token({
name: 'invite',
keyMap:{
claimed_by: {default:"__NONE__", isRequired: false, type: 'string',},
mail: {default:"__NONE__", isRequired: false, type: 'string',},
mail_token: {default: UUID, type: 'string', min: 36, max: 36},
}
}));
InviteToken.consume = async function(data){
try{
if(this.is_valid){
data['is_valid'] = false;
await this.update(data);
return true;
}
return false;
}catch(error){
throw error;
class AuthToken extends Token{
static async add(data){
data.created_by = data.created_by || data.uid;
return await super.add(data)
}
}
var AuthToken = Object.create(Token({
name: 'auth',
}));
AuthToken.add = async function(data){
data.created_by = data.uid;
return AuthToken.__proto__.add(data);
};
var PasswordResetToken = Object.create(Token({
name: 'auth',
}));
PasswordResetToken.add = async function(data){
data.created_by = data.uid;
return PasswordResetToken.__proto__.add(data);
};
module.exports = {Token, InviteToken, AuthToken, PasswordResetToken};
module.exports = {Token, AuthToken};

View File

@ -1,116 +1,12 @@
'use strict';
const { Client, Attribute, Change } = require('ldapts');
const crypto = require('crypto');
const {Mail} = require('./email');
const {Token, InviteToken, PasswordResetToken} = require('./token');
const {Client, Attribute} = require('ldapts');
const conf = require('../app').conf.ldap;
const client = new Client({
url: conf.url,
});
async function addPosixGroup(client, data){
try{
const groups = (await client.search(conf.groupBase, {
scope: 'sub',
filter: '(&(objectClass=posixGroup))',
})).searchEntries;
data.gidNumber = (Math.max(...groups.map(i => i.gidNumber))+1)+'';
await client.add(`cn=${data.cn},${conf.groupBase}`, {
cn: data.cn,
gidNumber: data.gidNumber,
objectclass: [ 'posixGroup', 'top' ]
});
return data;
}catch(error){
throw error;
}
}
async function addPosixAccount(client, data){
try{
const people = (await client.search(conf.userBase, {
scope: 'sub',
filter: conf.userFilter,
})).searchEntries;
data.uidNumber = (Math.max(...people.map(i => i.uidNumber))+1)+'';
await client.add(`cn=${data.cn},${conf.userBase}`, {
cn: data.cn,
sn: data.sn,
uid: data.uid,
uidNumber: data.uidNumber,
gidNumber: data.gidNumber,
givenName: data.givenName,
mail: data.mail,
mobile: data.mobile,
loginShell: data.loginShell,
homeDirectory: data.homeDirectory,
userPassword: data.userPassword,
description: data.description || ' ',
sudoHost: 'ALL',
sudoCommand: 'ALL',
sudoUser: data.uid,
sshPublicKey: data.sshPublicKey,
objectclass: ['inetOrgPerson', 'sudoRole', 'ldapPublicKey', 'posixAccount', 'top' ]
});
return data
}catch(error){
throw error;
}
}
async function addLdapUser(client, data){
var group;
try{
data.uid = `${data.givenName[0]}${data.sn}`.toLowerCase();
data.cn = data.uid;
data.loginShell = '/bin/bash';
data.homeDirectory= `/home/${data.uid}`;
data.userPassword = '{MD5}'+crypto.createHash('md5').update(data.userPassword, "binary").digest('base64');
group = await addPosixGroup(client, data);
data = await addPosixAccount(client, group);
return data;
}catch(error){
await deleteLdapDN(client, `cn=${data.uid},${conf.groupBase}`, true);
throw error;
}
}
async function deleteLdapUser(client, data){
try{
await client.del(`cn=${data.cn},${conf.groupBase}`);
await client.del(data.dn);
}catch(error){
throw error;
}
}
async function deleteLdapDN(client, dn, ignoreError){
try{
client.del(dn)
}catch(error){
if(!ignoreError) throw error;
console.error('ERROR: deleteLdapDN', error)
}
}
const user_parse = function(data){
if(data[conf.userNameAttribute]){
data.username = data[conf.userNameAttribute]
@ -225,200 +121,11 @@ User.exists = async function(data, key){
}
};
User.add = async function(data) {
try{
await client.bind(conf.bindDN, conf.bindPassword);
await addLdapUser(client, data);
await client.unbind();
let user = await this.get(data.uid);
await Mail.sendTemplate(
user.mail,
'welcome',
{
user: user
}
)
return user;
}catch(error){
if(error.message.includes('exists')){
let error = new Error('UserNameUsed');
error.name = 'UserNameUsed';
error.message = `LDAP:${data.uid} already exists`;
error.status = 409;
throw error;
}
throw error;
}
};
User.update = async function(data){
try{
let editableFeilds = ['mobile', 'sshPublicKey', 'description'];
await client.bind(conf.bindDN, conf.bindPassword);
for(let field of editableFeilds){
if(data[field]){
await client.modify(this.dn, [
new Change({
operation: 'replace',
modification: new Attribute({
type: field,
values: [data[field]]
})
}),
]);
}
}
await client.unbind()
return this;
}catch(error){
throw error;
}
};
User.addByInvite = async function(data){
try{
let token = await InviteToken.get(data.token);
if(!token.is_valid && data.mailToken !== token.mail_token){
let error = new Error('Token Invalid');
error.name = 'Token Invalid';
error.message = `Token is not valid or as allready been used. ${data.token}`;
error.status = 401;
throw error;
}
data.mail = token.mail;
let user = await this.add(data);
if(user){
await token.consume({claimed_by: user.uid});
return user;
}
}catch(error){
throw error;
}
};
User.verifyEmail = async function(data){
try{
let exists = await this.exists(data.mail, 'mail');
if(exists) throw new Error('EmailInUse');
let token = await InviteToken.get(data.token);
await token.update({mail: data.mail})
await Mail.sendTemplate(
data.mail,
'validate_link',
{
link:`${data.url}/login/invite/${token.token}/${token.mail_token}`
}
)
return this;
}catch(error){
throw error;
}
};
User.passwordReset = async function(url, mail){
try{
let user = await User.get({
searchKey: 'mail',
searchValue: mail
});
let token = await PasswordResetToken.add(user);
await Mail.sendTemplate(
user.mail,
'reset_link',
{
user: user,
link:`${url}/login/resetpassword/${token.token}`
}
)
return true;
}catch(error){
// if(error.name === 'UserNotFound') return false;
throw error;
}
};
User.remove = async function(data){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await deleteLdapUser(client, this);
await client.unbind();
return true;
}catch(error){
throw error;
}
};
User.setPassword = async function(data){
try{
await client.bind(conf.bindDN, conf.bindPassword);
await client.modify(this.dn, [
new Change({
operation: 'replace',
modification: new Attribute({
type: 'userPassword',
values: ['{MD5}'+crypto.createHash('md5').update(data.userPassword, "binary").digest('base64')]
})}),
]);
await client.unbind();
return this;
}catch(error){
throw error;
}
};
User.invite = async function(){
try{
let token = await InviteToken.add({created_by: this.uid});
return token;
}catch(error){
throw error;
}
};
User.login = async function(data){
try{
let user = await this.get(data.uid);
await client.bind(user.dn, data.password);
await client.unbind();

2250
nodejs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,15 +12,14 @@
"start": "node ./bin/www"
},
"dependencies": {
"@sendgrid/mail": "^7.4.0",
"axios": "^0.21.1",
"ejs": "^3.1.5",
"express": "~4.16.1",
"extend": "^3.0.2",
"ldapts": "^2.10.1",
"moment": "^2.29.1",
"mustache": "^4.1.0",
"redis": "^2.8.0",
"smtpc": "^0.1.2"
"redis": "^2.8.0"
},
"license": "MIT",
"repository": {

View File

@ -2,19 +2,19 @@
const router = require('express').Router();
const {User} = require('../models/user');
const {Auth, AuthToken} = require('../models/auth');
const {PasswordResetToken} = require('../models/token');
const {Auth, AuthToken} = require('../models/auth');
router.post('/login', async function(req, res, next){
try{
let auth = await Auth.login(req.body);
console.log('auth route', auth)
return res.json({
login: true,
token: auth.token.token,
message:`${req.body.uid} logged in!`,
});
}catch(error){
console.log('error route', error)
next(error);
}
});
@ -31,46 +31,14 @@ router.all('/logout', async function(req, res, next){
}
});
router.post('/resetpassword', async function(req, res, next){
try{
let sent = await User.passwordReset(`${req.protocol}://${req.hostname}`, req.body.mail);
console.info('resetpassword for', req.body.mail, 'sent')
return res.json({
message: 'If the emaill address is in our system, you will receive a message.'
});
}catch(error){
next(error);
}
});
router.post('/resetpassword/:token', async function(req, res, next){
try{
let token = await PasswordResetToken.get(req.params.token);
if(token.is_valid && 86400000+Number(token.created_on) > (new Date).getTime()){
let user = await User.get(token.created_by);
await user.setPassword(req.body);
token.update({is_valid: false});
return res.json({
message: 'Password has been changed.'
});
}
}catch(error){
next(error);
}
});
router.post('/invite/:token/:mailToken', async function(req, res, next) {
router.post('/invite/:token', async function(req, res, next) {
try{
req.body.token = req.params.token;
req.body.mailToken = req.params.mailToken;
let user = await User.addByInvite(req.body);
let token = await AuthToken.add(user);
return res.json({
user: user.uid,
user: user.username,
token: token.token
});
@ -80,21 +48,6 @@ router.post('/invite/:token/:mailToken', async function(req, res, next) {
});
router.post('/invite/:token', async function(req, res, next){
try{
let data = {
token: req.params.token,
url: `${req.protocol}://${req.hostname}`,
mail: req.body.mail,
}
await User.verifyEmail(data);
return res.send({message: 'sent'});
}catch(error){
next(error)
}
});
module.exports = router;
/*

View File

@ -0,0 +1,28 @@
'use strict';
const router = require('express').Router();
const {doDeploy} = require('../lib/deploy');
router.all('/', async function(req, res, next) {
try{
var event = req.headers['x-github-event'];
var call = (req.body.created && 'create') ||
(req.body.deleted && 'delete') ||
'update';
var branch = req.body.ref.replace('refs/heads/', '');
var sshURL = req.body.repository.ssh_url;
var commit = req.body.after;
let repo = req.body.repository.full_name;
let id = await doDeploy('create', repo, branch, sshURL, commit);
res.json({id});
}catch(error){
next(error)
}
});
module.exports = router;

View File

@ -1,125 +0,0 @@
'use strict';
const router = require('express').Router();
const {User} = require('../models/user_ldap');
const {Group} = require('../models/group_ldap');
const permission = require('../utils/permission');
router.get('/', async function(req, res, next){
try{
let member = req.query.member ? await User.get(req.query.member) : {}
return res.json({
results: await Group[req.query.detail ? "listDetail" : "list"](member.dn)
});
}catch(error){
next(error);
}
});
router.post('/', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin']);
req.body.owner = req.user.dn;
return res.json({
results: await Group.add(req.body),
message: `${req.body.name} was added!`
})
}catch(error){
next(error);
}
});
router.get('/:name', async function(req, res, next){
try{
return res.json({
results: await Group.get(req.params.name)
});
}catch(error){
next(error);
}
});
router.put('/owner/:group/:uid', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]);
var group = await Group.get(req.params.group);
var user = await User.get(req.params.uid);
return res.json({
results: group.addOwner(user),
message: `Added owner ${req.params.uid} to ${req.params.group} group.`
});
}catch(error){
next(error);
}
});
router.delete('/owner/:group/:uid', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]);
var group = await Group.get(req.params.group);
var user = await User.get(req.params.uid);
return res.json({
results: group.removeOwner(user),
message: `Removed Owner ${req.params.uid} from ${req.params.group} group.`
});
}catch(error){
next(error);
}
});
router.put('/:group/:uid', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]);
var group = await Group.get(req.params.group);
var user = await User.get(req.params.uid);
return res.json({
results: group.addMember(user),
message: `Added user ${req.params.uid} to ${req.params.group} group.`
});
}catch(error){
next(error);
}
});
router.delete('/:group/:uid', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]);
var group = await Group.get(req.params.group);
var user = await User.get(req.params.uid);
return res.json({
results: group.removeMember(user),
message: `Removed user ${req.params.uid} from ${req.params.group} group.`
});
}catch(error){
next(error);
}
});
router.delete('/:group', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'], [req.params.group]);
var group = await Group.get(req.params.group);
return res.json({
removed: await group.remove(),
results: group,
message: `Group ${req.params.group} Deleted`
});
}catch(error){
next(error);
}
});
module.exports = router;

View File

@ -1,125 +1,43 @@
'use strict';
const router = require('express').Router();
const {User} = require('../models/user');
const permission = require('../utils/permission');
const {User} = require('../models/user');
const {Auth, AuthToken} = require('../models/auth');
router.get('/', async function(req, res, next){
router.post('/login', async function(req, res, next){
try{
let auth = await Auth.login(req.body);
return res.json({
results: await User[req.query.detail ? "listDetail" : "list"]()
login: true,
token: auth.token.token,
});
}catch(error){
next(error);
}
});
router.post('/', async function(req, res, next){
router.all('/logout', async function(req, res, next){
try{
await permission.byGroup(req.user, ['app_sso_admin'])
req.body.created_by = req.user.uid
return res.json({results: await User.add(req.body)});
}catch(error){
next(error);
}
});
router.delete('/:uid', async function(req, res, next){
try{
let user;
if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){
user = req.user;
}else{
user = await User.get(req.params.uid);
await permission.byGroup(req.user, ['app_sso_admin'])
if(req.user){
await req.user.logout();
}
return res.json({uid: req.params.uid, results: await user.remove()})
res.json({message: 'Bye'})
}catch(error){
next(error);
}
});
router.put('/:uid', async function(req, res, next){
router.post('/invite/:token', async function(req, res, next) {
try{
let user;
if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){
user = req.user;
}else{
user = await User.get(req.params.uid);
await permission.byGroup(req.user, ['app_sso_admin'])
}
req.body.token = req.params.token;
let user = await User.addByInvite(req.body);
let token = await AuthToken.add(user);
return res.json({
results: await user.update(req.body),
message: `Updated ${req.params.uid} user`
});
}catch(error){
next(error);
}
});
router.get('/me', async function(req, res, next){
try{
return res.json(await User.get({uid: req.user.uid}));
}catch(error){
next(error);
}
});
router.put('/password', async function(req, res, next){
try{
return res.json({results: await req.user.setPassword(req.body)})
}catch(error){
next(error);
}
});
router.put('/:uid/password', async function(req, res, next){
try{
let user;
if(req.params.uid.toLowerCase() === req.user.uid.toLowerCase()){
user = req.user;
}else{
user = await User.get(req.params.uid);
await permission.byGroup(req.user, ['app_sso_admin'])
}
return res.json({
results: await user.setPassword(req.body),
message: `User ${user.uid} password changed.`
});
}catch(error){
next(error);
}
});
router.post('/invite', async function(req, res, next){
try{
let token = await req.user.invite();
return res.json({token: token.token});
}catch(error){
next(error);
}
});
router.post('/key', async function(req, res, next){
try{
let added = await User.addSSHkey({
uid: req.user.uid,
key: req.body.key
});
return res.status(added === true ? 200 : 400).json({
message: added
user: user.username,
token: token.token
});
}catch(error){
@ -128,14 +46,22 @@ router.post('/key', async function(req, res, next){
});
router.get('/:uid', async function(req, res, next){
try{
return res.json({
results: await User.get(req.params.uid),
});
}catch(error){
next(error);
}
});
module.exports = router;
/*
verify public ssh key
*/
// router.post('/verifykey', async function(req, res){
// let key = req.body.key;
// try{
// return res.json({
// info: await Users.verifyKey(key)
// });
// }catch(error){
// return res.status(400).json({
// message: 'Key is not a public key file!'
// });
// }
// });

View File

@ -8,7 +8,7 @@ const process_type = {
string: function(key, value){
if(key.min && value.length < key.min) return `is too short, min ${key.min}.`
if(key.max && value.length > key.max) return `is too short, max ${key.max}.`
}
},
}
function returnOrCall(value){
@ -20,6 +20,7 @@ function processKeys(map, data, partial){
let out = {};
for(let key of Object.keys(map)){
if(!map[key].always && partial && !data.hasOwnProperty(key)) continue;
if(!partial && map[key].isRequired && !data.hasOwnProperty(key)){
@ -57,6 +58,7 @@ function parseFromString(map, data){
boolean: function(value){ return value === 'false' ? false : true },
number: Number,
string: String,
object: JSON.parse
};
for(let key of Object.keys(data)){
@ -68,6 +70,14 @@ function parseFromString(map, data){
return data;
}
function parseToString(data){
let types = {
object: JSON.stringify
}
return (types[typeof(data)] || String)(data);
}
function ObjectValidateError(message) {
this.name = 'ObjectValidateError';
this.message = (message || {});
@ -77,4 +87,4 @@ function ObjectValidateError(message) {
ObjectValidateError.prototype = Error.prototype;
module.exports = {processKeys, parseFromString, ObjectValidateError};
module.exports = {processKeys, parseFromString, ObjectValidateError, parseToString};

View File

@ -4,7 +4,7 @@ const {createClient} = require('redis');
const {promisify} = require('util');
const config = {
prefix: 'sso_'
prefix: 'deploy_'
}
function client() {
@ -13,6 +13,9 @@ function client() {
const _client = client();
const SCAN = promisify(_client.SCAN).bind(_client);
module.exports = {
client: client,
HGET: promisify(_client.HGET).bind(_client),
@ -24,4 +27,18 @@ module.exports = {
HGETALL: promisify(_client.HGETALL).bind(_client),
SMEMBERS: promisify(_client.SMEMBERS).bind(_client),
RENAME: promisify(_client.RENAME).bind(_client),
HSCAN: promisify(_client.HSCAN).bind(_client),
SCAN: async function(match){
let coursor = 0;
let results = [];
do{
let res = await SCAN(coursor, 'MATCH', config.prefix+match);
coursor = Number(res[0]);
results.push(...res[1].map(e => e.replace(config.prefix, '')))
} while(coursor);
return results
}
};

View File

@ -4,187 +4,158 @@ const client = require('../utils/redis');
const objValidate = require('../utils/object_validate');
let table = {};
table.get = async function(data){
try{
// if the data argument was passed as the index key value, make a data
// object and add the index key to it.
if(typeof data !== 'object'){
let key = data;
data = {};
data[this._key] = key;
class Table{
constructor(data){
for(let key in data){
this[key] = data[key];
}
}
// Get all the hash keys for the passed index key.
let res = await client.HGETALL(`${this._name}_${data[this._key]}`);
static async get(index){
try{
// If the redis query resolved to something, prepare the data.
if(res){
let result = await client.HGETALL(`${this.prototype.constructor.name}_${index}`);
if(!result){
let error = new Error('EntryNotFound');
error.name = 'EntryNotFound';
error.message = `${this.prototype.constructor.name}:${index} does not exists`;
error.status = 404;
throw error;
}
// Redis always returns strings, use the keyMap schema to turn them
// back to native values.
res = objValidate.parseFromString(this._keyMap, res);
result = objValidate.parseFromString(this._keyMap, result);
// Make sure the index key in in the returned object.
res[this._key] = data[this._key];
// Create a instance for this redis entry.
var entry = Object.create(this);
// Insert the redis response into the instance.
Object.assign(entry, res);
// Return the instance to the caller.
return entry;
}
}catch(error){
throw error
}
let error = new Error('EntryNotFound');
error.name = 'EntryNotFound';
error.message = `${this._name}:${data[this._key]} does not exists`;
error.status = 404;
throw error;
};
table.exists = async function(data){
// Return true or false if the requested entry exists ignoring error's.
try{
await this.get(data);
return true
}catch(error){
return false;
}
};
table.list = async function(){
// return a list of all the index keys for this table.
try{
return await client.SMEMBERS(this._name);
}catch(error){
throw error;
}
};
table.listDetail = async function(){
// Return a list of the entries as instances.
let out = [];
for(let entry of await this.list()){
out.push(await this.get(entry));
}
return out
};
table.add = async function(data){
// Add a entry to this redis table.
try{
// Validate the passed data by the keyMap schema.
data = objValidate.processKeys(this._keyMap, data);
// Do not allow the caller to overwrite an existing index key,
if(data[this._key] && await this.exists(data)){
let error = new Error('EntryNameUsed');
error.name = 'EntryNameUsed';
error.message = `${this._name}:${data[this._key]} already exists`;
error.status = 409;
return new this.prototype.constructor(result)
}catch(error){
throw error;
}
// Add the key to the members for this redis table
await client.SADD(this._name, data[this._key]);
}
// Add the values for this entry.
for(let key of Object.keys(data)){
await client.HSET(`${this._name}_${data[this._key]}`, key, data[key]);
static async exists(index){
try{
await this.get(data);
return true
}catch(error){
return false;
}
}
static async list(){
// return a list of all the index keys for this table.
try{
return await client.SMEMBERS(this.prototype.constructor.name);
}catch(error){
throw error;
}
}
static async listDetail(){
// Return a list of the entries as instances.
let out = [];
for(let entry of await this.list()){
out.push(await this.get(entry));
}
// return the created redis entry as entry instance.
return await this.get(data[this._key]);
} catch(error){
throw error;
return out
}
};
table.update = async function(data, key){
// Update an existing entry.
try{
// If an index key is passed, we assume is passed, assume we are not
// part of an entry instance. Make one and recall this from from a entry
// instance,
if(key) return await (await this.get(key)).update(data);
static async add(data){
// Add a entry to this redis table.
try{
// Validate the passed data by the keyMap schema.
// Check to see if entry name changed.
if(data[this._key] && data[this._key] !== this[this._key]){
data = objValidate.processKeys(this._keyMap, data);
// Merge the current data into with the updated data
let newData = Object.assign({}, this, data);
// Do not allow the caller to overwrite an existing index key,
if(data[this._key] && await this.exists(data)){
let error = new Error('EntryNameUsed');
error.name = 'EntryNameUsed';
error.message = `${this.prototype.constructor.name}:${data[this._key]} already exists`;
error.status = 409;
// Remove the updated failed so it doesnt keep it
delete newData.updated;
// Create a new record for the updated entry. If that succeeds,
// delete the old recored
if(await this.add(newData)) await this.remove();
}else{
// Update what ever fields that where passed.
// Validate the passed data, ignoring required fields.
data = objValidate.processKeys(this._keyMap, data, true);
// Loop over the data fields and apply them to redis
for(let key of Object.keys(data)){
this[key] = data[key];
await client.HSET(`${this._name}_${this[this._key]}`, key, data[key]);
throw error;
}
// Add the key to the members for this redis table
await client.SADD(this.prototype.constructor.name, data[this._key]);
// Add the values for this entry.
for(let key of Object.keys(data)){
await client.HSET(`${this.prototype.constructor.name}_${data[this._key]}`, key, objValidate.parseToString(data[key]));
}
// return the created redis entry as entry instance.
return await this.get(data[this._key]);
} catch(error){
throw error;
}
return this;
} catch(error){
// Pass any error to the calling function
throw error;
}
};
table.remove = async function(data){
// Remove an entry from this table.
async update(data, key){
// Update an existing entry.
try{
// Check to see if entry name changed.
if(data[this.constructor._key] && data[this.constructor._key] !== this[this.constructor._key]){
data = data || this;
try{
// Remove the index key from the tables members list.
await client.SREM(this._name, data[this._key]);
// Merge the current data into with the updated data
let newData = Object.assign({}, this, data);
// Remove the entries hash values.
let count = await client.DEL(`${this._name}_${data[this._key]}`);
// Remove the updated failed so it doesnt keep it
delete newData.updated;
// Return the number of removed values to the caller.
return count;
// Create a new record for the updated entry. If that succeeds,
// delete the old recored
if(await this.add(newData)) await this.remove();
} catch(error) {
throw error;
}else{
// Update what ever fields that where passed.
// Validate the passed data, ignoring required fields.
data = objValidate.processKeys(this.constructor._keyMap, data, true);
// Loop over the data fields and apply them to redis
for(let key of Object.keys(data)){
this[key] = data[key];
await client.HSET(`${this.constructor.name}_${this[this.constructor._key]}`, key, data[key]);
}
}
return this;
} catch(error){
// Pass any error to the calling function
throw error;
}
}
};
function Table(data){
// Create a table instance.
let instance = Object.create(data);
Object.assign(instance, table);
async remove(data){
// Remove an entry from this table.
// Return the table instance to the caller.
return Object.create(instance);
try{
// Remove the index key from the tables members list.
await client.SREM(this.constructor.name, this[this.constructor._key]);
// Remove the entries hash values.
let count = await client.DEL(`${this.constructor.name}_${this[this.constructor._key]}`);
// Return the number of removed values to the caller.
return count;
} catch(error) {
throw error;
}
};
}
};
module.exports = Table;

View File

@ -16,7 +16,7 @@
<div class="card-deck">
<div class="shadow-lg card mb-3">
<div class="card-header shadow">
Password Log in
SSO Log in
</div>
<div class="card-header shadow actionMessage" style="display:none">
</div>
@ -48,53 +48,6 @@
</form>
</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>
<%- include('bottom') %>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>SSO Manager - Theta 42</title>
<title>Deployment Manager - Theta 42</title>
<!-- CSS are placed here -->
<link rel="stylesheet" href="/static/css/bootstrap-4.4.1.min.css">
<link rel='stylesheet' href='/static/css/styles.css' />
@ -25,7 +25,7 @@
</head>
<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">
<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">Deployment Manager - Theta 42</h5>
<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="/users"><i class="fad fa-users"></i>Users</a>