diff --git a/management/auth.py b/management/auth.py index 27d9f094..f3cae996 100644 --- a/management/auth.py +++ b/management/auth.py @@ -3,7 +3,7 @@ import base64, os, os.path, hmac from flask import make_response import utils, totp -from mailconfig import get_mail_password, get_mail_user_privileges +from mailconfig import get_mail_password, get_mail_user_privileges, get_mfa_state DEFAULT_KEY_PATH = '/var/lib/mailinabox/api.key' DEFAULT_AUTH_REALM = 'Mail-in-a-Box Management Server' @@ -136,15 +136,23 @@ class KeyAuthService: def create_user_key(self, email, env): # Store an HMAC with the client. The hashed message of the HMAC will be the user's - # email address & hashed password and the key will be the master API key. The user of - # course has their own email address and password. We assume they do not have the master - # API key (unless they are trusted anyway). The HMAC proves that they authenticated - # with us in some other way to get the HMAC. Including the password means that when + # email address & hashed password and the key will be the master API key. If TOTP + # is active, the key will also include the TOTP secret. The user of course has their + # own email address and password. We assume they do not have the master API key + # (unless they are trusted anyway). The HMAC proves that they authenticated with us + # in some other way to get the HMAC. Including the password means that when # a user's password is reset, the HMAC changes and they will correctly need to log # in to the control panel again. This method raises a ValueError if the user does # not exist, due to get_mail_password. msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8") - return hmac.new(self.key.encode('ascii'), msg, digestmod="sha256").hexdigest() + + mfa_state = get_mfa_state(email, env) + hash_key = self.key.encode('ascii') + + if mfa_state['type'] == 'totp': + hash_key = hash_key + mfa_state['secret'].encode('ascii') + + return hmac.new(hash_key, msg, digestmod="sha256").hexdigest() def _generate_key(self): raw_key = os.urandom(32) diff --git a/management/daemon.py b/management/daemon.py index 0ff8f2a5..0efbc03a 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -439,7 +439,7 @@ def totp_post_enable(): return json_response({ "error": 'bad_input' }, 400) if totp.validate(secret, token): - create_totp_credential(email, secret, token, env) + create_totp_credential(email, secret, env) return json_response({}) return json_response({ "error": 'token_mismatch' }, 400) diff --git a/management/mailconfig.py b/management/mailconfig.py index 6e31eae8..d25afea0 100755 --- a/management/mailconfig.py +++ b/management/mailconfig.py @@ -565,11 +565,11 @@ def get_mfa_state(email, env): 'mru_token': '' if mru_token is None else mru_token } -def create_totp_credential(email, secret, token, env): +def create_totp_credential(email, secret, env): validate_totp_secret(secret) conn, c = open_database(env, with_connection=True) - c.execute('INSERT INTO totp_credentials (user_email, secret, mru_token) VALUES (?, ?, ?)', (email, secret, token)) + c.execute('INSERT INTO totp_credentials (user_email, secret) VALUES (?, ?)', (email, secret)) conn.commit() return "OK" diff --git a/management/templates/index.html b/management/templates/index.html index 7c346e37..8fdb7c22 100644 --- a/management/templates/index.html +++ b/management/templates/index.html @@ -363,6 +363,16 @@ function api(url, method, data, callback, callback_error, headers) { var current_panel = null; var switch_back_to_panel = null; + +function do_logout() { + api_credentials = ["", ""]; + if (typeof localStorage != 'undefined') + localStorage.removeItem("miab-cp-credentials"); + if (typeof sessionStorage != 'undefined') + sessionStorage.removeItem("miab-cp-credentials"); + show_panel('login'); +} + function show_panel(panelid) { if (panelid.getAttribute) // we might be passed an HTMLElement . diff --git a/management/templates/login.html b/management/templates/login.html index 4d6155f6..db8dce84 100644 --- a/management/templates/login.html +++ b/management/templates/login.html @@ -166,15 +166,6 @@ function do_login() { }); } -function do_logout() { - api_credentials = ["", ""]; - if (typeof localStorage != 'undefined') - localStorage.removeItem("miab-cp-credentials"); - if (typeof sessionStorage != 'undefined') - sessionStorage.removeItem("miab-cp-credentials"); - show_panel('login'); -} - function show_login() { $('#loginForm').removeClass('is-twofactor'); $('#loginOtpInput').val(''); diff --git a/management/templates/two-factor-auth.html b/management/templates/two-factor-auth.html index bb519e46..bfb9177f 100644 --- a/management/templates/two-factor-auth.html +++ b/management/templates/two-factor-auth.html @@ -37,31 +37,35 @@
Loading...
+

After enabling two factor authentication, any login to the admin panel will require you to enter a time-limited 6-digit number from an authenticator app after entering your normal credentials.

+
-

Setup

-

After enabling two factor authentication, any login to the admin panel will require you to enter a time-limited 6-digit number from an authenticator app after entering your normal credentials.

+

Setup Instructions

1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)

+

You will have to log into the admin panel again after enabling two-factor authentication.

- +
-

Two factor authentication is active.

+

Two-factor authentication is active for your account. You can disable it by clicking below button.

+

You will have to log into the admin panel again after disabling two-factor authentication.

+
+
+
- -
@@ -173,7 +177,9 @@ '/mfa/totp/disable', 'POST', {}, - function() { show_two_factor_auth(); } + function() { + do_logout(); + } ); return false; @@ -190,7 +196,9 @@ token: $(el.totpSetupToken).val(), secret: $(el.totpSetupSecret).val() }, - function(res) { show_two_factor_auth(); }, + function(res) { + do_logout(); + }, function(res) { var errorMessage = 'Something went wrong.'; var parsed;