Show connected account names in UI

This commit is contained in:
Paul Huliganga 2026-04-22 10:52:55 -04:00
parent 0aba091d56
commit aaa72be54e
7 changed files with 103 additions and 9 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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"),

View File

@ -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) {

View File

@ -96,7 +96,7 @@ export function quickStartCardMarkup() {
<div class="card-body">
<div class="callout info" style="margin-bottom:16px">
<span class="callout-icon"></span>
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.
</div>
${quickStartStepsMarkup()}
</div>

View File

@ -193,14 +193,14 @@ async function _loadConnInfo() {
connEl.innerHTML = `
<div class="conn-info-row">
<span class="conn-info-label">Adobe Sign</span>
<span class="conn-info-value">${data.adobe ? 'Connected' : 'Not connected'}</span>
<span class="conn-info-value">${data.adobe ? `Connected${data.adobe_account_name ? `${escHtml(data.adobe_account_name)}` : ''}` : 'Not connected'}</span>
<span class="conn-info-status">
<span class="badge ${data.adobe ? 'badge-green' : 'badge-gray'}">${data.adobe ? '● Connected' : '○ Disconnected'}</span>
</span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Docusign</span>
<span class="conn-info-value">${data.docusign ? (data.docusign_account_selection_required ? 'Connected — account selection required' : 'Connected') : 'Not connected'}</span>
<span class="conn-info-value">${data.docusign ? (data.docusign_account_selection_required ? 'Connected — account selection required' : `Connected${data.docusign_account_name ? `${escHtml(data.docusign_account_name)}` : ''}`) : 'Not connected'}</span>
<span class="conn-info-status">
<span class="badge ${data.docusign ? (data.docusign_account_selection_required ? 'badge-amber' : 'badge-green') : 'badge-gray'}">${data.docusign ? (data.docusign_account_selection_required ? '● Choose account' : '● Connected') : '○ Disconnected'}</span>
</span>
@ -241,6 +241,11 @@ async function _loadConnInfo() {
<span class="conn-info-value">Use <strong>Choose Account</strong> or <strong>Switch Docusign Account</strong> to select from the DocuSign accounts available to this login. The picker is sorted alphabetically and supports search.</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Adobe Account ID</span>
<span class="conn-info-value mono">${escHtml(data.adobe_account_id || '—')}</span>
<span class="conn-info-status"></span>
</div>
<div class="conn-info-row">
<span class="conn-info-label">Docusign Account ID</span>
<span class="conn-info-value mono">${escHtml(data.docusign_account_id || '—')}</span>

View File

@ -8,6 +8,8 @@ export const state = {
adobe: false,
docusign: false,
adobeLabel: 'Adobe Sign',
adobeAccountId: null,
adobeAccountName: null,
docusignLabel: 'Docusign',
docusignAccountId: null,
docusignAccountName: null,