diff options
| author | Alexander Kavon <me+git@alexkavon.com> | 2024-01-22 14:56:36 -0500 |
|---|---|---|
| committer | Alexander Kavon <me+git@alexkavon.com> | 2024-01-22 14:56:36 -0500 |
| commit | a1df83e8b5737a198a3fba4de23ca2c80828f623 (patch) | |
| tree | 95ae9bc9a87b945e149373d9753c8e158f003a6f | |
| parent | 4f15e271f541ecd525268efa40992e0f5c057e12 (diff) | |
added validation for user fields on sqlboiler.BeforeInsertHook, added hashing of secret before insert
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 4 | ||||
| -rw-r--r-- | src/user/hooks.go | 72 | ||||
| -rw-r--r-- | src/user/routes.go | 20 | ||||
| -rw-r--r-- | src/user/secret.go | 17 |
5 files changed, 85 insertions, 30 deletions
@@ -19,8 +19,10 @@ require ( ) require ( + github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 // indirect github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/gofrs/uuid v4.2.0+incompatible // indirect @@ -81,6 +81,8 @@ github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmV github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496 h1:zV3ejI06GQ59hwDQAvmK1qxOQGB3WuVTRoY0okPTAv0= +github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -148,6 +150,8 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es= +github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= diff --git a/src/user/hooks.go b/src/user/hooks.go new file mode 100644 index 0000000..0ab5ab0 --- /dev/null +++ b/src/user/hooks.go @@ -0,0 +1,72 @@ +package user + +import ( + "context" + "crypto/rand" + "encoding/base64" + "fmt" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/go-ozzo/ozzo-validation/v4/is" + "github.com/volatiletech/sqlboiler/v4/boil" + "gitlab.com/alexkavon/newsstand/src/models" + "golang.org/x/crypto/argon2" +) + +func init() { + models.AddUserHook(boil.BeforeInsertHook, validate) + // should always be last + models.AddUserHook(boil.BeforeInsertHook, hashSecret) +} + +func validate(ctx context.Context, exec boil.ContextExecutor, u *models.User) error { + // validate user + err := validation.ValidateStruct(u, + validation.Field(&u.Username, validation.Required, validation.Length(3, 50)), + validation.Field(&u.Secret, validation.Required, validation.Length(8, 128)), + validation.Field(&u.Email, validation.Required, is.Email), + ) + if err != nil { + return err + } + + return nil +} + +func hashSecret(ctx context.Context, exec boil.ContextExecutor, u *models.User) error { + hashconf := &struct { + memory uint32 + iterations uint32 + parallelism uint8 + keyLength uint32 + saltLength uint32 + }{64 * 1024, 3, 2, 12, 16} + salt := make([]byte, hashconf.saltLength) + _, err := rand.Read(salt) + if err != nil { + return err + } + + hash := argon2.IDKey( + []byte(u.Secret), + salt, + hashconf.iterations, + hashconf.memory, + hashconf.parallelism, + hashconf.keyLength, + ) + b64Salt := base64.RawStdEncoding.EncodeToString(salt) + b64Hash := base64.RawStdEncoding.EncodeToString(hash) + encodedHash := fmt.Sprintf( + "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", + argon2.Version, + hashconf.memory, + hashconf.iterations, + hashconf.parallelism, + b64Salt, + b64Hash, + ) + + u.Secret = encodedHash + return nil +} diff --git a/src/user/routes.go b/src/user/routes.go index b163fb7..7cbd3fb 100644 --- a/src/user/routes.go +++ b/src/user/routes.go @@ -4,7 +4,6 @@ import ( "log" "net/http" - "github.com/go-playground/validator/v10" "github.com/volatiletech/sqlboiler/v4/boil" "gitlab.com/alexkavon/newsstand/src/models" "gitlab.com/alexkavon/newsstand/src/server" @@ -70,23 +69,10 @@ func Store(s *server.Server) http.HandlerFunc { user.Username = r.PostFormValue("username") user.Secret = r.PostFormValue("secret") user.Email = r.PostFormValue("email") - // Validate User Input - v := validator.New() - err := v.Struct(user) - if err != nil { - log.Fatal("Validator failed", err.(validator.ValidationErrors)) - } - - // Hash secret - secret := &Secret{Raw: user.Secret} - err = secret.HashSecret() - if err != nil { - log.Fatal("Hash failure", err) - } - user.Secret = secret.Hash() - // Store user - err = user.Insert(r.Context(), s.Db.ToSqlDb(), boil.Infer()) + // Store user, this package (user) will init + // a validation hook to check values and hash secret + err := user.Insert(r.Context(), s.Db.ToSqlDb(), boil.Infer()) if err != nil { log.Fatal("Insert Error", err) } diff --git a/src/user/secret.go b/src/user/secret.go index 55b8bc6..b6382fe 100644 --- a/src/user/secret.go +++ b/src/user/secret.go @@ -8,12 +8,7 @@ import ( "golang.org/x/crypto/argon2" ) -type Secret struct { - Raw string - hash string -} - -func (s Secret) HashSecret() error { +func HashSecret(secret string) (string, error) { hashconf := &struct { memory uint32 iterations uint32 @@ -24,11 +19,11 @@ func (s Secret) HashSecret() error { salt := make([]byte, hashconf.saltLength) _, err := rand.Read(salt) if err != nil { - return err + return "", err } hash := argon2.IDKey( - []byte(s.Raw), + []byte(secret), salt, hashconf.iterations, hashconf.memory, @@ -46,10 +41,6 @@ func (s Secret) HashSecret() error { b64Salt, b64Hash, ) - s.hash = encodedHash - return err -} -func (s *Secret) Hash() string { - return s.hash + return encodedHash, nil } |
