auth: add Anthropic auth token storage
This commit is contained in:
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@ export {
|
||||
storeAnthropicAuth,
|
||||
clearAnthropicAuth,
|
||||
getAnthropicApiKey,
|
||||
loadStoredAnthropicAuthToken,
|
||||
storeAnthropicAuthToken,
|
||||
clearAnthropicAuthToken,
|
||||
getAnthropicAuthToken,
|
||||
type AnthropicAuthInfo,
|
||||
} from './anthropic.js';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user