1
0
Fork 0

web - add mfa-support

This commit is contained in:
Ruben Meyer 2021-08-14 02:51:44 +02:00
parent 734a910143
commit 00475d4619
Signed by: rxbn_
GPG Key ID: BE3BF898BE352FE2
9 changed files with 180 additions and 37 deletions

View File

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

View File

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

View File

@ -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()

View File

@ -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 &nbsp;(
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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.";