From 2af9f7e56ed34d2c036d473d10280e21d615b15b Mon Sep 17 00:00:00 2001 From: Alexander Rudenko Date: Fri, 15 Nov 2024 23:05:04 +0300 Subject: [PATCH 1/3] argon password hash --- .../db/migrations/00001_create_table_users.sql | 2 +- internal/models/errors.go | 4 ++++ internal/repository/postgres/users/add_user.go | 9 ++++++++- .../postgres/users/argon2_hash_pass.go | 18 ++++++++++++++++++ .../postgres/users/check_credentials.go | 13 ++++++++++--- 5 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 internal/repository/postgres/users/argon2_hash_pass.go diff --git a/internal/db/migrations/00001_create_table_users.sql b/internal/db/migrations/00001_create_table_users.sql index cf5b42d..8afe994 100644 --- a/internal/db/migrations/00001_create_table_users.sql +++ b/internal/db/migrations/00001_create_table_users.sql @@ -4,7 +4,7 @@ CREATE TABLE "USER" ( id SERIAL PRIMARY KEY, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, - password_hash TEXT NOT NULL, + password_hash BYTEA NOT NULL, URL_to_avatar TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, modified_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP diff --git a/internal/models/errors.go b/internal/models/errors.go index 8032818..53eba9c 100644 --- a/internal/models/errors.go +++ b/internal/models/errors.go @@ -37,6 +37,10 @@ var ( Field: "username", Message: "user already exists", } + ErrInvalidPassword = &AuthError{ + Field: "password", + Message: "wrong password or login", + } ) func (e AuthError) Error() string { diff --git a/internal/repository/postgres/users/add_user.go b/internal/repository/postgres/users/add_user.go index 7d0c6b7..ede697c 100644 --- a/internal/repository/postgres/users/add_user.go +++ b/internal/repository/postgres/users/add_user.go @@ -2,6 +2,7 @@ package userRepository import ( "context" + "crypto/rand" "fmt" "kudago/internal/models" @@ -13,11 +14,17 @@ const addUserQuery = ` RETURNING id, created_at` func (d *UserDB) AddUser(ctx context.Context, user models.User) (models.User, error) { + salt := make([]byte, 8) + if _, err := rand.Read(salt); err != nil { + return models.User{}, fmt.Errorf("failed to generate salt: %w", err) + } + passwordHash := hashPass(salt, user.Password) + var userInfo UserInfo err := d.pool.QueryRow(ctx, addUserQuery, user.Username, user.Email, - user.Password, + passwordHash, user.ImageURL, ).Scan( &userInfo.ID, diff --git a/internal/repository/postgres/users/argon2_hash_pass.go b/internal/repository/postgres/users/argon2_hash_pass.go new file mode 100644 index 0000000..3c2fa54 --- /dev/null +++ b/internal/repository/postgres/users/argon2_hash_pass.go @@ -0,0 +1,18 @@ +package userRepository + +import ( + "bytes" + "golang.org/x/crypto/argon2" +) + +func hashPass(salt []byte, plainPassword string) []byte { + hashedPass := argon2.IDKey([]byte(plainPassword), []byte(salt), 1, 64*1024, 4, 32) + return append(salt, hashedPass...) +} + +func checkPass(passHash []byte, plainPassword string) bool { + salt := make([]byte, 8) + copy(salt, passHash[:8]) + userPassHash := hashPass(salt, plainPassword) + return bytes.Equal(userPassHash, passHash) +} diff --git a/internal/repository/postgres/users/check_credentials.go b/internal/repository/postgres/users/check_credentials.go index 11bdc27..ca3b9cf 100644 --- a/internal/repository/postgres/users/check_credentials.go +++ b/internal/repository/postgres/users/check_credentials.go @@ -11,18 +11,21 @@ import ( ) const checkCredentialsQuery = ` - SELECT id, username, email, created_at, url_to_avatar + SELECT id, username, email, created_at, url_to_avatar, password_hash FROM "USER" - WHERE username = $1 AND password_hash = $2` + WHERE username = $1` func (d UserDB) CheckCredentials(ctx context.Context, username, password string) (models.User, error) { var userInfo UserInfo - err := d.pool.QueryRow(ctx, checkCredentialsQuery, username, password).Scan( + var storedPassHash []byte + + err := d.pool.QueryRow(ctx, checkCredentialsQuery, username).Scan( &userInfo.ID, &userInfo.Username, &userInfo.Email, &userInfo.CreatedAt, &userInfo.ImageURL, + &storedPassHash, ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { @@ -30,6 +33,10 @@ func (d UserDB) CheckCredentials(ctx context.Context, username, password string) } return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, err) } + if !checkPass(storedPassHash, password) { + return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, models.ErrInvalidPassword) + } + user := toDomainUser(userInfo) return user, nil } From a01974f2d6dc0be79bc40231a098d8e16c0f15c3 Mon Sep 17 00:00:00 2001 From: Alexander Rudenko Date: Fri, 15 Nov 2024 23:20:14 +0300 Subject: [PATCH 2/3] XSS Sanitizer --- go.mod | 3 +++ go.sum | 6 ++++++ internal/http/auth/register.go | 7 +++++++ internal/http/auth/update_user.go | 7 +++++++ internal/http/events/add_event.go | 7 +++++++ internal/http/events/update_event.go | 7 +++++++ internal/http/utils/utils.go | 23 +++++++++++++++++++++++ 7 files changed, 60 insertions(+) diff --git a/go.mod b/go.mod index 9f4c255..b9cc687 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cockroachdb/apd v1.1.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -24,6 +25,7 @@ require ( github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/gofrs/uuid v3.2.0+incompatible // indirect + github.com/gorilla/css v1.0.1 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect github.com/jackc/pgio v1.0.0 // indirect @@ -34,6 +36,7 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mfridman/interpolate v0.0.2 // indirect + github.com/microcosm-cc/bluemonday v1.0.27 // indirect github.com/pashagolub/pgxmock/v4 v4.3.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/swaggo/files v1.0.1 // indirect diff --git a/go.sum b/go.sum index bfda9a5..98cf539 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= @@ -33,6 +35,8 @@ github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= @@ -75,6 +79,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= +github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pashagolub/pgxmock/v4 v4.3.0 h1:DqT7fk0OCK6H0GvqtcMsLpv8cIwWqdxWgfZNLeHCb/s= diff --git a/internal/http/auth/register.go b/internal/http/auth/register.go index c5244a3..0dfd9d9 100644 --- a/internal/http/auth/register.go +++ b/internal/http/auth/register.go @@ -47,6 +47,13 @@ func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { return } + err = utils.SanitizeStruct(&req) + if err != nil { + h.logger.Error(r.Context(), "sanitize error", err) + utils.WriteResponse(w, http.StatusInternalServerError, httpErrors.ErrInternal) + return + } + _, err = govalidator.ValidateStruct(&req) if err != nil { h.logger.Error(r.Context(), "register", err) diff --git a/internal/http/auth/update_user.go b/internal/http/auth/update_user.go index 345343c..63f367e 100644 --- a/internal/http/auth/update_user.go +++ b/internal/http/auth/update_user.go @@ -46,6 +46,13 @@ func (h *AuthHandler) UpdateUser(w http.ResponseWriter, r *http.Request) { return } + err = utils.SanitizeStruct(&req) + if err != nil { + h.logger.Error(r.Context(), "sanitize error", err) + utils.WriteResponse(w, http.StatusInternalServerError, httpErrors.ErrInternal) + return + } + _, err = govalidator.ValidateStruct(&req) if err != nil { utils.ProcessValidationErrors(w, err) diff --git a/internal/http/events/add_event.go b/internal/http/events/add_event.go index e6decf8..25055e4 100644 --- a/internal/http/events/add_event.go +++ b/internal/http/events/add_event.go @@ -41,6 +41,13 @@ func (h EventHandler) AddEvent(w http.ResponseWriter, r *http.Request) { return } + err = utils.SanitizeStruct(&req) + if err != nil { + h.logger.Error(r.Context(), "sanitize error", err) + utils.WriteResponse(w, http.StatusInternalServerError, httpErrors.ErrInternal) + return + } + reqErr = checkNewEventRequest(req) if reqErr != nil { utils.WriteResponse(w, http.StatusBadRequest, reqErr) diff --git a/internal/http/events/update_event.go b/internal/http/events/update_event.go index 0ff9dc7..0698d4f 100644 --- a/internal/http/events/update_event.go +++ b/internal/http/events/update_event.go @@ -59,6 +59,13 @@ func (h EventHandler) UpdateEvent(w http.ResponseWriter, r *http.Request) { return } + err = utils.SanitizeStruct(&req) + if err != nil { + h.logger.Error(r.Context(), "sanitize error", err) + utils.WriteResponse(w, http.StatusInternalServerError, httpErrors.ErrInternal) + return + } + reqErr = checkNewEventRequest(req) if reqErr != nil { utils.WriteResponse(w, http.StatusBadRequest, reqErr) diff --git a/internal/http/utils/utils.go b/internal/http/utils/utils.go index 9166fb8..ebeccd0 100644 --- a/internal/http/utils/utils.go +++ b/internal/http/utils/utils.go @@ -7,9 +7,11 @@ import ( "encoding/json" "errors" "fmt" + "github.com/microcosm-cc/bluemonday" "mime/multipart" "net/http" "path/filepath" + "reflect" "strconv" "strings" "time" @@ -175,3 +177,24 @@ func HandleImageUpload(r *http.Request) (models.MediaFile, error) { File: file, }, nil } + +// SanitizeStruct чистит всех строковых полей структуры от XSS +func SanitizeStruct(input interface{}) error { + v := reflect.ValueOf(input) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return fmt.Errorf("input is not a struct") + } + + p := bluemonday.UGCPolicy() + for i := 0; i < v.NumField(); i++ { + field := v.Field(i) + if field.Kind() == reflect.String && field.CanSet() { + original := field.String() + field.SetString(p.Sanitize(original)) + } + } + return nil +} From 97c9d7fdea56162e289b0089ba7bc9d8a9dd519a Mon Sep 17 00:00:00 2001 From: Alexander Rudenko Date: Wed, 20 Nov 2024 22:35:33 +0300 Subject: [PATCH 3/3] hash and Sanitize small fix --- go.mod | 8 ++++---- go.sum | 9 ++++----- .../00004_fill_events_table_and_user.sql | 2 +- internal/http/utils/utils.go | 5 +++-- internal/repository/postgres/users/add_user.go | 4 +++- .../postgres/users/argon2_hash_pass.go | 18 ------------------ .../postgres/users/check_credentials.go | 11 ++++------- internal/service/auth/auth.go | 16 ++++++++++++++-- 8 files changed, 33 insertions(+), 40 deletions(-) delete mode 100644 internal/repository/postgres/users/argon2_hash_pass.go diff --git a/go.mod b/go.mod index b9cc687..d6acbc7 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,15 @@ go 1.23.1 require ( github.com/jackc/pgx/v5 v5.7.1 github.com/joho/godotenv v1.5.1 + github.com/microcosm-cc/bluemonday v1.0.27 + github.com/pashagolub/pgxmock/v4 v4.3.0 github.com/pkg/errors v0.9.1 github.com/pressly/goose/v3 v3.22.1 github.com/stretchr/testify v1.9.0 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.3 go.uber.org/zap v1.27.0 + golang.org/x/crypto v0.28.0 golang.org/x/net v0.30.0 ) @@ -36,20 +39,17 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mfridman/interpolate v0.0.2 // indirect - github.com/microcosm-cc/bluemonday v1.0.27 // indirect - github.com/pashagolub/pgxmock/v4 v4.3.0 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect github.com/swaggo/files v1.0.1 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.26.0 // indirect golang.org/x/text v0.19.0 // indirect golang.org/x/tools v0.26.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( - github.com/DATA-DOG/go-sqlmock v1.5.2 github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/golang/mock v1.6.0 diff --git a/go.sum b/go.sum index 98cf539..1cb03bf 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= -github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -50,6 +48,8 @@ github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= @@ -66,7 +66,6 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -96,8 +95,8 @@ github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= -github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= diff --git a/internal/db/migrations/00004_fill_events_table_and_user.sql b/internal/db/migrations/00004_fill_events_table_and_user.sql index 3206f10..f64feec 100644 --- a/internal/db/migrations/00004_fill_events_table_and_user.sql +++ b/internal/db/migrations/00004_fill_events_table_and_user.sql @@ -1,7 +1,7 @@ -- +goose Up -- +goose StatementBegin INSERT INTO "USER" (id, username, email, password_hash, URL_to_avatar) -VALUES (0, 'default_user', '123@mail.ru', '123', 'static/images/profile1.jpeg'); +VALUES (0, 'default_user', '123@mail.ru', E'\\xACCD573F35180773D3EE7A2D8A456E95A422914545EFD733A7486F687522A37AEC7D56AFBEFE292D', 'static/images/profile1.jpeg'); INSERT INTO EVENT (title, description, event_start, event_finish, location, capacity, user_id, category_id) VALUES diff --git a/internal/http/utils/utils.go b/internal/http/utils/utils.go index ebeccd0..7a3f297 100644 --- a/internal/http/utils/utils.go +++ b/internal/http/utils/utils.go @@ -33,6 +33,8 @@ type ValidationErrResponse struct { Errors []models.AuthError `json:"errors"` } +var sanitizePolicy = bluemonday.UGCPolicy() + type sessionKeyType struct{} var sessionKey sessionKeyType @@ -188,12 +190,11 @@ func SanitizeStruct(input interface{}) error { return fmt.Errorf("input is not a struct") } - p := bluemonday.UGCPolicy() for i := 0; i < v.NumField(); i++ { field := v.Field(i) if field.Kind() == reflect.String && field.CanSet() { original := field.String() - field.SetString(p.Sanitize(original)) + field.SetString(sanitizePolicy.Sanitize(original)) } } return nil diff --git a/internal/repository/postgres/users/add_user.go b/internal/repository/postgres/users/add_user.go index ede697c..e713945 100644 --- a/internal/repository/postgres/users/add_user.go +++ b/internal/repository/postgres/users/add_user.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "fmt" + "golang.org/x/crypto/argon2" "kudago/internal/models" ) @@ -18,7 +19,8 @@ func (d *UserDB) AddUser(ctx context.Context, user models.User) (models.User, er if _, err := rand.Read(salt); err != nil { return models.User{}, fmt.Errorf("failed to generate salt: %w", err) } - passwordHash := hashPass(salt, user.Password) + hashedPass := argon2.IDKey([]byte(user.Password), []byte(salt), 1, 64*1024, 4, 32) + passwordHash := append(salt, hashedPass...) var userInfo UserInfo err := d.pool.QueryRow(ctx, addUserQuery, diff --git a/internal/repository/postgres/users/argon2_hash_pass.go b/internal/repository/postgres/users/argon2_hash_pass.go deleted file mode 100644 index 3c2fa54..0000000 --- a/internal/repository/postgres/users/argon2_hash_pass.go +++ /dev/null @@ -1,18 +0,0 @@ -package userRepository - -import ( - "bytes" - "golang.org/x/crypto/argon2" -) - -func hashPass(salt []byte, plainPassword string) []byte { - hashedPass := argon2.IDKey([]byte(plainPassword), []byte(salt), 1, 64*1024, 4, 32) - return append(salt, hashedPass...) -} - -func checkPass(passHash []byte, plainPassword string) bool { - salt := make([]byte, 8) - copy(salt, passHash[:8]) - userPassHash := hashPass(salt, plainPassword) - return bytes.Equal(userPassHash, passHash) -} diff --git a/internal/repository/postgres/users/check_credentials.go b/internal/repository/postgres/users/check_credentials.go index ca3b9cf..1520cdd 100644 --- a/internal/repository/postgres/users/check_credentials.go +++ b/internal/repository/postgres/users/check_credentials.go @@ -15,7 +15,7 @@ const checkCredentialsQuery = ` FROM "USER" WHERE username = $1` -func (d UserDB) CheckCredentials(ctx context.Context, username, password string) (models.User, error) { +func (d UserDB) CheckCredentials(ctx context.Context, username, password string) (models.User, []byte, error) { var userInfo UserInfo var storedPassHash []byte @@ -29,14 +29,11 @@ func (d UserDB) CheckCredentials(ctx context.Context, username, password string) ) if err != nil { if errors.Is(err, pgx.ErrNoRows) { - return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, models.ErrUserNotFound) + return models.User{}, storedPassHash, fmt.Errorf("%s: %w", models.LevelDB, models.ErrUserNotFound) } - return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, err) - } - if !checkPass(storedPassHash, password) { - return models.User{}, fmt.Errorf("%s: %w", models.LevelDB, models.ErrInvalidPassword) + return models.User{}, storedPassHash, fmt.Errorf("%s: %w", models.LevelDB, err) } user := toDomainUser(userInfo) - return user, nil + return user, storedPassHash, nil } diff --git a/internal/service/auth/auth.go b/internal/service/auth/auth.go index 8e04d32..2a24ea5 100644 --- a/internal/service/auth/auth.go +++ b/internal/service/auth/auth.go @@ -3,7 +3,9 @@ package authService import ( + "bytes" "context" + "golang.org/x/crypto/argon2" "kudago/internal/models" ) @@ -17,7 +19,7 @@ type authService struct { type UserDB interface { AddUser(ctx context.Context, user models.User) (models.User, error) GetUserByID(ctx context.Context, ID int) (models.User, error) - CheckCredentials(ctx context.Context, username string, password string) (models.User, error) + CheckCredentials(ctx context.Context, username string, password string) (models.User, []byte, error) UserExists(ctx context.Context, username, email string) (bool, error) UpdateUser(ctx context.Context, user models.User) (models.User, error) CheckUsername(ctx context.Context, username string, ID int) (bool, error) @@ -111,7 +113,17 @@ func (a *authService) Unsubscribe(ctx context.Context, subscription models.Subsc } func (a *authService) CheckCredentials(ctx context.Context, creds models.Credentials) (models.User, error) { - return a.UserDB.CheckCredentials(ctx, creds.Username, creds.Password) + user, passHash, err := a.UserDB.CheckCredentials(ctx, creds.Username, creds.Password) + salt := make([]byte, 8) + copy(salt, passHash[:8]) + hashedPass := argon2.IDKey([]byte(creds.Password), []byte(salt), 1, 64*1024, 4, 32) + userPassHash := append(salt, hashedPass...) + + if !bytes.Equal(userPassHash, passHash) { + return user, models.ErrInvalidPassword + } + + return user, err } func (a *authService) Register(ctx context.Context, data models.NewUserData) (models.User, error) {