envoid/internal/datastore/datastore.go
Sagi Dayan 29e31c5b21
All checks were successful
CI / check for spelling errors (pull_request) Successful in 21s
CI / code quality (lint/tests) (pull_request) Successful in 2m6s
CI / make sure build does not fail (pull_request) Successful in 2m22s
CI / notify-fail (pull_request) Has been skipped
CI / check for spelling errors (push) Successful in 21s
CI / code quality (lint/tests) (push) Successful in 2m4s
CI / make sure build does not fail (push) Successful in 2m24s
CI / notify-fail (push) Has been skipped
Added lint to the CI. formatted all code accordingly
Signed-off-by: Sagi Dayan <sagidayan@gmail.com>
2024-12-17 13:10:42 +02:00

252 lines
4.7 KiB
Go

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
}