auth: add Anthropic auth token storage

This commit is contained in:
William Valentin
2026-02-15 10:27:32 -08:00
parent bcf6c377d5
commit 6375f56f67
3 changed files with 75 additions and 3 deletions
+30 -1
View File
@@ -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');
});
});
+41 -2
View File
@@ -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;
}
+4
View File
@@ -3,6 +3,10 @@ export {
storeAnthropicAuth,
clearAnthropicAuth,
getAnthropicApiKey,
loadStoredAnthropicAuthToken,
storeAnthropicAuthToken,
clearAnthropicAuthToken,
getAnthropicAuthToken,
type AnthropicAuthInfo,
} from './anthropic.js';