From 7d73bf52ae3d254d72181882800c1b49667e58fe Mon Sep 17 00:00:00 2001 From: le-xuan-quynh Date: Wed, 4 May 2022 10:57:05 +0700 Subject: [PATCH] update verify limit data --- cmd/authorization/auth.go | 31 ++++++-- internal/config.go | 3 +- internal/database/limit-data.go | 15 ++-- internal/database/postgres-repository.go | 63 +++++++++++++--- internal/database/repository.go | 8 +-- internal/database/verification-data.go | 10 +++ pkg/authorization/users-service.go | 92 ++++++++---------------- 7 files changed, 134 insertions(+), 88 deletions(-) diff --git a/cmd/authorization/auth.go b/cmd/authorization/auth.go index fed8732..846633b 100644 --- a/cmd/authorization/auth.go +++ b/cmd/authorization/auth.go @@ -93,7 +93,8 @@ const limitSchema = ` create table if not exists limits ( id Varchar(36) not null, userid Varchar(36) not null, - numofsendmail Int default 0, + numofsendmailverify Int default 0, + numofsendresetpassword Int default 0, numofchangepassword Int default 0, numoflogin Int default 0, createdat Timestamp not null, @@ -135,20 +136,42 @@ func main() { // authService contains all methods that help in authorizing a user request auth := middleware.NewAuthService(logger, configs) - // Reset limit data for users. s := gocron.NewScheduler(time.UTC) + // Reset limit send mail for users. _, err = s.Every(1).Day().At("00:01").Do(func() { logger.Info("Clear limit data for users after 1 day at 00:01.") var ctx = context.Background() - err := repository.ClearAllLimitData(ctx) + err := repository.ClearLimitData(ctx, database.LimitTypeSendVerifyMail) if err != nil { - logger.Error("Error clearing limit data", "error", err) + logger.Error("Error clearing limit send mail data", "error", err) + } + // Reset limit login for users. + err = repository.ClearLimitData(ctx, database.LimitTypeLogin) + if err != nil { + logger.Error("Error clearing limit login data", "error", err) } }) if err != nil { logger.Error("Error scheduling limit data", "error", err) return } + // Reset limit change password for users after 15 minutes. + _, err = s.Every(15).Minute().Do(func() { + logger.Info("Clear change password, send pass reset mail limit data for users after 15 minutes.") + var ctx = context.Background() + err := repository.ClearLimitData(ctx, database.LimitTypeChangePassword) + if err != nil { + logger.Error("Error clearing limit change password data", "error", err) + } + // Limit for send mail get password code reset + err = repository.ClearLimitData(ctx, database.LimitTypeSendPassResetMail) + if err != nil { + logger.Error("Error clearing limit send mail data", "error", err) + } + }) + if err != nil { + logger.Error("Error scheduling limit data", "error", err) + } s.StartAsync() // Create rate limiter for users. diff --git a/internal/config.go b/internal/config.go index 049aeba..550e11b 100644 --- a/internal/config.go +++ b/internal/config.go @@ -30,7 +30,8 @@ type Configurations struct { HttpPort string `mapstructure:"HTTP_PORT"` MailTitle string `mapstructure:"MAIL_TITLE"` ChangePasswordLimit int `mapstructure:"CHANGE_PASSWORD_LIMIT"` - SendMailLimit int `mapstructure:"SEND_MAIL_LIMIT"` + SendMailVerifyLimit int `mapstructure:"SEND_MAIL_VERIFY_LIMIT"` + SendMailResetPasswordLimit int `mapstructure:"SEND_MAIL_RESET_PASSWORD_LIMIT"` LoginLimit int `mapstructure:"LOGIN_LIMIT"` } diff --git a/internal/database/limit-data.go b/internal/database/limit-data.go index 150cf49..9e8cc1a 100644 --- a/internal/database/limit-data.go +++ b/internal/database/limit-data.go @@ -3,11 +3,12 @@ package database import "time" type LimitData struct { - ID string `json:"id" sql:"id"` - UserID string `json:"user_id" sql:"userid"` - NumOfSendMail int `json:"num_of_send_mail" sql:"numofsendmail"` - NumOfChangePassword int `json:"num_of_change_password" sql:"numofchangepassword"` - NumOfLogin int `json:"num_of_login" sql:"numoflogin"` - CreatedAt time.Time `json:"createdat" sql:"createdat"` - UpdatedAt time.Time `json:"updatedat" sql:"updatedat"` + ID string `json:"id" sql:"id"` + UserID string `json:"user_id" sql:"userid"` + NumOfSendMailVerify int `json:"num_of_send_mail_verify" sql:"numofsendmailverify"` + NumOfSendResetPassword int `json:"num_of_send_reset_password" sql:"numofsendresetpassword"` + NumOfChangePassword int `json:"num_of_change_password" sql:"numofchangepassword"` + NumOfLogin int `json:"num_of_login" sql:"numoflogin"` + CreatedAt time.Time `json:"createdat" sql:"createdat"` + UpdatedAt time.Time `json:"updatedat" sql:"updatedat"` } diff --git a/internal/database/postgres-repository.go b/internal/database/postgres-repository.go index e3ce16e..0423783 100644 --- a/internal/database/postgres-repository.go +++ b/internal/database/postgres-repository.go @@ -3,6 +3,7 @@ package database import ( "context" "database/sql" + "errors" "github.com/hashicorp/go-hclog" "github.com/jmoiron/sqlx" uuid "github.com/satori/go.uuid" @@ -219,26 +220,55 @@ func (repo *postgresRepository) InsertListOfPasswords(ctx context.Context, passw } // GetLimitData returns the limit data -func (repo *postgresRepository) GetLimitData(ctx context.Context, userID string) (*LimitData, error) { +func (repo *postgresRepository) GetLimitData(ctx context.Context, userID string, limitType LimitType) (*LimitData, error) { query := "select * from limits where userid = $1" limitData := &LimitData{} err := repo.db.GetContext(ctx, limitData, query, userID) + if err != nil { + if limitType == LimitTypeLogin { + limitData.NumOfLogin = 1 + } else if limitType == LimitTypeSendVerifyMail { + limitData.NumOfSendMailVerify = 1 + } else if limitType == LimitTypeSendPassResetMail { + limitData.NumOfSendResetPassword = 1 + } else if limitType == LimitTypeChangePassword { + limitData.NumOfChangePassword = 1 + } + } else { + if limitType == LimitTypeLogin { + limitData.NumOfLogin += 1 + } else if limitType == LimitTypeSendVerifyMail { + limitData.NumOfSendMailVerify += 1 + } else if limitType == LimitTypeSendPassResetMail { + limitData.NumOfSendResetPassword += 1 + } else if limitType == LimitTypeChangePassword { + limitData.NumOfChangePassword += 1 + } + } + limitData.UserID = userID return limitData, err } // InsertOrUpdateLimitData updates the limit data -func (repo *postgresRepository) InsertOrUpdateLimitData(ctx context.Context, limitData *LimitData, isInsert bool) error { +func (repo *postgresRepository) InsertOrUpdateLimitData(ctx context.Context, limitData *LimitData, limitType LimitType) error { + // Check exist or not limit data + _, err := repo.GetLimitData(ctx, limitData.UserID, limitType) + isInsert := false + if err != nil { + isInsert = true + } limitData.ID = uuid.NewV4().String() limitData.CreatedAt = time.Now() limitData.UpdatedAt = time.Now() // Insert or update if isInsert { // Insert the limit data - query := "insert into limits(id, userid, numofsendmail, numofchangepassword, numoflogin, createdat, updatedat) values($1, $2, $3, $4, $5, $6, $7)" + query := "insert into limits(id, userid, numofsendmailverify, numofsendresetpassword, numofchangepassword, numoflogin, createdat, updatedat) values($1, $2, $3, $4, $5, $6, $7, $8)" _, err := repo.db.ExecContext(ctx, query, limitData.ID, limitData.UserID, - limitData.NumOfSendMail, + limitData.NumOfSendMailVerify, + limitData.NumOfSendResetPassword, limitData.NumOfChangePassword, limitData.NumOfLogin, limitData.CreatedAt, @@ -246,9 +276,10 @@ func (repo *postgresRepository) InsertOrUpdateLimitData(ctx context.Context, lim return err } else { // Update the limit data - query := "update limits set numofsendmail = $1, numofchangepassword = $2, numoflogin = $3, updatedat = $4 where userid = $5" + query := "update limits set numofsendmailverify = $1, numofsendresetpassword = $2, numofchangepassword = $3, numoflogin = $4, updatedat = $5 where userid = $6" _, err := repo.db.ExecContext(ctx, query, - limitData.NumOfSendMail, + limitData.NumOfSendMailVerify, + limitData.NumOfSendResetPassword, limitData.NumOfChangePassword, limitData.NumOfLogin, limitData.UpdatedAt, @@ -257,9 +288,23 @@ func (repo *postgresRepository) InsertOrUpdateLimitData(ctx context.Context, lim } } -// ClearAllLimitData clears all limit data -func (repo *postgresRepository) ClearAllLimitData(ctx context.Context) error { - query := "delete from limits" +// ClearLimitData clears the limit data +func (repo *postgresRepository) ClearLimitData(ctx context.Context, limitType LimitType) error { + var query string + if limitType == LimitTypeLogin { + // Clear all num of login limit + query = "update limits set numoflogin = 0" + } else if limitType == LimitTypeSendVerifyMail { + // Clear all num of send mail limit + query = "update limits set numofsendmailverify = 0" + } else if limitType == LimitTypeSendPassResetMail { + query = "update limits set numofsendresetpassword = 0" + } else if limitType == LimitTypeChangePassword { + // Clear all num of change password limit + query = "update limits set numofchangepassword = 0" + } else { + return errors.New("limit type is not valid") + } _, err := repo.db.ExecContext(ctx, query) return err } diff --git a/internal/database/repository.go b/internal/database/repository.go index a6bae7d..349c783 100644 --- a/internal/database/repository.go +++ b/internal/database/repository.go @@ -34,9 +34,9 @@ type UserRepository interface { // InsertListOfPasswords Update password into list of passwords InsertListOfPasswords(ctx context.Context, passwordUsers *PassworUsers) error // GetLimitData Get limit table data - GetLimitData(ctx context.Context, userID string) (*LimitData, error) + GetLimitData(ctx context.Context, userID string, limitType LimitType) (*LimitData, error) // InsertOrUpdateLimitData Insert or update limit data - InsertOrUpdateLimitData(ctx context.Context, limitData *LimitData, isInsert bool) error - // ClearAllLimitData Clear all limit data - ClearAllLimitData(ctx context.Context) error + InsertOrUpdateLimitData(ctx context.Context, limitData *LimitData, limitType LimitType) error + // ClearLimitData Clear limit data + ClearLimitData(ctx context.Context, limitType LimitType) error } diff --git a/internal/database/verification-data.go b/internal/database/verification-data.go index e84da99..cfe5873 100644 --- a/internal/database/verification-data.go +++ b/internal/database/verification-data.go @@ -9,6 +9,16 @@ const ( PassReset ) +type LimitType int + +const ( + LimitTypeNone LimitType = iota + LimitTypeLogin + LimitTypeSendVerifyMail + LimitTypeSendPassResetMail + LimitTypeChangePassword +) + // Type of verification data const ( RefreshType = "refresh" diff --git a/pkg/authorization/users-service.go b/pkg/authorization/users-service.go index 59addb6..0812c70 100644 --- a/pkg/authorization/users-service.go +++ b/pkg/authorization/users-service.go @@ -131,16 +131,9 @@ func (s *userService) VerifyMail(ctx context.Context, request *VerifyMailRequest return "Email has been successfully verified.", nil } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, user.ID) + limitData, err := s.repo.GetLimitData(ctx, user.ID, database.LimitTypeLogin) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = user.ID - limitData.NumOfLogin = 1 - } else { - limitData.NumOfLogin += 1 } // Check if limit is reached if limitData.NumOfLogin > s.configs.LoginLimit { @@ -149,7 +142,7 @@ func (s *userService) VerifyMail(ctx context.Context, request *VerifyMailRequest return cusErr.Error(), cusErr } // Update limit data - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeLogin) if err != nil { s.logger.Error("Cannot insert or update limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -189,7 +182,7 @@ func (s *userService) VerifyMail(ctx context.Context, request *VerifyMailRequest } // Reset limit data limitData.NumOfLogin = 0 - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, false) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeLogin) if err != nil { s.logger.Error("Cannot reset number of login", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -239,16 +232,9 @@ func (s *userService) Login(ctx context.Context, request *LoginRequest) (interfa return cusErr.Error(), cusErr } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, user.ID) + limitData, err := s.repo.GetLimitData(ctx, user.ID, database.LimitTypeLogin) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = user.ID - limitData.NumOfLogin = 1 - } else { - limitData.NumOfLogin += 1 } // Check if limit is reached if limitData.NumOfLogin > s.configs.LoginLimit { @@ -257,7 +243,7 @@ func (s *userService) Login(ctx context.Context, request *LoginRequest) (interfa return cusErr.Error(), cusErr } // Update limit data - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeLogin) if err != nil { s.logger.Error("Cannot insert or update limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -286,7 +272,7 @@ func (s *userService) Login(ctx context.Context, request *LoginRequest) (interfa } // Reset limit data limitData.NumOfLogin = 0 - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, false) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeLogin) if err != nil { s.logger.Error("Cannot reset number of login", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -334,7 +320,7 @@ func (s *userService) Logout(ctx context.Context, request *LogoutRequest) error cusErr := utils.NewErrorResponse(utils.InternalServerError) return cusErr } - s.logger.Debug("successfully generated token", "refreshtoken", refreshToken) + s.logger.Debug("successfully generated token", "refresh token", refreshToken) // Update user token hash to database err = s.repo.UpdateUser(ctx, user) if err != nil { @@ -508,16 +494,9 @@ func (s *userService) UpdatePassword(ctx context.Context, request *UpdatePasswor return "User is banned", errors.New("user is banned") } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, userID) + limitData, err := s.repo.GetLimitData(ctx, userID, database.LimitTypeChangePassword) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = userID - limitData.NumOfChangePassword = 1 - } else { - limitData.NumOfChangePassword += 1 } // Check if limit is reached if limitData.NumOfChangePassword > s.configs.ChangePasswordLimit { @@ -526,7 +505,7 @@ func (s *userService) UpdatePassword(ctx context.Context, request *UpdatePasswor return cusErr.Error(), cusErr } // Update limit data - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeChangePassword) if err != nil { s.logger.Error("Cannot insert or update limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -587,7 +566,7 @@ func (s *userService) UpdatePassword(ctx context.Context, request *UpdatePasswor } // Reset limit data limitData.NumOfChangePassword = 0 - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, false) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeChangePassword) if err != nil { s.logger.Error("Cannot delete limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -618,26 +597,19 @@ func (s *userService) GetForgetPasswordCode(ctx context.Context, email string) e return cusErr } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, user.ID) + limitData, err := s.repo.GetLimitData(ctx, user.ID, database.LimitTypeSendPassResetMail) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = user.ID - limitData.NumOfSendMail = 1 - } else { - limitData.NumOfSendMail += 1 } // Check if user has reached limit send mail - if limitData.NumOfSendMail > s.configs.SendMailLimit { + if limitData.NumOfSendResetPassword > s.configs.SendMailResetPasswordLimit { s.logger.Error("User has reached limit send mail.", "error", err) cusErr := utils.NewErrorResponse(utils.TooManyRequests) return cusErr } // Insert or update limit data - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeSendPassResetMail) if err != nil { s.logger.Error("Cannot update limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -654,7 +626,7 @@ func (s *userService) GetForgetPasswordCode(ctx context.Context, email string) e ExpiresAt: time.Now().Add(time.Minute * time.Duration(s.configs.PassResetCodeExpiration)), } // Check insert or update - isInsert = false + isInsert := false _, err = s.repo.GetVerificationData(ctx, user.Email, database.PassReset) if err != nil { isInsert = true @@ -750,18 +722,11 @@ func (s *userService) ResetPassword(ctx context.Context, request *CreateNewPassw return utils.NewErrorResponse(utils.InternalServerError) } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, user.ID) + limitData, err := s.repo.GetLimitData(ctx, user.ID, database.LimitTypeChangePassword) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = user.ID - limitData.NumOfChangePassword = 1 - } else { - limitData.NumOfChangePassword += 1 } - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeChangePassword) if err != nil { s.logger.Error("Cannot delete limit data", "error", err) return utils.NewErrorResponse(utils.InternalServerError) @@ -772,6 +737,14 @@ func (s *userService) ResetPassword(ctx context.Context, request *CreateNewPassw s.logger.Error("unable to delete the verification data", "error", err) return utils.NewErrorResponse(utils.InternalServerError) } + // Reset change password limit data + limitData.NumOfSendResetPassword = 0 + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeSendPassResetMail) + if err != nil { + s.logger.Error("Cannot update limit data", "error", err) + cusErr := utils.NewErrorResponse(utils.InternalServerError) + return cusErr + } s.logger.Info("Password changed", "userID", user.ID) return nil } @@ -831,26 +804,19 @@ func (s *userService) GetVerifyMailCode(ctx context.Context) error { return cusErr } // Get limit data - isInsert := false - limitData, err := s.repo.GetLimitData(ctx, user.ID) + limitData, err := s.repo.GetLimitData(ctx, user.ID, database.LimitTypeSendVerifyMail) if err != nil { s.logger.Error("Empty row get limit data", "error", err) - // No row, need insert - isInsert = true - limitData.UserID = user.ID - limitData.NumOfSendMail = 1 - } else { - limitData.NumOfSendMail += 1 } // Check if user has reached limit send mail - if limitData.NumOfSendMail > s.configs.SendMailLimit { + if limitData.NumOfSendMailVerify > s.configs.SendMailVerifyLimit { s.logger.Error("User has reached limit send mail.", "error", err) cusErr := utils.NewErrorResponse(utils.TooManyRequests) return cusErr } // Insert or update limit data - err = s.repo.InsertOrUpdateLimitData(ctx, limitData, isInsert) + err = s.repo.InsertOrUpdateLimitData(ctx, limitData, database.LimitTypeSendVerifyMail) if err != nil { s.logger.Error("Cannot update limit data", "error", err) cusErr := utils.NewErrorResponse(utils.InternalServerError) @@ -864,10 +830,10 @@ func (s *userService) GetVerifyMailCode(ctx context.Context) error { Email: user.Email, Code: forgetPasswordCode, Type: database.MailConfirmation, - ExpiresAt: time.Now().Add(time.Minute * time.Duration(s.configs.PassResetCodeExpiration)), + ExpiresAt: time.Now().Add(time.Minute * time.Duration(s.configs.MailVerifCodeExpiration)), } // Check insert or update - isInsert = false + isInsert := false _, err = s.repo.GetVerificationData(ctx, user.Email, database.MailConfirmation) if err != nil { isInsert = true