diff --git a/cmd/pasty/main.go b/cmd/pasty/main.go index 06ce92c..7a3bb74 100644 --- a/cmd/pasty/main.go +++ b/cmd/pasty/main.go @@ -1,5 +1,8 @@ package main +import "github.com/Lukaesebrot/pasty/internal/env" + func main() { - // TODO: Implement startup logic + // Load the optional .env file + env.Load() } diff --git a/go.mod b/go.mod index 91976cc..399464a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,8 @@ module github.com/Lukaesebrot/pasty go 1.15 require ( + github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b github.com/bwmarrin/snowflake v0.3.0 - github.com/fasthttp/router v1.2.4 // indirect - github.com/valyala/fasthttp v1.16.0 // indirect + github.com/joho/godotenv v1.3.0 + golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect ) diff --git a/go.sum b/go.sum index cd30e78..b1f5123 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,15 @@ -github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= -github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b h1:rcCpjI1OMGtBY8nnBvExeM1pXNoaM35zqmXBGpgJR2o= +github.com/alexedwards/argon2id v0.0.0-20200802152012-2464efd3196b/go.mod h1:GFtu6vaWaRJV5EvSFaVqgq/3Iq95xyYElBV/aupGzUo= github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= -github.com/fasthttp/router v1.2.4 h1:RBWbCv4vVf+boczSZh/rX9PDSdR9F8I9zSnVJx5YJfU= -github.com/fasthttp/router v1.2.4/go.mod h1:Au2V1CaqqAdzQQcPKrbkFAsImd1aHpadrce21AIPnvE= -github.com/klauspost/compress v1.10.7 h1:7rix8v8GpI3ZBb0nSozFRgbtXKv+hOe+qfEpZqybrAg= -github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c h1:KKqhycXW1WVNkX7r4ekTV2gFkbhdyihlWD8c0/FiWmk= -github.com/savsgio/gotils v0.0.0-20200616100644-13ff1fd2c28c/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.15.1/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/fasthttp v1.16.0 h1:9zAqOYLl8Tuy3E5R6ckzGDJ1g8+pw15oQp2iL9Jl6gQ= -github.com/valyala/fasthttp v1.16.0/go.mod h1:YOKImeEosDdBPnxc0gy7INqi3m1zK6A+xl6TwOBhHCA= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 h1:OjiUf46hAmXblsZdnoSXsEUSKU8r1UEzcL5RVZ4gO9Y= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/env/env.go b/internal/env/env.go new file mode 100644 index 0000000..0a473b1 --- /dev/null +++ b/internal/env/env.go @@ -0,0 +1,21 @@ +package env + +import ( + "github.com/Lukaesebrot/pasty/internal/static" + "github.com/joho/godotenv" + "os" +) + +// Load loads an optional .env file +func Load() { + godotenv.Load() +} + +// Get returns the content of the environment variable with the given key or the given fallback +func Get(key, fallback string) string { + found := os.Getenv(static.EnvironmentVariablePrefix + key) + if found == "" { + return fallback + } + return found +} diff --git a/internal/pastes/deletion_token.go b/internal/pastes/deletion_token.go new file mode 100644 index 0000000..c9fb2a6 --- /dev/null +++ b/internal/pastes/deletion_token.go @@ -0,0 +1,27 @@ +package pastes + +import ( + "github.com/Lukaesebrot/pasty/internal/env" + "math/rand" + "strconv" +) + +// deletionTokenContents represents the characters a deletion token may contain +const deletionTokenContents = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#+-.," + +// generateDeletionToken generates a new deletion token +func generateDeletionToken() (string, error) { + // Read the deletion token length + rawLength := env.Get("DELETION_TOKEN_LENGTH", "12") + length, err := strconv.Atoi(rawLength) + if err != nil { + return "", err + } + + // Generate the deletion token + bytes := make([]byte, length) + for i := range bytes { + bytes[i] = deletionTokenContents[rand.Int63()%int64(len(deletionTokenContents))] + } + return string(bytes), nil +} diff --git a/internal/pastes/paste.go b/internal/pastes/paste.go index 1527add..85570dd 100644 --- a/internal/pastes/paste.go +++ b/internal/pastes/paste.go @@ -1,6 +1,16 @@ package pastes -import "github.com/bwmarrin/snowflake" +import ( + "github.com/alexedwards/argon2id" + "github.com/bwmarrin/snowflake" +) + +func init() { + snowflakeNode, _ = snowflake.NewNode(1) +} + +// snowflakeNode holds the current snowflake node +var snowflakeNode *snowflake.Node // Paste represents a saved paste type Paste struct { @@ -9,3 +19,39 @@ type Paste struct { SuggestedSyntaxType string `json:"suggestedSyntaxType" bson:"suggestedSyntaxType"` DeletionToken string `json:"deletionToken" bson:"deletionToken"` } + +// Create creates a new paste object using the given content +func Create(content string) (*Paste, error) { + // TODO: Generate the suggested syntax type + suggestedSyntaxType := "" + + // Generate the deletion token + deletionToken, err := generateDeletionToken() + if err != nil { + return nil, err + } + + // Return the paste object + return &Paste{ + ID: snowflakeNode.Generate(), + Content: content, + SuggestedSyntaxType: suggestedSyntaxType, + DeletionToken: deletionToken, + }, nil +} + +// HashDeletionToken hashes the current deletion token of a paste +func (paste *Paste) HashDeletionToken() error { + hash, err := argon2id.CreateHash(paste.DeletionToken, argon2id.DefaultParams) + if err != nil { + return err + } + paste.DeletionToken = hash + return nil +} + +// CheckDeletionToken checks whether or not the given deletion token is correct +func (paste *Paste) CheckDeletionToken(deletionToken string) bool { + match, err := argon2id.ComparePasswordAndHash(deletionToken, paste.DeletionToken) + return err != nil && match +} diff --git a/internal/static/static.go b/internal/static/static.go new file mode 100644 index 0000000..734e85e --- /dev/null +++ b/internal/static/static.go @@ -0,0 +1,7 @@ +package static + +// These variables represent the values that may be changed using ldflags +var ( + Version = "dev" + EnvironmentVariablePrefix = "PASTY_" +) diff --git a/internal/storage/storage.go b/internal/storage/storage.go new file mode 100644 index 0000000..4b6d448 --- /dev/null +++ b/internal/storage/storage.go @@ -0,0 +1,14 @@ +package storage + +import ( + "github.com/Lukaesebrot/pasty/internal/pastes" + "github.com/bwmarrin/snowflake" +) + +// Storage represents a storage type +type Storage interface { + initialize() error + get(id snowflake.ID) (*pastes.Paste, error) + save(paste *pastes.Paste) error + delete(id snowflake.ID) error +}