From aaa72be54eb0d6b391871d0a41abfdb1441b6b9d Mon Sep 17 00:00:00 2001 From: Paul Huliganga Date: Wed, 22 Apr 2026 10:52:55 -0400 Subject: [PATCH] Show connected account names in UI --- tests/test_api_auth.py | 11 ++++++- web/routers/auth.py | 65 +++++++++++++++++++++++++++++++++++++-- web/session.py | 4 +++ web/static/js/auth.js | 19 ++++++++++-- web/static/js/help.js | 2 +- web/static/js/settings.js | 9 ++++-- web/static/js/state.js | 2 ++ 7 files changed, 103 insertions(+), 9 deletions(-) diff --git a/tests/test_api_auth.py b/tests/test_api_auth.py index b675ac8..b1f1001 100644 --- a/tests/test_api_auth.py +++ b/tests/test_api_auth.py @@ -74,7 +74,13 @@ def test_adobe_connect_env_stores_token(monkeypatch): monkeypatch.setenv("ADOBE_REFRESH_TOKEN", "existing-refresh") from unittest.mock import patch - with patch("adobe_api._refresh_access_token", return_value="refreshed-token"): + with patch("adobe_api._refresh_access_token", return_value="refreshed-token"), \ + patch("web.routers.auth._fetch_adobe_profile", return_value={ + "adobe_user_name": "Paul Adobe", + "adobe_user_email": "paul@example.com", + "adobe_account_name": "Paul Sandbox", + "adobe_account_id": "adobe-account-123", + }): resp = client.get("/api/auth/adobe/connect") assert resp.status_code == 200 @@ -82,6 +88,9 @@ def test_adobe_connect_env_stores_token(monkeypatch): session_cookie = resp.cookies.get("migrator_session") status_resp = client.get("/api/auth/status", cookies={"migrator_session": session_cookie}) assert status_resp.json()["adobe"] is True + assert status_resp.json()["adobe_account_name"] == "Paul Sandbox" + assert status_resp.json()["adobe_account_id"] == "adobe-account-123" + assert status_resp.json()["adobe_label"] == "Paul Sandbox" def test_adobe_connect_env_fails_without_credentials(monkeypatch): diff --git a/web/routers/auth.py b/web/routers/auth.py index 30634e7..372b543 100644 --- a/web/routers/auth.py +++ b/web/routers/auth.py @@ -40,6 +40,59 @@ _ADOBE_AUTH_URL = "https://secure.eu2.adobesign.com/public/oauth/v2" _ADOBE_TOKEN_URL = "https://api.eu2.adobesign.com/oauth/v2/token" +async def _fetch_adobe_profile(access_token: str) -> dict: + """ + Best-effort Adobe Sign profile lookup used only for nicer UI labels. + This should never block a successful connection if Adobe returns sparse data. + """ + try: + async with httpx.AsyncClient() as client: + resp = await client.get( + f"{settings.adobe_sign_base_url}/users/me", + headers={"Authorization": f"Bearer {access_token}"}, + ) + except Exception: + return {} + + if not resp.is_success: + return {} + + data = resp.json() if resp.content else {} + if not isinstance(data, dict): + return {} + + company = data.get("company") or {} + account_name = ( + company.get("name") + or data.get("companyName") + or data.get("accountName") + or data.get("name") + ) + account_id = ( + company.get("id") + or data.get("companyId") + or data.get("accountId") + ) + + return { + "adobe_user_name": data.get("name"), + "adobe_user_email": data.get("email"), + "adobe_account_name": account_name, + "adobe_account_id": account_id, + } + + +async def _merge_adobe_profile(session: dict, access_token: str) -> dict: + profile = await _fetch_adobe_profile(access_token) + if not profile: + return session + updated = dict(session) + for key, value in profile.items(): + if value: + updated[key] = value + return updated + + # --------------------------------------------------------------------------- # Status # --------------------------------------------------------------------------- @@ -55,8 +108,10 @@ def auth_status(request: Request): docusign_account = None return { **session_public_view(session), - "adobe_label": "Adobe Sign", + "adobe_label": session.get("adobe_account_name") or session.get("adobe_user_name") or "Adobe Sign", "docusign_label": session.get("docusign_user_name") or "Docusign", + "adobe_account_name": session.get("adobe_account_name"), + "adobe_account_id": session.get("adobe_account_id"), "docusign_account_id": (docusign_account or {}).get("account_id"), "docusign_account_name": (docusign_account or {}).get("account_name"), "base_url": (docusign_account or {}).get("base_url", settings.docusign_base_url), @@ -135,6 +190,7 @@ async def adobe_exchange(body: AdobeExchangeRequest, request: Request): session["adobe_access_token"] = token_data.get("access_token") session["adobe_refresh_token"] = token_data.get("refresh_token") session["adobe_auth_mode"] = "session_oauth" + session = await _merge_adobe_profile(session, session["adobe_access_token"]) response = JSONResponse({"connected": True}) save_session(response, session) @@ -142,7 +198,7 @@ async def adobe_exchange(body: AdobeExchangeRequest, request: Request): @router.get("/adobe/connect") -def adobe_connect_env(request: Request): +async def adobe_connect_env(request: Request): """ Load Adobe Sign credentials directly from .env (ADOBE_ACCESS_TOKEN / ADOBE_REFRESH_TOKEN). Refreshes the token if needed. No browser login required @@ -173,6 +229,7 @@ def adobe_connect_env(request: Request): session["adobe_access_token"] = token session["adobe_refresh_token"] = refresh_token session["adobe_auth_mode"] = "shared_env" + session = await _merge_adobe_profile(session, token) response = JSONResponse({"connected": True}) save_session(response, session) @@ -184,6 +241,10 @@ def adobe_disconnect(request: Request): session = get_session(request) session.pop("adobe_access_token", None) session.pop("adobe_refresh_token", None) + session.pop("adobe_user_name", None) + session.pop("adobe_user_email", None) + session.pop("adobe_account_name", None) + session.pop("adobe_account_id", None) session["adobe_auth_mode"] = "disconnected" response = JSONResponse({"disconnected": "adobe"}) save_session(response, session) diff --git a/web/session.py b/web/session.py index f1e9fea..5a9cd34 100644 --- a/web/session.py +++ b/web/session.py @@ -155,6 +155,10 @@ def session_public_view(session: dict[str, Any]) -> dict[str, Any]: "adobe": bool(session.get("adobe_access_token")), "docusign": bool(session.get("docusign_access_token")), "adobe_auth_mode": session.get("adobe_auth_mode", "disconnected"), + "adobe_user_name": session.get("adobe_user_name"), + "adobe_user_email": session.get("adobe_user_email"), + "adobe_account_name": session.get("adobe_account_name"), + "adobe_account_id": session.get("adobe_account_id"), "docusign_auth_mode": session.get("docusign_auth_mode", "disconnected"), "docusign_user_name": session.get("docusign_user_name"), "docusign_user_email": session.get("docusign_user_email"), diff --git a/web/static/js/auth.js b/web/static/js/auth.js index 2a4f289..5e4c782 100644 --- a/web/static/js/auth.js +++ b/web/static/js/auth.js @@ -13,6 +13,8 @@ export async function refreshAuth() { adobe: !!data.adobe, docusign: !!data.docusign, adobeLabel: data.adobe_label || 'Adobe Sign', + adobeAccountId: data.adobe_account_id || null, + adobeAccountName: data.adobe_account_name || null, docusignLabel: data.docusign_label || 'Docusign', docusignAccountId: data.docusign_account_id || null, docusignAccountName: data.docusign_account_name || null, @@ -31,11 +33,16 @@ export async function refreshAuth() { // ── Render connection pills in top bar ───────────────────────────────────── export function renderAuthChips() { - renderChip('chip-adobe', state.auth.adobe, 'Adobe Sign', onClickAdobe); + renderChip( + 'chip-adobe', + state.auth.adobe, + state.auth.adobe ? `Adobe: ${state.auth.adobeAccountName || state.auth.adobeLabel || 'Connected'}` : 'Adobe Sign', + onClickAdobe + ); renderChip( 'chip-docusign', state.auth.docusign, - state.auth.docusignAccountName || 'Docusign', + state.auth.docusign ? `Docusign: ${state.auth.docusignAccountName || state.auth.docusignLabel || 'Connected'}` : 'Docusign', onClickDocusign ); renderAvatar(); @@ -97,7 +104,13 @@ export async function disconnectPlatform(platform, opts = {}) { docusignAccountSelectionRequired: false, }); } else { - setState('auth', { ...state.auth, adobe: false }); + setState('auth', { + ...state.auth, + adobe: false, + adobeAccountId: null, + adobeAccountName: null, + adobeLabel: 'Adobe Sign', + }); } renderAuthChips(); if (!skipRefresh) { diff --git a/web/static/js/help.js b/web/static/js/help.js index 56e9ffb..e66b9c7 100644 --- a/web/static/js/help.js +++ b/web/static/js/help.js @@ -96,7 +96,7 @@ export function quickStartCardMarkup() {
ℹ️ - This tool compares Adobe Sign templates with DocuSign templates, helps you migrate them, and gives you a place to verify the results afterward. + This app helps you migrate templates from Adobe Sign into DocuSign, review blockers and warnings, and send verification envelopes after migration.
${quickStartStepsMarkup()}
diff --git a/web/static/js/settings.js b/web/static/js/settings.js index 59517e0..54fc574 100644 --- a/web/static/js/settings.js +++ b/web/static/js/settings.js @@ -193,14 +193,14 @@ async function _loadConnInfo() { connEl.innerHTML = `
Adobe Sign - ${data.adobe ? 'Connected' : 'Not connected'} + ${data.adobe ? `Connected${data.adobe_account_name ? ` — ${escHtml(data.adobe_account_name)}` : ''}` : 'Not connected'} ${data.adobe ? '● Connected' : '○ Disconnected'}
Docusign - ${data.docusign ? (data.docusign_account_selection_required ? 'Connected — account selection required' : 'Connected') : 'Not connected'} + ${data.docusign ? (data.docusign_account_selection_required ? 'Connected — account selection required' : `Connected${data.docusign_account_name ? ` — ${escHtml(data.docusign_account_name)}` : ''}`) : 'Not connected'} @@ -241,6 +241,11 @@ async function _loadConnInfo() { Use Choose Account or Switch Docusign Account to select from the DocuSign accounts available to this login. The picker is sorted alphabetically and supports search.
+
+ Adobe Account ID + ${escHtml(data.adobe_account_id || '—')} + +
Docusign Account ID ${escHtml(data.docusign_account_id || '—')} diff --git a/web/static/js/state.js b/web/static/js/state.js index 09e6435..a7afd86 100644 --- a/web/static/js/state.js +++ b/web/static/js/state.js @@ -8,6 +8,8 @@ export const state = { adobe: false, docusign: false, adobeLabel: 'Adobe Sign', + adobeAccountId: null, + adobeAccountName: null, docusignLabel: 'Docusign', docusignAccountId: null, docusignAccountName: null,