diff --git a/backend/atlas_portal/routes/access_requests.py b/backend/atlas_portal/routes/access_requests.py index 1a24216..7184746 100644 --- a/backend/atlas_portal/routes/access_requests.py +++ b/backend/atlas_portal/routes/access_requests.py @@ -257,6 +257,36 @@ def _password_rotation_requested(conn, request_code: str) -> bool: return bool(row) +def _request_keycloak_password_rotation(conn, request_code: str, username: str) -> None: + if not username: + raise ValueError("username missing") + if not admin_client().ready(): + raise RuntimeError("keycloak admin unavailable") + + user = admin_client().find_user(username) or {} + user_id = user.get("id") if isinstance(user, dict) else None + if not isinstance(user_id, str) or not user_id: + raise RuntimeError("keycloak user not found") + + full = admin_client().get_user(user_id) + actions = full.get("requiredActions") + actions_list: list[str] = [] + if isinstance(actions, list): + actions_list = [a for a in actions if isinstance(a, str)] + if "UPDATE_PASSWORD" not in actions_list: + actions_list.append("UPDATE_PASSWORD") + admin_client().update_user_safe(user_id, {"requiredActions": actions_list}) + + conn.execute( + """ + INSERT INTO access_request_onboarding_artifacts (request_code, artifact, value_hash) + VALUES (%s, %s, NOW()::text) + ON CONFLICT (request_code, artifact) DO NOTHING + """, + (request_code, _KEYCLOAK_PASSWORD_ROTATION_REQUESTED_ARTIFACT), + ) + + def _extract_attr(attrs: Any, key: str) -> str: if not isinstance(attrs, dict): return "" @@ -940,6 +970,12 @@ def register(app) -> None: missing = sorted(prerequisites - current_completed) if missing: return jsonify({"error": "step is blocked", "blocked_by": missing}), 409 + if step == "vaultwarden_master_password": + try: + _request_keycloak_password_rotation(conn, code, row.get("username") or "") + except Exception: + return jsonify({"error": "failed to request keycloak password rotation"}), 502 + conn.execute( """ INSERT INTO access_request_onboarding_steps (request_code, step) diff --git a/media/onboarding/element/step1_web_access/1_Sign in to element.png b/media/onboarding/element/step1_web_access/1_Sign in to element.png new file mode 100644 index 0000000..e5e93d0 Binary files /dev/null and b/media/onboarding/element/step1_web_access/1_Sign in to element.png differ diff --git a/media/onboarding/element/step1_web_access/2_Just hit continue here.png b/media/onboarding/element/step1_web_access/2_Just hit continue here.png new file mode 100644 index 0000000..c85ec7c Binary files /dev/null and b/media/onboarding/element/step1_web_access/2_Just hit continue here.png differ diff --git a/media/onboarding/element/step1_web_access/3_Login with Keycloak.png b/media/onboarding/element/step1_web_access/3_Login with Keycloak.png new file mode 100644 index 0000000..ea57e54 Binary files /dev/null and b/media/onboarding/element/step1_web_access/3_Login with Keycloak.png differ diff --git a/media/onboarding/element/step1_web_access/4_Give it your username and the temporary password.png b/media/onboarding/element/step1_web_access/4_Give it your username and the temporary password.png new file mode 100644 index 0000000..3a2f561 Binary files /dev/null and b/media/onboarding/element/step1_web_access/4_Give it your username and the temporary password.png differ