(tokens: OAuthTokens)
| 48 | * and sets up the local auth state. |
| 49 | */ |
| 50 | export async function installOAuthTokens(tokens: OAuthTokens): Promise<void> { |
| 51 | // Clear old state before saving new credentials |
| 52 | await performLogout({ clearOnboarding: false }) |
| 53 | |
| 54 | // Reuse pre-fetched profile if available, otherwise fetch fresh |
| 55 | const profile = |
| 56 | tokens.profile ?? (await getOauthProfileFromOauthToken(tokens.accessToken)) |
| 57 | if (profile) { |
| 58 | storeOAuthAccountInfo({ |
| 59 | accountUuid: profile.account.uuid, |
| 60 | emailAddress: profile.account.email, |
| 61 | organizationUuid: profile.organization.uuid, |
| 62 | displayName: profile.account.display_name || undefined, |
| 63 | hasExtraUsageEnabled: |
| 64 | profile.organization.has_extra_usage_enabled ?? undefined, |
| 65 | billingType: profile.organization.billing_type ?? undefined, |
| 66 | subscriptionCreatedAt: |
| 67 | profile.organization.subscription_created_at ?? undefined, |
| 68 | accountCreatedAt: profile.account.created_at, |
| 69 | }) |
| 70 | } else if (tokens.tokenAccount) { |
| 71 | // Fallback to token exchange account data when profile endpoint fails |
| 72 | storeOAuthAccountInfo({ |
| 73 | accountUuid: tokens.tokenAccount.uuid, |
| 74 | emailAddress: tokens.tokenAccount.emailAddress, |
| 75 | organizationUuid: tokens.tokenAccount.organizationUuid, |
| 76 | }) |
| 77 | } |
| 78 | |
| 79 | const storageResult = saveOAuthTokensIfNeeded(tokens) |
| 80 | clearOAuthTokenCache() |
| 81 | |
| 82 | if (storageResult.warning) { |
| 83 | logEvent('tengu_oauth_storage_warning', { |
| 84 | warning: |
| 85 | storageResult.warning as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 86 | }) |
| 87 | } |
| 88 | |
| 89 | // Roles and first-token-date may fail for limited-scope tokens (e.g. |
| 90 | // inference-only from setup-token). They're not required for core auth. |
| 91 | await fetchAndStoreUserRoles(tokens.accessToken).catch(err => |
| 92 | logForDebugging(String(err), { level: 'error' }), |
| 93 | ) |
| 94 | |
| 95 | if (shouldUseClaudeAIAuth(tokens.scopes)) { |
| 96 | await fetchAndStoreClaudeCodeFirstTokenDate().catch(err => |
| 97 | logForDebugging(String(err), { level: 'error' }), |
| 98 | ) |
| 99 | } else { |
| 100 | // API key creation is critical for Console users — let it throw. |
| 101 | const apiKey = await createAndStoreApiKey(tokens.accessToken) |
| 102 | if (!apiKey) { |
| 103 | throw new Error( |
| 104 | 'Unable to create API key. The server accepted the request but did not return a key.', |
| 105 | ) |
| 106 | } |
| 107 | } |
no test coverage detected