Show connected account names in UI
This commit is contained in:
parent
0aba091d56
commit
aaa72be54e
|
|
@ -74,7 +74,13 @@ def test_adobe_connect_env_stores_token(monkeypatch):
|
||||||
monkeypatch.setenv("ADOBE_REFRESH_TOKEN", "existing-refresh")
|
monkeypatch.setenv("ADOBE_REFRESH_TOKEN", "existing-refresh")
|
||||||
|
|
||||||
from unittest.mock import patch
|
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")
|
resp = client.get("/api/auth/adobe/connect")
|
||||||
|
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
|
|
@ -82,6 +88,9 @@ def test_adobe_connect_env_stores_token(monkeypatch):
|
||||||
session_cookie = resp.cookies.get("migrator_session")
|
session_cookie = resp.cookies.get("migrator_session")
|
||||||
status_resp = client.get("/api/auth/status", cookies={"migrator_session": session_cookie})
|
status_resp = client.get("/api/auth/status", cookies={"migrator_session": session_cookie})
|
||||||
assert status_resp.json()["adobe"] is True
|
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):
|
def test_adobe_connect_env_fails_without_credentials(monkeypatch):
|
||||||
|
|
|
||||||
|
|
@ -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"
|
_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
|
# Status
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
@ -55,8 +108,10 @@ def auth_status(request: Request):
|
||||||
docusign_account = None
|
docusign_account = None
|
||||||
return {
|
return {
|
||||||
**session_public_view(session),
|
**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",
|
"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_id": (docusign_account or {}).get("account_id"),
|
||||||
"docusign_account_name": (docusign_account or {}).get("account_name"),
|
"docusign_account_name": (docusign_account or {}).get("account_name"),
|
||||||
"base_url": (docusign_account or {}).get("base_url", settings.docusign_base_url),
|
"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_access_token"] = token_data.get("access_token")
|
||||||
session["adobe_refresh_token"] = token_data.get("refresh_token")
|
session["adobe_refresh_token"] = token_data.get("refresh_token")
|
||||||
session["adobe_auth_mode"] = "session_oauth"
|
session["adobe_auth_mode"] = "session_oauth"
|
||||||
|
session = await _merge_adobe_profile(session, session["adobe_access_token"])
|
||||||
|
|
||||||
response = JSONResponse({"connected": True})
|
response = JSONResponse({"connected": True})
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
|
|
@ -142,7 +198,7 @@ async def adobe_exchange(body: AdobeExchangeRequest, request: Request):
|
||||||
|
|
||||||
|
|
||||||
@router.get("/adobe/connect")
|
@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 /
|
Load Adobe Sign credentials directly from .env (ADOBE_ACCESS_TOKEN /
|
||||||
ADOBE_REFRESH_TOKEN). Refreshes the token if needed. No browser login required
|
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_access_token"] = token
|
||||||
session["adobe_refresh_token"] = refresh_token
|
session["adobe_refresh_token"] = refresh_token
|
||||||
session["adobe_auth_mode"] = "shared_env"
|
session["adobe_auth_mode"] = "shared_env"
|
||||||
|
session = await _merge_adobe_profile(session, token)
|
||||||
|
|
||||||
response = JSONResponse({"connected": True})
|
response = JSONResponse({"connected": True})
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
|
|
@ -184,6 +241,10 @@ def adobe_disconnect(request: Request):
|
||||||
session = get_session(request)
|
session = get_session(request)
|
||||||
session.pop("adobe_access_token", None)
|
session.pop("adobe_access_token", None)
|
||||||
session.pop("adobe_refresh_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"
|
session["adobe_auth_mode"] = "disconnected"
|
||||||
response = JSONResponse({"disconnected": "adobe"})
|
response = JSONResponse({"disconnected": "adobe"})
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
|
|
|
||||||
|
|
@ -155,6 +155,10 @@ def session_public_view(session: dict[str, Any]) -> dict[str, Any]:
|
||||||
"adobe": bool(session.get("adobe_access_token")),
|
"adobe": bool(session.get("adobe_access_token")),
|
||||||
"docusign": bool(session.get("docusign_access_token")),
|
"docusign": bool(session.get("docusign_access_token")),
|
||||||
"adobe_auth_mode": session.get("adobe_auth_mode", "disconnected"),
|
"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_auth_mode": session.get("docusign_auth_mode", "disconnected"),
|
||||||
"docusign_user_name": session.get("docusign_user_name"),
|
"docusign_user_name": session.get("docusign_user_name"),
|
||||||
"docusign_user_email": session.get("docusign_user_email"),
|
"docusign_user_email": session.get("docusign_user_email"),
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ export async function refreshAuth() {
|
||||||
adobe: !!data.adobe,
|
adobe: !!data.adobe,
|
||||||
docusign: !!data.docusign,
|
docusign: !!data.docusign,
|
||||||
adobeLabel: data.adobe_label || 'Adobe Sign',
|
adobeLabel: data.adobe_label || 'Adobe Sign',
|
||||||
|
adobeAccountId: data.adobe_account_id || null,
|
||||||
|
adobeAccountName: data.adobe_account_name || null,
|
||||||
docusignLabel: data.docusign_label || 'Docusign',
|
docusignLabel: data.docusign_label || 'Docusign',
|
||||||
docusignAccountId: data.docusign_account_id || null,
|
docusignAccountId: data.docusign_account_id || null,
|
||||||
docusignAccountName: data.docusign_account_name || null,
|
docusignAccountName: data.docusign_account_name || null,
|
||||||
|
|
@ -31,11 +33,16 @@ export async function refreshAuth() {
|
||||||
// ── Render connection pills in top bar ─────────────────────────────────────
|
// ── Render connection pills in top bar ─────────────────────────────────────
|
||||||
|
|
||||||
export function renderAuthChips() {
|
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(
|
renderChip(
|
||||||
'chip-docusign',
|
'chip-docusign',
|
||||||
state.auth.docusign,
|
state.auth.docusign,
|
||||||
state.auth.docusignAccountName || 'Docusign',
|
state.auth.docusign ? `Docusign: ${state.auth.docusignAccountName || state.auth.docusignLabel || 'Connected'}` : 'Docusign',
|
||||||
onClickDocusign
|
onClickDocusign
|
||||||
);
|
);
|
||||||
renderAvatar();
|
renderAvatar();
|
||||||
|
|
@ -97,7 +104,13 @@ export async function disconnectPlatform(platform, opts = {}) {
|
||||||
docusignAccountSelectionRequired: false,
|
docusignAccountSelectionRequired: false,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setState('auth', { ...state.auth, adobe: false });
|
setState('auth', {
|
||||||
|
...state.auth,
|
||||||
|
adobe: false,
|
||||||
|
adobeAccountId: null,
|
||||||
|
adobeAccountName: null,
|
||||||
|
adobeLabel: 'Adobe Sign',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
renderAuthChips();
|
renderAuthChips();
|
||||||
if (!skipRefresh) {
|
if (!skipRefresh) {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export function quickStartCardMarkup() {
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="callout info" style="margin-bottom:16px">
|
<div class="callout info" style="margin-bottom:16px">
|
||||||
<span class="callout-icon">ℹ️</span>
|
<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>
|
</div>
|
||||||
${quickStartStepsMarkup()}
|
${quickStartStepsMarkup()}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -193,14 +193,14 @@ async function _loadConnInfo() {
|
||||||
connEl.innerHTML = `
|
connEl.innerHTML = `
|
||||||
<div class="conn-info-row">
|
<div class="conn-info-row">
|
||||||
<span class="conn-info-label">Adobe Sign</span>
|
<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="conn-info-status">
|
||||||
<span class="badge ${data.adobe ? 'badge-green' : 'badge-gray'}">${data.adobe ? '● Connected' : '○ Disconnected'}</span>
|
<span class="badge ${data.adobe ? 'badge-green' : 'badge-gray'}">${data.adobe ? '● Connected' : '○ Disconnected'}</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="conn-info-row">
|
<div class="conn-info-row">
|
||||||
<span class="conn-info-label">Docusign</span>
|
<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="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 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>
|
</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-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>
|
<span class="conn-info-status"></span>
|
||||||
</div>
|
</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">
|
<div class="conn-info-row">
|
||||||
<span class="conn-info-label">Docusign Account ID</span>
|
<span class="conn-info-label">Docusign Account ID</span>
|
||||||
<span class="conn-info-value mono">${escHtml(data.docusign_account_id || '—')}</span>
|
<span class="conn-info-value mono">${escHtml(data.docusign_account_id || '—')}</span>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ export const state = {
|
||||||
adobe: false,
|
adobe: false,
|
||||||
docusign: false,
|
docusign: false,
|
||||||
adobeLabel: 'Adobe Sign',
|
adobeLabel: 'Adobe Sign',
|
||||||
|
adobeAccountId: null,
|
||||||
|
adobeAccountName: null,
|
||||||
docusignLabel: 'Docusign',
|
docusignLabel: 'Docusign',
|
||||||
docusignAccountId: null,
|
docusignAccountId: null,
|
||||||
docusignAccountName: null,
|
docusignAccountName: null,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue