fix(client): сохранять client_id в доступный для записи каталог

На Android каталог рядом с бинарём read-only, ID не сохранялся и
ротировался на каждый запуск, ломая allowlist. Пробуем каталог бинаря,
UserConfigDir и TempDir по порядку.
This commit is contained in:
samosvalishe
2026-06-04 12:31:48 +03:00
parent a337bb0a63
commit 3bdc8e815a
+52 -6
View File
@@ -226,9 +226,14 @@ func resolveClientID(cliID string) string {
ClientID string `json:"client_id"`
}
path := filepath.Join(filepath.Dir(os.Args[0]), "client_config.json")
b, err := os.ReadFile(path) //nolint:gosec // фиксированное имя рядом с бинарём, не пользовательский ввод
if err == nil {
paths := clientConfigPaths()
// Чтение: первый файл с непустым client_id.
for _, path := range paths {
b, err := os.ReadFile(path) //nolint:gosec // фиксированное имя из clientConfigPaths, не пользовательский ввод
if err != nil {
continue
}
var lc localCfg
if err := json.Unmarshal(b, &lc); err == nil && lc.ClientID != "" {
return lc.ClientID
@@ -243,10 +248,51 @@ func resolveClientID(cliID string) string {
newID := hex.EncodeToString(idBytes)
lc := localCfg{ClientID: newID}
b, _ = json.MarshalIndent(lc, "", " ")
if err := os.WriteFile(path, b, 0o600); err != nil { //nolint:gosec // path фиксирован рядом с бинарём; 0o600 для auth-токена
log.Printf("warning: failed to save client ID to %s: %v", path, err) //nolint:gosec // path не пользовательский ввод
b, _ := json.MarshalIndent(lc, "", " ")
// Запись: первый доступный для записи путь. На Android каталог рядом с
// бинарём (/data/app/.../lib/arm64) read-only — падаем на UserConfigDir,
// затем TempDir. Иначе ID не сохраняется и ротируется на каждый запуск,
// ломая allowlist (-clients-file) и статистику.
for _, path := range paths {
if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil {
continue
}
if err := os.WriteFile(path, b, 0o600); err == nil { //nolint:gosec // 0o600 для auth-токена
return newID
}
}
log.Printf("warning: failed to persist client ID to any writable path (%v); ID will rotate next launch", paths)
return newID
}
// clientConfigPaths возвращает кандидатов client_config.json в порядке
// предпочтения: рядом с бинарём (desktop, переносимость), затем per-user
// UserConfigDir и TempDir (Android, где каталог бинаря read-only).
func clientConfigPaths() []string {
const name = "client_config.json"
seen := map[string]bool{}
var dirs []string
add := func(d string) {
if d == "" || seen[d] {
return
}
seen[d] = true
dirs = append(dirs, d)
}
if exe, err := os.Executable(); err == nil {
add(filepath.Dir(exe))
}
add(filepath.Dir(os.Args[0]))
if cfgDir, err := os.UserConfigDir(); err == nil {
add(filepath.Join(cfgDir, "free-turn-proxy"))
}
add(os.TempDir())
paths := make([]string, 0, len(dirs))
for _, d := range dirs {
paths = append(paths, filepath.Join(d, name))
}
return paths
}