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

Function refreshOAuthToken

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

Source from the content-addressed store, hash-verified

283}
284
285export async function refreshOAuthToken(
286 refreshToken: string,
287 { scopes: requestedScopes }: { scopes?: string[] } = {},
288): Promise<OAuthTokens> {
289 const fallbackScopes =
290 requestedScopes?.length ? requestedScopes : CLAUDE_AI_OAUTH_SCOPES
291 const requestBody = {
292 grant_type: 'refresh_token',
293 refresh_token: refreshToken,
294 client_id: getOauthClientId(),
295 // Request specific scopes, defaulting to the full Noumena auth set. The
296 // backend's refresh-token grant allows scope expansion beyond what the
297 // initial authorize granted (see ALLOWED_SCOPE_EXPANSIONS), so this is
298 // safe even for tokens issued before scopes were added to the app's
299 // registered oauth_scope.
300 scope: fallbackScopes.join(' '),
301 }
302
303 try {
304 const data = await getIdentityClient().refreshOAuthToken({
305 requestBody,
306 timeout: 15000,
307 })
308 const {
309 access_token: accessToken,
310 refresh_token: newRefreshToken = refreshToken,
311 expires_in: expiresIn,
312 } = data
313
314 const expiresAt = Date.now() + expiresIn * 1000
315 const scopes = parseScopes(data.scope)
316 const normalizedScopes = scopes.length > 0 ? scopes : fallbackScopes
317
318 logEvent('ncode_oauth_token_refresh_success', {})
319
320 // Skip the extra /api/oauth/profile round-trip when we already have both
321 // the global-config profile fields AND the secure-storage subscription data.
322 // Routine refreshes satisfy both, so we cut ~7M req/day fleet-wide.
323 //
324 // Checking secure storage (not just config) matters for the
325 // CLAUDE_CODE_OAUTH_REFRESH_TOKEN re-login path: installOAuthTokens runs
326 // performLogout() AFTER we return, wiping secure storage. If we returned
327 // null for subscriptionType here, saveOAuthTokensIfNeeded would persist
328 // null ?? (wiped) ?? null = null, and every future refresh would see the
329 // config guard fields satisfied and skip again, permanently losing the
330 // subscription type for paying users. By passing through existing values,
331 // the re-login path writes cached ?? wiped ?? null = cached; and if secure
332 // storage was already empty we fall through to the fetch.
333 const config = getGlobalConfig()
334 const existing = getCurrentOAuthClientSession()
335 const haveProfileAlready =
336 config.oauthAccount?.billingType !== undefined &&
337 config.oauthAccount?.accountCreatedAt !== undefined &&
338 config.oauthAccount?.subscriptionCreatedAt !== undefined &&
339 existing?.subscription.subscriptionType != null &&
340 existing?.subscription.rateLimitTier != null
341
342 const profileInfo = haveProfileAlready

Callers 3

client.test.tsFile · 0.70
runRefreshFunction · 0.50
performAuthLoginFunction · 0.50

Calls 9

getOauthClientIdFunction · 0.85
getIdentityClientFunction · 0.85
parseScopesFunction · 0.85
fetchProfileInfoFunction · 0.85
keysMethod · 0.80
logEventFunction · 0.50
getGlobalConfigFunction · 0.50
saveGlobalConfigFunction · 0.50

Tested by

no test coverage detected