Browse Source

web - add mfa-support

master
Ruben Meyer 11 months ago
parent
commit
00475d4619
Signed by: rxbn_ GPG Key ID: BE3BF898BE352FE2
  1. 137
      bin/web/routes/api/login.js
  2. 2
      bin/web/routes/static.js
  3. 4
      bin/web/views/index.pug
  4. 32
      bin/web/views/login.pug
  5. 4
      bin/web/views/logout.pug
  6. 4
      bin/web/views/request.pug
  7. 4
      bin/web/views/settings.pug
  8. 3
      package.json
  9. 29
      res/web/js/custom.js

137
bin/web/routes/api/login.js

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

2
bin/web/routes/static.js

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

4
bin/web/views/index.pug

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

32
bin/web/views/login.pug

@ -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 [email protected]")
.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 &nbsp;(
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 [email protected]")
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

4
bin/web/views/logout.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 [email protected]")
.uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right

4
bin/web/views/request.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": "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

4
bin/web/views/settings.pug

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

3
package.json

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

29
res/web/js/custom.js

@ -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 data = {
"email": user,
"password": pass
};
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 = {};
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…
Cancel
Save