diff --git a/README.md b/README.md index e521a55..7ce58aa 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ It downloads templates via the Adobe Sign API, converts them to DocuSign format, 1. **Authenticates** with Adobe Sign via OAuth (one-time browser flow, tokens saved to `.env`) 2. **Downloads** templates — PDF, metadata, and form field definitions 3. **Converts** each template to a DocuSign `envelopeTemplate` JSON, mapping all field types, coordinates, recipient roles, and conditional field logic -4. **Authenticates** with DocuSign via JWT grant (one-time browser consent, then fully automated) +4. **Authenticates** with DocuSign via OAuth Authorization Code Grant (one-time browser login, then refresh-token based) 5. **Uploads** the converted template to DocuSign via the REST API --- @@ -19,7 +19,7 @@ It downloads templates via the Adobe Sign API, converts them to DocuSign format, - Python 3.10+ - An Adobe Sign OAuth app (EU2 shard) with scopes: `library_read:self library_write:self user_read:self` -- A DocuSign developer account with an integration key and RSA keypair +- A DocuSign developer account with an OAuth app client ID and client secret --- @@ -46,14 +46,13 @@ python3 src/adobe_auth.py Opens a browser. After authorizing, paste the redirect URL back into the terminal. Tokens are saved to `.env` and auto-refreshed on subsequent runs. -**4. Grant consent for DocuSign** (one-time per user): +**4. Authorize DocuSign** (one-time per user): ```bash -python3 src/docusign_auth.py --consent +python3 src/docusign_auth.py --authorize ``` -Opens a browser for the DocuSign OAuth consent screen. After approving, paste the -redirect URL back into the terminal. This grants the `impersonation` scope required -for JWT grant. After this runs once, all subsequent API calls use JWT automatically — -no further browser interaction needed. +Opens a browser for the DocuSign OAuth screen. After approving, paste the +redirect URL back into the terminal. The app stores both access and refresh +tokens in `.env`, and later API calls refresh access tokens automatically. --- @@ -274,7 +273,7 @@ src/ adobe_api.py # Adobe Sign API client (auto token refresh) download_templates.py # List and download templates from Adobe Sign compose_docusign_template.py # Core conversion: Adobe Sign → DocuSign JSON - docusign_auth.py # DocuSign JWT auth + one-time consent flow + docusign_auth.py # DocuSign auth-code + refresh-token helper upload_docusign_template.py # Upsert upload: PUT if exists, POST if not migrate_template.py # End-to-end CLI runner (download → convert → upload) diff --git a/tests/UI-SMOKE-TEST.md b/tests/UI-SMOKE-TEST.md index 728c7c9..de519bf 100644 --- a/tests/UI-SMOKE-TEST.md +++ b/tests/UI-SMOKE-TEST.md @@ -33,7 +33,7 @@ Then open [http://localhost:8000](http://localhost:8000). - [ ] Top bar shows two disconnected chips (red dot): "Adobe Sign" and "DocuSign" - [ ] Click "Adobe Sign" chip → connects via `.env` refresh token → chip turns green -- [ ] Click "DocuSign" chip → connects via JWT grant → chip turns green +- [ ] Click "DocuSign" chip → redirects through OAuth if needed, then chip turns green - [ ] Disconnecting either chip → chip turns red → templates clear ## 4. Templates View diff --git a/tests/test_api_auth.py b/tests/test_api_auth.py index b001fc0..d610a4f 100644 --- a/tests/test_api_auth.py +++ b/tests/test_api_auth.py @@ -96,11 +96,10 @@ def test_adobe_exchange_rejects_missing_code(): def test_docusign_connect_stores_token(): - """GET /api/auth/docusign/connect uses JWT grant from .env → session connected.""" + """GET /api/auth/docusign/connect uses cached OAuth credentials → session connected.""" from unittest.mock import patch - import web.routers.auth as auth_module - with patch("docusign_auth.get_access_token", return_value="ds-jwt-token"): + with patch("docusign_auth.get_access_token", return_value="ds-oauth-token"): resp = client.get("/api/auth/docusign/connect") assert resp.status_code == 200 @@ -113,6 +112,19 @@ def test_docusign_connect_stores_token(): assert status_resp.json()["docusign"] is True +def test_docusign_connect_requests_authorization_when_refresh_token_missing(): + """GET /api/auth/docusign/connect returns an auth URL when first-time authorization is needed.""" + from unittest.mock import patch + + with patch("docusign_auth.get_access_token", side_effect=RuntimeError("No DocuSign refresh token found")), \ + patch("docusign_auth.build_authorization_url", return_value="https://example.com/oauth"): + resp = client.get("/api/auth/docusign/connect") + + assert resp.status_code == 200 + assert resp.json()["authorization_required"] is True + assert resp.json()["authorization_url"] == "https://example.com/oauth" + + @respx.mock def test_disconnect_clears_token(): """After disconnect, status shows platform as disconnected."""