diff --git a/nodejs/middleware/auth.js b/nodejs/middleware/auth.js
index 6b98731..808d57b 100755
--- a/nodejs/middleware/auth.js
+++ b/nodejs/middleware/auth.js
@@ -5,7 +5,8 @@ const {Auth} = require('../models/auth');
async function auth(req, res, next){
try{
let user = await Auth.checkToken({token: req.header('auth-token')});
- if(user.username){
+
+ if(user.uid){
req.user = user;
return next();
}
diff --git a/nodejs/models/auth.js b/nodejs/models/auth.js
index e5d68af..42e2dba 100644
--- a/nodejs/models/auth.js
+++ b/nodejs/models/auth.js
@@ -1,12 +1,14 @@
+'use strict';
+
const {User} = require('./user');
const {Token, AuthToken} = require('./token');
-Auth = {}
+var Auth = {}
Auth.errors = {}
Auth.errors.login = function(){
- let error = new Error('PamLoginFailed');
- error.name = 'PamLoginFailed';
+ let error = new Error('LDAPLoginFailed');
+ error.name = 'LDAPLoginFailed';
error.message = `Invalid Credentials, login failed.`;
error.status = 401;
diff --git a/nodejs/models/email.js b/nodejs/models/email.js
new file mode 100644
index 0000000..4dc8bc2
--- /dev/null
+++ b/nodejs/models/email.js
@@ -0,0 +1,32 @@
+'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 ',
+ 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};
diff --git a/nodejs/models/group_ldap.js b/nodejs/models/group_ldap.js
index a48c482..2c33609 100644
--- a/nodejs/models/group_ldap.js
+++ b/nodejs/models/group_ldap.js
@@ -4,7 +4,7 @@ const { Client, Attribute, Change } = require('ldapts');
const conf = require('../app').conf.ldap;
const client = new Client({
- url: conf.url,
+ url: conf.url,
});
async function getGroups(client){
@@ -25,21 +25,21 @@ async function getGroups(client){
}
async function addGroup(client, data){
- try{
+ try{
- await client.add(`cn=${data.name},${conf.groupBase}`, {
- cn: data.name,
- member: data.owner,
- description: data.description,
- owner: data.owner,
- objectclass: [ 'groupOfNames', 'top' ]
- });
+ 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;
+ return data;
- }catch(error){
- throw error;
- }
+ }catch(error){
+ throw error;
+ }
}
async function addMember(client, group, user){
@@ -61,18 +61,18 @@ async function addMember(client, group, user){
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;
- }
+ 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;
+ }
}
@@ -134,4 +134,19 @@ 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){
+
+ }
+}
+
module.exports = {Group};
diff --git a/nodejs/models/token.js b/nodejs/models/token.js
index 496e26c..9b0ac11 100644
--- a/nodejs/models/token.js
+++ b/nodejs/models/token.js
@@ -29,7 +29,9 @@ Token.check = async function(data){
var InviteToken = Object.create(Token({
name: 'invite',
keyMap:{
- claimed_by: {default:"__NONE__", isRequired: false, type: 'string',}
+ 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},
}
}));
@@ -53,7 +55,7 @@ var AuthToken = Object.create(Token({
}));
AuthToken.add = async function(data){
- data.created_by = data.username;
+ data.created_by = data.uid;
return AuthToken.__proto__.add(data);
};
diff --git a/nodejs/models/user_ldap.js b/nodejs/models/user_ldap.js
index 17842de..59eb631 100644
--- a/nodejs/models/user_ldap.js
+++ b/nodejs/models/user_ldap.js
@@ -3,6 +3,7 @@
const { Client, Attribute, Change } = require('ldapts');
const crypto = require('crypto');
+const {Mail} = require('./email');
const {Token, InviteToken} = require('./token');
const conf = require('../app').conf.ldap;
@@ -47,7 +48,9 @@ async function addPosixAccount(client, data){
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,
@@ -81,6 +84,14 @@ async function addLdapUser(client, data){
}
}
+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 changeLdapPassword(client, data){
try{
@@ -100,14 +111,8 @@ async function changeLdapPassword(client, data){
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;
}
@@ -115,11 +120,6 @@ 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);
@@ -170,14 +170,14 @@ User.listDetail = async function(){
User.get = async function(data){
try{
if(typeof data !== 'object'){
- let username = data;
+ let uid = data;
data = {};
- data.username = username;
+ data.uid = uid;
}
await client.bind(conf.bindDN, conf.bindPassword);
- let filter = `(&${conf.userFilter}(${conf.userNameAttribute}=${data.username}))`;
+ let filter = `(&${conf.userFilter}(${conf.userNameAttribute}=${data.uid}))`;
const res = await client.search(conf.userBase, {
scope: 'sub',
@@ -225,7 +225,18 @@ User.add = async function(data) {
await client.unbind();
- return this.get(data.uid);
+ let user = await this.get(data.uid);
+
+
+ await Mail.sendTemplate(
+ user.mail,
+ 'welcome',
+ {
+ user: user
+ }
+ )
+
+ return user;
}catch(error){
if(error.message.includes('exists')){
@@ -244,7 +255,7 @@ User.addByInvite = async function(data){
try{
let token = await InviteToken.get(data.token);
- if(!token.is_valid){
+ 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}`;
@@ -252,6 +263,8 @@ User.addByInvite = async function(data){
throw error;
}
+ data.mail = token.mail;
+
let user = await this.add(data);
if(user){
@@ -265,13 +278,37 @@ User.addByInvite = async function(data){
};
-// User.remove = async function(data){
-// try{
-// return await linuxUser.removeUser(this.username);
-// }catch(error){
-// throw error;
-// }
-// };
+User.verifyEmail = async function(data){
+ try{
+ 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}`
+ }
+ )
+ }catch(error){
+ 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{
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index f58349e..cdb4a3d 100644
--- a/nodejs/package-lock.json
+++ b/nodejs/package-lock.json
@@ -444,11 +444,21 @@
"brace-expansion": "^1.1.7"
}
},
+ "moment": {
+ "version": "2.25.3",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz",
+ "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg=="
+ },
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
+ "mustache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
+ "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA=="
+ },
"negotiator": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
diff --git a/nodejs/package.json b/nodejs/package.json
index d0ddd0d..80bd6e5 100755
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -17,6 +17,8 @@
"express": "~4.16.1",
"extend": "^3.0.2",
"ldapts": "^2.2.1",
+ "moment": "^2.25.3",
+ "mustache": "^4.0.1",
"redis": "^2.8.0",
"smtpc": "^0.1.2"
},
diff --git a/nodejs/public/css/styles.css b/nodejs/public/css/styles.css
index e69de29..ad05db6 100755
--- a/nodejs/public/css/styles.css
+++ b/nodejs/public/css/styles.css
@@ -0,0 +1,13 @@
+
+.hover-effect {
+ transition: all .5s ease;
+}
+
+.hover-effect:hover {
+ color: darkblue;
+ transition: all .5s ease;
+ background-color: inherit;
+ -ms-transform: scale(1.1);
+ -webkit-transform: scale(1.1);
+ transform: scale(1.1); /* Standard syntax */
+}
diff --git a/nodejs/public/js/app.js b/nodejs/public/js/app.js
index a1837be..f7415ea 100755
--- a/nodejs/public/js/app.js
+++ b/nodejs/public/js/app.js
@@ -112,8 +112,9 @@ app.auth = (function(app) {
});
}
- function logOut(){
+ function logOut(callack){
localStorage.removeItem('APIToken');
+ callack();
}
function makeUserFromInvite(args, callack){
@@ -150,13 +151,13 @@ app.user = (function(app){
}
function remove(args, callack){
- app.api.delete('user/'+ args.username, function(error, data){
+ app.api.delete('user/'+ args.uid, function(error, data){
callack(error, data);
});
}
function changePassword(args, callack){
- app.api.put('users/'+ arg.username || '', args, function(error, data){
+ app.api.put('users/'+ arg.uid || '', args, function(error, data){
callack(error, data);
});
}
@@ -313,8 +314,6 @@ function formAJAX( btn, del ) {
var formData = $form.find( '[name]' ).serializeObject(); // builds query formDataing
var method = $form.attr('method') || 'post';
- console.log('formAJAX method', method)
-
if( !$form.validate(
{
form: {
@@ -326,9 +325,9 @@ function formAJAX( btn, del ) {
}
app.api[method]($form.attr( 'action' ), formData, function(error, data){
- tableAJAX( data.message ); //re-populate table
+ app.util.actionMessage( data.message ); //re-populate table
if(!error){
- eval( $form.attr( 'evalAJAX' ) ); //gets JS to run after completion
+ eval($form.attr('evalAJAX')); //gets JS to run after completion
}
});
diff --git a/nodejs/public/js/val.js b/nodejs/public/js/val.js
index d5a75bc..331295a 100755
--- a/nodejs/public/js/val.js
+++ b/nodejs/public/js/val.js
@@ -20,8 +20,8 @@
return;
}
- $( '' ).html( ' - ' + error_message ).appendTo( $input.siblings( 'label' ) );
- $input.parent().addClass("has-error");
+ $( '' ).html( ' - ' + error_message ).appendTo( $input.parents('.form-group').children( 'label' ) );
+ $input.addClass("is-invalid");
failedCount++;
return false;
}
@@ -35,8 +35,8 @@
value = $input.val(), //link to input value
rule = attr[0];
- $input.siblings( 'label' ).children( 'b' ).remove(); //removes old error
- $input.parent().removeClass( "has-error" ); //removes has-error class
+ $input.parents('.form-group').children( 'label' ).children( 'b' ).remove(); //removes old error
+ $input.removeClass( "is-invalid" ); //removes is-invalid class
//checks if field is required, and length
if (isNaN(requirement) === false && requirement && value.length < requirement) {
diff --git a/nodejs/routes/auth.js b/nodejs/routes/auth.js
index 3c5aa0d..311e016 100755
--- a/nodejs/routes/auth.js
+++ b/nodejs/routes/auth.js
@@ -29,14 +29,15 @@ router.all('/logout', async function(req, res, next){
}
});
-router.post('/invite/:token', async function(req, res, next) {
+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.username,
+ user: user.uid,
token: token.token
});
@@ -46,6 +47,21 @@ router.post('/invite/:token', async function(req, res, next) {
});
+router.post('/invite/:token', async function(req, res, next){
+ try{
+ let data = {
+ token: req.params.token,
+ url: `${req.protocol}://${req.hostname}`,
+ mail: req.body.mail,
+ }
+
+ await User.verifyEmail(data);
+ return res.send({message: 'sent'});
+ }catch(error){
+ next(error)
+ }
+});
+
module.exports = router;
/*
diff --git a/nodejs/routes/index.js b/nodejs/routes/index.js
index 9073ba7..287a6ee 100755
--- a/nodejs/routes/index.js
+++ b/nodejs/routes/index.js
@@ -1,7 +1,11 @@
+'use strict';
+
var express = require('express');
var router = express.Router();
+const moment = require('moment');
const {InviteToken} = require('./../models/token');
+
/* GET home page. */
router.get('/', async function(req, res, next) {
res.render('home', { title: 'Express' });
@@ -12,21 +16,38 @@ router.get('/users', function(req, res, next) {
res.render('users', { title: 'Express' });
});
-router.get('/login/invite/:token', async function(req, res, next){
+router.get('/login/invite/:token/:mailToken', async function(req, res, next){
try{
- console.log('token', req.params.token)
+
let token = await InviteToken.get(req.params.token);
- console.log('invite', token);
- if(token.is_valid){
+
+ 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({status: 404});
+ 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) {
res.render('login', {redirect: req.query.redirect});
diff --git a/nodejs/routes/user.js b/nodejs/routes/user.js
index 06c054e..cb41914 100755
--- a/nodejs/routes/user.js
+++ b/nodejs/routes/user.js
@@ -15,7 +15,7 @@ router.get('/', async function(req, res, next){
router.post('/', async function(req, res, next){
try{
- req.body.created_by = req.user.username
+ req.body.created_by = req.user.uid
return res.json({results: await User.add(req.body)});
}catch(error){
@@ -23,11 +23,13 @@ router.post('/', async function(req, res, next){
}
});
-router.delete('/:username', async function(req, res, next){
+router.delete('/:uid', async function(req, res, next){
try{
- let user = await User.get(req.params.username);
+ let user = await User.get(req.params.uid);
- return res.json({username: req.params.username, results: await user.remove()})
+ console.log('delete user', user);
+
+ return res.json({uid: req.params.uid, results: await user.remove()})
}catch(error){
next(error);
}
@@ -36,7 +38,7 @@ router.delete('/:username', async function(req, res, next){
router.get('/me', async function(req, res, next){
try{
- return res.json(await User.get({username: req.user.username}));
+ return res.json(await User.get({uid: req.user.uid}));
}catch(error){
next(error);
}
@@ -50,9 +52,9 @@ router.put('/password', async function(req, res, next){
}
});
-router.put('/password/:username', async function(req, res, next){
+router.put('/password/:uid', async function(req, res, next){
try{
- let user = await User.get(req.params.username);
+ let user = await User.get(req.params.uid);
return res.json({results: await user.setPassword(req.body)});
}catch(error){
next(error);
@@ -72,7 +74,7 @@ router.post('/invite', async function(req, res, next){
router.post('/key', async function(req, res, next){
try{
let added = await User.addSSHkey({
- username: req.user.username,
+ uid: req.user.uid,
key: req.body.key
});
diff --git a/nodejs/testmail.js b/nodejs/testmail.js
deleted file mode 100644
index 2ab103c..0000000
--- a/nodejs/testmail.js
+++ /dev/null
@@ -1,13 +0,0 @@
-
-// using Twilio SendGrid's v3 Node.js Library
-// https://github.com/sendgrid/sendgrid-nodejs
-const sgMail = require('@sendgrid/mail');
-sgMail.setApiKey(process.env.SENDGRID_API_KEY);
-const msg = {
- to: 'wmantly@gmail.com',
- from: 'info@no-reply.theta42.com',
- subject: 'Sending with Twilio SendGrid is Fun',
- text: 'and easy to do anywhere, even with Node.js',
- html: 'and easy to do anywhere, even with Node.js ',
-};
-sgMail.send(msg);
\ No newline at end of file
diff --git a/nodejs/utils/redis.js b/nodejs/utils/redis.js
index e12e6e3..e032540 100755
--- a/nodejs/utils/redis.js
+++ b/nodejs/utils/redis.js
@@ -1,8 +1,10 @@
+'use strict';
+
const {createClient} = require('redis');
const {promisify} = require('util');
const config = {
- prefix: 'proxy_'
+ prefix: 'sso_'
}
function client() {
diff --git a/nodejs/views/email_templates/untitled b/nodejs/views/email_templates/untitled
new file mode 100644
index 0000000..e69de29
diff --git a/nodejs/views/email_templates/validate_link.js b/nodejs/views/email_templates/validate_link.js
new file mode 100644
index 0000000..ef608c6
--- /dev/null
+++ b/nodejs/views/email_templates/validate_link.js
@@ -0,0 +1,24 @@
+module.exports = {
+ subject: 'Validate email for Theta 42 account',
+ message: `
+ Theta 42 account
+
+
+ Welcome,
+
+
+
+ We need to verify the provided email address in order to continue. Please
+ follow the link below to verify this email address:
+
+
+
+ {{ link }}
+
+
+
+ Thank you,
+ Theta 42
+
+`
+};
diff --git a/nodejs/views/email_templates/welcome.js b/nodejs/views/email_templates/welcome.js
new file mode 100644
index 0000000..41b6c80
--- /dev/null
+++ b/nodejs/views/email_templates/welcome.js
@@ -0,0 +1,34 @@
+module.exports = {
+ subject: 'Welcome to Theta 42!',
+ message: `
+
+ Welcome {{user.givenName}},
+
+
+
+ Your new Theta 42 Single sign-on account is ready to use. Here is some
+ information to get you started.
+
+
+
+ Your username is {{user.uid}}
+
+
+
+ You can manage your account at https://sso.theta42.com
+
+
+
+ You account is ready to be used now, test it by SSHing into the Theta 42
+ jump host \`ssh {{user.uid}}@718it.biz\`
+
+
+
+ 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.
+
+ Thank you,
+ Theta 42
+
+`
+};
diff --git a/nodejs/views/home.ejs b/nodejs/views/home.ejs
index c839ee9..058dde7 100644
--- a/nodejs/views/home.ejs
+++ b/nodejs/views/home.ejs
@@ -11,8 +11,6 @@
LDAP DN: {{dn}}
Joined {{createTimestamp}}
Joined {{modifyTimestamp}}
-
-
-
+
New Invite Token
@@ -71,24 +69,47 @@
-
+
- <%- include('user_form') %>
-
+