diff --git a/src/auth/anthropic.test.ts b/src/auth/anthropic.test.ts index c5fc1a2..95df656 100644 --- a/src/auth/anthropic.test.ts +++ b/src/auth/anthropic.test.ts @@ -6,6 +6,7 @@ import { join } from 'path'; describe('auth/anthropic', () => { const originalHome = process.env.HOME; const originalEnvKey = process.env.ANTHROPIC_API_KEY; + const originalEnvToken = process.env.ANTHROPIC_AUTH_TOKEN; let homeDir: string; @@ -13,6 +14,7 @@ describe('auth/anthropic', () => { homeDir = mkdtempSync(join(tmpdir(), 'flynn-auth-anthropic-')); process.env.HOME = homeDir; delete process.env.ANTHROPIC_API_KEY; + delete process.env.ANTHROPIC_AUTH_TOKEN; vi.resetModules(); }); @@ -23,6 +25,12 @@ describe('auth/anthropic', () => { } else { delete process.env.ANTHROPIC_API_KEY; } + + if (originalEnvToken) { + process.env.ANTHROPIC_AUTH_TOKEN = originalEnvToken; + } else { + delete process.env.ANTHROPIC_AUTH_TOKEN; + } }); it('stores, loads, and clears Anthropic API key', async () => { @@ -41,10 +49,31 @@ describe('auth/anthropic', () => { expect(mod.loadStoredAnthropicAuth()).toBeNull(); }); + it('stores, loads, and clears Anthropic auth token', async () => { + const mod = await import('./anthropic.js'); + + expect(mod.loadStoredAnthropicAuthToken()).toBeNull(); + + mod.storeAnthropicAuthToken('tok-test'); + expect(mod.loadStoredAnthropicAuthToken()).toBe('tok-test'); + + const authFile = join(homeDir, '.config/flynn/auth.json'); + const mode = statSync(authFile).mode & 0o777; + expect(mode).toBe(0o600); + + mod.clearAnthropicAuthToken(); + expect(mod.loadStoredAnthropicAuthToken()).toBeNull(); + }); + it('getAnthropicApiKey prefers environment variable', async () => { process.env.ANTHROPIC_API_KEY = 'sk-env'; const mod = await import('./anthropic.js'); expect(mod.getAnthropicApiKey()).toBe('sk-env'); }); -}); + it('getAnthropicAuthToken prefers environment variable', async () => { + process.env.ANTHROPIC_AUTH_TOKEN = 'tok-env'; + const mod = await import('./anthropic.js'); + expect(mod.getAnthropicAuthToken()).toBe('tok-env'); + }); +}); diff --git a/src/auth/anthropic.ts b/src/auth/anthropic.ts index 464d3b0..851a213 100644 --- a/src/auth/anthropic.ts +++ b/src/auth/anthropic.ts @@ -7,7 +7,9 @@ const AUTH_FILE = resolve(AUTH_DIR, 'auth.json'); export interface AnthropicAuthInfo { /** Anthropic API key. */ - api_key: string; + api_key?: string; + /** Anthropic auth token (OAuth/token mode). */ + auth_token?: string; created_at: string; } @@ -51,7 +53,21 @@ export function storeAnthropicAuth(apiKey: string): void { throw new Error('Anthropic API key is empty'); } const store = readAuthStore(); - store.anthropic = { api_key: trimmed, created_at: new Date().toISOString() }; + store.anthropic = { ...store.anthropic, api_key: trimmed, created_at: new Date().toISOString() }; + writeAuthStore(store); +} + +export function loadStoredAnthropicAuthToken(): string | null { + return loadStoredAnthropicAuth()?.auth_token ?? null; +} + +export function storeAnthropicAuthToken(token: string): void { + const trimmed = token.trim(); + if (!trimmed) { + throw new Error('Anthropic auth token is empty'); + } + const store = readAuthStore(); + store.anthropic = { ...store.anthropic, auth_token: trimmed, created_at: new Date().toISOString() }; writeAuthStore(store); } @@ -61,6 +77,19 @@ export function clearAnthropicAuth(): void { writeAuthStore(store); } +export function clearAnthropicAuthToken(): void { + const store = readAuthStore(); + if (!store.anthropic) { + writeAuthStore(store); + return; + } + delete store.anthropic.auth_token; + if (!store.anthropic.api_key && !store.anthropic.auth_token) { + delete store.anthropic; + } + writeAuthStore(store); +} + /** * Get an Anthropic API key from any available source. * Priority: ANTHROPIC_API_KEY → stored auth.json. @@ -70,3 +99,13 @@ export function getAnthropicApiKey(): string | null { ?? loadStoredAnthropicAuth()?.api_key ?? null; } + +/** + * Get an Anthropic auth token from any available source. + * Priority: ANTHROPIC_AUTH_TOKEN → stored auth.json. + */ +export function getAnthropicAuthToken(): string | null { + return process.env.ANTHROPIC_AUTH_TOKEN + ?? loadStoredAnthropicAuth()?.auth_token + ?? null; +} diff --git a/src/auth/index.ts b/src/auth/index.ts index b42331c..48e870b 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -3,6 +3,10 @@ export { storeAnthropicAuth, clearAnthropicAuth, getAnthropicApiKey, + loadStoredAnthropicAuthToken, + storeAnthropicAuthToken, + clearAnthropicAuthToken, + getAnthropicAuthToken, type AnthropicAuthInfo, } from './anthropic.js';