diff --git a/backend/atlas_portal/routes/access_requests.py b/backend/atlas_portal/routes/access_requests.py index 3a25eaa..9c5643a 100644 --- a/backend/atlas_portal/routes/access_requests.py +++ b/backend/atlas_portal/routes/access_requests.py @@ -955,18 +955,35 @@ def register(app) -> None: if isinstance(completed, bool): mark_done = completed + request_username = row.get("username") or "" + if mark_done: prerequisites = ONBOARDING_STEP_PREREQUISITES.get(step, set()) if prerequisites: - current_completed = _completed_onboarding_steps(conn, code, row.get("username") or "") + current_completed = _completed_onboarding_steps(conn, code, request_username) 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 "") + _request_keycloak_password_rotation(conn, code, request_username) except Exception: return jsonify({"error": "failed to request keycloak password rotation"}), 502 + if request_username and admin_client().ready(): + try: + now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ") + admin_client().set_user_attribute( + request_username, + "vaultwarden_master_password_set_at", + now, + ) + admin_client().set_user_attribute( + request_username, + "vaultwarden_status", + "already_present", + ) + except Exception: + return jsonify({"error": "failed to update vaultwarden status"}), 502 conn.execute( """ @@ -983,7 +1000,6 @@ def register(app) -> None: ) # Re-evaluate completion to update request status to ready if applicable. - request_username = row.get("username") or "" status = _advance_status(conn, code, request_username, status) onboarding_payload = _onboarding_payload(conn, code, request_username) except Exception: diff --git a/frontend/src/views/OnboardingView.vue b/frontend/src/views/OnboardingView.vue index dc6d149..8bc83a8 100644 --- a/frontend/src/views/OnboardingView.vue +++ b/frontend/src/views/OnboardingView.vue @@ -917,7 +917,7 @@ async function setStepCompletion(stepId, completed) { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ request_code: requestCode.value.trim(), step: stepId, completed }), }); - if (resp.status === 401 && requester === authFetch) { + if ([401, 403].includes(resp.status) && requester === authFetch) { resp = await fetch("/api/access/request/onboarding/attest", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -981,7 +981,7 @@ async function requestKeycloakPasswordRotation() { headers: { "Content-Type": "application/json" }, body: JSON.stringify({ request_code: requestCode.value.trim() }), }); - if (resp.status === 401 && requester === authFetch) { + if ([401, 403].includes(resp.status) && requester === authFetch) { resp = await fetch("/api/access/request/onboarding/keycloak-password-rotate", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -1246,9 +1246,10 @@ button.copy:disabled { display: flex; align-items: center; justify-content: center; - gap: 8px; + gap: 10px; flex-wrap: nowrap; color: var(--text-muted); + width: 100%; } .stepper-meta .pill { @@ -1487,20 +1488,27 @@ button.copy:disabled { background: rgba(0, 0, 0, 0.2); padding: 0; cursor: zoom-in; + display: flex; + flex-direction: column; + gap: 6px; } .guide-shot figcaption { margin: 0; padding: 10px 12px 6px; - font-size: 15px; + font-size: 17px; font-weight: 600; color: var(--text-strong); } .guide-shot img { - width: 100%; display: block; border-radius: 10px; + max-width: 100%; + width: auto; + height: auto; + max-height: min(60vh, 520px); + margin: 0 auto 10px; } .guide-pagination {