Download src/bridge/trustedDevice.ts from chenbhao/codev: direct link, hf CLI and curl.
- Browser
- Download file 7.76 kB
-
https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/bridge/trustedDevice.ts
- Command line
-
hf download hf://chenbhao/codev@afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/bridge/trustedDevice.ts
-
curl -L -o trustedDevice.ts https://huggingface.co/chenbhao/codev/resolve/afbc7edaae4e6bb0102ba024b49ce8be89d2264c/src/bridge/trustedDevice.ts
7.76 kB
| import axios from 'axios' | |
| import memoize from 'lodash-es/memoize.js' | |
| import { hostname } from 'os' | |
| import { getOauthConfig } from '../constants/oauth.js' | |
| import { | |
| checkGate_CACHED_OR_BLOCKING, | |
| getFeatureValue_CACHED_MAY_BE_STALE, | |
| } from '../services/analytics/growthbook.js' | |
| import { logForDebugging } from '../utils/debug.js' | |
| import { errorMessage } from '../utils/errors.js' | |
| import { isEssentialTrafficOnly } from '../utils/privacyLevel.js' | |
| import { getSecureStorage } from '../utils/secureStorage/index.js' | |
| import { jsonStringify } from '../utils/slowOperations.js' | |
| /** | |
| * Trusted device token source for bridge (remote-control) sessions. | |
| * | |
| * Bridge sessions have SecurityTier=ELEVATED on the server (CCR v2). | |
| * The server gates ConnectBridgeWorker on its own flag | |
| * (sessions_elevated_auth_enforcement in Anthropic Main); this CLI-side | |
| * flag controls whether the CLI sends X-Trusted-Device-Token at all. | |
| * Two flags so rollout can be staged: flip CLI-side first (headers | |
| * start flowing, server still no-ops), then flip server-side. | |
| * | |
| * Enrollment (POST /auth/trusted_devices) is gated server-side by | |
| * account_session.created_at < 10min, so it must happen during /login. | |
| * Token is persistent (90d rolling expiry) and stored in keychain. | |
| * | |
| * See anthropics/anthropic#274559 (spec), #310375 (B1b tenant RPCs), | |
| * #295987 (B2 Python routes), #307150 (C1' CCR v2 gate). | |
| */ | |
| const TRUSTED_DEVICE_GATE = 'tengu_sessions_elevated_auth_enforcement' | |
| function isGateEnabled(): boolean { | |
| return getFeatureValue_CACHED_MAY_BE_STALE(TRUSTED_DEVICE_GATE, false) | |
| } | |
| // Memoized — secureStorage.read() spawns a macOS `security` subprocess (~40ms). | |
| // bridgeApi.ts calls this from getHeaders() on every poll/heartbeat/ack. | |
| // Cache cleared after enrollment (below) and on logout (clearAuthRelatedCaches). | |
| // | |
| // Only the storage read is memoized — the GrowthBook gate is checked live so | |
| // that a gate flip after GrowthBook refresh takes effect without a restart. | |
| const readStoredToken = memoize((): string | undefined => { | |
| // Env var takes precedence for testing/canary. | |
| const envToken = process.env.CLAUDE_TRUSTED_DEVICE_TOKEN | |
| if (envToken) { | |
| return envToken | |
| } | |
| return getSecureStorage().read()?.trustedDeviceToken | |
| }) | |
| export function getTrustedDeviceToken(): string | undefined { | |
| if (!isGateEnabled()) { | |
| return undefined | |
| } | |
| return readStoredToken() | |
| } | |
| export function clearTrustedDeviceTokenCache(): void { | |
| readStoredToken.cache?.clear?.() | |
| } | |
| /** | |
| * Clear the stored trusted device token from secure storage and the memo cache. | |
| * Called before enrollTrustedDevice() during /login so a stale token from the | |
| * previous account isn't sent as X-Trusted-Device-Token while enrollment is | |
| * in-flight (enrollTrustedDevice is async — bridge API calls between login and | |
| * enrollment completion would otherwise still read the old cached token). | |
| */ | |
| export function clearTrustedDeviceToken(): void { | |
| if (!isGateEnabled()) { | |
| return | |
| } | |
| const secureStorage = getSecureStorage() | |
| try { | |
| const data = secureStorage.read() | |
| if (data?.trustedDeviceToken) { | |
| delete data.trustedDeviceToken | |
| secureStorage.update(data) | |
| } | |
| } catch { | |
| // Best-effort — don't block login if storage is inaccessible | |
| } | |
| readStoredToken.cache?.clear?.() | |
| } | |
| /** | |
| * Enroll this device via POST /auth/trusted_devices and persist the token | |
| * to keychain. Best-effort — logs and returns on failure so callers | |
| * (post-login hooks) don't block the login flow. | |
| * | |
| * The server gates enrollment on account_session.created_at < 10min, so | |
| * this must be called immediately after a fresh /login. Calling it later | |
| * (e.g. lazy enrollment on /bridge 403) will fail with 403 stale_session. | |
| */ | |
| export async function enrollTrustedDevice(): Promise<void> { | |
| try { | |
| // checkGate_CACHED_OR_BLOCKING awaits any in-flight GrowthBook re-init | |
| // (triggered by refreshGrowthBookAfterAuthChange in login.tsx) before | |
| // reading the gate, so we get the post-refresh value. | |
| if (!(await checkGate_CACHED_OR_BLOCKING(TRUSTED_DEVICE_GATE))) { | |
| logForDebugging( | |
| `[trusted-device] Gate ${TRUSTED_DEVICE_GATE} is off, skipping enrollment`, | |
| ) | |
| return | |
| } | |
| // If CLAUDE_TRUSTED_DEVICE_TOKEN is set (e.g. by an enterprise wrapper), | |
| // skip enrollment — the env var takes precedence in readStoredToken() so | |
| // any enrolled token would be shadowed and never used. | |
| if (process.env.CLAUDE_TRUSTED_DEVICE_TOKEN) { | |
| logForDebugging( | |
| '[trusted-device] CLAUDE_TRUSTED_DEVICE_TOKEN env var is set, skipping enrollment (env var takes precedence)', | |
| ) | |
| return | |
| } | |
| // Lazy require — utils/auth.ts transitively pulls ~1300 modules | |
| // (config → file → permissions → sessionStorage → commands). Daemon callers | |
| // of getTrustedDeviceToken() don't need this; only /login does. | |
| /* eslint-disable @typescript-eslint/no-require-imports */ | |
| const { getClaudeAIOAuthTokens } = | |
| require('../utils/auth.js') as typeof import('../utils/auth.js') | |
| /* eslint-enable @typescript-eslint/no-require-imports */ | |
| const accessToken = getClaudeAIOAuthTokens()?.accessToken | |
| if (!accessToken) { | |
| logForDebugging('[trusted-device] No OAuth token, skipping enrollment') | |
| return | |
| } | |
| // Always re-enroll on /login — the existing token may belong to a | |
| // different account (account-switch without /logout). Skipping enrollment | |
| // would send the old account's token on the new account's bridge calls. | |
| const secureStorage = getSecureStorage() | |
| if (isEssentialTrafficOnly()) { | |
| logForDebugging( | |
| '[trusted-device] Essential traffic only, skipping enrollment', | |
| ) | |
| return | |
| } | |
| const baseUrl = getOauthConfig().BASE_API_URL | |
| let response | |
| try { | |
| response = await axios.post<{ | |
| device_token?: string | |
| device_id?: string | |
| }>( | |
| `${baseUrl}/api/auth/trusted_devices`, | |
| { display_name: `Claude Code on ${hostname()} · ${process.platform}` }, | |
| { | |
| headers: { | |
| Authorization: `Bearer ${accessToken}`, | |
| 'Content-Type': 'application/json', | |
| }, | |
| timeout: 10_000, | |
| validateStatus: s => s < 500, | |
| }, | |
| ) | |
| } catch (err: unknown) { | |
| logForDebugging( | |
| `[trusted-device] Enrollment request failed: ${errorMessage(err)}`, | |
| ) | |
| return | |
| } | |
| if (response.status !== 200 && response.status !== 201) { | |
| logForDebugging( | |
| `[trusted-device] Enrollment failed ${response.status}: ${jsonStringify(response.data).slice(0, 200)}`, | |
| ) | |
| return | |
| } | |
| const token = response.data?.device_token | |
| if (!token || typeof token !== 'string') { | |
| logForDebugging( | |
| '[trusted-device] Enrollment response missing device_token field', | |
| ) | |
| return | |
| } | |
| try { | |
| const storageData = secureStorage.read() | |
| if (!storageData) { | |
| logForDebugging( | |
| '[trusted-device] Cannot read storage, skipping token persist', | |
| ) | |
| return | |
| } | |
| storageData.trustedDeviceToken = token | |
| const result = secureStorage.update(storageData) | |
| if (!result.success) { | |
| logForDebugging( | |
| `[trusted-device] Failed to persist token: ${result.warning ?? 'unknown'}`, | |
| ) | |
| return | |
| } | |
| readStoredToken.cache?.clear?.() | |
| logForDebugging( | |
| `[trusted-device] Enrolled device_id=${response.data.device_id ?? 'unknown'}`, | |
| ) | |
| } catch (err: unknown) { | |
| logForDebugging( | |
| `[trusted-device] Storage write failed: ${errorMessage(err)}`, | |
| ) | |
| } | |
| } catch (err: unknown) { | |
| logForDebugging(`[trusted-device] Enrollment error: ${errorMessage(err)}`) | |
| } | |
| } | |