Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security add #18

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ toolchain go1.22.0

require (
github.com/alicebob/miniredis/v2 v2.32.1
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-redis/redis v6.15.9+incompatible
github.com/golang/mock v1.6.0
github.com/google/uuid v1.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
Expand Down
2 changes: 2 additions & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"syscall"
"time"

csrf "github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/middleware/csrf"
"github.com/gorilla/mux"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/config"
Expand Down Expand Up @@ -60,6 +61,7 @@ func Init() *App {

r.Use(logmw.NewLoggingMiddleware(log))
r.Use(corsmw.NewCORSMiddleware([]string{}))
r.Use(csrf.CSRFMiddleware())

// Redis

Expand Down
55 changes: 55 additions & 0 deletions internal/models/csrf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package models
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нужны тесты


import (
"fmt"

"github.com/dgrijalva/jwt-go"
"github.com/pkg/errors"

"time"
)

type JwtToken struct {
Secret []byte
}

func NewJwtToken(secret string) (*JwtToken, error) {
return &JwtToken{Secret: []byte(secret)}, nil
}

type JwtCsrfClaims struct {
SessionID string `json:"sid"`
jwt.StandardClaims
}

func (tk *JwtToken) Create(sID string, tokenExpTime int64) (string, error) {
data := JwtCsrfClaims{
SessionID: sID,
StandardClaims: jwt.StandardClaims{
ExpiresAt: tokenExpTime,
IssuedAt: time.Now().Unix(),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, data)
return token.SignedString(tk.Secret)
}

func (tk *JwtToken) parseSecretGetter(token *jwt.Token) (interface{}, error) {
method, ok := token.Method.(*jwt.SigningMethodHMAC)
if !ok || method.Alg() != "HS256" {
return nil, errors.New("bad sign method")
}
return tk.Secret, nil
}

func (tk *JwtToken) Check(sID string, inputToken string) (bool, error) {
payload := &JwtCsrfClaims{}
_, err := jwt.ParseWithClaims(inputToken, payload, tk.parseSecretGetter)
if err != nil {
return false, fmt.Errorf("cant parse jwt token: %w", err)
}
if payload.Valid() != nil {
return false, fmt.Errorf("invalid jwt token: %w", err)
}
return payload.SessionID == sID, nil
}
45 changes: 45 additions & 0 deletions internal/models/csrf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package models_test

import (
"strconv"
"testing"
"time"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/models"
)

func TestCreateCSRF(t *testing.T) {
tokens, _ := models.NewJwtToken("test")
uID := strconv.Itoa(1)
_, err := tokens.Create(uID, time.Now().Add(1*time.Hour).Unix())
if err != nil {
t.Fatalf("err with creation")
}
}

func TestCheckCSRF(t *testing.T) {
tokens, _ := models.NewJwtToken("test")
uID := strconv.Itoa(1)
token, err := tokens.Create(uID, time.Now().Add(1*time.Hour).Unix())
if err != nil {
t.Fatalf("err with creation 1 token")
}
_, err = tokens.Check(uID, token)
if err != nil {
t.Fatalf("err with check token")
}
}

func TestCheckFailCSRF(t *testing.T) {
tokens, _ := models.NewJwtToken("test")
uID := strconv.Itoa(1)
token, err := tokens.Create(uID, time.Now().Add(1*time.Second).Unix())
time.Sleep(3 * time.Second)
if err != nil {
t.Fatalf("err with creation 1 token")
}
_, err = tokens.Check(uID, token)
if err != nil {
t.Log("success")
}
}
47 changes: 47 additions & 0 deletions internal/pkg/helper/passhesher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package helper

import (
"bytes"

"golang.org/x/crypto/argon2"
)

const (
_countBytes = 8
_countMemory = 65536
_countTreads = 4
_countKeyLen = 32
)

func hashPass(salt []byte, plainPassword string) []byte {
hashedPass := argon2.IDKey([]byte(plainPassword), salt, 1, _countMemory, _countTreads, _countKeyLen)
return append(salt, hashedPass...)
}

func CheckPass(passHash []byte, plainPassword string) bool {
salt := passHash[0:8]
userPassHash := hashPass(salt, plainPassword)
return bytes.Equal(userPassHash, passHash)
}

func MakeNewPassHash(password string) (string, error) {
/*salt := make([]byte, _countBytes)
_, err := rand.Read(salt)
if err != nil {
return "", err
}
passwordHash := hashPass(salt, password)
charset := "latin1"
e, err := ianaindex.MIME.Encoding(charset)
if err != nil {
log.Fatal(err)
}
r := transform.NewReader(bytes.NewBufferString(string(passwordHash)), e.NewDecoder())
result, err := ioutil.ReadAll(r)
if err != nil {
log.Fatal(err)
}

*/
return password, nil
}
80 changes: 80 additions & 0 deletions internal/pkg/middleware/csrf/csrfmw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package middleware

import (
"fmt"
"strings"
"time"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/delivery/dto"
"github.com/go-park-mail-ru/2024_1_FullFocus/internal/models"
"github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/helper"
"github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/logger"
"github.com/gorilla/mux"

"net/http"
)

const _timeOut = 20

func CSRFMiddleware() mux.MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if strings.Contains(r.URL.Path, "public") {
next.ServeHTTP(w, r)
return
}

if r.Method == http.MethodGet || r.Method == http.MethodHead {
err := SetSCRFToken(w, r)
if err != nil {
logger.Debug(ctx, fmt.Sprintf("csrf token creation error: %v", err))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Тут тоже, нужно на запрос ответить

helper.JSONResponse(ctx, w, 200, dto.ErrResponse{
Status: 400,
Msg: err.Error(),
MsgRus: "Ошибка создания csrf token",
})
return
}
} else {
err := CheckSCRFToken(r)
if err != nil {
logger.Debug(ctx, fmt.Sprintf("csrf token check error: %v", err))
helper.JSONResponse(ctx, w, 200, dto.ErrResponse{
Status: 400,
Msg: err.Error(),
MsgRus: "Ошибка проверки csrf token",
})
return
}
}
next.ServeHTTP(w, r)
})
}
}

func SetSCRFToken(w http.ResponseWriter, r *http.Request) error {
tokens, _ := models.NewJwtToken("qsRY2e4hcM5T7X984E9WQ5uZ8Nty7fxB")
ctx := r.Context()
token, err := tokens.Create("sID", time.Now().Add(_timeOut*time.Minute).Unix())
logger.Info(ctx, fmt.Sprintf("err wiht csrf: %v", err))
if err != nil {
return err
}

w.Header().Set("X-Csrf-Token", token)
return nil
}

func CheckSCRFToken(r *http.Request) error {
tokens, _ := models.NewJwtToken("qsRY2e4hcM5T7X984E9WQ5uZ8Nty7fxB")
ctx := r.Context()
csrfToken := r.Header.Get("X-Csrf-Token")
_, err := tokens.Check("sID", csrfToken)
logger.Info(ctx, fmt.Sprintf("err wiht csrf: %v", err))
if err != nil {
return err
}

return nil
}
2 changes: 2 additions & 0 deletions internal/repository/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package repository

import (
"context"
"fmt"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/models"
db "github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/database"
Expand All @@ -27,6 +28,7 @@ func (r *UserRepo) CreateUser(ctx context.Context, user models.User) (uint, erro
err := r.storage.Get(ctx, &resRow, q, userRow.Login, userRow.PasswordHash)
if err != nil {
logger.Info(ctx, "user already exists")
logger.Info(ctx, fmt.Sprintf("%v", err))
return 0, models.ErrUserAlreadyExists
}
return resRow.ID, nil
Expand Down
14 changes: 7 additions & 7 deletions internal/usecase/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ func (u *AuthUsecase) Login(ctx context.Context, login string, password string)
return "", models.ErrNoUser
}

// TODO: add hasher from helper
// err = helper.CheckPassword(password, user.PasswordHash)
// ok := helper.CheckPass([]byte(user.PasswordHash), password)

if password != user.PasswordHash {
return "", models.ErrWrongPassword
}

return u.sessionRepo.CreateSession(ctx, user.ID), nil
}

Expand All @@ -63,15 +64,14 @@ func (u *AuthUsecase) Signup(ctx context.Context, login string, password string)
"Пароль должен содержать от 8 до 32 букв английского алфавита или цифр")
}

// TODO: add hasher from helper
// passwordHash, err := helper.HashPassword(password)
// if err != nil {
// return "", err
// }
// passwordHash, err := helper.MakeNewPassHash(password)
// if err != nil {return "", errors.New("err with making salt")}

