diff --git a/api/mailinabox.yml b/api/mailinabox.yml index 118c0ce9..15a048f9 100644 --- a/api/mailinabox.yml +++ b/api/mailinabox.yml @@ -2637,10 +2637,6 @@ components: type: string type: type: string - secret: - type: string - mru_token: - type: string label: type: string nullable: true @@ -2681,4 +2677,4 @@ components: type: string nullable: true MfaDisableSuccessResponse: - type: string \ No newline at end of file + type: string diff --git a/management/daemon.py b/management/daemon.py index 4c1784a2..5f4aa67d 100755 --- a/management/daemon.py +++ b/management/daemon.py @@ -9,7 +9,7 @@ import auth, utils, mfa from mailconfig import get_mail_users, get_mail_users_ex, get_admins, add_mail_user, set_mail_password, set_mail_display_name, remove_mail_user from mailconfig import get_mail_user_privileges, add_remove_mail_user_privilege from mailconfig import get_mail_aliases, get_mail_aliases_ex, get_mail_domains, add_mail_alias, remove_mail_alias -from mfa import get_mfa_state, enable_mfa, disable_mfa +from mfa import get_public_mfa_state, enable_mfa, disable_mfa import mfa_totp env = utils.load_environment() @@ -413,7 +413,7 @@ def ssl_provision_certs(): @authorized_personnel_only def mfa_get_status(): return json_response({ - "enabled_mfa": get_mfa_state(request.user_email, env), + "enabled_mfa": get_public_mfa_state(request.user_email, env), "new_mfa": { "totp": mfa_totp.provision(request.user_email, env) } diff --git a/management/mfa.py b/management/mfa.py index 2b027ec6..260b80a4 100644 --- a/management/mfa.py +++ b/management/mfa.py @@ -57,6 +57,19 @@ def get_mfa_state(email, env): state_list += mfa_totp.get_state(user) return state_list +def get_public_mfa_state(email, env): + '''return details about what MFA schemes are enabled for a user + ordered by the priority that the scheme will be tried, with index + zero being the first. No secrets are returned by this function - + only those details that are needed by the end user to identify a + particular MFA by label and the id of each so it may be disabled. + + ''' + user = get_mfa_user(email, env) + state_list = [] + state_list += mfa_totp.get_public_state(user) + return state_list + def enable_mfa(email, type, secret, token, label, env): '''enable MFA using the scheme specified in `type`. users may have multiple mfa schemes enabled of the same type. diff --git a/management/mfa_totp.py b/management/mfa_totp.py index 3bea84e0..931ef021 100644 --- a/management/mfa_totp.py +++ b/management/mfa_totp.py @@ -45,6 +45,18 @@ def get_state(user): }) return state_list +def get_public_state(user): + state_list = [] + + # totp + for idx in range(0, len(user['totpSecret'])): + state_list.append({ + 'id': totp_id_from_index(user, idx), + 'type': 'totp', + 'label': user['totpLabel'][idx] + }) + return state_list + def enable(user, secret, token, label, env): validate_secret(secret) # Sanity check with the provide current token. diff --git a/setup/migrate.py b/setup/migrate.py index a4c2c6ac..43a0b30b 100755 --- a/setup/migrate.py +++ b/setup/migrate.py @@ -185,7 +185,7 @@ def migration_12(env): def migration_13(env): # Add the "mfa" table for configuring MFA for login to the control panel. db = os.path.join(env["STORAGE_ROOT"], 'mail/users.sqlite') - shell("check_call", ["sqlite3", db, "CREATE TABLE mfa (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL UNIQUE, type TEXT NOT NULL, secret TEXT NOT NULL, mru_token TEXT, label TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE);"]) + shell("check_call", ["sqlite3", db, "CREATE TABLE mfa (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL, secret TEXT NOT NULL, mru_token TEXT, label TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE);"]) ###########################################################