(context: Parameters<LocalCommandCall>[1])
| 32 | } |
| 33 | |
| 34 | async function runRefresh(context: Parameters<LocalCommandCall>[1]): Promise<string> { |
| 35 | const authRuntime = getAuthRuntime() |
| 36 | const session = await authRuntime.resolveSession({ allowRefresh: false }) |
| 37 | |
| 38 | if (session.headersKind !== 'bearer' || !session.accessToken) { |
| 39 | return 'Cannot refresh OAuth: no OAuth token is currently available.' |
| 40 | } |
| 41 | |
| 42 | if (!session.refreshTokenPresent) { |
| 43 | return [ |
| 44 | 'Cannot refresh OAuth: current token has no refresh token.', |
| 45 | `Source: ${session.rawAuthTokenSource ?? session.principalSource}`, |
| 46 | 'This usually means env/FD-provided service token auth, which is non-refreshable.', |
| 47 | ].join('\n') |
| 48 | } |
| 49 | |
| 50 | const refreshToken = authRuntime.getCurrentManagedRefreshToken() |
| 51 | if (!refreshToken) { |
| 52 | return 'Cannot refresh OAuth: refresh token is unavailable.' |
| 53 | } |
| 54 | |
| 55 | const requestedScopes = getOAuthRefreshRequestedScopes(session) |
| 56 | |
| 57 | const before = { |
| 58 | expiresAt: session.accessTokenExpiresAt, |
| 59 | source: session.rawAuthTokenSource ?? session.principalSource, |
| 60 | } |
| 61 | |
| 62 | const refreshed = await refreshOAuthToken(refreshToken, { |
| 63 | scopes: requestedScopes, |
| 64 | }) |
| 65 | const saveResult = authRuntime.persistOAuthTokensIfNeeded(refreshed) |
| 66 | authRuntime.clearManagedTokenCache() |
| 67 | |
| 68 | if (!saveResult.success) { |
| 69 | throw new Error(saveResult.warning ?? 'Failed to persist refreshed OAuth token') |
| 70 | } |
| 71 | |
| 72 | let rolesRefreshNote = '' |
| 73 | try { |
| 74 | await fetchAndStoreUserRoles(refreshed.accessToken) |
| 75 | } catch (error) { |
| 76 | // Limited-scope tokens can fail role fetch; keep refresh successful. |
| 77 | rolesRefreshNote = `\nRole refresh skipped: ${errorMessage(error)}` |
| 78 | logForDebugging(`oauth-refresh roles update skipped: ${errorMessage(error)}`, { |
| 79 | level: 'error', |
| 80 | }) |
| 81 | } |
| 82 | |
| 83 | // Same post-auth cache refresh style used by other auth-changing flows. |
| 84 | resetUserCache() |
| 85 | refreshGrowthBookAfterAuthChange() |
| 86 | void refreshPolicyLimits() |
| 87 | void refreshRemoteManagedSettings() |
| 88 | context.setAppState(prev => ({ |
| 89 | ...prev, |
| 90 | authVersion: prev.authVersion + 1, |
| 91 | })) |
no test coverage detected