实现双授权系统:支持FACTORY_API_KEY环境变量优先级和客户端授权回退机制

- 新增FACTORY_API_KEY环境变量支持(最高优先级)
- 保留现有refresh token自动刷新机制
- 添加客户端authorization头作为fallback
- 优化启动流程,无认证配置时不报错退出
- 更新所有端点支持新的授权优先级系统
- 修改GPT-5-Codex推理级别为off
This commit is contained in:
1e0n
2025-10-08 19:42:39 +08:00
parent 2dc8c89270
commit 25f89a12b7
4 changed files with 72 additions and 50 deletions

109
auth.js
View File

@@ -9,8 +9,9 @@ let currentApiKey = null;
let currentRefreshToken = null;
let lastRefreshTime = null;
let clientId = null;
let authSource = null; // 'env' or 'file'
let authSource = null; // 'env' or 'file' or 'factory_key' or 'client'
let authFilePath = null;
let factoryApiKey = null; // From FACTORY_API_KEY environment variable
const REFRESH_URL = 'https://api.workos.com/user_management/authenticate';
const REFRESH_INTERVAL_HOURS = 6; // Refresh every 6 hours
@@ -57,19 +58,29 @@ function generateClientId() {
}
/**
* Load initial refresh token from environment or config file
* Load auth configuration with priority system
* Priority: FACTORY_API_KEY > refresh token mechanism > client authorization
*/
function loadRefreshToken() {
// 1. Check environment variable DROID_REFRESH_KEY
function loadAuthConfig() {
// 1. Check FACTORY_API_KEY environment variable (highest priority)
const factoryKey = process.env.FACTORY_API_KEY;
if (factoryKey && factoryKey.trim() !== '') {
logInfo('Using fixed API key from FACTORY_API_KEY environment variable');
factoryApiKey = factoryKey.trim();
authSource = 'factory_key';
return { type: 'factory_key', value: factoryKey.trim() };
}
// 2. Check refresh token mechanism (DROID_REFRESH_KEY)
const envRefreshKey = process.env.DROID_REFRESH_KEY;
if (envRefreshKey && envRefreshKey.trim() !== '') {
logInfo('Using refresh token from DROID_REFRESH_KEY environment variable');
authSource = 'env';
authFilePath = path.join(process.cwd(), 'auth.json');
return envRefreshKey.trim();
return { type: 'refresh', value: envRefreshKey.trim() };
}
// 2. Check ~/.factory/auth.json
// 3. Check ~/.factory/auth.json
const homeDir = os.homedir();
const factoryAuthPath = path.join(homeDir, '.factory', 'auth.json');
@@ -88,37 +99,17 @@ function loadRefreshToken() {
currentApiKey = authData.access_token.trim();
}
return authData.refresh_token.trim();
return { type: 'refresh', value: authData.refresh_token.trim() };
}
}
} catch (error) {
logError('Error reading ~/.factory/auth.json', error);
}
// 3. No refresh token found - throw error
const errorMessage = `
╔════════════════════════════════════════════════════════════════╗
║ Refresh Token Not Found ║
╚════════════════════════════════════════════════════════════════╝
No valid refresh token found. Please use one of the following methods:
1. Set environment variable:
export DROID_REFRESH_KEY="your_refresh_token_here"
2. Or ensure ~/.factory/auth.json exists with valid refresh_token:
{
"access_token": "your_access_token",
"refresh_token": "your_refresh_token"
}
Current status:
- DROID_REFRESH_KEY: ${envRefreshKey ? 'empty or whitespace' : 'not set'}
- ~/.factory/auth.json: ${fs.existsSync(factoryAuthPath) ? 'exists but invalid/empty refresh_token' : 'not found'}
`;
logError(errorMessage);
throw new Error('Refresh token not found. Please configure DROID_REFRESH_KEY or ~/.factory/auth.json');
// 4. No configured auth found - will use client authorization
logInfo('No auth configuration found, will use client authorization headers');
authSource = 'client';
return { type: 'client', value: null };
}
/**
@@ -235,14 +226,26 @@ function shouldRefresh() {
}
/**
* Initialize auth system - load refresh token and get initial API key
* Initialize auth system - load auth config and setup initial API key if needed
*/
export async function initializeAuth() {
try {
currentRefreshToken = loadRefreshToken();
const authConfig = loadAuthConfig();
// Always refresh on startup to get fresh token
await refreshApiKey();
if (authConfig.type === 'factory_key') {
// Using fixed FACTORY_API_KEY, no refresh needed
logInfo('Auth system initialized with fixed API key');
} else if (authConfig.type === 'refresh') {
// Using refresh token mechanism
currentRefreshToken = authConfig.value;
// Always refresh on startup to get fresh token
await refreshApiKey();
logInfo('Auth system initialized with refresh token mechanism');
} else {
// Using client authorization, no setup needed
logInfo('Auth system initialized for client authorization mode');
}
logInfo('Auth system initialized successfully');
} catch (error) {
@@ -252,18 +255,36 @@ export async function initializeAuth() {
}
/**
* Get API key, refresh if needed
* Get API key based on configured authorization method
* @param {string} clientAuthorization - Authorization header from client request (optional)
*/
export async function getApiKey() {
// Check if we need to refresh
if (shouldRefresh()) {
logInfo('API key needs refresh (6+ hours old)');
await refreshApiKey();
export async function getApiKey(clientAuthorization = null) {
// Priority 1: FACTORY_API_KEY environment variable
if (authSource === 'factory_key' && factoryApiKey) {
return `Bearer ${factoryApiKey}`;
}
if (!currentApiKey) {
throw new Error('No API key available. Please initialize auth system first.');
// Priority 2: Refresh token mechanism
if (authSource === 'env' || authSource === 'file') {
// Check if we need to refresh
if (shouldRefresh()) {
logInfo('API key needs refresh (6+ hours old)');
await refreshApiKey();
}
if (!currentApiKey) {
throw new Error('No API key available from refresh token mechanism.');
}
return `Bearer ${currentApiKey}`;
}
return `Bearer ${currentApiKey}`;
// Priority 3: Client authorization header
if (clientAuthorization) {
logDebug('Using client authorization header');
return clientAuthorization;
}
// No authorization available
throw new Error('No authorization available. Please configure FACTORY_API_KEY, refresh token, or provide client authorization.');
}

View File

@@ -43,7 +43,7 @@
"name": "GPT-5-Codex",
"id": "gpt-5-codex",
"type": "openai",
"reasoning": "high"
"reasoning": "off"
},
{
"name": "GLM-4.6",

View File

@@ -66,7 +66,7 @@ async function handleChatCompletions(req, res) {
// Get API key (will auto-refresh if needed)
let authHeader;
try {
authHeader = await getApiKey();
authHeader = await getApiKey(req.headers.authorization);
} catch (error) {
logError('Failed to get API key', error);
return res.status(500).json({
@@ -209,7 +209,7 @@ async function handleDirectResponses(req, res) {
// Get API key
let authHeader;
try {
authHeader = await getApiKey();
authHeader = await getApiKey(req.headers.authorization);
} catch (error) {
logError('Failed to get API key', error);
return res.status(500).json({
@@ -338,7 +338,7 @@ async function handleDirectMessages(req, res) {
// Get API key
let authHeader;
try {
authHeader = await getApiKey();
authHeader = await getApiKey(req.headers.authorization);
} catch (error) {
logError('Failed to get API key', error);
return res.status(500).json({

View File

@@ -110,7 +110,8 @@ app.use((err, req, res, next) => {
logInfo('Configuration loaded successfully');
logInfo(`Dev mode: ${isDevMode()}`);
// Initialize auth system (load and refresh API key)
// Initialize auth system (load and setup API key if needed)
// This won't throw error if no auth config is found - will use client auth
await initializeAuth();
const PORT = getPort();