mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-31 19:00:54 +00:00 
			
		
		
		
	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 | ||||
| 
 | ||||
| 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 New Issue
	
	Block a user