diff --git a/.gitignore b/.gitignore index 2d49c1f..e901d22 100755 --- a/.gitignore +++ b/.gitignore @@ -81,4 +81,7 @@ Berksfile.lock .zero-knife.rb Policyfile.lock.json -ops/cookbooks/vendor \ No newline at end of file +ops/cookbooks/vendor + +secrets.json +secrets.js diff --git a/nodejs/app.js b/nodejs/app.js index 08fddb7..1cf6f5f 100755 --- a/nodejs/app.js +++ b/nodejs/app.js @@ -3,11 +3,19 @@ const path = require('path'); const ejs = require('ejs') const express = require('express'); -const middleware = require('./middleware/auth'); // Set up the express app. const app = express(); +// Allow the express app to be exported into other files. +module.exports = app; + +// Build the conf object from the conf files. +app.conf = require('./conf/conf'); + +// Hold onto the auth middleware +const middleware = require('./middleware/auth'); + // load the JSON parser middleware. Express will parse JSON into native objects // for any request that has JSON in its content type. app.use(express.json()); @@ -51,6 +59,3 @@ app.use(function(err, req, res, next) { res.status(err.status || 500); res.json({name: err.name, message: err.message}); }); - -// Allow the express app to be exported into other files. -module.exports = app; \ No newline at end of file diff --git a/nodejs/conf/base.js b/nodejs/conf/base.js new file mode 100644 index 0000000..73ef0c9 --- /dev/null +++ b/nodejs/conf/base.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = { + userModel: 'redis', // pam, redis, ldap + ldap: { + url: 'ldap://192.168.1.55:389', + bindDN: 'cn=ldapclient service,ou=people,dc=theta42,dc=com', + bindPassword: '__IN SRECREST FILE__', + searchBase: 'ou=people,dc=theta42,dc=com', + userFilter: '(objectClass=inetOrgPerson)', + userNameAttribute: 'uid' + } +}; diff --git a/nodejs/conf/conf.js b/nodejs/conf/conf.js new file mode 100644 index 0000000..ce09580 --- /dev/null +++ b/nodejs/conf/conf.js @@ -0,0 +1,32 @@ +'use strict'; + +const extend = require('extend'); + +const environment = process.env.NODE_ENV || 'development'; + +function load(filePath, required){ + try { + return require(filePath); + } catch(error){ + if(error.name === 'SyntaxError'){ + console.error(`Loading ${filePath} file failed!\n`, error); + process.exit(1); + } else if (error.code === 'MODULE_NOT_FOUND'){ + console.warn(`No config file ${filePath} FOUND! This may cause issues...`); + if (required){ + process.exit(1); + } + return {}; + }else{ + console.dir(`Unknown error in loading ${filePath} config file.\n`, error); + } + } +}; + +module.exports = extend( + true, // enable deep copy + load('./base', true), + load(`./${environment}`), + load('./secrets'), + {environment} +); diff --git a/nodejs/middleware/auth.js b/nodejs/middleware/auth.js index 777620b..6b98731 100755 --- a/nodejs/middleware/auth.js +++ b/nodejs/middleware/auth.js @@ -1,6 +1,6 @@ 'use strict'; -const {Auth} = require('../models/auth_redis'); +const {Auth} = require('../models/auth'); async function auth(req, res, next){ try{ diff --git a/nodejs/models/auth_pam.js b/nodejs/models/auth.js similarity index 73% rename from nodejs/models/auth_pam.js rename to nodejs/models/auth.js index 4c1eb80..e5d68af 100644 --- a/nodejs/models/auth_pam.js +++ b/nodejs/models/auth.js @@ -1,7 +1,3 @@ -const {promisify} = require('util'); -const pam = require('authenticate-pam'); -const authenticate = promisify(pam.authenticate); - const {User} = require('./user'); const {Token, AuthToken} = require('./token'); @@ -19,19 +15,16 @@ Auth.errors.login = function(){ Auth.login = async function(data){ try{ - let auth = await authenticate(data.username, data.password); - let user = await User.get(data); + let user = await User.login(data); let token = await AuthToken.add(user); return {user, token} }catch(error){ - if (error == 'Authentication failure'){ - throw this.errors.login() - } throw error; } }; + Auth.checkToken = async function(data){ try{ let token = await AuthToken.get(data); diff --git a/nodejs/models/auth_red.js b/nodejs/models/auth_red.js deleted file mode 100644 index 4c1eb80..0000000 --- a/nodejs/models/auth_red.js +++ /dev/null @@ -1,55 +0,0 @@ -const {promisify} = require('util'); -const pam = require('authenticate-pam'); -const authenticate = promisify(pam.authenticate); - -const {User} = require('./user'); -const {Token, AuthToken} = require('./token'); - -Auth = {} -Auth.errors = {} - -Auth.errors.login = function(){ - let error = new Error('PamLoginFailed'); - error.name = 'PamLoginFailed'; - error.message = `Invalid Credentials, login failed.`; - error.status = 401; - - return error; -} - -Auth.login = async function(data){ - try{ - let auth = await authenticate(data.username, data.password); - let user = await User.get(data); - let token = await AuthToken.add(user); - - return {user, token} - }catch(error){ - if (error == 'Authentication failure'){ - throw this.errors.login() - } - throw error; - } -}; - -Auth.checkToken = async function(data){ - try{ - let token = await AuthToken.get(data); - if(token.is_valid){ - return await User.get(token.created_by); - } - }catch(error){ - throw this.errors.login(); - } -}; - -Auth.logOut = async function(data){ - try{ - let token = await AuthToken.get(data); - await token.remove(); - }catch(error){ - throw error; - } -} - -module.exports = {Auth, AuthToken}; diff --git a/nodejs/models/auth_redis.js b/nodejs/models/auth_redis.js deleted file mode 100644 index 4c01934..0000000 --- a/nodejs/models/auth_redis.js +++ /dev/null @@ -1,62 +0,0 @@ -'use strict'; - -const bcrypt = require('bcrypt'); -const saltRounds = 10; - -const {User} = require('./user_redis'); -const {Token, AuthToken} = require('./token'); - -var Auth = {} -Auth.errors = {} - -Auth.errors.login = function(){ - let error = new Error('ResisLoginFailed'); - error.name = 'RedisLoginFailed'; - error.message = `Invalid Credentials, login failed.`; - error.status = 401; - - return error; -} - -Auth.login = async function(data){ - try{ - let user = await User.get(data); - - let auth = await bcrypt.compare(data.password, user.password); - - if(auth){ - let token = await AuthToken.add(user); - - return {user, token} - }else{ - throw this.errors.login(); - } - }catch(error){ - if (error == 'Authentication failure'){ - throw this.errors.login() - } - throw error; - } -}; - -Auth.checkToken = async function(data){ - try{ - let token = await AuthToken.get(data); - if(token.is_valid){ - return await User.get(token.created_by); - } - }catch(error){ - throw this.errors.login(); - } -}; - -Auth.logOut = async function(data){ - try{ - let token = await AuthToken.get(data); - await token.remove(); - }catch(error){ - throw error; - } -} - -module.exports = {Auth, AuthToken}; diff --git a/nodejs/models/user.js b/nodejs/models/user.js new file mode 100644 index 0000000..67338a0 --- /dev/null +++ b/nodejs/models/user.js @@ -0,0 +1,7 @@ +'use strict'; + +const conf = require('../app').conf; + +const User = require(`./user_${conf.userModel}`) + +module.exports = User; diff --git a/nodejs/models/user_ldap.js b/nodejs/models/user_ldap.js new file mode 100644 index 0000000..7d8f993 --- /dev/null +++ b/nodejs/models/user_ldap.js @@ -0,0 +1,235 @@ +'use strict'; + +const { Client, Attribute, Change } = require('ldapts'); +const {Token, InviteToken} = require('./token'); +const conf = require('../app').conf.ldap; + +const client = new Client({ + url: conf.url, +}); + + +const user_parse = function(data){ + if(data[conf.userNameAttribute]){ + data.username = data[conf.userNameAttribute] + delete data[conf.userNameAttribute]; + } + + if(data.uidNumber){ + data.uid = data.uidNumber; + delete data.uidNumber; + } + + return data; +} + +var User = {} + +User.backing = "LDAP"; + +User.keyMap = { + 'username': {isRequired: true, type: 'string', min: 3, max: 500}, + 'password': {isRequired: true, type: 'string', min: 3, max: 500}, +} + +User.list = async function(){ + try{ + await client.bind(conf.bindDN, conf.bindPassword); + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: conf.userFilter, + }); + + await client.unbind(); + + return res.searchEntries.map(function(user){return user.uid}); + }catch(error){ + throw error; + } +}; + +User.listDetail = async function(){ + try{ + await client.bind(conf.bindDN, conf.bindPassword); + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: conf.userFilter, + }); + + await client.unbind(); + + let users = [] + + for(let user of res.searchEntries){ + let obj = Object.create(this); + Object.assign(obj, user_parse(user)); + + users.push(obj) + + } + + return users; + + }catch(error){ + throw error; + } +}; + +User.get = async function(data){ + try{ + if(typeof data !== 'object'){ + let username = data; + data = {}; + data.username = username; + } + + await client.bind(conf.bindDN, conf.bindPassword); + + let filter = `(&${conf.userFilter}(${conf.userNameAttribute}=${data.username}))`; + + const res = await client.search(conf.searchBase, { + scope: 'sub', + filter: filter, + }); + + await client.unbind(); + + let user = res.searchEntries[0] + + if(user){ + let obj = Object.create(this); + Object.assign(obj, user_parse(user)); + + return obj; + }else{ + let error = new Error('UserNotFound'); + error.name = 'UserNotFound'; + error.message = `LDAP:${data.username} does not exists`; + error.status = 404; + throw error; + } + }catch(error){ + throw error; + } +}; + +User.exists = async function(data){ + // Return true or false if the requested entry exists ignoring error's. + try{ + await this.get(data); + + return true + }catch(error){ + return false; + } +}; + +// User.add = async function(data) { +// try{ +// data = objValidate.processKeys(this.keyMap, data); +// let systemUser = await linuxUser.addUser(data.username); +// await require('util').promisify(setTimeout)(500) +// let systemUserPassword = await linuxUser.setPassword(data.username, data.password); + +// return this.get(data.username); + +// }catch(error){ +// if(error.message.includes('exists')){ +// let error = new Error('UserNameUsed'); +// error.name = 'UserNameUsed'; +// error.message = `PAM:${data.username} already exists`; +// error.status = 409; + +// throw error; +// } +// throw error; +// } +// }; + +// User.addByInvite = async function(data){ +// try{ +// let token = await InviteToken.get(data.token); + +// if(!token.is_valid){ +// let error = new Error('Token Invalid'); +// error.name = 'Token Invalid'; +// error.message = `Token is not valid or as allready been used. ${data.token}`; +// error.status = 401; +// throw error; +// } + +// let user = await this.add(data); + +// if(user){ +// await token.consume({claimed_by: user.username}); +// return user; +// } + +// }catch(error){ +// throw error; +// } + +// }; + +// User.remove = async function(data){ +// try{ +// return await linuxUser.removeUser(this.username); +// }catch(error){ +// throw error; +// } +// }; + +// User.setPassword = async function(data){ +// try{ +// await linuxUser.setPassword(this.username, data.password); + +// return this; +// }catch(error){ +// throw error; +// } +// }; + +User.invite = async function(){ + try{ + let token = await InviteToken.add({created_by: this.username}); + + return token; + + }catch(error){ + throw error; + } +}; + +User.login = async function(data){ + try{ + let user = await this.get(data.username); + + await client.bind(user.dn, data.password); + + await client.unbind(); + + return user; + + }catch(error){ + throw error; + } +}; + + +module.exports = {User}; + + +// (async function(){ +// try{ +// console.log(await User.list()); + +// console.log(await User.listDetail()); + +// console.log(await User.get('wmantly')) + +// }catch(error){ +// console.error(error) +// } +// })() \ No newline at end of file diff --git a/nodejs/models/user_pam.js b/nodejs/models/user_pam.js index b68e768..998e940 100644 --- a/nodejs/models/user_pam.js +++ b/nodejs/models/user_pam.js @@ -3,6 +3,9 @@ const linuxUser = require('linux-sys-user').promise(); const objValidate = require('../utils/object_validate'); const {Token, InviteToken} = require('./token'); +const {promisify} = require('util'); +const pam = require('authenticate-pam'); +const authenticate = promisify(pam.authenticate); var User = {} @@ -11,6 +14,8 @@ User.keyMap = { 'password': {isRequired: true, type: 'string', min: 3, max: 500}, } +User.backing = "PAM"; + User.list = async function(){ try{ let users = await linuxUser.getUsers(); @@ -139,4 +144,18 @@ User.invite = async function(){ } }; +User.login = async function(data){ + try{ + let auth = await authenticate(data.username, data.password); + let user = await User.get(data); + + return user; + }catch(error){ + if (error == 'Authentication failure'){ + throw this.errors.login() + } + throw error; + } +}; + module.exports = {User}; diff --git a/nodejs/models/user_redis.js b/nodejs/models/user_redis.js index e9fdb06..f76fc3c 100644 --- a/nodejs/models/user_redis.js +++ b/nodejs/models/user_redis.js @@ -19,6 +19,9 @@ const User = require('../utils/redis_model')({ } }); +User.backing = "redis"; + + User.add = async function(data) { try{ data['password'] = await bcrypt.hash(data['password'], saltRounds); @@ -78,6 +81,25 @@ User.invite = async function(){ } }; +User.login = async function(data){ + try{ + let user = await User.get(data); + + let auth = await bcrypt.compare(data.password, user.password); + + if(auth){ + return user + }else{ + throw this.errors.login(); + } + }catch(error){ + if (error == 'Authentication failure'){ + throw this.errors.login() + } + throw error; + } +}; + module.exports = {User}; diff --git a/nodejs/package.json b/nodejs/package.json index fac02f4..9dae9e2 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -13,6 +13,8 @@ }, "dependencies": { "authenticate-pam": "github:WeiAnAn/node-authenticate-pam", + "ldapts": "^2.2.1", + "extend": "^3.0.2", "ejs": "^3.0.1", "express": "~4.16.1", "linux-sys-user": "^1.1.0", diff --git a/nodejs/routes/auth.js b/nodejs/routes/auth.js index d4c41a0..3c5aa0d 100755 --- a/nodejs/routes/auth.js +++ b/nodejs/routes/auth.js @@ -1,8 +1,8 @@ 'use strict'; const router = require('express').Router(); -const {User} = require('../models/user_redis'); -const {Auth, AuthToken} = require('../models/auth_redis'); +const {User} = require('../models/user'); +const {Auth, AuthToken} = require('../models/auth'); router.post('/login', async function(req, res, next){ diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js index c607c49..121a61f 100755 --- a/nodejs/routes/user.js +++ b/nodejs/routes/user.js @@ -1,7 +1,7 @@ 'use strict'; const router = require('express').Router(); -const {User} = require('../models/user_redis'); +const {User} = require('../models/user'); router.get('/', async function(req, res, next){ try{