|
1 | | -import * as React from 'react'; |
2 | | -import { clearTrustedDeviceTokenCache } from '../../bridge/trustedDevice.js'; |
3 | | -import { Text } from '../../ink.js'; |
4 | | -import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js'; |
5 | | -import { getGroveNoticeConfig, getGroveSettings } from '../../services/api/grove.js'; |
6 | | -import { clearPolicyLimitsCache } from '../../services/policyLimits/index.js'; |
7 | | -import { clearRemoteManagedSettingsCache } from '../../services/remoteManagedSettings/index.js'; |
| 1 | +import * as React from 'react' |
| 2 | +import { clearTrustedDeviceTokenCache } from '../../bridge/trustedDevice.js' |
| 3 | +import { Text } from '../../ink.js' |
| 4 | +import { refreshGrowthBookAfterAuthChange } from '../../services/analytics/growthbook.js' |
| 5 | +import { |
| 6 | + getGroveNoticeConfig, |
| 7 | + getGroveSettings, |
| 8 | +} from '../../services/api/grove.js' |
| 9 | +import { clearPolicyLimitsCache } from '../../services/policyLimits/index.js' |
| 10 | +import { clearRemoteManagedSettingsCache } from '../../services/remoteManagedSettings/index.js' |
8 | 11 | import { isVerbooMode } from '../../constants/oauth.js' |
9 | 12 | import { clearVerbooModelsCache } from '../../services/api/verbooModels.js' |
10 | 13 | import { resetVerbooSessionValidation } from '../../services/oauth/verbooStartupAuth.js' |
11 | | -import { getClaudeAIOAuthTokens, removeApiKey } from '../../utils/auth.js'; |
12 | | -import { clearBetasCaches } from '../../utils/betas.js'; |
13 | | -import { saveGlobalConfig } from '../../utils/config.js'; |
14 | | -import { gracefulShutdownSync } from '../../utils/gracefulShutdown.js'; |
15 | | -import { getSecureStorage } from '../../utils/secureStorage/index.js'; |
16 | | -import { clearToolSchemaCache } from '../../utils/toolSchemaCache.js'; |
17 | | -import { resetUserCache } from '../../utils/user.js'; |
| 14 | +import { revokeVerbooRefreshToken } from '../../services/oauth/client.js' |
| 15 | +import { |
| 16 | + getAuthTokenSource, |
| 17 | + getClaudeAIOAuthTokens, |
| 18 | + removeApiKey, |
| 19 | +} from '../../utils/auth.js' |
| 20 | +import { clearBetasCaches } from '../../utils/betas.js' |
| 21 | +import { saveGlobalConfig } from '../../utils/config.js' |
| 22 | +import { gracefulShutdownSync } from '../../utils/gracefulShutdown.js' |
| 23 | +import { getSecureStorage } from '../../utils/secureStorage/index.js' |
| 24 | +import { clearToolSchemaCache } from '../../utils/toolSchemaCache.js' |
| 25 | +import { resetUserCache } from '../../utils/user.js' |
| 26 | +import { removeStoredVerbooOauth } from './logoutState.js' |
| 27 | +export type LogoutResult = { |
| 28 | + localCleared: boolean |
| 29 | + remoteRevocation: 'revoked' | 'unconfirmed' | 'not_applicable' |
| 30 | + externalTokenSource?: string |
| 31 | +} |
| 32 | + |
| 33 | +function isExternallyInjectedTokenSource(source: string): boolean { |
| 34 | + return [ |
| 35 | + 'CLAUDE_CODE_OAUTH_TOKEN', |
| 36 | + 'CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR', |
| 37 | + 'CCR_OAUTH_TOKEN_FILE', |
| 38 | + 'ANTHROPIC_AUTH_TOKEN', |
| 39 | + ].includes(source) |
| 40 | +} |
| 41 | + |
18 | 42 | export async function performLogout({ |
19 | | - clearOnboarding = false |
20 | | -}): Promise<void> { |
21 | | - await removeApiKey(); |
| 43 | + clearOnboarding = false, |
| 44 | +}): Promise<LogoutResult> { |
| 45 | + const authTokenSource = getAuthTokenSource().source |
| 46 | + const refreshToken = removeStoredVerbooOauth(getSecureStorage()) |
| 47 | + const remoteRevocation = refreshToken |
| 48 | + ? (await revokeVerbooRefreshToken(refreshToken)) |
| 49 | + ? ('revoked' as const) |
| 50 | + : ('unconfirmed' as const) |
| 51 | + : ('not_applicable' as const) |
| 52 | + |
| 53 | + // Verboo does not use the generic Anthropic API-key slot. Preserve it and |
| 54 | + // credentials for MCP, Codex, and plugins when the user signs out of Verboo. |
| 55 | + if (!isVerbooMode()) { |
| 56 | + await removeApiKey() |
| 57 | + } |
22 | 58 |
|
23 | 59 | if (isVerbooMode()) { |
24 | | - resetVerbooSessionValidation(); |
25 | | - clearVerbooModelsCache(); |
| 60 | + resetVerbooSessionValidation() |
| 61 | + clearVerbooModelsCache() |
26 | 62 | } |
27 | 63 |
|
28 | | - // Wipe all secure storage data on logout |
29 | | - const secureStorage = getSecureStorage(); |
30 | | - secureStorage.delete(); |
31 | | - await clearAuthRelatedCaches(); |
32 | | - saveGlobalConfig(current => { |
| 64 | + await clearAuthRelatedCaches() |
| 65 | + saveGlobalConfig((current) => { |
33 | 66 | const updated = { |
34 | | - ...current |
35 | | - }; |
| 67 | + ...current, |
| 68 | + } |
36 | 69 | if (clearOnboarding) { |
37 | | - updated.hasCompletedOnboarding = false; |
38 | | - updated.subscriptionNoticeCount = 0; |
39 | | - updated.hasAvailableSubscription = false; |
| 70 | + updated.hasCompletedOnboarding = false |
| 71 | + updated.subscriptionNoticeCount = 0 |
| 72 | + updated.hasAvailableSubscription = false |
40 | 73 | if (updated.customApiKeyResponses?.approved) { |
41 | 74 | updated.customApiKeyResponses = { |
42 | 75 | ...updated.customApiKeyResponses, |
43 | | - approved: [] |
44 | | - }; |
| 76 | + approved: [], |
| 77 | + } |
45 | 78 | } |
46 | 79 | } |
47 | | - updated.oauthAccount = undefined; |
48 | | - return updated; |
49 | | - }); |
| 80 | + updated.oauthAccount = undefined |
| 81 | + return updated |
| 82 | + }) |
| 83 | + |
| 84 | + return { |
| 85 | + localCleared: true, |
| 86 | + remoteRevocation, |
| 87 | + ...(isExternallyInjectedTokenSource(authTokenSource) |
| 88 | + ? { externalTokenSource: authTokenSource } |
| 89 | + : {}), |
| 90 | + } |
50 | 91 | } |
51 | 92 |
|
52 | 93 | // clearing anything memoized that must be invalidated when user/session/auth changes |
53 | 94 | export async function clearAuthRelatedCaches(): Promise<void> { |
54 | 95 | // Clear the OAuth token cache |
55 | | - getClaudeAIOAuthTokens.cache?.clear?.(); |
56 | | - clearTrustedDeviceTokenCache(); |
57 | | - clearBetasCaches(); |
58 | | - clearToolSchemaCache(); |
| 96 | + getClaudeAIOAuthTokens.cache?.clear?.() |
| 97 | + clearTrustedDeviceTokenCache() |
| 98 | + clearBetasCaches() |
| 99 | + clearToolSchemaCache() |
59 | 100 |
|
60 | 101 | // Clear user data cache BEFORE GrowthBook refresh so it picks up fresh credentials |
61 | | - resetUserCache(); |
62 | | - refreshGrowthBookAfterAuthChange(); |
| 102 | + resetUserCache() |
| 103 | + refreshGrowthBookAfterAuthChange() |
63 | 104 |
|
64 | 105 | // Clear Grove config cache |
65 | | - getGroveNoticeConfig.cache?.clear?.(); |
66 | | - getGroveSettings.cache?.clear?.(); |
| 106 | + getGroveNoticeConfig.cache?.clear?.() |
| 107 | + getGroveSettings.cache?.clear?.() |
67 | 108 |
|
68 | 109 | // Clear remotely managed settings cache |
69 | | - await clearRemoteManagedSettingsCache(); |
| 110 | + await clearRemoteManagedSettingsCache() |
70 | 111 |
|
71 | 112 | // Clear policy limits cache |
72 | | - await clearPolicyLimitsCache(); |
| 113 | + await clearPolicyLimitsCache() |
73 | 114 | } |
74 | 115 | export async function call(): Promise<React.ReactNode> { |
75 | | - await performLogout({ |
76 | | - clearOnboarding: true |
77 | | - }); |
78 | | - const message = isVerbooMode() |
79 | | - ? <Text>Saiu da conta Verboo com sucesso.</Text> |
80 | | - : <Text>Successfully logged out from your Anthropic account.</Text>; |
| 116 | + const result = await performLogout({ |
| 117 | + clearOnboarding: true, |
| 118 | + }) |
| 119 | + const message = isVerbooMode() ? ( |
| 120 | + <Text> |
| 121 | + Saiu da conta Verboo localmente. |
| 122 | + {result.remoteRevocation === 'unconfirmed' |
| 123 | + ? ' Não foi possível confirmar a revogação da sessão no servidor.' |
| 124 | + : ''} |
| 125 | + {result.externalTokenSource |
| 126 | + ? ` ${result.externalTokenSource} ainda fornece uma credencial neste ambiente.` |
| 127 | + : ''} |
| 128 | + </Text> |
| 129 | + ) : ( |
| 130 | + <Text>Successfully logged out from your Anthropic account.</Text> |
| 131 | + ) |
81 | 132 | setTimeout(() => { |
82 | | - gracefulShutdownSync(0, 'logout'); |
83 | | - }, 200); |
84 | | - return message; |
| 133 | + gracefulShutdownSync(0, 'logout') |
| 134 | + }, 200) |
| 135 | + return message |
85 | 136 | } |
0 commit comments