fix(source-salesloft): use native OAuth authenticator so token refresh works - #84300
fix(source-salesloft): use native OAuth authenticator so token refresh works#84300devin-ai-integration[bot] wants to merge 2 commits into
Conversation
Co-Authored-By: bot_apk <apk@cognition.ai>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. 💡 Show Tips and TricksPR Slash CommandsAirbyte Maintainers (that's you!) can execute the following slash commands on your PR:
📚 Show Repo GuidanceHelpful Resources
|
|
Note Autopilot progressive rollouts are not enabled for the following modified connector(s):
This is a courtesy heads-up only — it does not block merge or fail any check. |
Co-Authored-By: bot_apk <apk@cognition.ai>
|
|
Deploy preview for airbyte-docs ready!
Deployed with vercel-action |
CI status: the two red connector checks look independent of this changeNew Salesloft unit tests pass in CI ( 1.
No request to 2. The generated Dockerfile ends with Presence or absence of |
What
OAuth-configured
source-salesloftsources work while the configured access token is valid and fail on the first token refresh (bothcheckand long syncs) with:Resolves https://github.com/airbytehq/oncall/issues/13289:
Community report: #84297
Requested by
aaronsteersvia the/ai-fixworkflow.How
The connector shipped a custom component:
The CDK parent is not a dataclass, so the dataclass-generated
__init__(self, config)shadows the CDK constructor andsuper().__init__()is never called —_token_refresh_endpoint,_client_id,_client_secret,_refresh_tokenand the expiry/grant-type attributes never exist on the instance.ModelToComponentFactory.create_custom_componentfilters manifest kwargs throughget_type_hints, so the manifest'stoken_refresh_endpoint/client_id/client_secret/refresh_token/grant_typewere silently dropped and the component was built withconfigonly. While the configured access token is unexpired,access_tokenis read straight from config and the refresh path is never entered — hence "works, then breaks". (The two config paths were also"a/b"strings where the CDK expects aSequence[str].)Rather than hand-setting more private CDK attributes, the
oauth2.0branch of the existingSelectiveAuthenticatornow uses the CDK's nativeOAuthAuthenticatorwith arefresh_token_updater, andcomponents.pyis deleted:Salesloft revokes all previous refresh tokens on each refresh (docs), so the write-back behaviour of
refresh_token_updateris required, not optional — a plainOAuthAuthenticatorwould break on the second refresh.refresh_token_updateris available in the CDK version behind the connector's base image (5.15.0), which the manifest already declares.The
api_key(BearerAuthenticator) branch is untouched. The spec already containscredentials.access_token,credentials.refresh_tokenandcredentials.token_expiry_date, so no spec change was needed and this is not a breaking change (no schema, PK, cursor, stream or state change) — patch bump to 1.5.3.Not in scope: the base image is still
source-declarative-manifest:5.15.0(Oct 2024). Bumping it is independent of this fix; one upside would be that current CDK reports this refresh failure class as a classifiedAirbyteTracedExceptioninstead of a bareException.Declarative-First Evaluation
Used the fully declarative path: the CDK's built-in
OAuthAuthenticator+RefreshTokenUpdatercovers everything the custom component was trying to do (refresh against Salesloft's token endpoint and persist the rotated access/refresh token and expiry back into the config), so no custom Python component is needed. Same pattern assource-gong,source-airtable,source-gitlab,source-pinterestandsource-quickbooks.Test Coverage
Added
unit_tests/(the connector had none), modelled onsource-gong's manifest-only test setup.unit_tests/test_oauth_refresh.pybuilds the source frommanifest.yamlwith an OAuth config whosetoken_expiry_dateis in the past, mocks the token endpoint, and asserts that the refresh succeeds, that the outgoing request hitshttps://accounts.salesloft.com/oauth/tokenwith the exact expected form body (client_id,client_secret,refresh_token,grant_type=refresh_token), and that the rotated access token, refresh token and expiry are written back into the config. A second test asserts theapi_keybranch still resolves to the configured bearer token.Verified the behavioral test fails on the pre-fix manifest with the reported
AttributeError, and passes after. The failure was also reproduced standalone onairbyte-cdk==5.15.0by instantiating the shipped component the way the factory does (configonly) — it raises before any HTTP call, so no credentials are required.Not verified: an end-to-end live refresh against Salesloft (needs the OAuth test credentials in GSM plus waiting out the 2-hour access-token lifetime).
Review guide
airbyte-integrations/connectors/source-salesloft/manifest.yaml— the authenticator swapairbyte-integrations/connectors/source-salesloft/components.py— deletedairbyte-integrations/connectors/source-salesloft/unit_tests/test_oauth_refresh.pymetadata.yaml/docs/integrations/sources/salesloft.md— version + changelogUser Impact
OAuth-configured Salesloft sources can survive access-token expiry:
checkand syncs no longer fail once the access token needs refreshing, and the rotated refresh token is persisted. No user action or reconfiguration required. API-key users are unaffected.Can this PR be safely reverted and rolled back?
Link to Devin session: https://app.devin.ai/sessions/5c1434fece8b479497fe709c8c0547ce