package datastore import ( "crypto/aes" "crypto/cipher" "crypto/rand" "errors" "fmt" "os" "sort" "git.dayanhub.com/sagi/envoid/internal/common" "git.dayanhub.com/sagi/envoid/internal/config" intErrors "git.dayanhub.com/sagi/envoid/internal/errors" "git.dayanhub.com/sagi/envoid/internal/types" "git.dayanhub.com/sagi/envoid/internal/variables" "golang.org/x/crypto/scrypt" "golang.org/x/sync/errgroup" ) type Datastore struct { db *db } func NewDataStore() (*Datastore, error) { db, err := newDB() if err != nil { return nil, err } return &Datastore{ db: db, }, nil } func (d *Datastore) CreateEnv(name string) error { tableName := envNameToTableName(name) return d.db.createTableIfNotExists(tableName) } func (d *Datastore) DoesFileExists() bool { pwd := config.GetConfig().PWD filePath := fmt.Sprintf("%s/%s", pwd, variables.DBFileName) if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { return false } return true } func (d *Datastore) ListEnvironments() ([]string, error) { return d.db.listTables() } func (d *Datastore) CreateEnvOffExsisting(newEnv string, baseEnv string) error { tableNameName := envNameToTableName(newEnv) tableNameBase := envNameToTableName(baseEnv) err := d.CreateEnv(newEnv) if err != nil { return err } err = d.db.copyContentFromTo(tableNameBase, tableNameName) if err != nil { return err } return nil } func (d *Datastore) Close() error { return d.db.close() } func (d *Datastore) SetValue(key string, value string, encrypted *bool, envs []*types.Environment) error { if encrypted == nil { encrypted = common.BoolP(false) } if *encrypted { v, err := enc(value) if err != nil { return err } value = *v } for _, env := range envs { tableName := envNameToTableName(env.Name) if err := d.db.setVar(tableName, key, value, *encrypted); err != nil { return err } } return nil } func (d *Datastore) GetAll(envName string) ([]*types.EnvVar, error) { tableName := envNameToTableName(envName) vars, err := d.db.getAll(tableName) if err != nil { return vars, err } g := new(errgroup.Group) for _, v := range vars { g.Go(func() error { if v.Encrypted { if v.Value, err = dec(v.Value); err != nil { return &intErrors.InvalidPasswordError{} } } return nil }) } if err := g.Wait(); err != nil { return vars, err } sort.SliceStable(vars, func(i, j int) bool { return vars[i].Key < vars[j].Key }) return vars, nil } func (d *Datastore) RemoveVar(key string, envs []*types.Environment) { for _, env := range envs { tableName := envNameToTableName(env.Name) d.db.rmVar(tableName, key) } } func (d *Datastore) GetVar(envName string, key string) (*types.EnvVar, error) { tableName := envNameToTableName(envName) v, err := d.db.getVar(tableName, key) if err != nil { return v, err } if v.Encrypted { if v.Value, err = dec(v.Value); err != nil { return v, &intErrors.InvalidPasswordError{} } } return v, err } func (d *Datastore) RemoveEnv(envName string) error { tableName := envNameToTableName(envName) return d.db.deleteTable(tableName) } func enc(s string) (*string, error) { conf := config.GetConfig() proj, _ := conf.GetProject(conf.PWD) key, salt, err := deriveKey([]byte(proj.Password), nil) data := []byte(s) if err != nil { return nil, err } blockCipher, err := aes.NewCipher(key) if err != nil { return nil, err } gcm, err := cipher.NewGCM(blockCipher) if err != nil { return nil, err } nonce := make([]byte, gcm.NonceSize()) if _, err = rand.Read(nonce); err != nil { return nil, err } ciphertext := gcm.Seal(nonce, nonce, data, nil) ciphertext = append(ciphertext, salt...) str := string(ciphertext) return &str, nil } func dec(s string) (string, error) { data := []byte(s) salt, data := data[len(data)-32:], data[:len(data)-32] conf := config.GetConfig() proj, _ := conf.GetProject(conf.PWD) key, _, err := deriveKey([]byte(proj.Password), salt) if err != nil { return "", err } blockCipher, err := aes.NewCipher(key) if err != nil { return "", err } gcm, err := cipher.NewGCM(blockCipher) if err != nil { return "", err } nonce, ciphertext := data[:gcm.NonceSize()], data[gcm.NonceSize():] plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) if err != nil { return "", err } str := string(plaintext) return str, nil } func deriveKey(password, salt []byte) ([]byte, []byte, error) { saltSize := 32 if salt == nil { salt = make([]byte, saltSize) if _, err := rand.Read(salt); err != nil { return nil, nil, err } } N := 1048576 r := 8 p := 1 key, err := scrypt.Key(password, salt, N, r, p, saltSize) if err != nil { return nil, nil, err } return key, salt, nil }