diff --git a/cmd/server/main.go b/cmd/server/main.go index 7c5d35ac..e5df3fec 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -8,7 +8,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "github.com/router-for-me/CLIProxyAPI/v6/internal/cmd" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" @@ -123,22 +122,10 @@ func main() { // Set the log level based on the configuration. util.SetLogLevel(cfg) - // Expand the tilde (~) in the auth directory path to the user's home directory. - if strings.HasPrefix(cfg.AuthDir, "~") { - home, errUserHomeDir := os.UserHomeDir() - if errUserHomeDir != nil { - log.Fatalf("failed to get home directory: %v", errUserHomeDir) - } - // Reconstruct the path by replacing the tilde with the user's home directory. - remainder := strings.TrimPrefix(cfg.AuthDir, "~") - remainder = strings.TrimLeft(remainder, "/\\") - if remainder == "" { - cfg.AuthDir = home - } else { - // Normalize any slash style in the remainder so Windows paths keep nested directories. - normalized := strings.ReplaceAll(remainder, "\\", "/") - cfg.AuthDir = filepath.Join(home, filepath.FromSlash(normalized)) - } + if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil { + log.Fatalf("failed to resolve auth directory: %v", errResolveAuthDir) + } else { + cfg.AuthDir = resolvedAuthDir } // Create login options to be used in authentication flows. diff --git a/internal/util/util.go b/internal/util/util.go index bad67aae..d14f1637 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -4,6 +4,7 @@ package util import ( + "fmt" "io/fs" "os" "path/filepath" @@ -30,23 +31,42 @@ func SetLogLevel(cfg *config.Config) { } } -// CountAuthFiles returns the number of JSON auth files located under the provided directory. -// The function resolves leading tildes to the user's home directory and performs a case-insensitive -// match on the ".json" suffix so that files saved with uppercase extensions are also counted. -func CountAuthFiles(authDir string) int { +// ResolveAuthDir normalizes the auth directory path for consistent reuse throughout the app. +// It expands a leading tilde (~) to the user's home directory and returns a cleaned path. +func ResolveAuthDir(authDir string) (string, error) { if authDir == "" { - return 0 + return "", nil } if strings.HasPrefix(authDir, "~") { home, err := os.UserHomeDir() if err != nil { - log.Debugf("countAuthFiles: failed to resolve home directory: %v", err) - return 0 + return "", fmt.Errorf("resolve auth dir: %w", err) } - authDir = filepath.Join(home, authDir[1:]) + remainder := strings.TrimPrefix(authDir, "~") + remainder = strings.TrimLeft(remainder, "/\\") + if remainder == "" { + return filepath.Clean(home), nil + } + normalized := strings.ReplaceAll(remainder, "\\", "/") + return filepath.Clean(filepath.Join(home, filepath.FromSlash(normalized))), nil + } + return filepath.Clean(authDir), nil +} + +// CountAuthFiles returns the number of JSON auth files located under the provided directory. +// The function resolves leading tildes to the user's home directory and performs a case-insensitive +// match on the ".json" suffix so that files saved with uppercase extensions are also counted. +func CountAuthFiles(authDir string) int { + dir, err := ResolveAuthDir(authDir) + if err != nil { + log.Debugf("countAuthFiles: failed to resolve auth directory: %v", err) + return 0 + } + if dir == "" { + return 0 } count := 0 - walkErr := filepath.WalkDir(authDir, func(path string, d fs.DirEntry, err error) error { + walkErr := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil { log.Debugf("countAuthFiles: error accessing %s: %v", path, err) return nil diff --git a/internal/watcher/watcher.go b/internal/watcher/watcher.go index 2fa7da20..874ed1ce 100644 --- a/internal/watcher/watcher.go +++ b/internal/watcher/watcher.go @@ -463,6 +463,12 @@ func (w *Watcher) reloadConfig() bool { return false } + if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(newConfig.AuthDir); errResolveAuthDir != nil { + log.Errorf("failed to resolve auth directory from config: %v", errResolveAuthDir) + } else { + newConfig.AuthDir = resolvedAuthDir + } + w.clientsMutex.Lock() oldConfig := w.config w.config = newConfig @@ -582,19 +588,22 @@ func (w *Watcher) reloadClients(rescanAuth bool) { // Rebuild auth file hash cache for current clients w.lastAuthHashes = make(map[string]string) - // Recompute hashes for current auth files - _ = filepath.Walk(cfg.AuthDir, func(path string, info fs.FileInfo, err error) error { - if err != nil { - return nil - } - if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") { - if data, errReadFile := os.ReadFile(path); errReadFile == nil && len(data) > 0 { - sum := sha256.Sum256(data) - w.lastAuthHashes[path] = hex.EncodeToString(sum[:]) + if resolvedAuthDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir); errResolveAuthDir != nil { + log.Errorf("failed to resolve auth directory for hash cache: %v", errResolveAuthDir) + } else if resolvedAuthDir != "" { + _ = filepath.Walk(resolvedAuthDir, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return nil } - } - return nil - }) + if !info.IsDir() && strings.HasSuffix(strings.ToLower(info.Name()), ".json") { + if data, errReadFile := os.ReadFile(path); errReadFile == nil && len(data) > 0 { + sum := sha256.Sum256(data) + w.lastAuthHashes[path] = hex.EncodeToString(sum[:]) + } + } + return nil + }) + } w.clientsMutex.Unlock() } @@ -869,14 +878,13 @@ func (w *Watcher) loadFileClients(cfg *config.Config) int { authFileCount := 0 successfulAuthCount := 0 - authDir := cfg.AuthDir - if strings.HasPrefix(authDir, "~") { - home, err := os.UserHomeDir() - if err != nil { - log.Errorf("failed to get home directory: %v", err) - return 0 - } - authDir = filepath.Join(home, authDir[1:]) + authDir, errResolveAuthDir := util.ResolveAuthDir(cfg.AuthDir) + if errResolveAuthDir != nil { + log.Errorf("failed to resolve auth directory: %v", errResolveAuthDir) + return 0 + } + if authDir == "" { + return 0 } errWalk := filepath.Walk(authDir, func(path string, info fs.FileInfo, err error) error {