airbytehq / airbytehq/airbyte

[source-salesloft] OAuth refresh fails in v1.5.2 due to SingleUseOauth2Authenticator attribute error

Aberta
#84,297 2 comentários 0 reações 0 responsáveis Ver no GitHub
area/connectors autoteam community connectors/source/salesloft needs-triage team/extensibility team/use type/bug
Linguagem predominante
Python
Estrelas
22.1k
Forks
5.4k
Merge médio
5h
PRs com merge (30d)
671

Descrição

### Connector Name

source-salesloft

### Connector Version

1.5.2

### What step the error happened?

Other

### Relevant information

## What step did the error happen?

During source connection check and during sync after the OAuth access token expires.

## Summary

The Salesloft Marketplace connector works while the configured OAuth access token is still valid, but fails as soon as Airbyte attempts to refresh the token.

The refresh path raises:

```text
AttributeError: 'SingleUseOauth2Authenticator' object has no attribute '_token_refresh_endpoint'
Did you mean: 'token_refresh_endpoint'?
```

This prevents long-running Salesloft syncs from continuing beyond the access-token lifetime.

## Reproduction

1. Configure `source-salesloft:1.5.2` using valid OAuth credentials:
- access token
- refresh token
- client ID
- client secret
- token expiry date
2. Set `token_expiry_date` a few minutes in the future.
3. Run `Test source` before expiry.
4. The connection succeeds.
5. Wait until `token_expiry_date` passes.
6. Run `Test source` again without changing credentials.
7. The connector fails while attempting to refresh the token.

## Observed error

```text
File ".../abstract_oauth.py", line 118, in _get_refresh_access_token_response
response = requests.request(
method="POST",
url=self.get_token_refresh_endpoint(),
data=self.build_refresh_request_body()
)

File ".../oauth.py", line 56, in get_token_refresh_endpoint
return self._token_refresh_endpoint

AttributeError:
'SingleUseOauth2Authenticator' object has no attribute
'_token_refresh_endpoint'.
Did you mean: 'token_refresh_endpoint'?
```

The exception is then surfaced as:

```text
Exception: Error while refreshing access token:
'SingleUseOauth2Authenticator' object has no attribute '_token_refresh_endpoint'
```

## Expected behavior

When the access token expires, the connector should POST the refresh token to:

```text
https://accounts.salesloft.com/oauth/token
```

and continue using the refreshed access token.

## Additional evidence

I reproduced this both:

- during a long-running sync, after substantial data had already been successfully read from Salesloft
- during a source connection test by deliberately setting `token_expiry_date` shortly in the future

This confirms that initial OAuth authentication works and that the failure occurs specifically in the refresh path.

## Possible cause

The declarative manifest uses:

```yaml
type: CustomAuthenticator
class_name: source_declarative_manifest.components.SingleUseOauth2Authenticator
token_refresh_endpoint: https://accounts.salesloft.com/oauth/token
```

but the CDK method `get_token_refresh_endpoint()` attempts to read:

```python
self._token_refresh_endpoint
```

while the authenticator appears to expose/use `token_refresh_endpoint`.

## Workaround / confirmation

I copied the connector manifest into Connector Builder and replaced the custom authenticator with Airbyte's native `OAuthAuthenticator`.

With the native authenticator, the same forced-expiry test succeeds after token expiry, including repeated `Retest saved source` checks.

This strongly suggests the Salesloft API credentials and refresh endpoint are valid and that the failure is specific to the Marketplace connector's custom authenticator implementation.

### Relevant log output

```shell
## Relevant log output

### Connection check failure after token expiry

2026-08-12 08:16:37 error Encountered an error trying to connect to stream users. Error:

File "/usr/local/lib/python3.10/site-packages/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py", line 118, in _get_refresh_access_token_response
response = requests.request(
method="POST",
url=self.get_token_refresh_endpoint(),
data=self.build_refresh_request_body()
)

File "/usr/local/lib/python3.10/site-packages/airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py", line 56, in get_token_refresh_endpoint
return self._token_refresh_endpoint

AttributeError: 'SingleUseOauth2Authenticator' object has no attribute '_token_refresh_endpoint'. Did you mean: 'token_refresh_endpoint'?

The exception then propagates through the OAuth refresh path:

File "/usr/local/lib/python3.10/site-packages/airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py", line 231, in get_access_token
new_access_token, access_token_expires_in, new_refresh_token = self.refresh_access_token()

File "/usr/local/lib/python3.10/site-packages/airbyte_cdk/sources/streams/http/requests_native_auth/oauth.py", line 246, in refresh_access_token
response_json = self._get_refresh_access_token_response()

File "/usr/local/lib/python3.10/site-packages/airbyte_cdk/sources/streams/http/requests_native_auth/abstract_oauth.py", line 142, in _get_refresh_access_token_response
raise Exception(f"Error while refreshing access token: {e}") from e

Exception: Error while refreshing access token: 'SingleUseOauth2Authenticator' object has no attribute '_token_refresh_endpoint'

2026-08-12 08:16:37 error Check failed

This confirms that the failure occurs specifically when the connector enters the OAuth refresh path. :contentReference[oaicite:0]{index=0} :contentReference[oaicite:1]{index=1}

### Evidence that the connector works normally before refresh is required

During the earlier sync, streams were successfully reading records before the token refresh failure occurred:

2026-08-11 16:14:15 info Read 10 records from person_stages stream
2026-08-11 16:14:15 info Finished syncing person_stages

2026-08-11 16:14:15 info Read 27 records from phone_number_assignments stream
2026-08-11 16:14:15 info Finished syncing phone_number_assignments

2026-08-11 16:14:15 info Syncing stream: steps
2026-08-11 16:14:15 info Read 0 records from steps stream

This shows that initial authentication and API requests were functioning before token refresh was triggered. :contentReference[oaicite:2]{index=2}
```

### Contribute

- [ ] Yes, I want to contribute

---
**Internal Tracking:** https://github.com/airbytehq/oncall/issues/13289

Guia de contribuição

Abrir o guia de contribuição

Direção de pesquisa

Start with source-salesloft's CustomAuthenticator declaration and the SingleUseOauth2Authenticator class, then trace get_token_refresh_endpoint() through abstract_oauth.py and oauth.py. Reproduce the forced-expiry connection check and compare it with the native OAuthAuthenticator workaround. Done means the refresh request reaches https://accounts.salesloft.com/oauth/token and repeated post-expiry checks continue successfully.

Escrita pelo modelo de indexação a partir do texto da issue.

Avaliação

Stack de tecnologia
python
Domínio
api, authentication
Tipo de issue
Bug
Dificuldade
3/5
Tempo estimado
1-2 dias
Status de atividade
Pouca atividade
Clareza
Claramente especificada
Facilidade para iniciantes
64/100

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.