From 43ac37cf5dc9af0adf605f66a1cfce5d303902ec Mon Sep 17 00:00:00 2001 From: Koliy82 Date: Thu, 1 Aug 2024 00:28:13 +0300 Subject: [PATCH 1/5] feat(auth): fields validation --- internal/grpc/auth/server.go | 60 +++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/internal/grpc/auth/server.go b/internal/grpc/auth/server.go index 892ce6a..586e347 100644 --- a/internal/grpc/auth/server.go +++ b/internal/grpc/auth/server.go @@ -8,9 +8,12 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" + "math" + "regexp" "taskem-server/internal/repositories/user" "taskem-server/internal/service/auth" "taskem-server/tools/gen/grpc/v1" + "unicode" ) type Opts struct { @@ -71,6 +74,10 @@ func (s *Server) Login( }, nil } +const ( + emailRegex = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` +) + func (s *Server) SignUp( ctx context.Context, req *v1.SignupRequest, @@ -87,7 +94,16 @@ func (s *Server) SignUp( return nil, status.Error(codes.InvalidArgument, "Missing argument: password") } - err := s.auth.Registration( + if !regexp.MustCompile(emailRegex).MatchString(req.Email) { + return nil, status.Error(codes.InvalidArgument, "Invalid email: use format example@example.com") + } + + err := PasswordComplexity(req.Password) + if err != nil { + return nil, err + } + + err = s.auth.Registration( ctx, auth.RegistrationOpts{ Email: req.Email, @@ -101,3 +117,45 @@ func (s *Server) SignUp( return &emptypb.Empty{}, nil } + +func PasswordComplexity(password string) error { + isDigit := false + isUpper := false + isLower := false + isSpecial := false + + for _, c := range password { + if unicode.IsDigit(c) { + isDigit = true + } + if unicode.IsUpper(c) { + isUpper = true + } + if unicode.IsLower(c) { + isLower = true + } + if unicode.IsPunct(c) || unicode.IsSymbol(c) { + isSpecial = true + } + } + + var symbolPool int + if isLower && isUpper && isDigit && isSpecial { + symbolPool = 95 // contains (a-z, A-Z, ASCII, space) + } else if isLower && isUpper && isDigit { + symbolPool = 62 // contains (a-z, A-Z, 0-9) + } else if isLower && isDigit { + symbolPool = 36 // contains (a-z, 0-9) + } else { + symbolPool = 26 // contains (a-z) + } + + passwordComplexity := math.Log2(float64(symbolPool)) * float64(len(password)) + + const minComplexity = 40.0 + if passwordComplexity < minComplexity { + return status.Error(codes.InvalidArgument, "Password is too weak") + } + + return nil +} From 2274f096b8362eeebfb3475f4290c6df80ade653 Mon Sep 17 00:00:00 2001 From: Koliy82 Date: Thu, 1 Aug 2024 03:55:43 +0300 Subject: [PATCH 2/5] refactor(auth) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Убрал MustCompile Переименовал PasswordComplexity в IsPwdComplex Вынес EmailRegex и IsPwdComplex в pgs/validation TDT сам сделаешь, мне и себе лень тесты писать --- internal/grpc/auth/server.go | 57 +++-------------------------- internal/pkg/validation/password.go | 48 ++++++++++++++++++++++++ internal/pkg/validation/regex.go | 5 +++ 3 files changed, 58 insertions(+), 52 deletions(-) create mode 100644 internal/pkg/validation/password.go create mode 100644 internal/pkg/validation/regex.go diff --git a/internal/grpc/auth/server.go b/internal/grpc/auth/server.go index 586e347..72502d7 100644 --- a/internal/grpc/auth/server.go +++ b/internal/grpc/auth/server.go @@ -8,12 +8,11 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "math" "regexp" + "taskem-server/internal/pkg/validation" "taskem-server/internal/repositories/user" "taskem-server/internal/service/auth" "taskem-server/tools/gen/grpc/v1" - "unicode" ) type Opts struct { @@ -74,10 +73,6 @@ func (s *Server) Login( }, nil } -const ( - emailRegex = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` -) - func (s *Server) SignUp( ctx context.Context, req *v1.SignupRequest, @@ -94,13 +89,13 @@ func (s *Server) SignUp( return nil, status.Error(codes.InvalidArgument, "Missing argument: password") } - if !regexp.MustCompile(emailRegex).MatchString(req.Email) { + isValidMail, err := regexp.MatchString(validation.EmailRegex, req.Email) + if !isValidMail || err != nil { return nil, status.Error(codes.InvalidArgument, "Invalid email: use format example@example.com") } - err := PasswordComplexity(req.Password) - if err != nil { - return nil, err + if !validation.IsPwdComplex(req.Password) { + return nil, status.Error(codes.InvalidArgument, "Password is too weak") } err = s.auth.Registration( @@ -117,45 +112,3 @@ func (s *Server) SignUp( return &emptypb.Empty{}, nil } - -func PasswordComplexity(password string) error { - isDigit := false - isUpper := false - isLower := false - isSpecial := false - - for _, c := range password { - if unicode.IsDigit(c) { - isDigit = true - } - if unicode.IsUpper(c) { - isUpper = true - } - if unicode.IsLower(c) { - isLower = true - } - if unicode.IsPunct(c) || unicode.IsSymbol(c) { - isSpecial = true - } - } - - var symbolPool int - if isLower && isUpper && isDigit && isSpecial { - symbolPool = 95 // contains (a-z, A-Z, ASCII, space) - } else if isLower && isUpper && isDigit { - symbolPool = 62 // contains (a-z, A-Z, 0-9) - } else if isLower && isDigit { - symbolPool = 36 // contains (a-z, 0-9) - } else { - symbolPool = 26 // contains (a-z) - } - - passwordComplexity := math.Log2(float64(symbolPool)) * float64(len(password)) - - const minComplexity = 40.0 - if passwordComplexity < minComplexity { - return status.Error(codes.InvalidArgument, "Password is too weak") - } - - return nil -} diff --git a/internal/pkg/validation/password.go b/internal/pkg/validation/password.go new file mode 100644 index 0000000..fa3b6d7 --- /dev/null +++ b/internal/pkg/validation/password.go @@ -0,0 +1,48 @@ +package validation + +import ( + "math" + "unicode" +) + +func IsPwdComplex(password string) bool { + isDigit := false + isUpper := false + isLower := false + isSpecial := false + + for _, c := range password { + if unicode.IsDigit(c) { + isDigit = true + } + if unicode.IsUpper(c) { + isUpper = true + } + if unicode.IsLower(c) { + isLower = true + } + if unicode.IsPunct(c) || unicode.IsSymbol(c) { + isSpecial = true + } + } + + var symbolPool int + if isLower && isUpper && isDigit && isSpecial { + symbolPool = 95 // contains (a-z, A-Z, ASCII, space) + } else if isLower && isUpper && isDigit { + symbolPool = 62 // contains (a-z, A-Z, 0-9) + } else if isLower && isDigit { + symbolPool = 36 // contains (a-z, 0-9) + } else { + symbolPool = 26 // contains (a-z) + } + + pwdComplexity := math.Log2(float64(symbolPool)) * float64(len(password)) + + const minComplexity = 40.0 + if pwdComplexity < minComplexity { + return false + } + + return true +} diff --git a/internal/pkg/validation/regex.go b/internal/pkg/validation/regex.go new file mode 100644 index 0000000..6c2fb6a --- /dev/null +++ b/internal/pkg/validation/regex.go @@ -0,0 +1,5 @@ +package validation + +const ( + EmailRegex = `^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$` +) From 24224d5f9f38038c8cf8b3806a4a160f876a3100 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:14:35 +0300 Subject: [PATCH 3/5] feat(validation): add test for `IsPwdComplex` --- internal/pkg/validation/password_test.go | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 internal/pkg/validation/password_test.go diff --git a/internal/pkg/validation/password_test.go b/internal/pkg/validation/password_test.go new file mode 100644 index 0000000..9746110 --- /dev/null +++ b/internal/pkg/validation/password_test.go @@ -0,0 +1,72 @@ +package validation + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestIsPwdComplex(t *testing.T) { + tests := []struct { + name string + password string + expect bool + }{ + { + name: "Empty password", + password: "", + expect: false, + }, + { + name: "Lowercase only, too short", + password: "abcde", + expect: false, + }, + { + name: "Lowercase only, sufficient length", + password: "abcdefghijabcdefghij", + expect: true, + }, + { + name: "Lowercase and digits, too short", + password: "abc123", + expect: false, + }, + { + name: "Lowercase and digits, sufficient length", + password: "abc123abc123", + expect: true, + }, + { + name: "Lowercase, uppercase, and digits, too short", + password: "Abc123", + expect: false, + }, + { + name: "Lowercase, uppercase, and digits, sufficient length", + password: "Abc123Abc123", + expect: true, + }, + { + name: "Lowercase, uppercase, digits, and special chars, sufficient length", + password: "Abc123!@#Abc123!@#", + expect: true, + }, + { + name: "Only special characters, too short", + password: "!@#$%^", + expect: false, + }, + { + name: "Only special characters, sufficient length", + password: "!@#$%^&*()_+!@#$%^&*()_+", + expect: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + actual := IsPwdComplex(tt.password) + assert.Equalf(t, tt.expect, actual, "IsPwdComplex() = %v, expect %v", actual, tt.expect) + }) + } +} From 5d450d7ec465a3a1e540b3763868940125c18bf2 Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:15:03 +0300 Subject: [PATCH 4/5] refactor(validation): change returned expr --- internal/pkg/validation/password.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/pkg/validation/password.go b/internal/pkg/validation/password.go index fa3b6d7..38a2782 100644 --- a/internal/pkg/validation/password.go +++ b/internal/pkg/validation/password.go @@ -40,9 +40,6 @@ func IsPwdComplex(password string) bool { pwdComplexity := math.Log2(float64(symbolPool)) * float64(len(password)) const minComplexity = 40.0 - if pwdComplexity < minComplexity { - return false - } - return true + return pwdComplexity > minComplexity } From e9b2f204701d00c929885d627885d0defe3ed75f Mon Sep 17 00:00:00 2001 From: ripls56 <84716344+ripls56@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:44:04 +0300 Subject: [PATCH 5/5] refactor(validation): extract func from `IsPwdComplex` Extract calculation value of `symbol pool` in another function --- internal/pkg/validation/password.go | 35 ++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/internal/pkg/validation/password.go b/internal/pkg/validation/password.go index 38a2782..24150d6 100644 --- a/internal/pkg/validation/password.go +++ b/internal/pkg/validation/password.go @@ -26,16 +26,7 @@ func IsPwdComplex(password string) bool { } } - var symbolPool int - if isLower && isUpper && isDigit && isSpecial { - symbolPool = 95 // contains (a-z, A-Z, ASCII, space) - } else if isLower && isUpper && isDigit { - symbolPool = 62 // contains (a-z, A-Z, 0-9) - } else if isLower && isDigit { - symbolPool = 36 // contains (a-z, 0-9) - } else { - symbolPool = 26 // contains (a-z) - } + symbolPool := calcSymbolPool(isDigit, isUpper, isLower, isSpecial) pwdComplexity := math.Log2(float64(symbolPool)) * float64(len(password)) @@ -43,3 +34,27 @@ func IsPwdComplex(password string) bool { return pwdComplexity > minComplexity } + +func calcSymbolPool( + isDigit, + isUpper, + isLower, + isSpecial bool, +) int { + switch { + case isLower && isUpper && isDigit && isSpecial: + return 95 // contains (a-z, A-Z, ASCII, space) + case isLower && isUpper && isDigit: + return 62 // contains (a-z, A-Z, 0-9) + case (isLower || isUpper) && isDigit: + return 36 // contains (a-z or A-Z, 0-9) + case isSpecial: + return 32 + case isLower || isUpper: + return 26 // contains (a-z or A-Z) + case isDigit: + return 10 // contains (0-9) + } + + return 0 +}