Add TOTP secret to user_key hash
thanks @downtownallday * this invalidates all user_keys after TOTP status is changed for user * after changing TOTP state, a login is required * due to the forced login, we can't and don't need to store the code used for setup in `mru_code`
This commit is contained in:
parent
2ea97f0643
commit
dcb93d071c
6 changed files with 43 additions and 26 deletions
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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 <a>.
|
||||
|
|
|
@ -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('');
|
||||
|
|
|
@ -37,31 +37,35 @@
|
|||
<div class="loading-indicator">Loading...</div>
|
||||
|
||||
<form id="totp-setup">
|
||||
<p>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.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<h3>Setup</h3>
|
||||
<p>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.</p>
|
||||
<h3>Setup Instructions</h3>
|
||||
<p>1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)</p>
|
||||
<div id="totp-setup-qr"></div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="otp">2. Enter the code displayed in the Authenticator app</label>
|
||||
<p>You will have to log into the admin panel again after enabling two-factor authentication.</p>
|
||||
<input type="text" id="totp-setup-token" class="form-control" placeholder="6-digit code" />
|
||||
</div>
|
||||
|
||||
<input type="hidden" id="totp-setup-secret" />
|
||||
|
||||
<div class="form-group">
|
||||
<button id="totp-setup-submit" disabled type="submit" class="btn">Enable two factor authentication</button>
|
||||
<button id="totp-setup-submit" disabled type="submit" class="btn">Enable two-factor authentication</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="disable-2fa">
|
||||
<div class="form-group">
|
||||
<p>Two factor authentication is active.</p>
|
||||
<p>Two-factor authentication is active for your account. You can disable it by clicking below button.</p>
|
||||
<p>You will have to log into the admin panel again after disabling two-factor authentication.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-danger">Disable two-factor authentication</button>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-danger">Disable two factor authentication</button>
|
||||
</form>
|
||||
|
||||
<div id="output-2fa" class="panel panel-danger">
|
||||
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue