claude-web-chat/config.go
djuka 3bb8d289af
All checks were successful
Tests / unit-tests (push) Successful in 45s
Deadlock u promeni šifre i TERM za PTY
- Ispravljen deadlock: SetPassword zaključa mu pa pozove save() koji
  opet zaključa mu. Razdvojeno u saveLocked() (bez lock-a) i save()
- Vraćeno normalno ponašanje kursora (Claude Code sam upravlja)
- PTY okruženje: TERM=xterm-256color, COLORTERM=truecolor

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 06:14:28 +00:00

117 lines
2.8 KiB
Go

package main
import (
"encoding/json"
"fmt"
"os"
"strings"
"sync"
"golang.org/x/crypto/bcrypt"
)
type APIConfig struct {
Key string `json:"key"`
Model string `json:"model"`
}
type Config struct {
Port int `json:"port"`
Mode string `json:"mode"`
ProjectsPath string `json:"projects_path"`
Username string `json:"username"`
Password string `json:"password"`
SessionSecret string `json:"session_secret"`
API APIConfig `json:"api"`
path string // putanja do config fajla (ne serijalizuje se)
mu sync.Mutex // zaštita za upis
}
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("reading config: %w", err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parsing config: %w", err)
}
if cfg.Port == 0 {
cfg.Port = 9100
}
if cfg.Mode == "" {
cfg.Mode = "cli"
}
if cfg.ProjectsPath == "" {
cfg.ProjectsPath = "/root/projects"
}
if cfg.Username == "" {
return nil, fmt.Errorf("username is required")
}
if cfg.Password == "" {
return nil, fmt.Errorf("password is required")
}
if cfg.SessionSecret == "" {
return nil, fmt.Errorf("session_secret is required")
}
cfg.path = path
// Ako šifra nije hešovana, heširaj je i sačuvaj
if !isHashed(cfg.Password) {
hashed, err := bcrypt.GenerateFromPassword([]byte(cfg.Password), bcrypt.DefaultCost)
if err != nil {
return nil, fmt.Errorf("hashing password: %w", err)
}
cfg.Password = string(hashed)
if err := cfg.save(); err != nil {
return nil, fmt.Errorf("saving hashed password: %w", err)
}
}
return &cfg, nil
}
// CheckPassword proverava da li se data šifra poklapa sa hešom.
func (c *Config) CheckPassword(password string) bool {
return bcrypt.CompareHashAndPassword([]byte(c.Password), []byte(password)) == nil
}
// SetPassword postavlja novu šifru (hešuje i čuva u config).
func (c *Config) SetPassword(newPassword string) error {
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("hashing password: %w", err)
}
c.mu.Lock()
c.Password = string(hashed)
err = c.saveLocked()
c.mu.Unlock()
return err
}
// save upisuje trenutni config nazad u fajl (pozivalac mora držati c.mu).
func (c *Config) saveLocked() error {
data, err := json.MarshalIndent(c, "", " ")
if err != nil {
return fmt.Errorf("marshaling config: %w", err)
}
data = append(data, '\n')
return os.WriteFile(c.path, data, 0600)
}
// save upisuje trenutni config nazad u fajl.
func (c *Config) save() error {
c.mu.Lock()
defer c.mu.Unlock()
return c.saveLocked()
}
// isHashed proverava da li je string bcrypt heš.
func isHashed(s string) bool {
return strings.HasPrefix(s, "$2a$") || strings.HasPrefix(s, "$2b$")
}