| 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' |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| const TRUSTED_DEVICE_GATE = 'tengu_sessions_elevated_auth_enforcement' |
|
|
| function isGateEnabled(): boolean { |
| return getFeatureValue_CACHED_MAY_BE_STALE(TRUSTED_DEVICE_GATE, false) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| const readStoredToken = memoize((): string | undefined => { |
| |
| 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?.() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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 { |
| |
| } |
| readStoredToken.cache?.clear?.() |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function enrollTrustedDevice(): Promise<void> { |
| try { |
| |
| |
| |
| if (!(await checkGate_CACHED_OR_BLOCKING(TRUSTED_DEVICE_GATE))) { |
| logForDebugging( |
| `[trusted-device] Gate ${TRUSTED_DEVICE_GATE} is off, skipping enrollment`, |
| ) |
| return |
| } |
| |
| |
| |
| 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 |
| } |
| |
| |
| |
| |
| const { getClaudeAIOAuthTokens } = |
| require('../utils/auth.js') as typeof import('../utils/auth.js') |
| |
| const accessToken = getClaudeAIOAuthTokens()?.accessToken |
| if (!accessToken) { |
| logForDebugging('[trusted-device] No OAuth token, skipping enrollment') |
| return |
| } |
| |
| |
| |
| 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)}`) |
| } |
| } |
|
|