diff --git a/bin/web/routes/api/login.js b/bin/web/routes/api/login.js index ccee42b..abd3374 100644 --- a/bin/web/routes/api/login.js +++ b/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} + })); + } } } diff --git a/bin/web/routes/static.js b/bin/web/routes/static.js index c1cb7d6..2eaad53 100644 --- a/bin/web/routes/static.js +++ b/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); diff --git a/bin/web/views/index.pug b/bin/web/views/index.pug index 7049981..71b840e 100644 --- a/bin/web/views/index.pug +++ b/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() diff --git a/bin/web/views/login.pug b/bin/web/views/login.pug index 95f8289..b4b6948 100644 --- a/bin/web/views/login.pug +++ b/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 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 diff --git a/bin/web/views/logout.pug b/bin/web/views/logout.pug index 766a7f4..e60e303 100644 --- a/bin/web/views/logout.pug +++ b/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 uk-width-1-4@s") .uk-flex.uk-flex-auto.uk-flex-column.uk-flex-center.uk-margin-left.uk-margin-right diff --git a/bin/web/views/request.pug b/bin/web/views/request.pug index ed33e76..7497a82 100644 --- a/bin/web/views/request.pug +++ b/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 diff --git a/bin/web/views/settings.pug b/bin/web/views/settings.pug index 707378c..6fd2f7a 100644 --- a/bin/web/views/settings.pug +++ b/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 diff --git a/package.json b/package.json index b6c88ed..1dd0d51 100644 --- a/package.json +++ b/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" } } diff --git a/res/web/js/custom.js b/res/web/js/custom.js index 7bae046..449ed1d 100644 --- a/res/web/js/custom.js +++ b/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 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.
Username or Password is wrong.";