Return auth callbacks to current page
This commit is contained in:
parent
e1ae1c91af
commit
e995ac2764
|
|
@ -170,6 +170,26 @@ def test_adobe_callback_requires_matching_state():
|
||||||
assert "invalid oauth state" in resp.json()["error"]
|
assert "invalid oauth state" in resp.json()["error"]
|
||||||
|
|
||||||
|
|
||||||
|
@respx.mock
|
||||||
|
def test_adobe_callback_redirects_back_to_requested_page():
|
||||||
|
respx.post(_ADOBE_TOKEN_URL).mock(
|
||||||
|
return_value=httpx.Response(200, json={
|
||||||
|
"access_token": "adobe-test-token",
|
||||||
|
"refresh_token": "adobe-refresh",
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
cookie = create_test_session({"adobe_oauth_state": "expected-state", "adobe_return_to": "#/help"})
|
||||||
|
resp = client.get(
|
||||||
|
"/api/auth/adobe/callback?code=authcode123&state=expected-state",
|
||||||
|
cookies={"migrator_session": cookie},
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code in (302, 307)
|
||||||
|
assert resp.headers["location"] == "#/help"
|
||||||
|
|
||||||
|
|
||||||
def test_adobe_exchange_rejects_missing_code():
|
def test_adobe_exchange_rejects_missing_code():
|
||||||
"""POST /api/auth/adobe/exchange with no code in URL → 400."""
|
"""POST /api/auth/adobe/exchange with no code in URL → 400."""
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
|
|
@ -245,6 +265,25 @@ def test_docusign_callback_stores_per_session_tokens():
|
||||||
assert status_resp.json()["docusign"] is True
|
assert status_resp.json()["docusign"] is True
|
||||||
assert status_resp.json()["docusign_auth_mode"] == "session_oauth"
|
assert status_resp.json()["docusign_auth_mode"] == "session_oauth"
|
||||||
assert status_resp.json()["docusign_account_selection_required"] is True
|
assert status_resp.json()["docusign_account_selection_required"] is True
|
||||||
|
assert resp.headers["location"] == "#/templates"
|
||||||
|
|
||||||
|
|
||||||
|
def test_docusign_callback_redirects_back_to_requested_page():
|
||||||
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
|
cookie = create_test_session({"docusign_oauth_state": "expected-state", "docusign_return_to": "#/help"})
|
||||||
|
with patch(
|
||||||
|
"docusign_auth.exchange_code_for_token",
|
||||||
|
return_value={"access_token": "access-123", "refresh_token": "refresh-123", "expires_in": 3600},
|
||||||
|
), patch("web.routers.auth.fetch_userinfo", new=AsyncMock(return_value=_userinfo_payload())):
|
||||||
|
resp = client.get(
|
||||||
|
"/api/auth/docusign/callback?code=authcode123&state=expected-state",
|
||||||
|
cookies={"migrator_session": cookie},
|
||||||
|
follow_redirects=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert resp.status_code in (302, 307)
|
||||||
|
assert resp.headers["location"] == "#/help"
|
||||||
|
|
||||||
|
|
||||||
def test_docusign_sessions_are_isolated():
|
def test_docusign_sessions_are_isolated():
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,12 @@ def _adobe_redirect_uri() -> str:
|
||||||
return settings.adobe_redirect_uri
|
return settings.adobe_redirect_uri
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_return_to(value: str | None) -> str:
|
||||||
|
if value and value.startswith("#/"):
|
||||||
|
return value
|
||||||
|
return "#/templates"
|
||||||
|
|
||||||
|
|
||||||
def _build_adobe_authorization_url(state: str) -> str:
|
def _build_adobe_authorization_url(state: str) -> str:
|
||||||
return (
|
return (
|
||||||
f"{_ADOBE_AUTH_URL}"
|
f"{_ADOBE_AUTH_URL}"
|
||||||
|
|
@ -156,11 +162,12 @@ def auth_status(request: Request):
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@router.get("/adobe/url")
|
@router.get("/adobe/url")
|
||||||
def adobe_auth_url(request: Request):
|
def adobe_auth_url(request: Request, return_to: str | None = None):
|
||||||
session = get_session(request)
|
session = get_session(request)
|
||||||
state = secrets.token_urlsafe(24)
|
state = secrets.token_urlsafe(24)
|
||||||
session["adobe_oauth_state"] = state
|
session["adobe_oauth_state"] = state
|
||||||
session["adobe_auth_mode"] = "authorization_pending"
|
session["adobe_auth_mode"] = "authorization_pending"
|
||||||
|
session["adobe_return_to"] = _sanitize_return_to(return_to)
|
||||||
response = JSONResponse({"url": _build_adobe_authorization_url(state)})
|
response = JSONResponse({"url": _build_adobe_authorization_url(state)})
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
return response
|
return response
|
||||||
|
|
@ -231,7 +238,7 @@ async def adobe_exchange(body: AdobeExchangeRequest, request: Request):
|
||||||
|
|
||||||
|
|
||||||
@router.get("/adobe/connect")
|
@router.get("/adobe/connect")
|
||||||
async def adobe_connect(request: Request, force_oauth: bool = False):
|
async def adobe_connect(request: Request, force_oauth: bool = False, return_to: str | None = None):
|
||||||
"""
|
"""
|
||||||
Obtain an Adobe Sign access token for this browser session.
|
Obtain an Adobe Sign access token for this browser session.
|
||||||
If session/env tokens are unavailable or force_oauth=true, return an
|
If session/env tokens are unavailable or force_oauth=true, return an
|
||||||
|
|
@ -299,6 +306,7 @@ async def adobe_connect(request: Request, force_oauth: bool = False):
|
||||||
state = secrets.token_urlsafe(24)
|
state = secrets.token_urlsafe(24)
|
||||||
session["adobe_oauth_state"] = state
|
session["adobe_oauth_state"] = state
|
||||||
session["adobe_auth_mode"] = "authorization_pending"
|
session["adobe_auth_mode"] = "authorization_pending"
|
||||||
|
session["adobe_return_to"] = _sanitize_return_to(return_to)
|
||||||
authorization_url = _build_adobe_authorization_url(state)
|
authorization_url = _build_adobe_authorization_url(state)
|
||||||
log_event(
|
log_event(
|
||||||
request,
|
request,
|
||||||
|
|
@ -359,7 +367,7 @@ async def adobe_callback(request: Request, code: str = "", state: str = ""):
|
||||||
{"auth_mode": "session_oauth", "source": "browser_callback"},
|
{"auth_mode": "session_oauth", "source": "browser_callback"},
|
||||||
)
|
)
|
||||||
|
|
||||||
response = RedirectResponse("/#/settings")
|
response = RedirectResponse(session.pop("adobe_return_to", "#/templates"))
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
@ -392,7 +400,7 @@ def adobe_disconnect(request: Request):
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
@router.get("/docusign/connect")
|
@router.get("/docusign/connect")
|
||||||
async def docusign_connect(request: Request):
|
async def docusign_connect(request: Request, return_to: str | None = None):
|
||||||
"""
|
"""
|
||||||
Obtain a DocuSign access token from the current browser session.
|
Obtain a DocuSign access token from the current browser session.
|
||||||
If the session has not been authorized yet, return an authorization URL so
|
If the session has not been authorized yet, return an authorization URL so
|
||||||
|
|
@ -423,6 +431,7 @@ async def docusign_connect(request: Request):
|
||||||
state = secrets.token_urlsafe(24)
|
state = secrets.token_urlsafe(24)
|
||||||
session["docusign_oauth_state"] = state
|
session["docusign_oauth_state"] = state
|
||||||
session["docusign_auth_mode"] = "authorization_pending"
|
session["docusign_auth_mode"] = "authorization_pending"
|
||||||
|
session["docusign_return_to"] = _sanitize_return_to(return_to)
|
||||||
authorization_url = build_authorization_url(state=state)
|
authorization_url = build_authorization_url(state=state)
|
||||||
log_event(
|
log_event(
|
||||||
request,
|
request,
|
||||||
|
|
@ -480,6 +489,7 @@ def docusign_start(request: Request):
|
||||||
state = secrets.token_urlsafe(24)
|
state = secrets.token_urlsafe(24)
|
||||||
session["docusign_oauth_state"] = state
|
session["docusign_oauth_state"] = state
|
||||||
session["docusign_auth_mode"] = "authorization_pending"
|
session["docusign_auth_mode"] = "authorization_pending"
|
||||||
|
session["docusign_return_to"] = "#/templates"
|
||||||
authorization_url = build_authorization_url(state=state)
|
authorization_url = build_authorization_url(state=state)
|
||||||
log_event(
|
log_event(
|
||||||
request,
|
request,
|
||||||
|
|
@ -528,7 +538,7 @@ async def docusign_callback(request: Request, code: str = "", state: str = ""):
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
response = RedirectResponse("/#/settings" if account_picker_required(session) else "/")
|
response = RedirectResponse(session.pop("docusign_return_to", "#/templates"))
|
||||||
save_session(response, session)
|
save_session(response, session)
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,11 @@ export const api = {
|
||||||
status() {
|
status() {
|
||||||
return GET('/api/auth/status');
|
return GET('/api/auth/status');
|
||||||
},
|
},
|
||||||
connectAdobe(forceOauth = false) {
|
connectAdobe(forceOauth = false, returnTo = '#/templates') {
|
||||||
return GET(`/api/auth/adobe/connect${forceOauth ? '?force_oauth=true' : ''}`);
|
const params = new URLSearchParams();
|
||||||
|
if (forceOauth) params.set('force_oauth', 'true');
|
||||||
|
params.set('return_to', returnTo);
|
||||||
|
return GET(`/api/auth/adobe/connect?${params.toString()}`);
|
||||||
},
|
},
|
||||||
adobeUrl() {
|
adobeUrl() {
|
||||||
return GET('/api/auth/adobe/url');
|
return GET('/api/auth/adobe/url');
|
||||||
|
|
@ -35,8 +38,8 @@ export const api = {
|
||||||
exchangeAdobe(redirectUrl) {
|
exchangeAdobe(redirectUrl) {
|
||||||
return POST('/api/auth/adobe/exchange', { redirect_url: redirectUrl });
|
return POST('/api/auth/adobe/exchange', { redirect_url: redirectUrl });
|
||||||
},
|
},
|
||||||
connectDocusign() {
|
connectDocusign(returnTo = '#/templates') {
|
||||||
return GET('/api/auth/docusign/connect');
|
return GET(`/api/auth/docusign/connect?return_to=${encodeURIComponent(returnTo)}`);
|
||||||
},
|
},
|
||||||
docusignAccounts() {
|
docusignAccounts() {
|
||||||
return GET('/api/auth/docusign/accounts');
|
return GET('/api/auth/docusign/accounts');
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ async function connectAdobe(forceOauth = false) {
|
||||||
closeAuthMenu();
|
closeAuthMenu();
|
||||||
setChipConnecting('chip-adobe');
|
setChipConnecting('chip-adobe');
|
||||||
try {
|
try {
|
||||||
const data = await api.auth.connectAdobe(forceOauth);
|
const data = await api.auth.connectAdobe(forceOauth, window.location.hash || '#/templates');
|
||||||
if (data.connected) {
|
if (data.connected) {
|
||||||
setState('auth', { ...state.auth, adobe: true });
|
setState('auth', { ...state.auth, adobe: true });
|
||||||
renderAuthChips();
|
renderAuthChips();
|
||||||
|
|
@ -165,7 +165,7 @@ async function connectDocusign() {
|
||||||
closeAuthMenu();
|
closeAuthMenu();
|
||||||
setChipConnecting('chip-docusign');
|
setChipConnecting('chip-docusign');
|
||||||
try {
|
try {
|
||||||
const data = await api.auth.connectDocusign();
|
const data = await api.auth.connectDocusign(window.location.hash || '#/templates');
|
||||||
if (data.connected) {
|
if (data.connected) {
|
||||||
await refreshAuth();
|
await refreshAuth();
|
||||||
if (!data.account_selection_required) {
|
if (!data.account_selection_required) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue