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$") }