diff --git a/backend/atlas_portal/routes/access_requests.py b/backend/atlas_portal/routes/access_requests.py index ede55f2..edd83e5 100644 --- a/backend/atlas_portal/routes/access_requests.py +++ b/backend/atlas_portal/routes/access_requests.py @@ -148,6 +148,7 @@ def _verify_request(conn, code: str, token: str) -> str: ONBOARDING_STEPS: tuple[str, ...] = ( "vaultwarden_master_password", + "vaultwarden_store_temp_password", "vaultwarden_browser_extension", "vaultwarden_mobile_app", "keycloak_password_rotated", @@ -199,6 +200,7 @@ _KEYCLOAK_PASSWORD_ROTATION_REQUESTED_ARTIFACT = "keycloak_password_rotation_req ONBOARDING_STEP_PREREQUISITES: dict[str, set[str]] = { "vaultwarden_master_password": set(), + "vaultwarden_store_temp_password": {"vaultwarden_master_password"}, "vaultwarden_browser_extension": {"vaultwarden_master_password"}, "vaultwarden_mobile_app": {"vaultwarden_master_password"}, "keycloak_password_rotated": {"vaultwarden_master_password"}, @@ -359,6 +361,23 @@ def _extract_attr(attrs: Any, key: str) -> str: return "" +def _vaultwarden_status_for_user(username: str) -> str: + if not username: + return "" + if not admin_client().ready(): + return "" + try: + 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: + return "" + full = admin_client().get_user(user_id) + attrs = full.get("attributes") if isinstance(full, dict) else {} + return _extract_attr(attrs, "vaultwarden_status") + except Exception: + return "" + + def _auto_completed_service_steps(attrs: Any) -> set[str]: completed: set[str] = set() if not isinstance(attrs, dict): @@ -486,7 +505,12 @@ def _advance_status(conn, request_code: str, username: str, status: str) -> str: if status == "awaiting_onboarding": completed = _completed_onboarding_steps(conn, request_code, username) - if set(ONBOARDING_REQUIRED_STEPS).issubset(completed): + required_steps = set(ONBOARDING_REQUIRED_STEPS) + grandfathered, _ = _vaultwarden_grandfathered(conn, request_code, username) + vaultwarden_status = _vaultwarden_status_for_user(username) + if grandfathered and vaultwarden_status == "grandfathered": + required_steps.add("vaultwarden_store_temp_password") + if required_steps.issubset(completed): conn.execute( "UPDATE access_requests SET status = 'ready' WHERE request_code = %s AND status = 'awaiting_onboarding'", (request_code,), @@ -501,8 +525,13 @@ def _onboarding_payload(conn, request_code: str, username: str) -> dict[str, Any password_rotation_requested = _password_rotation_requested(conn, request_code) grandfathered, contact_email = _vaultwarden_grandfathered(conn, request_code, username) recovery_email = _resolve_recovery_email(username, contact_email) if grandfathered else "" + vaultwarden_status = _vaultwarden_status_for_user(username) + vaultwarden_matched = grandfathered and vaultwarden_status == "grandfathered" + required_steps = list(ONBOARDING_REQUIRED_STEPS) + if vaultwarden_matched: + required_steps.append("vaultwarden_store_temp_password") return { - "required_steps": list(ONBOARDING_REQUIRED_STEPS), + "required_steps": required_steps, "optional_steps": sorted(ONBOARDING_OPTIONAL_STEPS), "completed_steps": completed_steps, "keycloak": { @@ -511,6 +540,7 @@ def _onboarding_payload(conn, request_code: str, username: str) -> dict[str, Any "vaultwarden": { "grandfathered": grandfathered, "recovery_email": recovery_email, + "matched": vaultwarden_matched, }, } diff --git a/frontend/src/views/OnboardingView.vue b/frontend/src/views/OnboardingView.vue index 09eb7b4..7691932 100644 --- a/frontend/src/views/OnboardingView.vue +++ b/frontend/src/views/OnboardingView.vue @@ -85,8 +85,8 @@
- Onboarding is fully self-service. Work through each section in order; you can pause and return later. Vaultwarden - comes first because it stores every credential that follows. + Onboarding is fully self-service. There are 8 steps for 8 services below. Each step has smaller tasks; press + Confirm when you finish a task. You can pause and return later.
{{ step.description }}
{{ stepNote(step) }}
-- Already have a Vaultwarden account? Claim it with - {{ vaultwardenRecoveryEmail || "your recovery email" }}. - This skips the invite flow and keeps your existing vault. -
- - - -