实现双授权系统:支持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 currentRefreshToken = null;
let lastRefreshTime = null; let lastRefreshTime = null;
let clientId = 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 authFilePath = null;
let factoryApiKey = null; // From FACTORY_API_KEY environment variable
const REFRESH_URL = 'https://api.workos.com/user_management/authenticate'; const REFRESH_URL = 'https://api.workos.com/user_management/authenticate';
const REFRESH_INTERVAL_HOURS = 6; // Refresh every 6 hours 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() { function loadAuthConfig() {
// 1. Check environment variable DROID_REFRESH_KEY // 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; const envRefreshKey = process.env.DROID_REFRESH_KEY;
if (envRefreshKey && envRefreshKey.trim() !== '') { if (envRefreshKey && envRefreshKey.trim() !== '') {
logInfo('Using refresh token from DROID_REFRESH_KEY environment variable'); logInfo('Using refresh token from DROID_REFRESH_KEY environment variable');
authSource = 'env'; authSource = 'env';
authFilePath = path.join(process.cwd(), 'auth.json'); 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 homeDir = os.homedir();
const factoryAuthPath = path.join(homeDir, '.factory', 'auth.json'); const factoryAuthPath = path.join(homeDir, '.factory', 'auth.json');
@@ -88,37 +99,17 @@ function loadRefreshToken() {
currentApiKey = authData.access_token.trim(); currentApiKey = authData.access_token.trim();
} }
return authData.refresh_token.trim(); return { type: 'refresh', value: authData.refresh_token.trim() };
} }
} }
} catch (error) { } catch (error) {
logError('Error reading ~/.factory/auth.json', error); logError('Error reading ~/.factory/auth.json', error);
} }
// 3. No refresh token found - throw error // 4. No configured auth found - will use client authorization
const errorMessage = ` logInfo('No auth configuration found, will use client authorization headers');
╔════════════════════════════════════════════════════════════════╗ authSource = 'client';
║ Refresh Token Not Found ║ return { type: 'client', value: null };
╚════════════════════════════════════════════════════════════════╝
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');
} }
/** /**
@@ -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() { export async function initializeAuth() {
try { try {
currentRefreshToken = loadRefreshToken(); const authConfig = loadAuthConfig();
// Always refresh on startup to get fresh token if (authConfig.type === 'factory_key') {
await refreshApiKey(); // 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'); logInfo('Auth system initialized successfully');
} catch (error) { } 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() { export async function getApiKey(clientAuthorization = null) {
// Check if we need to refresh // Priority 1: FACTORY_API_KEY environment variable
if (shouldRefresh()) { if (authSource === 'factory_key' && factoryApiKey) {
logInfo('API key needs refresh (6+ hours old)'); return `Bearer ${factoryApiKey}`;
await refreshApiKey();
} }
if (!currentApiKey) { // Priority 2: Refresh token mechanism
throw new Error('No API key available. Please initialize auth system first.'); 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", "name": "GPT-5-Codex",
"id": "gpt-5-codex", "id": "gpt-5-codex",
"type": "openai", "type": "openai",
"reasoning": "high" "reasoning": "off"
}, },
{ {
"name": "GLM-4.6", "name": "GLM-4.6",

View File

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

View File

@@ -110,7 +110,8 @@ app.use((err, req, res, next) => {
logInfo('Configuration loaded successfully'); logInfo('Configuration loaded successfully');
logInfo(`Dev mode: ${isDevMode()}`); 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(); await initializeAuth();
const PORT = getPort(); const PORT = getPort();