From 759bb88a900fb1ba9031d161f82edf88fc049cda Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:03:47 +0800 Subject: [PATCH 1/2] fix(auth): Improve file-based auth handling and consistency --- internal/watcher/watcher.go | 8 +++++++- sdk/cliproxy/auth/filestore.go | 9 +++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index a0fa4128..458b8cf5 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -526,8 +526,14 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth { if email, _ := metadata["email"].(string); email != "" { label = email } + // Use relative path under authDir as ID to stay consistent with FileStore + id := full + if rel, errRel := filepath.Rel(w.authDir, full); errRel == nil && rel != "" { + id = rel + } + a := &coreauth.Auth{ - ID: full, + ID: id, Provider: provider, Label: label, Status: coreauth.StatusActive, diff --git a/sdk/cliproxy/auth/filestore.go b/sdk/cliproxy/auth/filestore.go index ac303aec..6fbcbd54 100644 --- a/sdk/cliproxy/auth/filestore.go +++ b/sdk/cliproxy/auth/filestore.go @@ -64,6 +64,15 @@ func (s *FileStore) Save(ctx context.Context, auth *Auth) error { if path == "" { return fmt.Errorf("auth filestore: missing file path attribute for %s", auth.ID) } + // If the auth has been disabled and the original file was removed, avoid + // recreating it on disk. This lets operators delete auth files explicitly. + if auth.Disabled { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return nil + } + } + } s.mu.Lock() defer s.mu.Unlock() if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { From 314125e7ecb5a625c76a6fead7b687a0d7e9407b Mon Sep 17 00:00:00 2001 From: hkfires <10558748+hkfires@users.noreply.github.com> Date: Mon, 22 Sep 2025 16:46:01 +0800 Subject: [PATCH 2/2] fix(logging): Prevent race conditions in FileStreamingLogWriter --- internal/logging/request_logger.go | 48 ++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/internal/logging/request_logger.go b/internal/logging/request_logger.go index 17ed7715..838fd678 100644 --- a/internal/logging/request_logger.go +++ b/internal/logging/request_logger.go @@ -13,6 +13,7 @@ import ( "path/filepath" "regexp" "strings" + "sync" "time" "github.com/router-for-me/CLIProxyAPI/v6/internal/interfaces" @@ -493,6 +494,12 @@ type FileStreamingLogWriter struct { // statusWritten indicates whether the response status has been written. statusWritten bool + + // mu protects concurrent access to the writer state. + mu sync.RWMutex + + // closed indicates whether the streaming writer has been closed. + closed bool } // WriteChunkAsync writes a response chunk asynchronously (non-blocking). @@ -500,7 +507,10 @@ type FileStreamingLogWriter struct { // Parameters: // - chunk: The response chunk to write func (w *FileStreamingLogWriter) WriteChunkAsync(chunk []byte) { - if w.chunkChan == nil { + w.mu.RLock() + defer w.mu.RUnlock() + + if w.chunkChan == nil || w.closed { return } @@ -525,6 +535,9 @@ func (w *FileStreamingLogWriter) WriteChunkAsync(chunk []byte) { // Returns: // - error: An error if writing fails, nil otherwise func (w *FileStreamingLogWriter) WriteStatus(status int, headers map[string][]string) error { + w.mu.Lock() + defer w.mu.Unlock() + if w.file == nil || w.statusWritten { return nil } @@ -553,21 +566,38 @@ func (w *FileStreamingLogWriter) WriteStatus(status int, headers map[string][]st // Returns: // - error: An error if closing fails, nil otherwise func (w *FileStreamingLogWriter) Close() error { - if w.chunkChan != nil { - close(w.chunkChan) + w.mu.Lock() + if w.closed { + w.mu.Unlock() + return nil + } + w.closed = true + chunkChan := w.chunkChan + closeChan := w.closeChan + file := w.file + w.mu.Unlock() + + if chunkChan != nil { + close(chunkChan) } // Wait for async writer to finish - if w.closeChan != nil { - <-w.closeChan - w.chunkChan = nil + if closeChan != nil { + <-closeChan } - if w.file != nil { - return w.file.Close() + var err error + if file != nil { + err = file.Close() } - return nil + w.mu.Lock() + w.chunkChan = nil + w.closeChan = nil + w.file = nil + w.mu.Unlock() + + return err } // asyncWriter runs in a goroutine to handle async chunk writing.