web - add mfa-support
This commit is contained in:
parent
734a910143
commit
00475d4619
@ -1,4 +1,5 @@
|
|||||||
var sanitize = require('mongo-sanitize');
|
var sanitize = require('mongo-sanitize');
|
||||||
|
var speakeasy = require('speakeasy');
|
||||||
let db = global['requireModule']('database');
|
let db = global['requireModule']('database');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -10,12 +11,117 @@ module.exports = {
|
|||||||
* @POST ['email', 'password']
|
* @POST ['email', 'password']
|
||||||
*/
|
*/
|
||||||
post: async (req, res) => {
|
post: async (req, res) => {
|
||||||
// if user is logged in (existing session); FAIL
|
// if user is logged in (existing session); check MFA
|
||||||
if(req.session.user) {
|
if(req.session.user) {
|
||||||
return res.type('json').end(JSON.stringify({
|
if(!req.session.user.loggedInFull) {
|
||||||
status: 401,
|
if(!req.body.mfa) {
|
||||||
message: 'msg.auth.logout.required'
|
return res.type('json').status(401).end(JSON.stringify({
|
||||||
}));
|
status: 401,
|
||||||
|
message: [
|
||||||
|
'msg.request.data.missing',
|
||||||
|
'msg.auth.login.failed'
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
let mfa = sanitize(req.body.mfa);
|
||||||
|
user = await db.getUser(req.session.user.id);
|
||||||
|
|
||||||
|
// if database error
|
||||||
|
if(user.err) {
|
||||||
|
// log error while debugging
|
||||||
|
global['logs'].debug(user.err);
|
||||||
|
|
||||||
|
// login failed because of database error
|
||||||
|
return res.type('json').status(500).end(JSON.stringify({
|
||||||
|
status: 500,
|
||||||
|
message: [
|
||||||
|
'msg.database.error',
|
||||||
|
'msg.auth.login.failed'
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// no reply (user does not exist) or password is wrong
|
||||||
|
if(!user.reply || user.reply === null || user.reply.length == 0 || user.reply.length > 1) {
|
||||||
|
return res.type('json').status(401).end(JSON.stringify({
|
||||||
|
status: 401,
|
||||||
|
message: 'msg.auth.login.failed'
|
||||||
|
}));
|
||||||
|
// do login
|
||||||
|
} else {
|
||||||
|
if(req.session.user.login_step+1 <= user.reply.mfa.data.length) {
|
||||||
|
let test = false;
|
||||||
|
switch (req.session.user.login_step_type) {
|
||||||
|
case "TOTP":
|
||||||
|
test = speakeasy.totp.verify({
|
||||||
|
secret: user.reply.mfa.data[req.session.user.login_step].data,
|
||||||
|
encoding: 'base32',
|
||||||
|
token: mfa
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case "HOTP":
|
||||||
|
test = speakeasy.hotp.verify({
|
||||||
|
secret: user.reply.mfa.data[req.session.user.login_step].data.split("|")[0],
|
||||||
|
counter: user.reply.mfa.data[req.session.user.login_step].data.split("|")[1],
|
||||||
|
encoding: 'base32',
|
||||||
|
token: mfa
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(test) {
|
||||||
|
if(req.session.user.login_step+1 >= user.reply.mfa.data.length) {
|
||||||
|
req.session.user.loggedInFull = true;
|
||||||
|
delete req.session.user.login_step;
|
||||||
|
delete req.session.user.login_step_type;
|
||||||
|
} else {
|
||||||
|
req.session.user.login_step++;
|
||||||
|
req.session.user.login_step_type = user.reply.mfa.data[req.session.user.login_step].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.type('json').end(JSON.stringify({
|
||||||
|
status: 200,
|
||||||
|
message: 'msg.auth.login.mfa',
|
||||||
|
type: 'form' // TODO: types - { form, access_app}
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
if(req.session.user.login_step_type == 'HOTP') {
|
||||||
|
let mfaobj = user.reply.mfa;
|
||||||
|
mfaobj[req.session.user.login_step].data = user.reply.mfa[req.session.user.login_step].data.split("|")[0] + (user.reply.mfa[req.session.user.login_step].data.split("|")[1]++);
|
||||||
|
|
||||||
|
await db.updateUser(user.id, {
|
||||||
|
mfa: mfaobj
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.type('json').status(401).end(JSON.stringify({
|
||||||
|
status: 401,
|
||||||
|
message: [
|
||||||
|
'msg.auth.login.failed',
|
||||||
|
'msg.auth.login.mfa'
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.type('json').end(JSON.stringify({
|
||||||
|
status: 200,
|
||||||
|
message: 'msg.auth.login.mfa',
|
||||||
|
type: 'form' // TODO: types - { form, access_app}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return res.type('json').status(401).end(JSON.stringify({
|
||||||
|
status: 401,
|
||||||
|
message: [
|
||||||
|
'msg.request.data.missing',
|
||||||
|
'msg.auth.login.failed'
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return res.type('json').end(JSON.stringify({
|
||||||
|
status: 401,
|
||||||
|
message: 'msg.auth.logout.required'
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check body variables
|
// check body variables
|
||||||
@ -65,12 +171,23 @@ module.exports = {
|
|||||||
'id': user.reply._id,
|
'id': user.reply._id,
|
||||||
'group': user.reply.group
|
'group': user.reply.group
|
||||||
};
|
};
|
||||||
|
req.session.user.loggedInFull = user.reply.mfa.active == false; // mfa active?
|
||||||
|
if(!req.session.user.loggedInFull) { // mfa is active
|
||||||
|
req.session.user.login_step_type = user.reply.mfa.data[0].type;
|
||||||
|
req.session.user.login_step = 0;
|
||||||
|
|
||||||
return res.type('json').end(JSON.stringify({
|
return res.type('json').end(JSON.stringify({
|
||||||
status: 200,
|
status: 200,
|
||||||
message: 'msg.auth.login.successful',
|
message: 'msg.auth.login.mfa',
|
||||||
type: 'form' // TODO: types - { form, access_app}
|
type: 'form' // TODO: types - { form, access_app}
|
||||||
}));
|
}));
|
||||||
|
} else {
|
||||||
|
return res.type('json').end(JSON.stringify({
|
||||||
|
status: 200,
|
||||||
|
message: 'msg.auth.login.successful',
|
||||||
|
type: 'form' // TODO: types - { form, access_app}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ let getRoutes = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if user is logged in, show request page
|
// if user is logged in, show request page
|
||||||
if(req.session && req.session.user) {
|
if(req.session && req.session.user && req.session.user.loggedInFull) {
|
||||||
user = await db.getUser(req.session.user.id);
|
user = await db.getUser(req.session.user.id);
|
||||||
group = await db.getGroup(user.reply.group);
|
group = await db.getGroup(user.reply.group);
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
extends blocks/layout.pug
|
extends blocks/layout.pug
|
||||||
append var
|
append var
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Apps", "active": true}};
|
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Apps", "active": true}};
|
||||||
- var title = "Apps";
|
- var title = "Apps";
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ mixin items()
|
|||||||
p.text-center No applications were found.
|
p.text-center No applications were found.
|
||||||
|
|
||||||
append content
|
append content
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
.uk-container
|
.uk-container
|
||||||
h1 Apps
|
h1 Apps
|
||||||
+items()
|
+items()
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
extends blocks/layout.pug
|
extends blocks/layout.pug
|
||||||
append var
|
append var
|
||||||
if(session && !session.user)
|
if(session && !session.user || session && session.user && !session.user.loggedInFull)
|
||||||
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Login", "active": true}};
|
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Login", "active": true}};
|
||||||
- var title = "Login";
|
- var title = "Login";
|
||||||
|
|
||||||
append content
|
append content
|
||||||
if(session && !session.user)
|
if(session && !session.user || session && session.user && !session.user.loggedInFull)
|
||||||
.uk-flex.uk-margin-medium-top.uk-margin-medium-bottom
|
.uk-flex.uk-margin-medium-top.uk-margin-medium-bottom
|
||||||
div(class="uk-width-auto uk-width-1-4@s")
|
div(class="uk-width-auto uk-width-1-4@s")
|
||||||
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
|
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
|
||||||
@ -14,18 +14,28 @@ append content
|
|||||||
a.uk-close-alt.uk-alert-close(href="#")
|
a.uk-close-alt.uk-alert-close(href="#")
|
||||||
p
|
p
|
||||||
form.uk-form-horizontal(onsubmit="return loginEvent();")
|
form.uk-form-horizontal(onsubmit="return loginEvent();")
|
||||||
.uk-margin
|
if(session && !session.user)
|
||||||
label.uk-form-label(for="login_user") Username / Email
|
.uk-margin
|
||||||
.uk-form-controls
|
label.uk-form-label(for="login_user") Username / Email
|
||||||
input.uk-input#login_user(type="text", placeholder="tetrahedron")
|
.uk-form-controls
|
||||||
.uk-margin
|
input.uk-input#login_user(type="text", placeholder="tetrahedron")
|
||||||
label.uk-form-label(for="login_pass") Password
|
.uk-margin
|
||||||
.uk-form-controls
|
label.uk-form-label(for="login_pass") Password
|
||||||
input.uk-input#login_pass(type="password")
|
.uk-form-controls
|
||||||
|
input.uk-input#login_pass(type="password")
|
||||||
|
else
|
||||||
|
.uk-margin
|
||||||
|
label.uk-form-label(for="login_mfa") Multifactor: #
|
||||||
|
span=session.user.login_step+1
|
||||||
|
span (
|
||||||
|
span=session.user.login_step_type
|
||||||
|
span )
|
||||||
|
.uk-form-controls
|
||||||
|
input.uk-input#login_mfa(type="text")
|
||||||
input(hidden,type="submit")
|
input(hidden,type="submit")
|
||||||
button(onclick="login()").uk-button.uk-button-default Login
|
button(onclick="login()").uk-button.uk-button-default Login
|
||||||
div(class="uk-width-auto uk-width-1-4@s")
|
div(class="uk-width-auto uk-width-1-4@s")
|
||||||
else
|
else
|
||||||
append var
|
append var
|
||||||
- overwrite_vars = (session && session.user) ? true : false;
|
- overwrite_vars = (session && session.user && session.user.loggedInFull) ? true : false;
|
||||||
include blocks/error/permission.pug
|
include blocks/error/permission.pug
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
extends blocks/layout.pug
|
extends blocks/layout.pug
|
||||||
append var
|
append var
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Logout", "active": true}};
|
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Logout", "active": true}};
|
||||||
- var title = "Logout";
|
- var title = "Logout";
|
||||||
|
|
||||||
append content
|
append content
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
.uk-flex.uk-margin-medium-top.uk-margin-medium-bottom
|
.uk-flex.uk-margin-medium-top.uk-margin-medium-bottom
|
||||||
div(class="uk-width-auto uk-width-1-4@s")
|
div(class="uk-width-auto uk-width-1-4@s")
|
||||||
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
|
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
extends blocks/layout.pug
|
extends blocks/layout.pug
|
||||||
append var
|
append var
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Authorization", "active": true}};
|
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Authorization", "active": true}};
|
||||||
- var title = "authorize App";
|
- var title = "authorize App";
|
||||||
|
|
||||||
append content
|
append content
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
.uk-container.uk-margin-bottom
|
.uk-container.uk-margin-bottom
|
||||||
h1 Authorize App
|
h1 Authorize App
|
||||||
each app in apps
|
each app in apps
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
extends blocks/layout.pug
|
extends blocks/layout.pug
|
||||||
append var
|
append var
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Settings", "active": true}};
|
- var breadcrumb = {0: {"name": cfg.app.name, "href": "/"}, 1: {"name": "Settings", "active": true}};
|
||||||
- var title = "Settings";
|
- var title = "Settings";
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ mixin settings()
|
|||||||
|
|
||||||
|
|
||||||
append content
|
append content
|
||||||
if(session && session.user)
|
if(session && session.user && session.user.loggedInFull)
|
||||||
.uk-container
|
.uk-container
|
||||||
+settings()
|
+settings()
|
||||||
else
|
else
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"mongo-sanitize": "^1.1.0",
|
"mongo-sanitize": "^1.1.0",
|
||||||
"mongoose": "^5.13.7",
|
"mongoose": "^5.13.7",
|
||||||
"prom-client": "^13.2.0",
|
"prom-client": "^13.2.0",
|
||||||
"pug": "^3.0.2"
|
"pug": "^3.0.2",
|
||||||
|
"speakeasy": "^2.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,13 +2,21 @@
|
|||||||
// '/login', login user
|
// '/login', login user
|
||||||
function loginEvent() { login(); return false; } // btnEvent handling
|
function loginEvent() { login(); return false; } // btnEvent handling
|
||||||
function login() {
|
function login() {
|
||||||
let user = document.getElementById("login_user").value;
|
let user = document.getElementById("login_user") != null ? document.getElementById("login_user").value : null;
|
||||||
let pass = document.getElementById("login_pass").value;
|
let pass = document.getElementById("login_pass") != null ? document.getElementById("login_pass").value : null;
|
||||||
|
let mfa = document.getElementById("login_mfa") != null ? document.getElementById("login_mfa").value : null;
|
||||||
|
let data = {};
|
||||||
|
|
||||||
let data = {
|
if(user != null) {
|
||||||
"email": user,
|
data = {
|
||||||
"password": pass
|
"email": user,
|
||||||
};
|
"password": pass
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
data = {
|
||||||
|
"mfa": mfa
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
let ajax = new XMLHttpRequest();
|
let ajax = new XMLHttpRequest();
|
||||||
ajax.open("POST", "/api/login", true);
|
ajax.open("POST", "/api/login", true);
|
||||||
@ -30,6 +38,13 @@ function login() {
|
|||||||
|
|
||||||
box.classList.add("uk-alert-success");
|
box.classList.add("uk-alert-success");
|
||||||
box.getElementsByTagName("p")[0].innerHTML = "Logged in. You will be redirected";
|
box.getElementsByTagName("p")[0].innerHTML = "Logged in. You will be redirected";
|
||||||
|
} else if(json.message && json.message == "msg.auth.login.mfa") {
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.reload();
|
||||||
|
}, 150);
|
||||||
|
|
||||||
|
box.classList.add("uk-alert-warning");
|
||||||
|
box.getElementsByTagName("p")[0].innerHTML = "Logged in. Next step: Multifactor Authentication";
|
||||||
} else if(json.message && json.message == "msg.auth.login.failed") {
|
} else if(json.message && json.message == "msg.auth.login.failed") {
|
||||||
box.classList.add("uk-alert-danger");
|
box.classList.add("uk-alert-danger");
|
||||||
box.getElementsByTagName("p")[0].innerHTML = "Login failed.<br> Username or Password is wrong.";
|
box.getElementsByTagName("p")[0].innerHTML = "Login failed.<br> Username or Password is wrong.";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user