Skip to content

Commit 3d9e87f

Browse files
carlpatchettpatches-krollCopilotiwillspeakanna-singleton-resolver
authored
Per-Request Auth Token Refresh (#103)
* refactor: simplify token acquisition by removing async calls and unused tests * Introduce TokenData class * Passing () to avoid type checking problems * Test fixups * fix: update test cases to suppress unused return value warnings, now only scanning relevant files * Address PR review feedback: preserve token scheme casing and fix documentation (#106) * Initial plan * fix: address PR review comments - token scheme, docs, and tests Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * chore: remove 'include' and revert removal of .venv from exclude list for pyright * chore: remove some extraneous comments * test: consistent casing in assertions and provided mock data * Address review feedback: require issued_at field and prevent refresh stampede (#107) * Initial plan * Implement background token refresh for old tokens - Add issued_at field to TokenData to track token creation time - Add is_old() method to check if token should be refreshed (< 50% lifetime) - Trigger non-blocking background refresh when token is old but valid - Keep blocking refresh for expired tokens - Add comprehensive tests for background refresh behavior Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * fix: update tests to match preserved token scheme casing Tests were expecting capitalized schemes but implementation was changed in 4b7e73a to preserve server-provided casing. Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * Address code review feedback - Add logging for background refresh failures - Improve comments explaining magic numbers - Add test for server casing preservation - Add comment explaining test tolerance Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * Address PR review comments - Make issued_at a required field (remove default value) - Remove fallback logic for legacy tokens in is_old() - Add stampede prevention in _background_refresh - Remove obsolete tests for fallback logic - Add test for stampede prevention Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * Revert Bearer/DPoP test assertion changes from c50327d The target branch now properly fixes the test by providing "Bearer" in the mock data. Reverting my previous fix that changed the assertion to match the old mock data. Co-authored-by: anna-singleton-resolver <199753965+anna-singleton-resolver@users.noreply.github.com> * fix: start refresh thread checks for already refreshed token * chore: remove extraneous comment * feat: reduce eagerness of background refresh from at 50% remaining to 25% remaining * doc: update is_old docstring * fix: correct boolean logic in _start_background_refresh Changed from OR to AND logic: refresh should start if (refresh_not_active AND token_needs_refresh), not if any condition is true. This prevents unnecessary refresh attempts. Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> * test: fix test assertions to be on 25% instead of 50% * feat: allow configurable proactive_refresh_threshold --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> Co-authored-by: anna-singleton-resolver <199753965+anna-singleton-resolver@users.noreply.github.com> Co-authored-by: anna-singleton-resolver <anna.singleton@resolver.com> * refactor: TokenData as frozen dataclass and param validity checks at init * doc: remove old part of docstring --------- Co-authored-by: Carl Patchett <carl.patchett@kroll.com> Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com> Co-authored-by: iwillspeak <1004401+iwillspeak@users.noreply.github.com> Co-authored-by: anna-singleton-resolver <anna.singleton@resolver.com> Co-authored-by: anna-singleton-resolver <199753965+anna-singleton-resolver@users.noreply.github.com>
1 parent 6827f3a commit 3d9e87f

9 files changed

Lines changed: 699 additions & 251 deletions

File tree

docs/api/exceptions.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ OAuth Error Handling
129129
client_id=client_id,
130130
client_secret=client_secret
131131
)
132-
token = await credential_helper.get_token()
132+
token_data = credential_helper.get_token()
133+
access_token = token_data.access_token
133134
except OAuthError as e:
134135
logger.error(f"OAuth authentication failed: {e}")
135136
# Handle OAuth failure - check credentials

docs/authentication.rst

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,6 @@ Set these environment variables for OAuth authentication:
7676
audience=os.getenv("OAUTH_AUDIENCE", "crisp-athena-live"),
7777
)
7878
79-
# Test token acquisition
80-
try:
81-
token = await credential_helper.get_token()
82-
print(f"Successfully acquired token (length: {len(token)})")
83-
except Exception as e:
84-
print(f"Failed to acquire OAuth token: {e}")
85-
return
86-
8779
# Create authenticated channel
8880
channel = await create_channel_with_credentials(
8981
host=os.getenv("ATHENA_HOST"),
@@ -258,11 +250,12 @@ Handle OAuth-specific errors gracefully:
258250

259251
.. code-block:: python
260252
261-
from resolver_athena_client.client.exceptions import AuthenticationError
253+
from resolver_athena_client.client.exceptions import OAuthError
262254
263255
try:
264-
token = await credential_helper.get_token()
265-
except AuthenticationError as e:
256+
token_data = credential_helper.get_token()
257+
access_token = token_data.access_token
258+
except OAuthError as e:
266259
logger.error(f"OAuth authentication failed: {e}")
267260
# Handle authentication failure
268261
except Exception as e:
@@ -356,8 +349,8 @@ Test your authentication setup:
356349
client_secret=os.getenv("OAUTH_CLIENT_SECRET"),
357350
)
358351
359-
token = await credential_helper.get_token()
360-
print(f"✓ Authentication successful (token length: {len(token)})")
352+
token_data = credential_helper.get_token()
353+
print(f"✓ Authentication successful (token length: {len(token_data.access_token)})")
361354
return True
362355
363356
except Exception as e:

examples/classify_single_example.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -213,15 +213,6 @@ async def main() -> int:
213213
audience=audience,
214214
)
215215

216-
# Test token acquisition
217-
try:
218-
logger.info("Acquiring OAuth token...")
219-
token = await credential_helper.get_token()
220-
logger.info("Successfully acquired token (length: %d)", len(token))
221-
except Exception:
222-
logger.exception("Failed to acquire OAuth token")
223-
return 1
224-
225216
# Configure client options
226217
options = AthenaOptions(
227218
host=host,

examples/example.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -163,15 +163,6 @@ async def main() -> int:
163163
audience=audience,
164164
)
165165

166-
# Test token acquisition
167-
try:
168-
logger.info("Acquiring OAuth token...")
169-
token = await credential_helper.get_token()
170-
logger.info("Successfully acquired token (length: %d)", len(token))
171-
except Exception:
172-
logger.exception("Failed to acquire OAuth token")
173-
return 1
174-
175166
# Get available deployment
176167
channel = await create_channel_with_credentials(host, credential_helper)
177168
async with DeploymentSelector(channel) as deployment_selector:

src/resolver_athena_client/client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from resolver_athena_client.client.channel import (
77
CredentialHelper,
8+
TokenData,
89
create_channel_with_credentials,
910
)
1011
from resolver_athena_client.client.exceptions import (
@@ -26,6 +27,7 @@
2627
"CredentialError",
2728
"CredentialHelper",
2829
"OAuthError",
30+
"TokenData",
2931
"TokenExpiredError",
3032
"create_channel_with_credentials",
3133
"get_output_error_summary",

0 commit comments

Comments
 (0)