mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 12:20:52 +08:00
fix(auth): persist access_token on refresh for providers that need it
Previously, metadataEqualIgnoringTimestamps() ignored access_token for all providers, which prevented refreshed tokens from being persisted to disk/database. This caused tokens to be lost on server restart for providers like iFlow. This change makes the behavior provider-specific: - Providers like gemini/gemini-cli that issue new tokens on every refresh and can re-fetch when needed will continue to ignore access_token (optimization) - Other providers like iFlow will now persist access_token changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -74,7 +74,7 @@ func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (str
|
||||
if existing, errRead := os.ReadFile(path); errRead == nil {
|
||||
// Use metadataEqualIgnoringTimestamps to skip writes when only timestamp fields change.
|
||||
// This prevents the token refresh loop caused by timestamp/expired/expires_in changes.
|
||||
if metadataEqualIgnoringTimestamps(existing, raw) {
|
||||
if metadataEqualIgnoringTimestamps(existing, raw, auth.Provider) {
|
||||
return path, nil
|
||||
}
|
||||
} else if errRead != nil && !os.IsNotExist(errRead) {
|
||||
@@ -284,7 +284,10 @@ func jsonEqual(a, b []byte) bool {
|
||||
// ignoring fields that change on every refresh but don't affect functionality.
|
||||
// This prevents unnecessary file writes that would trigger watcher events and
|
||||
// create refresh loops.
|
||||
func metadataEqualIgnoringTimestamps(a, b []byte) bool {
|
||||
// The provider parameter controls whether access_token is ignored: providers like
|
||||
// Google OAuth (gemini, gemini-cli) can re-fetch tokens when needed, while others
|
||||
// like iFlow require the refreshed token to be persisted.
|
||||
func metadataEqualIgnoringTimestamps(a, b []byte, provider string) bool {
|
||||
var objA, objB map[string]any
|
||||
if err := json.Unmarshal(a, &objA); err != nil {
|
||||
return false
|
||||
@@ -295,9 +298,18 @@ func metadataEqualIgnoringTimestamps(a, b []byte) bool {
|
||||
|
||||
// Fields to ignore: these change on every refresh but don't affect authentication logic.
|
||||
// - timestamp, expired, expires_in, last_refresh: time-related fields that change on refresh
|
||||
// - access_token: Google OAuth returns a new access_token on each refresh, this is expected
|
||||
// and shouldn't trigger file writes (the new token will be fetched again when needed)
|
||||
ignoredFields := []string{"timestamp", "expired", "expires_in", "last_refresh", "access_token"}
|
||||
ignoredFields := []string{"timestamp", "expired", "expires_in", "last_refresh"}
|
||||
|
||||
// Providers that issue new access_token on every refresh and can re-fetch when needed.
|
||||
// For these providers, we also ignore access_token to avoid unnecessary file writes.
|
||||
providersIgnoringAccessToken := map[string]bool{
|
||||
"gemini": true,
|
||||
"gemini-cli": true,
|
||||
}
|
||||
if providersIgnoringAccessToken[provider] {
|
||||
ignoredFields = append(ignoredFields, "access_token")
|
||||
}
|
||||
|
||||
for _, field := range ignoredFields {
|
||||
delete(objA, field)
|
||||
delete(objB, field)
|
||||
|
||||
Reference in New Issue
Block a user