auth: add Anthropic auth token storage
This commit is contained in:
@@ -6,6 +6,7 @@ import { join } from 'path';
|
|||||||
describe('auth/anthropic', () => {
|
describe('auth/anthropic', () => {
|
||||||
const originalHome = process.env.HOME;
|
const originalHome = process.env.HOME;
|
||||||
const originalEnvKey = process.env.ANTHROPIC_API_KEY;
|
const originalEnvKey = process.env.ANTHROPIC_API_KEY;
|
||||||
|
const originalEnvToken = process.env.ANTHROPIC_AUTH_TOKEN;
|
||||||
|
|
||||||
let homeDir: string;
|
let homeDir: string;
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ describe('auth/anthropic', () => {
|
|||||||
homeDir = mkdtempSync(join(tmpdir(), 'flynn-auth-anthropic-'));
|
homeDir = mkdtempSync(join(tmpdir(), 'flynn-auth-anthropic-'));
|
||||||
process.env.HOME = homeDir;
|
process.env.HOME = homeDir;
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
delete process.env.ANTHROPIC_API_KEY;
|
||||||
|
delete process.env.ANTHROPIC_AUTH_TOKEN;
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -23,6 +25,12 @@ describe('auth/anthropic', () => {
|
|||||||
} else {
|
} else {
|
||||||
delete process.env.ANTHROPIC_API_KEY;
|
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 () => {
|
it('stores, loads, and clears Anthropic API key', async () => {
|
||||||
@@ -41,10 +49,31 @@ describe('auth/anthropic', () => {
|
|||||||
expect(mod.loadStoredAnthropicAuth()).toBeNull();
|
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 () => {
|
it('getAnthropicApiKey prefers environment variable', async () => {
|
||||||
process.env.ANTHROPIC_API_KEY = 'sk-env';
|
process.env.ANTHROPIC_API_KEY = 'sk-env';
|
||||||
const mod = await import('./anthropic.js');
|
const mod = await import('./anthropic.js');
|
||||||
expect(mod.getAnthropicApiKey()).toBe('sk-env');
|
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 {
|
export interface AnthropicAuthInfo {
|
||||||
/** Anthropic API key. */
|
/** Anthropic API key. */
|
||||||
api_key: string;
|
api_key?: string;
|
||||||
|
/** Anthropic auth token (OAuth/token mode). */
|
||||||
|
auth_token?: string;
|
||||||
created_at: string;
|
created_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +53,21 @@ export function storeAnthropicAuth(apiKey: string): void {
|
|||||||
throw new Error('Anthropic API key is empty');
|
throw new Error('Anthropic API key is empty');
|
||||||
}
|
}
|
||||||
const store = readAuthStore();
|
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);
|
writeAuthStore(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,6 +77,19 @@ export function clearAnthropicAuth(): void {
|
|||||||
writeAuthStore(store);
|
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.
|
* Get an Anthropic API key from any available source.
|
||||||
* Priority: ANTHROPIC_API_KEY → stored auth.json.
|
* Priority: ANTHROPIC_API_KEY → stored auth.json.
|
||||||
@@ -70,3 +99,13 @@ export function getAnthropicApiKey(): string | null {
|
|||||||
?? loadStoredAnthropicAuth()?.api_key
|
?? loadStoredAnthropicAuth()?.api_key
|
||||||
?? null;
|
?? 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,
|
storeAnthropicAuth,
|
||||||
clearAnthropicAuth,
|
clearAnthropicAuth,
|
||||||
getAnthropicApiKey,
|
getAnthropicApiKey,
|
||||||
|
loadStoredAnthropicAuthToken,
|
||||||
|
storeAnthropicAuthToken,
|
||||||
|
clearAnthropicAuthToken,
|
||||||
|
getAnthropicAuthToken,
|
||||||
type AnthropicAuthInfo,
|
type AnthropicAuthInfo,
|
||||||
} from './anthropic.js';
|
} from './anthropic.js';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user