portal: stop forcing MFA on first login
This commit is contained in:
parent
632cb9c17b
commit
59830e19c8
@ -204,7 +204,10 @@ def provision_access_request(request_code: str) -> ProvisionResult:
|
|||||||
raise RuntimeError("email is already associated with an existing Atlas account")
|
raise RuntimeError("email is already associated with an existing Atlas account")
|
||||||
# Always enforce email verification in Keycloak itself (even if the portal
|
# Always enforce email verification in Keycloak itself (even if the portal
|
||||||
# already verified an external email before approval).
|
# already verified an external email before approval).
|
||||||
required_actions = ["UPDATE_PASSWORD", "VERIFY_EMAIL", "CONFIGURE_TOTP"]
|
# Do not force MFA enrollment during initial login: some users prefer to
|
||||||
|
# enable MFA later and some clients are too friction-heavy when MFA is
|
||||||
|
# mandatory for every service.
|
||||||
|
required_actions = ["UPDATE_PASSWORD", "VERIFY_EMAIL"]
|
||||||
payload = {
|
payload = {
|
||||||
"username": username,
|
"username": username,
|
||||||
"enabled": True,
|
"enabled": True,
|
||||||
@ -222,6 +225,11 @@ def provision_access_request(request_code: str) -> ProvisionResult:
|
|||||||
try:
|
try:
|
||||||
full = admin_client().get_user(user_id)
|
full = admin_client().get_user(user_id)
|
||||||
attrs = full.get("attributes") or {}
|
attrs = full.get("attributes") or {}
|
||||||
|
actions = full.get("requiredActions")
|
||||||
|
if isinstance(actions, list) and "CONFIGURE_TOTP" in actions:
|
||||||
|
# Backfill earlier accounts created when we forced MFA enrollment.
|
||||||
|
new_actions = [a for a in actions if a != "CONFIGURE_TOTP"]
|
||||||
|
admin_client().update_user(user_id, {"requiredActions": new_actions})
|
||||||
mailu_from_attr: str | None = None
|
mailu_from_attr: str | None = None
|
||||||
if isinstance(attrs, dict):
|
if isinstance(attrs, dict):
|
||||||
raw_mailu = attrs.get(MAILU_EMAIL_ATTR)
|
raw_mailu = attrs.get(MAILU_EMAIL_ATTR)
|
||||||
|
|||||||
@ -58,14 +58,12 @@ def _verify_url(request_code: str, token: str) -> str:
|
|||||||
|
|
||||||
ONBOARDING_STEPS: tuple[str, ...] = (
|
ONBOARDING_STEPS: tuple[str, ...] = (
|
||||||
"keycloak_password_changed",
|
"keycloak_password_changed",
|
||||||
"keycloak_mfa_configured",
|
|
||||||
"vaultwarden_master_password",
|
"vaultwarden_master_password",
|
||||||
"element_recovery_key",
|
"element_recovery_key",
|
||||||
"element_recovery_key_stored",
|
"element_recovery_key_stored",
|
||||||
)
|
)
|
||||||
|
|
||||||
KEYCLOAK_MANAGED_STEPS: set[str] = {"keycloak_password_changed", "keycloak_mfa_configured"}
|
KEYCLOAK_MANAGED_STEPS: set[str] = {"keycloak_password_changed"}
|
||||||
KEYCLOAK_OTP_CRED_TYPES: set[str] = {"otp", "totp"}
|
|
||||||
|
|
||||||
|
|
||||||
def _normalize_status(status: str) -> str:
|
def _normalize_status(status: str) -> str:
|
||||||
@ -109,25 +107,25 @@ def _auto_completed_keycloak_steps(username: str) -> set[str]:
|
|||||||
|
|
||||||
actions = full.get("requiredActions")
|
actions = full.get("requiredActions")
|
||||||
required_actions: set[str] = set()
|
required_actions: set[str] = set()
|
||||||
|
actions_list: list[str] = []
|
||||||
if isinstance(actions, list):
|
if isinstance(actions, list):
|
||||||
required_actions = {a for a in actions if isinstance(a, str)}
|
actions_list = [a for a in actions if isinstance(a, str)]
|
||||||
|
required_actions = set(actions_list)
|
||||||
|
|
||||||
if "UPDATE_PASSWORD" not in required_actions:
|
if "UPDATE_PASSWORD" not in required_actions:
|
||||||
completed.add("keycloak_password_changed")
|
completed.add("keycloak_password_changed")
|
||||||
|
|
||||||
otp_present = False
|
# Backfill: earlier accounts were created with CONFIGURE_TOTP as a required action,
|
||||||
|
# which forces users to enroll MFA at first login. We no longer require that, so
|
||||||
|
# remove it if present.
|
||||||
|
if "CONFIGURE_TOTP" in required_actions:
|
||||||
try:
|
try:
|
||||||
creds = admin_client().get_user_credentials(user_id)
|
admin_client().update_user(
|
||||||
for cred in creds:
|
user_id,
|
||||||
ctype = cred.get("type") if isinstance(cred, dict) else None
|
{"requiredActions": [a for a in actions_list if a != "CONFIGURE_TOTP"]},
|
||||||
if isinstance(ctype, str) and ctype.lower() in KEYCLOAK_OTP_CRED_TYPES:
|
)
|
||||||
otp_present = True
|
|
||||||
break
|
|
||||||
except Exception:
|
except Exception:
|
||||||
otp_present = False
|
pass
|
||||||
|
|
||||||
if otp_present or "CONFIGURE_TOTP" not in required_actions:
|
|
||||||
completed.add("keycloak_mfa_configured")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|||||||
@ -81,7 +81,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Some steps are verified automatically from Keycloak (password + MFA). Others can't be verified yet — mark them complete once you're done.
|
Some steps are verified automatically from Keycloak (password). Others can't be verified yet — mark them complete once you're done.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div v-if="initialPassword" class="initial-password">
|
<div v-if="initialPassword" class="initial-password">
|
||||||
@ -131,27 +131,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="check-item">
|
|
||||||
<label>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:checked="isStepDone('keycloak_mfa_configured')"
|
|
||||||
disabled
|
|
||||||
/>
|
|
||||||
<span>Enable Keycloak MFA</span>
|
|
||||||
<span
|
|
||||||
class="pill mono auto-pill"
|
|
||||||
:class="isStepDone('keycloak_mfa_configured') ? 'pill-ok' : 'pill-warn'"
|
|
||||||
>
|
|
||||||
{{ isStepDone("keycloak_mfa_configured") ? "verified" : "pending" }}
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<p class="muted">
|
|
||||||
Add a TOTP authenticator (2FA) in Keycloak:
|
|
||||||
<a href="https://sso.bstein.dev/realms/atlas/account" target="_blank" rel="noreferrer">account console</a>.
|
|
||||||
</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="check-item">
|
<li class="check-item">
|
||||||
<label>
|
<label>
|
||||||
<input
|
<input
|
||||||
@ -335,7 +314,7 @@ async function toggleStep(step, event) {
|
|||||||
event?.preventDefault?.();
|
event?.preventDefault?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (step === "keycloak_password_changed" || step === "keycloak_mfa_configured") {
|
if (step === "keycloak_password_changed") {
|
||||||
event?.preventDefault?.();
|
event?.preventDefault?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user