MCPcopy Create free account
hub / github.com/freecodexyz/free-code / refreshOAuthToken

Function refreshOAuthToken

src/services/oauth/client.ts:174–302  ·  view source on GitHub ↗
(
  refreshToken: string,
  { scopes: requestedScopes }: { scopes?: string[] } = {},
)

Source from the content-addressed store, hash-verified

172}
173
174export async function refreshOAuthToken(
175 refreshToken: string,
176 { scopes: requestedScopes }: { scopes?: string[] } = {},
177): Promise<OAuthTokens> {
178 const requestBody = {
179 grant_type: 'refresh_token',
180 refresh_token: refreshToken,
181 client_id: getOauthConfig().CLIENT_ID,
182 // Request specific scopes, defaulting to the full Claude AI set. The
183 // backend's refresh-token grant allows scope expansion beyond what the
184 // initial authorize granted (see ALLOWED_SCOPE_EXPANSIONS), so this is
185 // safe even for tokens issued before scopes were added to the app's
186 // registered oauth_scope.
187 scope: (requestedScopes?.length
188 ? requestedScopes
189 : CLAUDE_AI_OAUTH_SCOPES
190 ).join(' '),
191 }
192
193 try {
194 const response = await axios.post(getOauthConfig().TOKEN_URL, requestBody, {
195 headers: { 'Content-Type': 'application/json' },
196 timeout: 15000,
197 })
198
199 if (response.status !== 200) {
200 throw new Error(`Token refresh failed: ${response.statusText}`)
201 }
202
203 const data = response.data as OAuthTokenExchangeResponse
204 const {
205 access_token: accessToken,
206 refresh_token: newRefreshToken = refreshToken,
207 expires_in: expiresIn,
208 } = data
209
210 const expiresAt = Date.now() + expiresIn * 1000
211 const scopes = parseScopes(data.scope)
212
213 logEvent('tengu_oauth_token_refresh_success', {})
214
215 // Skip the extra /api/oauth/profile round-trip when we already have both
216 // the global-config profile fields AND the secure-storage subscription data.
217 // Routine refreshes satisfy both, so we cut ~7M req/day fleet-wide.
218 //
219 // Checking secure storage (not just config) matters for the
220 // CLAUDE_CODE_OAUTH_REFRESH_TOKEN re-login path: installOAuthTokens runs
221 // performLogout() AFTER we return, wiping secure storage. If we returned
222 // null for subscriptionType here, saveOAuthTokensIfNeeded would persist
223 // null ?? (wiped) ?? null = null, and every future refresh would see the
224 // config guard fields satisfied and skip again, permanently losing the
225 // subscription type for paying users. By passing through existing values,
226 // the re-login path writes cached ?? wiped ?? null = cached; and if secure
227 // storage was already empty we fall through to the fetch.
228 const config = getGlobalConfig()
229 const existing = getClaudeAIOAuthTokens()
230 const haveProfileAlready =
231 config.oauthAccount?.billingType !== undefined &&

Callers 2

authLoginFunction · 0.85

Calls 7

getOauthConfigFunction · 0.85
parseScopesFunction · 0.85
logEventFunction · 0.85
getGlobalConfigFunction · 0.85
fetchProfileInfoFunction · 0.85
saveGlobalConfigFunction · 0.85
keysMethod · 0.80

Tested by

no test coverage detected