diff --git a/backend/atlas_portal/routes/account.py b/backend/atlas_portal/routes/account.py index a0dab96..2cd63e0 100644 --- a/backend/atlas_portal/routes/account.py +++ b/backend/atlas_portal/routes/account.py @@ -3,11 +3,12 @@ from __future__ import annotations import time from typing import Any +import httpx from flask import jsonify, g from .. import settings from ..keycloak import admin_client, require_auth, require_account_access -from ..utils import best_effort_post, random_password +from ..utils import random_password def register(app) -> None: @@ -78,27 +79,19 @@ def register(app) -> None: except Exception: return jsonify({"error": "failed to update mail password"}), 502 - best_effort_post(settings.MAILU_SYNC_URL) - return jsonify({"password": password}) + sync_ok = False + sync_error = "" + if settings.MAILU_SYNC_URL: + try: + with httpx.Client(timeout=30) as client: + resp = client.post( + settings.MAILU_SYNC_URL, + json={"ts": int(time.time()), "wait": True, "reason": "portal_mailu_rotate"}, + ) + sync_ok = resp.status_code == 200 + if not sync_ok: + sync_error = f"sync status {resp.status_code}" + except Exception: + sync_error = "sync request failed" - @app.route("/api/account/jellyfin/rotate", methods=["POST"]) - @require_auth - def account_jellyfin_rotate() -> Any: - ok, resp = require_account_access() - if not ok: - return resp - if not admin_client().ready(): - return jsonify({"error": "server not configured"}), 503 - - username = g.keycloak_username - if not username: - return jsonify({"error": "missing username"}), 400 - - password = random_password() - try: - admin_client().set_user_attribute(username, "jellyfin_app_password", password) - except Exception: - return jsonify({"error": "failed to update jellyfin password"}), 502 - - best_effort_post(settings.JELLYFIN_SYNC_URL) - return jsonify({"password": password}) + return jsonify({"password": password, "sync_ok": sync_ok, "sync_error": sync_error}) diff --git a/frontend/src/views/AccountView.vue b/frontend/src/views/AccountView.vue index e568a79..dcdf9c7 100644 --- a/frontend/src/views/AccountView.vue +++ b/frontend/src/views/AccountView.vue @@ -50,7 +50,10 @@
Current password
- + @@ -65,7 +68,10 @@
Show once
- +
{{ mailu.newPassword }}
Update your mail client password to match.
@@ -82,7 +88,8 @@ {{ jellyfin.status }}

- Jellyfin authentication is backed by LDAP. If your login ever fails, rotate an app password and re-sync. + Jellyfin authentication is backed by LDAP (Keycloak is the source of truth). Use your Keycloak username and + password.

@@ -94,19 +101,6 @@ {{ jellyfin.username }}
-
- -
-
-
-
Show once
- -
-
{{ jellyfin.newPassword }}
-
Use your Keycloak username and this password on Jellyfin.
-
{{ jellyfin.error }}
@@ -161,7 +155,7 @@ @@ -473,6 +498,14 @@ button.primary { border-radius: 10px; padding: 6px 10px; cursor: pointer; + display: inline-flex; + align-items: center; + gap: 8px; +} + +.copied { + font-size: 12px; + color: rgba(120, 255, 160, 0.9); } .error-box { diff --git a/frontend/src/views/RequestAccessView.vue b/frontend/src/views/RequestAccessView.vue index e22513b..0de4664 100644 --- a/frontend/src/views/RequestAccessView.vue +++ b/frontend/src/views/RequestAccessView.vue @@ -170,7 +170,21 @@ async function submit() { async function copyRequestCode() { if (!requestCode.value) return; try { - await navigator.clipboard.writeText(requestCode.value); + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(requestCode.value); + } else { + const textarea = document.createElement("textarea"); + textarea.value = requestCode.value; + textarea.setAttribute("readonly", ""); + textarea.style.position = "fixed"; + textarea.style.top = "-9999px"; + textarea.style.left = "-9999px"; + document.body.appendChild(textarea); + textarea.select(); + textarea.setSelectionRange(0, textarea.value.length); + document.execCommand("copy"); + document.body.removeChild(textarea); + } copied.value = true; setTimeout(() => (copied.value = false), 1500); } catch (err) {