feat: 增强 Token 过期验证机制
- 添加 tokenExpiresAt 状态变量追踪实际过期时间 - saveTokens() 保存 expires_at 字段到文件 - loadAuthConfig() 启动时验证 token 是否过期 - shouldRefresh() 优先使用实际过期时间判断 - 提前 30 分钟刷新避免临界问题 - 修复 refreshApiKey() 中的代码缩进问题 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
52
auth.js
52
auth.js
@@ -10,6 +10,7 @@ import { getRefreshConfig, requestRefreshToken } from './refresh-client.js';
|
|||||||
let currentApiKey = null;
|
let currentApiKey = null;
|
||||||
let currentRefreshToken = null;
|
let currentRefreshToken = null;
|
||||||
let lastRefreshTime = null;
|
let lastRefreshTime = null;
|
||||||
|
let tokenExpiresAt = null; // Token 过期时间戳 (ms)
|
||||||
let clientId = null;
|
let clientId = null;
|
||||||
let authSource = null; // 'env' or 'file' or 'factory_key' or 'client' or 'multi_account'
|
let authSource = null; // 'env' or 'file' or 'factory_key' or 'client' or 'multi_account'
|
||||||
let authFilePath = null;
|
let authFilePath = null;
|
||||||
@@ -21,6 +22,7 @@ let refreshInFlight = null; // 刷新锁,避免并发刷新
|
|||||||
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
|
||||||
const TOKEN_VALID_HOURS = 8; // Token valid for 8 hours
|
const TOKEN_VALID_HOURS = 8; // Token valid for 8 hours
|
||||||
|
const REFRESH_BUFFER_MS = 30 * 60 * 1000; // 提前 30 分钟刷新
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a ULID (Universally Unique Lexicographically Sortable Identifier)
|
* Generate a ULID (Universally Unique Lexicographically Sortable Identifier)
|
||||||
@@ -123,9 +125,25 @@ function loadAuthConfig() {
|
|||||||
authSource = 'file';
|
authSource = 'file';
|
||||||
authFilePath = factoryAuthPath;
|
authFilePath = factoryAuthPath;
|
||||||
|
|
||||||
// Also load access_token if available
|
// Also load access_token if available and not expired
|
||||||
if (authData.access_token) {
|
if (authData.access_token) {
|
||||||
|
const expiresAt = authData.expires_at ? new Date(authData.expires_at).getTime() : null;
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
if (expiresAt && expiresAt > now + REFRESH_BUFFER_MS) {
|
||||||
|
// Token 还有效(且距离过期超过30分钟)
|
||||||
currentApiKey = authData.access_token.trim();
|
currentApiKey = authData.access_token.trim();
|
||||||
|
tokenExpiresAt = expiresAt;
|
||||||
|
lastRefreshTime = authData.last_updated ? new Date(authData.last_updated).getTime() : now;
|
||||||
|
logInfo(`Loaded valid token from file, expires at: ${new Date(expiresAt).toISOString()}`);
|
||||||
|
} else if (expiresAt) {
|
||||||
|
// Token 已过期或即将过期
|
||||||
|
logInfo(`Stored token expired or expiring soon (expires_at: ${authData.expires_at}), will refresh`);
|
||||||
|
} else {
|
||||||
|
// 没有过期时间记录,按旧逻辑处理
|
||||||
|
currentApiKey = authData.access_token.trim();
|
||||||
|
logInfo('Loaded token from file (no expiry info, will check on first use)');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { type: 'refresh', value: authData.refresh_token.trim() };
|
return { type: 'refresh', value: authData.refresh_token.trim() };
|
||||||
@@ -176,6 +194,8 @@ async function refreshApiKey() {
|
|||||||
currentApiKey = data.access_token;
|
currentApiKey = data.access_token;
|
||||||
currentRefreshToken = data.refresh_token;
|
currentRefreshToken = data.refresh_token;
|
||||||
lastRefreshTime = Date.now();
|
lastRefreshTime = Date.now();
|
||||||
|
// 设置过期时间(默认8小时)
|
||||||
|
tokenExpiresAt = lastRefreshTime + TOKEN_VALID_HOURS * 60 * 60 * 1000;
|
||||||
|
|
||||||
// Log user info
|
// Log user info
|
||||||
if (data.user) {
|
if (data.user) {
|
||||||
@@ -188,6 +208,7 @@ async function refreshApiKey() {
|
|||||||
saveTokens(data.access_token, data.refresh_token);
|
saveTokens(data.access_token, data.refresh_token);
|
||||||
|
|
||||||
logInfo(`New Refresh-Key: ${currentRefreshToken}`);
|
logInfo(`New Refresh-Key: ${currentRefreshToken}`);
|
||||||
|
logInfo(`Token expires at: ${new Date(tokenExpiresAt).toISOString()}`);
|
||||||
logInfo('API key refreshed successfully');
|
logInfo('API key refreshed successfully');
|
||||||
return data.access_token;
|
return data.access_token;
|
||||||
|
|
||||||
@@ -206,13 +227,20 @@ async function refreshApiKey() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Save tokens to appropriate file
|
* Save tokens to appropriate file
|
||||||
|
* @param {string} accessToken - Access token to save
|
||||||
|
* @param {string} refreshToken - Refresh token to save
|
||||||
|
* @param {number} expiresInMs - Token validity duration in milliseconds (default: TOKEN_VALID_HOURS)
|
||||||
*/
|
*/
|
||||||
function saveTokens(accessToken, refreshToken) {
|
function saveTokens(accessToken, refreshToken, expiresInMs = TOKEN_VALID_HOURS * 60 * 60 * 1000) {
|
||||||
try {
|
try {
|
||||||
|
const now = Date.now();
|
||||||
|
const expiresAt = new Date(now + expiresInMs).toISOString();
|
||||||
|
|
||||||
const authData = {
|
const authData = {
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
last_updated: new Date().toISOString()
|
expires_at: expiresAt,
|
||||||
|
last_updated: new Date(now).toISOString()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure directory exists
|
// Ensure directory exists
|
||||||
@@ -228,6 +256,7 @@ function saveTokens(accessToken, refreshToken) {
|
|||||||
Object.assign(authData, existingData, {
|
Object.assign(authData, existingData, {
|
||||||
access_token: accessToken,
|
access_token: accessToken,
|
||||||
refresh_token: refreshToken,
|
refresh_token: refreshToken,
|
||||||
|
expires_at: expiresAt,
|
||||||
last_updated: authData.last_updated
|
last_updated: authData.last_updated
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -244,14 +273,27 @@ function saveTokens(accessToken, refreshToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if API key needs refresh (older than 6 hours)
|
* Check if API key needs refresh
|
||||||
|
* Uses actual expiration time if available, falls back to time-based check
|
||||||
*/
|
*/
|
||||||
function shouldRefresh() {
|
function shouldRefresh() {
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// 如果有过期时间,使用过期时间判断(提前30分钟刷新)
|
||||||
|
if (tokenExpiresAt) {
|
||||||
|
const shouldRefreshByExpiry = now + REFRESH_BUFFER_MS >= tokenExpiresAt;
|
||||||
|
if (shouldRefreshByExpiry) {
|
||||||
|
logDebug(`Token expiring soon (expires_at: ${new Date(tokenExpiresAt).toISOString()})`);
|
||||||
|
}
|
||||||
|
return shouldRefreshByExpiry;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回退到基于刷新时间的判断
|
||||||
if (!lastRefreshTime) {
|
if (!lastRefreshTime) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hoursSinceRefresh = (Date.now() - lastRefreshTime) / (1000 * 60 * 60);
|
const hoursSinceRefresh = (now - lastRefreshTime) / (1000 * 60 * 60);
|
||||||
return hoursSinceRefresh >= REFRESH_INTERVAL_HOURS;
|
return hoursSinceRefresh >= REFRESH_INTERVAL_HOURS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user