summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorAlexander Kiryukhin <a.kiryukhin@mail.ru>2021-03-18 02:06:42 +0300
committerAlexander Kiryukhin <a.kiryukhin@mail.ru>2021-03-18 02:06:42 +0300
commit6f31f35c7b38fbf63d7a0c9322458e0b75828495 (patch)
tree2fcb8cb31bb6604e85cf390dbc01f2e9a8b26ee7 /internal
Initial
Diffstat (limited to 'internal')
-rw-r--r--internal/encryption/encryption.go93
-rw-r--r--internal/renderer/renderer.go45
-rw-r--r--internal/storer/storer.go124
3 files changed, 262 insertions, 0 deletions
diff --git a/internal/encryption/encryption.go b/internal/encryption/encryption.go
new file mode 100644
index 0000000..0f9c748
--- /dev/null
+++ b/internal/encryption/encryption.go
@@ -0,0 +1,93 @@
+package encryption
+
+import (
+ "bytes"
+ "crypto/aes"
+ "crypto/cipher"
+ "crypto/rand"
+ "encoding/base64"
+ "errors"
+ "io"
+ "strings"
+)
+
+func addBase64Padding(value string) string {
+ m := len(value) % 4
+ if m != 0 {
+ value += strings.Repeat("=", 4-m)
+ }
+
+ return value
+}
+
+func removeBase64Padding(value string) string {
+ return strings.Replace(value, "=", "", -1)
+}
+
+func pad(src []byte) []byte {
+ padding := aes.BlockSize - len(src)%aes.BlockSize
+ padtext := bytes.Repeat([]byte{byte(padding)}, padding)
+
+ return append(src, padtext...)
+}
+
+func Unpad(src []byte) ([]byte, error) {
+ length := len(src)
+ unpadding := int(src[length-1])
+
+ if unpadding > length {
+ return nil, errors.New("unpad error. This could happen when incorrect encryption key is used")
+ }
+
+ return src[:(length - unpadding)], nil
+}
+
+func Encrypt(key []byte, text string) (string, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return "", err
+ }
+
+ msg := pad([]byte(text))
+ ciphertext := make([]byte, aes.BlockSize+len(msg))
+ iv := ciphertext[:aes.BlockSize]
+
+ if _, err := io.ReadFull(rand.Reader, iv); err != nil {
+ return "", err
+ }
+
+ cfb := cipher.NewCFBEncrypter(block, iv)
+ cfb.XORKeyStream(ciphertext[aes.BlockSize:], []byte(msg))
+ finalMsg := removeBase64Padding(base64.URLEncoding.EncodeToString(ciphertext))
+
+ return finalMsg, nil
+}
+
+func Decrypt(key []byte, text string) (string, error) {
+ block, err := aes.NewCipher(key)
+ if err != nil {
+ return "", err
+ }
+
+ decodedMsg, err := base64.URLEncoding.DecodeString(addBase64Padding(text))
+ if err != nil {
+ return "", err
+ }
+
+ if (len(decodedMsg) % aes.BlockSize) != 0 {
+ return "", errors.New("blocksize must be multipe of decoded message length")
+ }
+
+ iv := decodedMsg[:aes.BlockSize]
+ msg := decodedMsg[aes.BlockSize:]
+
+ cfb := cipher.NewCFBDecrypter(block, iv)
+ cfb.XORKeyStream(msg, msg)
+
+ unpadMsg, err := Unpad(msg)
+ if err != nil {
+ return "", err
+ }
+
+ return string(unpadMsg), nil
+}
diff --git a/internal/renderer/renderer.go b/internal/renderer/renderer.go
new file mode 100644
index 0000000..bed603c
--- /dev/null
+++ b/internal/renderer/renderer.go
@@ -0,0 +1,45 @@
+package renderer
+
+import (
+ "html/template"
+ "net/http"
+ "path"
+ "path/filepath"
+)
+
+// Renderer represents html template renderer
+type Renderer struct {
+ templates map[string]*template.Template
+}
+
+// New Renderer
+func New(gpath string) (*Renderer, error) {
+ t := &Renderer{
+ templates: make(map[string]*template.Template),
+ }
+
+ includes, err := filepath.Glob(path.Join(gpath, "includes/*.gohtml"))
+ if err != nil {
+ return nil, err
+ }
+
+ pages, err := filepath.Glob(path.Join(gpath, "pages/*.gohtml"))
+ if err != nil {
+ return nil, err
+ }
+
+ for _, p := range pages {
+ tpls := append(includes, p)
+ tname := path.Base(p)
+ t.templates[tname] = template.Must(template.New(tname).ParseFiles(tpls...))
+ }
+
+ return t, nil
+}
+
+func (r *Renderer) Render(tpl string, data Map, rw http.ResponseWriter) error {
+ rw.Header().Set("Content-Type", "text/html")
+ return r.templates[tpl].Execute(rw, data)
+}
+
+type Map map[string]interface{}
diff --git a/internal/storer/storer.go b/internal/storer/storer.go
new file mode 100644
index 0000000..b8b4ffd
--- /dev/null
+++ b/internal/storer/storer.go
@@ -0,0 +1,124 @@
+package storer
+
+import (
+ "bytes"
+ "encoding/gob"
+ "time"
+
+ "github.com/dgraph-io/badger"
+ "github.com/neonxp/sendsafe/internal/encryption"
+ "github.com/rs/xid"
+)
+
+type Store struct {
+ db *badger.DB
+}
+
+func New(dbFile string) (*Store, error) {
+ db, err := badger.Open(badger.DefaultOptions(dbFile))
+ if err != nil {
+ return nil, err
+ }
+
+ return &Store{
+ db: db,
+ }, nil
+}
+
+func (s *Store) Save(text string, pin string, ttl int) (string, error) {
+ var err error
+
+ encrypted := false
+
+ if pin != "" {
+ text, err = encryption.Encrypt([]byte(pin), text)
+ if err != nil {
+ return "", err
+ }
+
+ encrypted = true
+ }
+
+ record := memo{
+ Text: text,
+ Encrypted: encrypted,
+ }
+
+ buf := bytes.NewBuffer([]byte{})
+ if err := gob.NewEncoder(buf).Encode(record); err != nil {
+ return "", err
+ }
+
+ id := xid.New()
+ err = s.db.Update(func(txn *badger.Txn) error {
+ return txn.SetEntry(&badger.Entry{
+ Key: id.Bytes(),
+ Value: buf.Bytes(),
+ ExpiresAt: uint64(time.Now().Add(time.Duration(ttl) * time.Minute).Unix()),
+ })
+ })
+
+ return id.String(), err
+}
+
+func (s *Store) IsEncrypted(id string) (bool, error) {
+ var encrypted bool
+
+ return encrypted, s.db.View(func(txn *badger.Txn) error {
+ value, err := txn.Get([]byte(id))
+ if err != nil {
+ return err
+ }
+ record := new(memo)
+ return value.Value(func(val []byte) error {
+ if err := gob.NewDecoder(bytes.NewBuffer(val)).Decode(record); err != nil {
+ return err
+ }
+ encrypted = record.Encrypted
+ return nil
+ })
+ })
+}
+
+func (s *Store) Get(id string, pin string) (string, error) {
+ var text string
+
+ return text, s.db.Update(func(txn *badger.Txn) error {
+ uid, err := xid.FromString(id)
+ if err != nil {
+ return err
+ }
+ value, err := txn.Get(uid.Bytes())
+ if err != nil {
+ return err
+ }
+ record := new(memo)
+ err = value.Value(func(val []byte) error {
+ if err := gob.NewDecoder(bytes.NewBuffer(val)).Decode(record); err != nil {
+ return err
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+
+ text = record.Text
+ if record.Encrypted {
+ text, err = encryption.Decrypt([]byte(pin), text)
+ if err != nil {
+ return err
+ }
+ }
+ return txn.Delete(uid.Bytes())
+ })
+}
+
+func init() {
+ gob.Register(memo{})
+}
+
+type memo struct {
+ Text string
+ Encrypted bool
+}