web - add mfa-support
This commit is contained in:
parent
734a910143
commit
00475d4619
@ -1,4 +1,5 @@
|
||||
var sanitize = require('mongo-sanitize');
|
||||
var speakeasy = require('speakeasy');
|
||||
let db = global['requireModule']('database');
|
||||
|
||||
module.exports = {
|
||||
@ -10,12 +11,117 @@ module.exports = {
|
||||
* @POST ['email', 'password']
|
||||
*/
|
||||
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) {
|
||||
return res.type('json').end(JSON.stringify({
|
||||
status: 401,
|
||||
message: 'msg.auth.logout.required'
|
||||
}));
|
||||
if(!req.session.user.loggedInFull) {
|
||||
if(!req.body.mfa) {
|
||||
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
|
||||
@ -65,12 +171,23 @@ module.exports = {
|
||||
'id': user.reply._id,
|
||||
'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({
|
||||
status: 200,
|
||||
message: 'msg.auth.login.successful',
|
||||
type: 'form' // TODO: types - { form, access_app}
|
||||
}));
|
||||
return res.type('json').end(JSON.stringify({
|
||||
status: 200,
|
||||
message: 'msg.auth.login.mfa',
|
||||
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(req.session && req.session.user) {
|
||||
if(req.session && req.session.user && req.session.user.loggedInFull) {
|
||||
user = await db.getUser(req.session.user.id);
|
||||
group = await db.getGroup(user.reply.group);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
extends blocks/layout.pug
|
||||
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 title = "Apps";
|
||||
|
||||
@ -23,7 +23,7 @@ mixin items()
|
||||
p.text-center No applications were found.
|
||||
|
||||
append content
|
||||
if(session && session.user)
|
||||
if(session && session.user && session.user.loggedInFull)
|
||||
.uk-container
|
||||
h1 Apps
|
||||
+items()
|
||||
|
@ -1,11 +1,11 @@
|
||||
extends blocks/layout.pug
|
||||
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 title = "Login";
|
||||
|
||||
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
|
||||
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
|
||||
@ -14,18 +14,28 @@ append content
|
||||
a.uk-close-alt.uk-alert-close(href="#")
|
||||
p
|
||||
form.uk-form-horizontal(onsubmit="return loginEvent();")
|
||||
.uk-margin
|
||||
label.uk-form-label(for="login_user") Username / Email
|
||||
.uk-form-controls
|
||||
input.uk-input#login_user(type="text", placeholder="tetrahedron")
|
||||
.uk-margin
|
||||
label.uk-form-label(for="login_pass") Password
|
||||
.uk-form-controls
|
||||
input.uk-input#login_pass(type="password")
|
||||
if(session && !session.user)
|
||||
.uk-margin
|
||||
label.uk-form-label(for="login_user") Username / Email
|
||||
.uk-form-controls
|
||||
input.uk-input#login_user(type="text", placeholder="tetrahedron")
|
||||
.uk-margin
|
||||
label.uk-form-label(for="login_pass") 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")
|
||||
button(onclick="login()").uk-button.uk-button-default Login
|
||||
div(class="uk-width-auto uk-width-1-4@s")
|
||||
else
|
||||
append var
|
||||
- overwrite_vars = (session && session.user) ? true : false;
|
||||
- overwrite_vars = (session && session.user && session.user.loggedInFull) ? true : false;
|
||||
include blocks/error/permission.pug
|
||||
|
@ -1,11 +1,11 @@
|
||||
extends blocks/layout.pug
|
||||
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 title = "Logout";
|
||||
|
||||
append content
|
||||
if(session && session.user)
|
||||
if(session && session.user && session.user.loggedInFull)
|
||||
.uk-flex.uk-margin-medium-top.uk-margin-medium-bottom
|
||||
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
|
||||
|
@ -1,11 +1,11 @@
|
||||
extends blocks/layout.pug
|
||||
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 title = "authorize App";
|
||||
|
||||
append content
|
||||
if(session && session.user)
|
||||
if(session && session.user && session.user.loggedInFull)
|
||||
.uk-container.uk-margin-bottom
|
||||
h1 Authorize App
|
||||
each app in apps
|
||||
|
@ -1,6 +1,6 @@
|
||||
extends blocks/layout.pug
|
||||
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 title = "Settings";
|
||||
|
||||
@ -86,7 +86,7 @@ mixin settings()
|
||||
|
||||
|
||||
append content
|
||||
if(session && session.user)
|
||||
if(session && session.user && session.user.loggedInFull)
|
||||
.uk-container
|
||||
+settings()
|
||||
else
|
||||
|
@ -15,6 +15,7 @@
|
||||
"mongo-sanitize": "^1.1.0",
|
||||
"mongoose": "^5.13.7",
|
||||
"prom-client": "^13.2.0",
|
||||
"pug": "^3.0.2"
|
||||
"pug": "^3.0.2",
|
||||
"speakeasy": "^2.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -2,13 +2,21 @@
|
||||
// '/login', login user
|
||||
function loginEvent() { login(); return false; } // btnEvent handling
|
||||
function login() {
|
||||
let user = document.getElementById("login_user").value;
|
||||
let pass = document.getElementById("login_pass").value;
|
||||
let user = document.getElementById("login_user") != null ? document.getElementById("login_user").value : null;
|
||||
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 = {
|
||||
"email": user,
|
||||
"password": pass
|
||||
};
|
||||
if(user != null) {
|
||||
data = {
|
||||
"email": user,
|
||||
"password": pass
|
||||
};
|
||||
} else {
|
||||
data = {
|
||||
"mfa": mfa
|
||||
};
|
||||
}
|
||||
|
||||
let ajax = new XMLHttpRequest();
|
||||
ajax.open("POST", "/api/login", true);
|
||||
@ -30,6 +38,13 @@ function login() {
|
||||
|
||||
box.classList.add("uk-alert-success");
|
||||
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") {
|
||||
box.classList.add("uk-alert-danger");
|
||||
box.getElementsByTagName("p")[0].innerHTML = "Login failed.<br> Username or Password is wrong.";
|
||||
|
Loading…
x
Reference in New Issue
Block a user