This commit is contained in:
William Mantly 2020-05-05 23:07:00 -04:00
parent f2309463a4
commit 4d51a4ac9e
26 changed files with 476 additions and 189 deletions

View File

@ -5,7 +5,8 @@ const {Auth} = require('../models/auth');
async function auth(req, res, next){ async function auth(req, res, next){
try{ try{
let user = await Auth.checkToken({token: req.header('auth-token')}); let user = await Auth.checkToken({token: req.header('auth-token')});
if(user.username){
if(user.uid){
req.user = user; req.user = user;
return next(); return next();
} }

View File

@ -1,12 +1,14 @@
'use strict';
const {User} = require('./user'); const {User} = require('./user');
const {Token, AuthToken} = require('./token'); const {Token, AuthToken} = require('./token');
Auth = {} var Auth = {}
Auth.errors = {} Auth.errors = {}
Auth.errors.login = function(){ Auth.errors.login = function(){
let error = new Error('PamLoginFailed'); let error = new Error('LDAPLoginFailed');
error.name = 'PamLoginFailed'; error.name = 'LDAPLoginFailed';
error.message = `Invalid Credentials, login failed.`; error.message = `Invalid Credentials, login failed.`;
error.status = 401; error.status = 401;

32
nodejs/models/email.js Normal file
View File

@ -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 <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};

View File

@ -4,7 +4,7 @@ const { Client, Attribute, Change } = require('ldapts');
const conf = require('../app').conf.ldap; const conf = require('../app').conf.ldap;
const client = new Client({ const client = new Client({
url: conf.url, url: conf.url,
}); });
async function getGroups(client){ async function getGroups(client){
@ -25,21 +25,21 @@ async function getGroups(client){
} }
async function addGroup(client, data){ async function addGroup(client, data){
try{ try{
await client.add(`cn=${data.name},${conf.groupBase}`, { await client.add(`cn=${data.name},${conf.groupBase}`, {
cn: data.name, cn: data.name,
member: data.owner, member: data.owner,
description: data.description, description: data.description,
owner: data.owner, owner: data.owner,
objectclass: [ 'groupOfNames', 'top' ] objectclass: [ 'groupOfNames', 'top' ]
}); });
return data; return data;
}catch(error){ }catch(error){
throw error; throw error;
} }
} }
async function addMember(client, group, user){ async function addMember(client, group, user){
@ -61,18 +61,18 @@ async function addMember(client, group, user){
async function removeMember(client, group, user){ async function removeMember(client, group, user){
try{ try{
await client.modify(group.dn, [ await client.modify(group.dn, [
new Change({ new Change({
operation: 'delete', operation: 'delete',
modification: new Attribute({ modification: new Attribute({
type: 'member', type: 'member',
values: [user.dn] values: [user.dn]
})}), })}),
]); ]);
}catch(error){ }catch(error){
if(error = "TypeOrValueExistsError")return ; if(error = "TypeOrValueExistsError")return ;
throw error; 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}; module.exports = {Group};

View File

@ -29,7 +29,9 @@ Token.check = async function(data){
var InviteToken = Object.create(Token({ var InviteToken = Object.create(Token({
name: 'invite', name: 'invite',
keyMap:{ 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){ AuthToken.add = async function(data){
data.created_by = data.username; data.created_by = data.uid;
return AuthToken.__proto__.add(data); return AuthToken.__proto__.add(data);
}; };

View File

@ -3,6 +3,7 @@
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} = require('./token'); const {Token, InviteToken} = require('./token');
const conf = require('../app').conf.ldap; const conf = require('../app').conf.ldap;
@ -47,7 +48,9 @@ async function addPosixAccount(client, data){
uid: data.uid, uid: data.uid,
uidNumber: data.uidNumber, uidNumber: data.uidNumber,
gidNumber: data.gidNumber, gidNumber: data.gidNumber,
givenName: data.givenName,
mail: data.mail, mail: data.mail,
mobile: data.mobile,
loginShell: data.loginShell, loginShell: data.loginShell,
homeDirectory: data.homeDirectory, homeDirectory: data.homeDirectory,
userPassword: data.userPassword, 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){ async function changeLdapPassword(client, data){
try{ try{
@ -100,14 +111,8 @@ async function changeLdapPassword(client, data){
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]
// delete data[conf.userNameAttribute];
} }
// if(data.uidNumber){
// data.uid = data.uidNumber;
// delete data.uidNumber;
// }
return data; return data;
} }
@ -115,11 +120,6 @@ var User = {}
User.backing = "LDAP"; 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(){ User.list = async function(){
try{ try{
await client.bind(conf.bindDN, conf.bindPassword); await client.bind(conf.bindDN, conf.bindPassword);
@ -170,14 +170,14 @@ User.listDetail = async function(){
User.get = async function(data){ User.get = async function(data){
try{ try{
if(typeof data !== 'object'){ if(typeof data !== 'object'){
let username = data; let uid = data;
data = {}; data = {};
data.username = username; data.uid = uid;
} }
await client.bind(conf.bindDN, conf.bindPassword); 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, { const res = await client.search(conf.userBase, {
scope: 'sub', scope: 'sub',
@ -225,7 +225,18 @@ User.add = async function(data) {
await client.unbind(); 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){ }catch(error){
if(error.message.includes('exists')){ if(error.message.includes('exists')){
@ -244,7 +255,7 @@ User.addByInvite = async function(data){
try{ try{
let token = await InviteToken.get(data.token); 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'); let error = new Error('Token Invalid');
error.name = 'Token Invalid'; error.name = 'Token Invalid';
error.message = `Token is not valid or as allready been used. ${data.token}`; error.message = `Token is not valid or as allready been used. ${data.token}`;
@ -252,6 +263,8 @@ User.addByInvite = async function(data){
throw error; throw error;
} }
data.mail = token.mail;
let user = await this.add(data); let user = await this.add(data);
if(user){ if(user){
@ -265,13 +278,37 @@ User.addByInvite = async function(data){
}; };
// User.remove = async function(data){ User.verifyEmail = async function(data){
// try{ try{
// return await linuxUser.removeUser(this.username); let token = await InviteToken.get(data.token);
// }catch(error){ await token.update({mail: data.mail})
// throw error; 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){ // User.setPassword = async function(data){
// try{ // try{

View File

@ -444,11 +444,21 @@
"brace-expansion": "^1.1.7" "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": { "ms": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" "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": { "negotiator": {
"version": "0.6.2", "version": "0.6.2",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",

View File

@ -17,6 +17,8 @@
"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",
"mustache": "^4.0.1",
"redis": "^2.8.0", "redis": "^2.8.0",
"smtpc": "^0.1.2" "smtpc": "^0.1.2"
}, },

View File

@ -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 */
}

View File

@ -112,8 +112,9 @@ app.auth = (function(app) {
}); });
} }
function logOut(){ function logOut(callack){
localStorage.removeItem('APIToken'); localStorage.removeItem('APIToken');
callack();
} }
function makeUserFromInvite(args, callack){ function makeUserFromInvite(args, callack){
@ -150,13 +151,13 @@ app.user = (function(app){
} }
function remove(args, callack){ 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); callack(error, data);
}); });
} }
function changePassword(args, callack){ 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); callack(error, data);
}); });
} }
@ -313,8 +314,6 @@ function formAJAX( btn, del ) {
var formData = $form.find( '[name]' ).serializeObject(); // builds query formDataing var formData = $form.find( '[name]' ).serializeObject(); // builds query formDataing
var method = $form.attr('method') || 'post'; var method = $form.attr('method') || 'post';
console.log('formAJAX method', method)
if( !$form.validate( if( !$form.validate(
{ {
form: { form: {
@ -326,9 +325,9 @@ function formAJAX( btn, del ) {
} }
app.api[method]($form.attr( 'action' ), formData, function(error, data){ 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){ if(!error){
eval( $form.attr( 'evalAJAX' ) ); //gets JS to run after completion eval($form.attr('evalAJAX')); //gets JS to run after completion
} }
}); });

View File

@ -20,8 +20,8 @@
return; return;
} }
$( '<b>' ).html( ' - ' + error_message ).appendTo( $input.siblings( 'label' ) ); $( '<b>' ).html( ' - ' + error_message ).appendTo( $input.parents('.form-group').children( 'label' ) );
$input.parent().addClass("has-error"); $input.addClass("is-invalid");
failedCount++; failedCount++;
return false; return false;
} }
@ -35,8 +35,8 @@
value = $input.val(), //link to input value value = $input.val(), //link to input value
rule = attr[0]; rule = attr[0];
$input.siblings( 'label' ).children( 'b' ).remove(); //removes old error $input.parents('.form-group').children( 'label' ).children( 'b' ).remove(); //removes old error
$input.parent().removeClass( "has-error" ); //removes has-error class $input.removeClass( "is-invalid" ); //removes is-invalid class
//checks if field is required, and length //checks if field is required, and length
if (isNaN(requirement) === false && requirement && value.length < requirement) { if (isNaN(requirement) === false && requirement && value.length < requirement) {

View File

@ -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{ try{
req.body.token = req.params.token; req.body.token = req.params.token;
req.body.mailToken = req.params.mailToken;
let user = await User.addByInvite(req.body); let user = await User.addByInvite(req.body);
let token = await AuthToken.add(user); let token = await AuthToken.add(user);
return res.json({ return res.json({
user: user.username, user: user.uid,
token: token.token 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; module.exports = router;
/* /*

View File

@ -1,7 +1,11 @@
'use strict';
var express = require('express'); var express = require('express');
var router = express.Router(); var router = express.Router();
const moment = require('moment');
const {InviteToken} = require('./../models/token'); const {InviteToken} = require('./../models/token');
/* GET home page. */ /* GET home page. */
router.get('/', async function(req, res, next) { router.get('/', async function(req, res, next) {
res.render('home', { title: 'Express' }); res.render('home', { title: 'Express' });
@ -12,21 +16,38 @@ router.get('/users', function(req, res, next) {
res.render('users', { title: 'Express' }); 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{ try{
console.log('token', req.params.token)
let token = await InviteToken.get(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 }); res.render('invite', { title: 'Express', invite: token });
}else{ }else{
next({status: 404}); next({message: 'token not found', status: 404});
} }
}catch(error){ }catch(error){
next(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. */ /* 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});

View File

@ -15,7 +15,7 @@ router.get('/', async function(req, res, next){
router.post('/', async function(req, res, next){ router.post('/', async function(req, res, next){
try{ try{
req.body.created_by = req.user.username req.body.created_by = req.user.uid
return res.json({results: await User.add(req.body)}); return res.json({results: await User.add(req.body)});
}catch(error){ }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{ 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){ }catch(error){
next(error); next(error);
} }
@ -36,7 +38,7 @@ router.delete('/:username', async function(req, res, next){
router.get('/me', async function(req, res, next){ router.get('/me', async function(req, res, next){
try{ try{
return res.json(await User.get({username: req.user.username})); return res.json(await User.get({uid: req.user.uid}));
}catch(error){ }catch(error){
next(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{ 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)}); return res.json({results: await user.setPassword(req.body)});
}catch(error){ }catch(error){
next(error); next(error);
@ -72,7 +74,7 @@ router.post('/invite', async function(req, res, next){
router.post('/key', async function(req, res, next){ router.post('/key', async function(req, res, next){
try{ try{
let added = await User.addSSHkey({ let added = await User.addSSHkey({
username: req.user.username, uid: req.user.uid,
key: req.body.key key: req.body.key
}); });

View File

@ -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: '<strong>and easy to do anywhere, even with Node.js</strong>',
};
sgMail.send(msg);

View File

@ -1,8 +1,10 @@
'use strict';
const {createClient} = require('redis'); const {createClient} = require('redis');
const {promisify} = require('util'); const {promisify} = require('util');
const config = { const config = {
prefix: 'proxy_' prefix: 'sso_'
} }
function client() { function client() {

View File

View File

@ -0,0 +1,24 @@
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>
`
};

View File

@ -0,0 +1,34 @@
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>
`
};

View File

@ -11,8 +11,6 @@
<i>LDAP DN:</i> <b>{{dn}} </b><br /> <i>LDAP DN:</i> <b>{{dn}} </b><br />
<i>Joined</i> <b>{{createTimestamp}} </b><br /> <i>Joined</i> <b>{{createTimestamp}} </b><br />
<i>Joined</i> <b>{{modifyTimestamp}} </b><br /> <i>Joined</i> <b>{{modifyTimestamp}} </b><br />
<img id="profile_photo" > <img id="profile_photo" >
</script> </script>
<script type="text/javascript"> <script type="text/javascript">
@ -43,7 +41,6 @@
function getInvite(){ function getInvite(){
app.user.createInvite(function(error, data){ app.user.createInvite(function(error, data){
console.log(data)
$('#invite_token').html(location.origin+"/login/invite/"+data.token); $('#invite_token').html(location.origin+"/login/invite/"+data.token);
}); });
} }
@ -54,14 +51,15 @@
</script> </script>
<div class="row" style="display:none"> <div class="row" style="display:none">
<div class="col-md-4"> <div class="col-md-4">
<div class="card mb-3 card-default"> <div class="shadow-lg card mb-3 card-default">
<div class="card-header"> <div class="card-header">
<span class="card-title"> <div class="float-right">
<div class="float-right">
<i class="far fa-arrows-v"></i> <i class="far fa-arrows-v"></i>
</div>
Invite User </div>
</span> Invite User
</div> </div>
<div class="card-body"> <div class="card-body">
<button onclick="getInvite(this)">New Invite Token</button> <button onclick="getInvite(this)">New Invite Token</button>
@ -71,24 +69,47 @@
</div> </div>
</div> </div>
<div class="card mb-3 card-default"> <div class="shadow-lg card mb-3 card-default">
<div class="card-header"> <div class="card-header">
<div class="float-right"> <div class="float-right">
<i class="far fa-arrows-v"></i> <i class="far fa-arrows-v"></i>
</div> </div>
<i class="fas fa-user-plus"></i> Add new user <i class="fas fa-user-plus"></i>
Services
</div> </div>
<div class="card-body"> <div class="card-body">
<%- include('user_form') %> <ul class="list-group text-dark">
<a href="https://emby.718it.biz/" target="_blank" class="text-dark">
<li class="list-group-item text-dark">
<i class="fad fa-film"></i>
Emby
</li>
</a>
<a href="https://git.theta42.com" target="_blank" class="text-dark">
<li class="list-group-item text-dark">
<i class="fab fa-git"></i>
Git server
</li>
</a>
<a href="https://pve.admin.vm42.us" target="_blank" class="text-dark">
<li class="list-group-item text-dark">
<i class="fad fa-server"></i>
Promox (contact wmanlty for access)
</li>
</a>
<li class="list-group-item text-dark">
<i class="fad fa-terminal"></i>
ssh:718it.biz
</li>
</ul>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<div class="card card-default"> <div class="shadow-lg card card-default">
<div class="card-header"> <div class="card-header">
User List User List
<div class="float-right"> <div class="hover-effect float-right">
<i class="far fa-arrows-v"></i> <i class="far fa-arrows-v"></i>
</div> </div>
</div> </div>

View File

@ -5,25 +5,20 @@
} }
$(document).ready(function(){ $(document).ready(function(){
$('form').attr('action', 'auth/invite/<%= invite.token %>').attr('evalAJAX', 'location.replace("/login");') $('form').attr('action', 'auth/invite/<%= invite.token %>/<%= invite.mail_token %>').attr('evalAJAX', 'location.replace("/login");')
$('[name="mail"').val('<%= invite.mail %>').prop("disabled", true);
}); });
</script> </script>
<div class="row" style="display:none"> <div class="row" style="display:none">
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="card">
<div class="panel-heading"> <div class="card-header">
<div class="panel-title"> Add new user
Add new user
<div class="pull-right">
<label class="glyphicon glyphicon-circle-arrow-down panel-toggle"></label>
</div>
</div>
</div> </div>
<div class="panel-body"> <div class="card-body">
<h3> <p>
Invited By: <b><%= invite.created_by %></b>, On <b><%= invite.created_on %></b> Invited By: <b><%= invite.created_by %></b>, <%= invite.created_on %>
</h3> </p>
<div class="alert alert-warning actionMessage" style="display:none"> <div class="alert alert-warning actionMessage" style="display:none">
<!-- Message after AJAX action is preformed --> <!-- Message after AJAX action is preformed -->
</div> </div>

View File

@ -0,0 +1,48 @@
<%- 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>
<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-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">
<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" required />
</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') %>

View File

@ -32,28 +32,84 @@
</script> </script>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="card-deck">
<div class="panel panel-default"> <div class="shadow-lg card mb-3">
<div class="panel-heading"> <div class="card-header">
<div class="panel-title">Log in</div> Password Log in
</div> </div>
<div class="panel-body"> <div class="card-body">
<div class="alert alert-warning actionMessage" style="display:none"> <div class="alert alert-warning actionMessage" style="display:none">
</div> </div>
<form action="login" onsubmit="$(this).validate()"> <form action="login" onsubmit="$(this).validate()">
<input type="hidden" name="redirect" value="<%= redirect %>"> <input type="hidden" name="redirect" value="<%= redirect %>">
<div class="form-group"> <div class="form-group">
<label class="control-label">User name</label> <label class="control-label">User name</label>
<input type="text" name="uid" class="form-control" placeholder="User" validate="user:3" /> <div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" ><i class="fad fa-user"></i></span>
</div>
<input type="text" name="uid" class="form-control" placeholder="jsmith" validate="user:3" />
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Password</label> <label class="control-label">Password</label>
<input type="password" name="password" class="form-control" placeholder="Password" validate="password:5" /> <div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" ><i class="fad fa-key"></i></span>
</div>
<input type="password" name="password" class="form-control" placeholder="hunter123!" validate=":3" />
</div>
</div> </div>
<button type="submit" class="btn btn-default" >Log in</button>
<button type="submit" class="btn btn-outline-dark"><i class="fad fa-sign-in"></i> Log in</button>
</form>
</div>
</div>
<div class="shadow-lg card border-danger mb-3">
<div class="card-header">
Social Login
</div>
<div class="card-body">
<div class="alert alert-warning actionMessage" style="display:none">
</div>
<h3>Coming soon!</h3>
<ul>
<li><i class="fab fa-google"></i> Login with google OATH</li>
<li><i class="fab fa-github"></i> Login with github OATH</li>
<li><i class="fab fa-facebook"></i> Login with facebook OATH</li>
</ul>
</div>
</div>
<div class="shadow-lg card border-danger mb-3">
<div class="card-header">
Password Reset
</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="reset_password" onsubmit="$(this).validate()">
<input type="hidden" name="redirect" value="<%= redirect %>">
<div class="form-group">
<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" required />
</div>
</div>
<button type="submit" class="btn btn-outline-dark"><i class="fad fa-question"></i> Help me!</button>
</form> </form>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
<%- include('bottom') %> <%- include('bottom') %>

View File

@ -26,15 +26,15 @@
<![endif]--> <![endif]-->
</head> </head>
<body> <body>
<div class="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="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">SSO Manager - 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="p-2 text-dark" href="/">Home</a> <a class="hover-effect text-dark p-2" href="/"><i class="fad fa-tachometer-alt-fastest"></i> Home</a>
<a class="p-2 text-dark" href="/users"><i class="fad fa-users"></i> User Admin</a> <a class="hover-effect text-dark p-2" href="/users"><i class="fad fa-users"></i> User Admin</a>
</nav> </nav>
<a class="btn btn-outline-primary" onclick="app.auth.logOut()"><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>
</div> </header>
<!-- Container --> <!-- Container -->
<div class="container"> <div class="container">

View File

@ -12,12 +12,12 @@
<div class="form-group"> <div class="form-group">
<label class="control-label">Email</label> <label class="control-label">Email</label>
<input type="text" class="form-control" name="mail" placeholder="jsmith@gmail.com" validate="email" /> <input type="text" class="form-control" name="mail" placeholder="jsmith@gmail.com" validate="email:3" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="control-label">Mobile Phone</label> <label class="control-label">Mobile Phone</label>
<input type="text" class="form-control" name="mobile" placeholder="jsmith@gmail.com" validate="email" /> <input type="text" class="form-control" name="mobile" placeholder="9175551234" validate=":9" />
</div> </div>
<div class="form-group"> <div class="form-group">

View File

@ -2,21 +2,13 @@
<script id="rowTemplate" type="text/html"> <script id="rowTemplate" type="text/html">
<tr action="user/password/{{ username }}" method="put" evalAJAX="$form.trigger('reset')"> <tr action="user/password/{{ username }}" method="put" evalAJAX="$form.trigger('reset')">
<td> <td>
{{ uid }}:{{ username }} {{ uidNumber }}:{{ uid }}
</td> </td>
<td> <td>
<input type="hidden" name="username" value="{{ username }}" /> {{givenName}} {{sn}} {{mail}}
<div class="col-xs-10 form-group">
<label class="control-label"></label>
<input type="password" size="15" class="form-control" name="password" placeholder="New password" validate="password:5"/>
</div>
<button type="button btn-warn" onclick="formAJAX(this)" class="btn btn-sm btn-warn">
Change
</button>
</td> </td>
<td> <td>
<button type="button" onclick="app.user.remove({username: '{{username}}'}, function(){tableAJAX('username {{username}} delete.')})" class="btn btn-sm btn-danger"> <button type="button" onclick="app.user.remove({uid: '{{uid}}'}, function(){tableAJAX('username {{uid}} delete.')})" class="btn btn-sm btn-danger">
Delete Delete
</button> </button>
</td> </td>
@ -49,46 +41,22 @@
</script> </script>
<div class="row" style="display:none"> <div class="row" style="display:none">
<div class="col-md-4"> <div class="col-md-4">
<div class="panel panel-default"> <div class="card">
<div class="panel-heading"> <div class="card-header">
<div class="panel-title"> Add new user
Add new user
<div class="pull-right">
<label class="glyphicon glyphicon-circle-arrow-down panel-toggle"></label>
</div>
</div>
</div> </div>
<div class="panel-body"> <div class="card-body">
<form action="user/" evalAJAX="$form.trigger('reset')"> <%- include('user_form') %>
<input type="hidden" class="form-control" name="delete" value="false" />
<div class="form-group">
<label class="control-label">User-name</label>
<input type="text" class="form-control" name="username" placeholder="Letter, numbers, -, _, . and @ only" validate="user:3" />
</div>
<div class="form-group">
<label class="control-label">Password</label>
<input type="password" class="form-control" name="password" placeholder="Atleast 5 char. long" validate="password:5"/>
</div>
<div class="form-group">
<label class="control-label">Again</label>
<input type="password" class="form-control" name="passwordMatch" placeholder="Retype password" validate="eq:password"/>
</div>
<button type="button" onclick="formAJAX(this)" class="btn btn-default">Add</button>
</form>
</div> </div>
</div> </div>
</div> </div>
<div class="col-md-8"> <div class="col-md-8">
<div class="panel panel-default"> <div class="card">
<div class="panel-heading"> <div class="card-header">
<div class="panel-title"> User List
User List
<div class="pull-right">
<label class="glyphicon glyphicon-circle-arrow-down panel-toggle"></label>
</div>
</div>
</div> </div>
<div class="panel-body" style="padding-bottom:0"> <div class="card-body" style="padding-bottom:0">
<div class="alert alert-warning actionMessage" style="display:none"> <div class="alert alert-warning actionMessage" style="display:none">
<!-- Message after AJAX action is preformed --> <!-- Message after AJAX action is preformed -->
</div> </div>