From 944cb24538edf798b39057de2aaee8a17479b77a Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Fri, 23 Jan 2026 00:08:13 -0300 Subject: [PATCH] portal: allow onboarding confirms without login --- .../atlas_portal/routes/access_requests.py | 22 ++++++++++++++----- frontend/src/views/OnboardingView.vue | 7 +++--- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/backend/atlas_portal/routes/access_requests.py b/backend/atlas_portal/routes/access_requests.py index 231b5c5..1a24216 100644 --- a/backend/atlas_portal/routes/access_requests.py +++ b/backend/atlas_portal/routes/access_requests.py @@ -15,7 +15,7 @@ import psycopg from .. import ariadne_client from ..db import connect, configured -from ..keycloak import admin_client, require_auth +from ..keycloak import admin_client, oidc_client, require_auth from ..mailer import MailerError, access_request_verification_body, send_text_email from ..rate_limit import rate_limit_allow from ..provisioning import provision_access_request, provision_tasks_complete @@ -878,7 +878,6 @@ def register(app) -> None: return jsonify({"error": "failed to load status"}), 502 @app.route("/api/access/request/onboarding/attest", methods=["POST"]) - @require_auth def request_access_onboarding_attest() -> Any: if not configured(): return jsonify({"error": "server not configured"}), 503 @@ -895,9 +894,20 @@ def register(app) -> None: if step in KEYCLOAK_MANAGED_STEPS: return jsonify({"error": "step is managed by keycloak"}), 400 - username = getattr(g, "keycloak_username", "") or "" - if not username: - return jsonify({"error": "invalid token"}), 401 + username = "" + bearer = request.headers.get("Authorization", "") + if bearer: + parts = bearer.split(None, 1) + if len(parts) != 2 or parts[0].lower() != "bearer": + return jsonify({"error": "invalid token"}), 401 + token = parts[1].strip() + if not token: + return jsonify({"error": "invalid token"}), 401 + try: + claims = oidc_client().verify(token) + except Exception: + return jsonify({"error": "invalid token"}), 401 + username = claims.get("preferred_username") or "" try: with connect() as conn: @@ -907,7 +917,7 @@ def register(app) -> None: ).fetchone() if not row: return jsonify({"error": "not found"}), 404 - if (row.get("username") or "") != username: + if username and (row.get("username") or "") != username: return jsonify({"error": "forbidden"}), 403 status = _normalize_status(row.get("status") or "") diff --git a/frontend/src/views/OnboardingView.vue b/frontend/src/views/OnboardingView.vue index 752688a..8dc6b02 100644 --- a/frontend/src/views/OnboardingView.vue +++ b/frontend/src/views/OnboardingView.vue @@ -944,8 +944,8 @@ async function toggleStep(stepId, event) { } async function setStepCompletion(stepId, completed) { - if (!auth.authenticated) { - error.value = "Log in to update onboarding steps."; + if (!requestCode.value.trim()) { + error.value = "Request code is missing."; return; } if (isStepBlocked(stepId)) { @@ -957,7 +957,8 @@ async function setStepCompletion(stepId, completed) { loading.value = true; error.value = ""; try { - const resp = await authFetch("/api/access/request/onboarding/attest", { + const requester = auth.authenticated ? authFetch : fetch; + const resp = await requester("/api/access/request/onboarding/attest", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ request_code: requestCode.value.trim(), step: stepId, completed }),