OutlookAuthManager.exchangeToken's authorization-code exchange already requests openid email offline_access $OUTLOOK_SCOPE, so tokenResponse.accessToken is already a valid outlook.office.com (Exchange Online) token usable for IMAP verification, and the resulting AuthState already carries the refresh token and access-token expiry.
It nonetheless immediately called refreshForScope(authState, OUTLOOK_SCOPE) — a second round-trip for the same resource that only rotated the just-issued refresh token and added a needless onboarding failure point (a transient network error there failed the whole sign-in after consent + code-exchange had already succeeded).
Change
Build OAuthResult directly from the code-exchange tokenResponse (accessToken + authState.jsonSerializeString()), dropping the extra refresh call.
Durable auth state still persisted for refresh: the AuthState is updated with the code-exchange tokenResponse and serialized via jsonSerializeString() into OAuthResult.authStateJson, which addOutlookAccount stores through credentialStore.saveSecret. It carries the refresh token and the access token's expiry for later refreshes.
Graph token unaffected: it is a distinct resource, still minted on demand via freshGraphToken.
Downstream (AccountRepositoryImpl.addOutlookAccount) uses accessToken once for IMAP verification and authStateJson as the stored credential — both preserved.
Tests
Updated OutlookAuthManagerTest's happy-path test to assert the result is built from the code-exchange token (accessToken == "code-access"), that the durable AuthState (refresh token) is serialized into authStateJson, and — via verify(exactly = 1) — that performTokenRequest is called exactly once (no second refresh). Simplified the preferred_username fallback test to a single token response.
Gate run locally (no emulator, per scope): :app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt — all green.
Closes #306
## What & why
`OutlookAuthManager.exchangeToken`'s authorization-code exchange already requests `openid email offline_access $OUTLOOK_SCOPE`, so `tokenResponse.accessToken` is already a valid `outlook.office.com` (Exchange Online) token usable for IMAP verification, and the resulting `AuthState` already carries the refresh token and access-token expiry.
It nonetheless immediately called `refreshForScope(authState, OUTLOOK_SCOPE)` — a second round-trip for the **same** resource that only rotated the just-issued refresh token and added a needless onboarding failure point (a transient network error there failed the whole sign-in after consent + code-exchange had already succeeded).
## Change
Build `OAuthResult` directly from the code-exchange `tokenResponse` (`accessToken` + `authState.jsonSerializeString()`), dropping the extra refresh call.
- **Durable auth state still persisted for refresh:** the `AuthState` is updated with the code-exchange `tokenResponse` and serialized via `jsonSerializeString()` into `OAuthResult.authStateJson`, which `addOutlookAccount` stores through `credentialStore.saveSecret`. It carries the refresh token and the access token's expiry for later refreshes.
- **Graph token unaffected:** it is a distinct resource, still minted on demand via `freshGraphToken`.
- Downstream (`AccountRepositoryImpl.addOutlookAccount`) uses `accessToken` once for IMAP verification and `authStateJson` as the stored credential — both preserved.
## Tests
Updated `OutlookAuthManagerTest`'s happy-path test to assert the result is built from the code-exchange token (`accessToken == "code-access"`), that the durable `AuthState` (refresh token) is serialized into `authStateJson`, and — via `verify(exactly = 1)` — that `performTokenRequest` is called exactly once (no second refresh). Simplified the `preferred_username` fallback test to a single token response.
Gate run locally (no emulator, per scope): `:app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt` — all green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Coordinator review: the analysis is sound (code-exchange token is already outlook.office.com-scoped; Graph token minted separately; AuthState persisted) and unit-tested. Leaving auto-merge OFF pending a real-Outlook-account sign-in test — this touches the OAuth token flow (3 prior bugs in this area) and CI can't validate against a live MS account. It's a LOW/efficiency optimization, so no urgency.
Coordinator review: the analysis is sound (code-exchange token is already outlook.office.com-scoped; Graph token minted separately; AuthState persisted) and unit-tested. Leaving auto-merge OFF pending a real-Outlook-account sign-in test — this touches the OAuth token flow (3 prior bugs in this area) and CI can't validate against a live MS account. It's a LOW/efficiency optimization, so no urgency.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #306
What & why
OutlookAuthManager.exchangeToken's authorization-code exchange already requestsopenid email offline_access $OUTLOOK_SCOPE, sotokenResponse.accessTokenis already a validoutlook.office.com(Exchange Online) token usable for IMAP verification, and the resultingAuthStatealready carries the refresh token and access-token expiry.It nonetheless immediately called
refreshForScope(authState, OUTLOOK_SCOPE)— a second round-trip for the same resource that only rotated the just-issued refresh token and added a needless onboarding failure point (a transient network error there failed the whole sign-in after consent + code-exchange had already succeeded).Change
Build
OAuthResultdirectly from the code-exchangetokenResponse(accessToken+authState.jsonSerializeString()), dropping the extra refresh call.AuthStateis updated with the code-exchangetokenResponseand serialized viajsonSerializeString()intoOAuthResult.authStateJson, whichaddOutlookAccountstores throughcredentialStore.saveSecret. It carries the refresh token and the access token's expiry for later refreshes.freshGraphToken.AccountRepositoryImpl.addOutlookAccount) usesaccessTokenonce for IMAP verification andauthStateJsonas the stored credential — both preserved.Tests
Updated
OutlookAuthManagerTest's happy-path test to assert the result is built from the code-exchange token (accessToken == "code-access"), that the durableAuthState(refresh token) is serialized intoauthStateJson, and — viaverify(exactly = 1)— thatperformTokenRequestis called exactly once (no second refresh). Simplified thepreferred_usernamefallback test to a single token response.Gate run locally (no emulator, per scope):
:app:testDebugUnitTest :app:compileDebugAndroidTestKotlin :app:ktlintCheck :app:detekt— all green.🤖 Generated with Claude Code
Coordinator review: the analysis is sound (code-exchange token is already outlook.office.com-scoped; Graph token minted separately; AuthState persisted) and unit-tested. Leaving auto-merge OFF pending a real-Outlook-account sign-in test — this touches the OAuth token flow (3 prior bugs in this area) and CI can't validate against a live MS account. It's a LOW/efficiency optimization, so no urgency.