package store import ( "crypto/md5" "encoding/json" "errors" "fmt" fs "io/ioutil" "net/url" "os" "path/filepath" "strings" "github.com/jsmartx/giter/util" ) const ROOT = "~/.giter/" type User struct { Name string `json:"name"` Email string `json:"email"` Scheme string `json:"scheme"` Host string `json:"host"` Port string `json:"port"` } type Options struct { KeyPath string Password string } func (u *User) Hash() string { host := util.JoinHostPort(u.Host, u.Port) url := fmt.Sprintf("%s://%s@%s", u.Scheme, u.Name, host) return fmt.Sprintf("%x", md5.Sum([]byte(url))) } func (u *User) URL(pwd string) string { fullURL := &url.URL{ Scheme: u.Scheme, User: url.UserPassword(u.Name, pwd), Host: util.JoinHostPort(u.Host, u.Port), } return fullURL.String() } func (u *User) KeyPath() (string, error) { return util.JoinPath(ROOT, "keys", u.Hash()) } func (u *User) FullHost() string { host := util.JoinHostPort(u.Host, u.Port) return fmt.Sprintf("%s://%s", u.Scheme, host) } func (u *User) IsSSH() bool { if u.Scheme == "http" || u.Scheme == "https" { return false } return true } func (u *User) String() string { return fmt.Sprintf("%s - %s", u.Name, u.FullHost()) } type Config struct { Users []*User `json:"users"` } type Store struct { c *Config } func loadConfig() *Config { cfgPath, err := util.JoinPath(ROOT, "config.json") if err != nil { return nil } f, err := os.Open(cfgPath) if err != nil { return nil } defer f.Close() var cfg Config parser := json.NewDecoder(f) if err := parser.Decode(&cfg); err != nil { panic(err) } return &cfg } func saveConfig(cfg *Config) error { root, err := util.Mkdir(ROOT) if err != nil { return err } b, err := json.MarshalIndent(cfg, "", " ") if err != nil { return err } cfgPath := filepath.Join(root, "config.json") // write marshaled data to the file return fs.WriteFile(cfgPath, b, 0755) } func New() *Store { cfg := loadConfig() if cfg != nil { return &Store{c: cfg} } cfg = &Config{ Users: make([]*User, 0), } if err := saveConfig(cfg); err != nil { panic(err) } return &Store{c: cfg} } func (s *Store) check(user *User) error { for _, u := range s.c.Users { if u.Hash() == user.Hash() { return errors.New("user already exist") } } return nil } func (s *Store) Add(u *User, opts *Options) error { if err := s.check(u); err != nil { return err } keysDir, err := util.Mkdir(ROOT, "keys") if err != nil { return err } if u.IsSSH() { pvtPath := filepath.Join(keysDir, u.Hash()) pubPath := filepath.Join(keysDir, u.Hash()+".pub") if opts.KeyPath == "" { if err := util.Keygen(pubPath, pvtPath, 4096); err != nil { return err } } else { if err := util.Copy(opts.KeyPath, pvtPath); err != nil { return err } if err := util.Copy(opts.KeyPath+".pub", pubPath); err != nil { return err } } } else { pwdPath := filepath.Join(keysDir, u.Hash()+".credential") data := []byte(u.URL(opts.Password)) if err := fs.WriteFile(pwdPath, data, 0755); err != nil { return err } } s.c.Users = append(s.c.Users, u) return saveConfig(s.c) } func (s *Store) Update(hash string, u *User, opts *Options) error { if hash != u.Hash() { if err := s.check(u); err != nil { return err } } keysDir, err := util.Mkdir(ROOT, "keys") if err != nil { return err } if u.IsSSH() { pvtPath := filepath.Join(keysDir, u.Hash()) pubPath := filepath.Join(keysDir, u.Hash()+".pub") if opts.KeyPath == "" { if err := util.Keygen(pubPath, pvtPath, 4096); err != nil { return err } } else if opts.KeyPath != pvtPath { if err := util.Copy(opts.KeyPath, pvtPath); err != nil { return err } if err := util.Copy(opts.KeyPath+".pub", pubPath); err != nil { return err } } } else { pwdPath := filepath.Join(keysDir, u.Hash()+".credential") if opts.Password != "" { data := []byte(u.URL(opts.Password)) if err := fs.WriteFile(pwdPath, data, 0600); err != nil { return err } } else if hash != u.Hash() { oldPath := filepath.Join(keysDir, hash+".credential") if err := util.Copy(oldPath, pwdPath); err != nil { return err } } } for i := 0; i < len(s.c.Users); i++ { if s.c.Users[i].Hash() == hash { s.c.Users[i] = u } } return saveConfig(s.c) } func (s *Store) Delete(u *User) error { p, err := u.KeyPath() if err != nil { return err } if u.IsSSH() { os.Remove(p) os.Remove(p + ".pub") } else { os.Remove(p + ".credential") } users := make([]*User, 0) for _, v := range s.c.Users { if v.Hash() != u.Hash() { users = append(users, v) } } s.c.Users = users return saveConfig(s.c) } func (s *Store) List(filter string, strict bool) []*User { users := make([]*User, 0) for _, u := range s.c.Users { if !strict && strings.Contains(u.Name, filter) { users = append(users, u) } if strict && u.Name == filter { users = append(users, u) } } return users }