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
|
@ -3,7 +3,7 @@ import base64, os, os.path, hmac
|
||||||
from flask import make_response
|
from flask import make_response
|
||||||
|
|
||||||
import utils, totp
|
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_KEY_PATH = '/var/lib/mailinabox/api.key'
|
||||||
DEFAULT_AUTH_REALM = 'Mail-in-a-Box Management Server'
|
DEFAULT_AUTH_REALM = 'Mail-in-a-Box Management Server'
|
||||||
|
@ -136,15 +136,23 @@ class KeyAuthService:
|
||||||
|
|
||||||
def create_user_key(self, email, env):
|
def create_user_key(self, email, env):
|
||||||
# Store an HMAC with the client. The hashed message of the HMAC will be the user's
|
# 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
|
# email address & hashed password and the key will be the master API key. If TOTP
|
||||||
# course has their own email address and password. We assume they do not have the master
|
# is active, the key will also include the TOTP secret. The user of course has their
|
||||||
# API key (unless they are trusted anyway). The HMAC proves that they authenticated
|
# own email address and password. We assume they do not have the master API key
|
||||||
# with us in some other way to get the HMAC. Including the password means that when
|
# (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
|
# 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
|
# in to the control panel again. This method raises a ValueError if the user does
|
||||||
# not exist, due to get_mail_password.
|
# not exist, due to get_mail_password.
|
||||||
msg = b"AUTH:" + email.encode("utf8") + b" " + get_mail_password(email, env).encode("utf8")
|
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):
|
def _generate_key(self):
|
||||||
raw_key = os.urandom(32)
|
raw_key = os.urandom(32)
|
||||||
|
|
|
@ -439,7 +439,7 @@ def totp_post_enable():
|
||||||
return json_response({ "error": 'bad_input' }, 400)
|
return json_response({ "error": 'bad_input' }, 400)
|
||||||
|
|
||||||
if totp.validate(secret, token):
|
if totp.validate(secret, token):
|
||||||
create_totp_credential(email, secret, token, env)
|
create_totp_credential(email, secret, env)
|
||||||
return json_response({})
|
return json_response({})
|
||||||
|
|
||||||
return json_response({ "error": 'token_mismatch' }, 400)
|
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
|
'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)
|
validate_totp_secret(secret)
|
||||||
|
|
||||||
conn, c = open_database(env, with_connection=True)
|
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()
|
conn.commit()
|
||||||
return "OK"
|
return "OK"
|
||||||
|
|
||||||
|
|
|
@ -363,6 +363,16 @@ function api(url, method, data, callback, callback_error, headers) {
|
||||||
|
|
||||||
var current_panel = null;
|
var current_panel = null;
|
||||||
var switch_back_to_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) {
|
function show_panel(panelid) {
|
||||||
if (panelid.getAttribute)
|
if (panelid.getAttribute)
|
||||||
// we might be passed an HTMLElement <a>.
|
// 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() {
|
function show_login() {
|
||||||
$('#loginForm').removeClass('is-twofactor');
|
$('#loginForm').removeClass('is-twofactor');
|
||||||
$('#loginOtpInput').val('');
|
$('#loginOtpInput').val('');
|
||||||
|
|
|
@ -37,31 +37,35 @@
|
||||||
<div class="loading-indicator">Loading...</div>
|
<div class="loading-indicator">Loading...</div>
|
||||||
|
|
||||||
<form id="totp-setup">
|
<form id="totp-setup">
|
||||||
<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>
|
<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 Instructions</h3>
|
||||||
<p>1. Scan the QR code or enter the secret into an authenticator app (e.g. Google Authenticator)</p>
|
<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 id="totp-setup-qr"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="otp">2. Enter the code displayed in the Authenticator app</label>
|
<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" />
|
<input type="text" id="totp-setup-token" class="form-control" placeholder="6-digit code" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<input type="hidden" id="totp-setup-secret" />
|
<input type="hidden" id="totp-setup-secret" />
|
||||||
|
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<form id="disable-2fa">
|
<form id="disable-2fa">
|
||||||
<div class="form-group">
|
<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>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-danger">Disable two factor authentication</button>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div id="output-2fa" class="panel panel-danger">
|
<div id="output-2fa" class="panel panel-danger">
|
||||||
|
@ -173,7 +177,9 @@
|
||||||
'/mfa/totp/disable',
|
'/mfa/totp/disable',
|
||||||
'POST',
|
'POST',
|
||||||
{},
|
{},
|
||||||
function() { show_two_factor_auth(); }
|
function() {
|
||||||
|
do_logout();
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -190,7 +196,9 @@
|
||||||
token: $(el.totpSetupToken).val(),
|
token: $(el.totpSetupToken).val(),
|
||||||
secret: $(el.totpSetupSecret).val()
|
secret: $(el.totpSetupSecret).val()
|
||||||
},
|
},
|
||||||
function(res) { show_two_factor_auth(); },
|
function(res) {
|
||||||
|
do_logout();
|
||||||
|
},
|
||||||
function(res) {
|
function(res) {
|
||||||
var errorMessage = 'Something went wrong.';
|
var errorMessage = 'Something went wrong.';
|
||||||
var parsed;
|
var parsed;
|
||||||
|
|
Loading…
Reference in New Issue