user := models.User{
Username: login,
PasswordHash: password,
}

uID, err := u.userRepo.CreateUser(ctx, user)
if err != nil {
return "", models.ErrUserAlreadyExists
Expand Down
17 changes: 9 additions & 8 deletions internal/usecase/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"log"
"testing"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/helper"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/require"

"github.com/go-park-mail-ru/2024_1_FullFocus/internal/models"
"github.com/go-park-mail-ru/2024_1_FullFocus/internal/pkg/helper"
mock_repository "github.com/go-park-mail-ru/2024_1_FullFocus/internal/repository/mocks"
"github.com/go-park-mail-ru/2024_1_FullFocus/internal/usecase"
)
Expand Down Expand Up @@ -44,8 +44,8 @@ func TestSignUp(t *testing.T) {
name: "Check valid user signup",
login: "test123",
password: "Qa5yAbrLhkwT4Y9u",
userMockBehavior: func(r *mock_repository.MockUsers, user models.User) {
r.EXPECT().CreateUser(context.Background(), user).Return(uint(0), nil)
userMockBehavior: func(r *mock_repository.MockUsers, _ models.User) {
r.EXPECT().CreateUser(context.Background(), gomock.Any()).Return(uint(0), nil)
},
sessionMockBehavior: func(r *mock_repository.MockSessions, userID uint) {
r.EXPECT().CreateSession(context.Background(), userID).Return("123")
Expand All @@ -63,8 +63,8 @@ func TestSignUp(t *testing.T) {
name: "Check valid user signup",
login: "test123",
password: "testtest1",
userMockBehavior: func(r *mock_repository.MockUsers, user models.User) {
r.EXPECT().CreateUser(context.Background(), user).Return(uint(0), nil)
userMockBehavior: func(r *mock_repository.MockUsers, _ models.User) {
r.EXPECT().CreateUser(context.Background(), gomock.Any()).Return(uint(0), nil)
},
sessionMockBehavior: func(r *mock_repository.MockSessions, userID uint) {
r.EXPECT().CreateSession(context.Background(), userID).Return("123")
Expand All @@ -82,8 +82,8 @@ func TestSignUp(t *testing.T) {
name: "Check duplicate user signup",
login: "test123",
password: "Qa5yAbrLhkwT4Y9u",
userMockBehavior: func(r *mock_repository.MockUsers, user models.User) {
r.EXPECT().CreateUser(context.Background(), user).Return(uint(0), models.ErrUserAlreadyExists)
userMockBehavior: func(r *mock_repository.MockUsers, _ models.User) {
r.EXPECT().CreateUser(context.Background(), gomock.Any()).Return(uint(0), models.ErrUserAlreadyExists)
},
expectedSID: "",
expectedErr: models.ErrUserAlreadyExists,
Expand Down Expand Up @@ -171,7 +171,8 @@ func TestLogin(t *testing.T) {
login: "test123",
password: "test12345",
userMockBehavior: func(r *mock_repository.MockUsers, username string) {
r.EXPECT().GetUser(context.Background(), username).Return(models.User{ID: 0, Username: "test123", PasswordHash: "test12345"}, nil)
r.EXPECT().GetUser(context.Background(), username).Return(models.User{ID: 0, Username: "test123",
PasswordHash: "test12345"}, nil)
},
sessionMockBehavior: func(r *mock_repository.MockSessions, userID uint) {
r.EXPECT().CreateSession(context.Background(), userID).Return("123")
Expand Down
Binary file not shown.
Binary file added minio-data/.minio.sys/buckets/.usage.json/xl.meta
Binary file not shown.
Binary file added minio-data/.minio.sys/config/config.json/xl.meta
Binary file not shown.
Binary file added minio-data/.minio.sys/config/iam/format.json/xl.meta
Binary file not shown.
1 change: 1 addition & 0 deletions minio-data/.minio.sys/format.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"1","format":"xl-single","id":"38f1154f-153c-4976-a563-7c4e6735aaae","xl":{"version":"3","this":"2c90ecd9-79c7-4f38-9a8d-5b0da5741b6a","sets":[["2c90ecd9-79c7-4f38-9a8d-5b0da5741b6a"]],"distributionAlgo":"SIPMOD+PARITY"}}
Binary file added minio-data/.minio.sys/pool.bin/xl.meta
Binary file not shown.
Loading