diff --git a/.idea/.gitignore b/.idea/.gitignore index 13566b8..a9d7db9 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -6,3 +6,5 @@ # Datasource local storage ignored files /dataSources/ /dataSources.local.xml +# GitHub Copilot persisted chat sessions +/copilot/chatSessions diff --git a/rest-api/controler/auth.go b/rest-api/controler/auth.go index d93c962..4a83847 100644 --- a/rest-api/controler/auth.go +++ b/rest-api/controler/auth.go @@ -1 +1,100 @@ package controler + +import ( + "context" + "encoding/json" + "fmt" + "linhdevtran99/rest-api/models" + "linhdevtran99/rest-api/rest-api/services" + "linhdevtran99/rest-api/utils" + "net/http" + "sync" + "time" +) + +func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error { + if r.Method == http.MethodPost { + var registerInfo models.CreateUser + + // get data from body + _ = json.NewDecoder(r.Body).Decode(®isterInfo) + + //call function check info user type in + _, responseAPI := services.CheckAndValidRegisterFiled(®isterInfo, w) + //[fairy ok] + + if responseAPI != nil { + return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err) + } + + //call function check data user use in past or not + isError, responseAPI := services.CheckAccountValid(registerInfo.Username, registerInfo.Email) + + if isError == true { + return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err) + } + + preUserData := &models.PreusersMongo{ + Username: registerInfo.Username, + Email: registerInfo.Email, + PhoneNumber: registerInfo.PhoneNumber, + HashPassword: registerInfo.Password, + UUID: registerInfo.UUID, + CreatedDate: time.Now(), + UpdateDate: time.Now(), + VerifySentCount: 1, + } + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var wg sync.WaitGroup + + //otpChannel := make(chan models.OtpGenerate, 1) + //mailChannel := make(chan models.MailVefiry, 1) + done := make(chan struct{}) + + var pipelineError error + + wg.Add(1) + counter, timerCreate := services.CheckAndWritePreuser(preUserData, &wg, cancel, pipelineError) + fmt.Println("counter", counter) + fmt.Println("timerCreate", timerCreate) + time.Sleep(20 * time.Second) + cancel() + //wg.Add(1) + //go utils.GenOTP(preUserData, counter, 6, w, otpChannel, pipelineError, cancel, &wg) + //wg.Add(1) + ////go utils.EncryptAESMailLink(preUserData, w, mailChannel, &wg, timerCreate) + ////wg.Add(1) + ////go services.CreateMailVerify(preUserData, otpChannel, mailChannel, w, errChan) + ////go services.WriteOTPInRedis(counter, preUserData, otpChannel, w) + ////wg.Done() + ////err := <-errChan + ////close(errChan) + ////if err != nil { + //// fmt.Println("Internal Log: Fail to send email check smtp") + //// return err + ////} + ////if err == nil { + //// _ = utils.WriteJSON(w, http.StatusOK, models.SuccessAPI{ + //// Message: "Success register account please verify your account", + //// }) + ////} + //// + wg.Wait() + ////create dummy error + //err = fmt.Errorf("dummy error") + select { + case <-done: + // Error occurred, wait for all goroutines to finish + wg.Wait() + case <-ctx.Done(): + // Context cancelled, wait for all goroutines to finish + wg.Wait() + } + return nil + + } + return nil +} diff --git a/rest-api/routes/auth.go b/rest-api/routes/auth.go index 104eab4..558f955 100644 --- a/rest-api/routes/auth.go +++ b/rest-api/routes/auth.go @@ -6,62 +6,12 @@ import ( "github.com/google/uuid" "github.com/gorilla/mux" "linhdevtran99/rest-api/models" + "linhdevtran99/rest-api/rest-api/controler" "linhdevtran99/rest-api/rest-api/services" "linhdevtran99/rest-api/utils" "net/http" - "time" ) -func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error { - if r.Method == http.MethodPost { - var registerInfo models.CreateUser - - _ = json.NewDecoder(r.Body).Decode(®isterInfo) - //[ok] - - //call function check info user type in - validRegisterInfo, responseAPI := services.CheckAndValidRegisterFiled(®isterInfo) - //[fairy ok] - - if responseAPI != nil { - return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err) - } - - fmt.Println(validRegisterInfo) - - //call function check data user use in past or not - - isValidData, responseAPI := services.CheckAccountValid(registerInfo.Username, registerInfo.Email) - - fmt.Println(isValidData) - - if isValidData != true { - return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err) - } - - preUserData := &models.PreusersMongo{ - Username: registerInfo.Username, - Email: registerInfo.Email, - PhoneNumber: registerInfo.PhoneNumber, - HashPassword: registerInfo.Password, - UUID: registerInfo.UUID, - CreatedDate: time.Now(), - UpdateDate: time.Now(), - VerifySentCount: 1, - } - - services.GenerateVerifyAccount(preUserData, w) - - //debug - //if isValidData == true || validRegisterInfo == true { - // return utils.WriteJSON(w, http.StatusOK, "USER HAVE VALID INFO FOR REGISTER ACCOUNT") - //} - //debug - - } - return nil -} - func VerifyWithOTP(w http.ResponseWriter, r *http.Request) error { if r.Method == http.MethodPost { @@ -103,7 +53,7 @@ func VerifyWithJWT(w http.ResponseWriter, r *http.Request) error { } func AuthRouterSetup(router *mux.Router) { authRouter := router.PathPrefix("/account").Subrouter() - authRouter.Handle("/register", utils.MakeHTTPHandlerFn(RegisterNewAccount)).Methods("POST") + authRouter.Handle("/register", utils.MakeHTTPHandlerFn(controler.RegisterNewAccount)).Methods("POST") authRouter.Handle("/register/verifyAccountOTP", utils.MakeHTTPHandlerFn(VerifyWithOTP)).Methods("POST") authRouter.Handle("/register/verifyAccountJWT", utils.MakeHTTPHandlerFn(VerifyWithJWT)).Methods("POST") } diff --git a/rest-api/services/register.go b/rest-api/services/register.go index 5fb97f6..fec7ce3 100644 --- a/rest-api/services/register.go +++ b/rest-api/services/register.go @@ -4,11 +4,13 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "github.com/go-mail/mail" "github.com/go-playground/validator/v10" "github.com/google/uuid" "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo" "golang.org/x/crypto/bcrypt" "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/utils" @@ -16,12 +18,13 @@ import ( "net/http" "os" "strconv" + "strings" "sync" "time" ) // CheckAndValidRegisterFiled Check the user register field is valid or not -func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *models.ResponseError) { +func CheckAndValidRegisterFiled(registerData *models.CreateUser, w http.ResponseWriter) (bool, *models.ResponseError) { _ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator) errs := models.Validate.Struct(registerData) type errStack []string @@ -61,20 +64,25 @@ func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *models. } } //handle repose error - return false, &models.ResponseError{Code: http.StatusTooManyRequests, Err: errorAPI} + return false, &models.ResponseError{Code: http.StatusBadRequest, Err: errorAPI} } return true, nil } -// CheckAccountExist Checking in db is user input same data in +// CheckAccountExist Checking in db is user input same data in add time to resend verify mail func CheckAccountValid(userName string, email string) (bool, *models.ResponseError) { //filter in mongodb - var isValid bool + var isError bool var errAPI *models.ResponseError var dataRedisRetrive models.RedisOTP + //create context handler with timeout + timeout := 2 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + filter := bson.D{ {"$or", bson.A{ bson.D{{"username", userName}}, @@ -82,13 +90,25 @@ func CheckAccountValid(userName string, email string) (bool, *models.ResponseErr }}, } - _, err := utils.User.FindOne(context.TODO(), filter).Raw() + cursor, err := utils.User.Find(ctx, filter) + if err != nil { - isValid = true - errAPI = nil - } else { + fmt.Println("Internal Log: Internal error") + isError = true + errAPI = &models.ResponseError{ + Code: http.StatusInternalServerError, + Err: models.ErrorAPI{ + Errors: []string{"Internal error"}, + Message: "Internal error please try again later", + Type: "InternalError", + }, + } + return isError, errAPI + } + + if cursor.Next(ctx) { fmt.Println("Internal Log: Email or username already had register") - isValid = false + isError = true errAPI = &models.ResponseError{ Code: http.StatusBadRequest, Err: models.ErrorAPI{ @@ -98,16 +118,34 @@ func CheckAccountValid(userName string, email string) (bool, *models.ResponseErr }, } } - retrievedValue, err := utils.Redis.Get(context.Background(), "otp:"+email).Result() - err = json.Unmarshal([]byte(retrievedValue), &dataRedisRetrive) + + retrievedValue, err := utils.Redis.Get(ctx, "otp:"+email).Result() if err != nil { - fmt.Println("Internal Log: Can't get data from redis") + + if strings.Contains(err.Error(), "dial tcp 10.10.0.217:6391: connect: connection refused") { + fmt.Println("Internal Log: Can not connect to redis server") + isError = true + errAPI = &models.ResponseError{ + Code: http.StatusInternalServerError, + Err: models.ErrorAPI{ + Errors: []string{"Internal error"}, + Message: "Internal error please try again later", + Type: "InternalError", + }, + } + return isError, errAPI + } + + if err.Error() == "redis: nil" { + fmt.Println("Internal Log: Can't get data from redis or data not exist or first time register") + } } + err = json.Unmarshal([]byte(retrievedValue), &dataRedisRetrive) timeDiff := time.Now().Unix() - dataRedisRetrive.CreatedDate if timeDiff < 30 { - isValid = false + isError = true fmt.Println("Internal Log: Rate limit send verify mail") errAPI = &models.ResponseError{ Code: http.StatusBadRequest, @@ -120,7 +158,7 @@ func CheckAccountValid(userName string, email string) (bool, *models.ResponseErr } - return isValid, errAPI + return isError, errAPI } // GeneratorOtp Generate OTP Verify Link and Qr Link @@ -129,30 +167,8 @@ const ( otpDigits = 6 ) -func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWriter) error { - var wg sync.WaitGroup - - otpChannel := make(chan models.OtpGenerate, 1) - mailChannel := make(chan models.MailVefiry, 1) - - counter, timerCreate := CheckAndWritePreuser(registerInfo, w) - wg.Add(1) - go utils.GenOTP(registerInfo, counter, otpDigits, w, otpChannel, &wg) - wg.Add(1) - go utils.EncryptAESMailLink(registerInfo, w, mailChannel, &wg, timerCreate) - go createMailVerify(registerInfo, otpChannel, mailChannel, w) - go writeOTPInRedis(counter, registerInfo, otpChannel, w) - - wg.Wait() - err := utils.WriteJSON(w, http.StatusOK, models.SuccessAPI{ - Message: "Success register account please verify your account", - }) - return err - -} - // CreateLinkVerify Create a alternative verify link -func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, mailChan chan models.MailVefiry, w http.ResponseWriter) { +func CreateMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, mailChan chan models.MailVefiry, w http.ResponseWriter, errChan chan error) { smtpPass := os.Getenv("SMTP_PASS") m := mail.NewMessage() @@ -179,64 +195,87 @@ func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.Ot // Send the email to Bob, Cora and Dan. if err := d.DialAndSend(m); err != nil { - fmt.Println("Internal Log: Fail to send email check smtp") - _ = utils.WriteJSONInternalError(w, "Fail to send email check smtp") - panic(err) + errChan <- err + defer func() { + _ = os.Remove("./temp/" + qrFileName) + }() + return } + errChan <- nil defer func() { _ = os.Remove("./temp/" + qrFileName) }() } -// check and write pre use into mongo db -func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWriter) (uint64, time.Time) { +// check and write pre use into mongodb +func CheckAndWritePreuser(registerInfo *models.PreusersMongo, wg *sync.WaitGroup, ctxCancel context.CancelFunc, errorChan error) (uint64, time.Time) { //generate timestamp timeCreate := time.Now() registerInfo.CreatedDate = timeCreate + //create context handler with timeout + timeout := 2 * time.Second + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() //checking email := registerInfo.Email count := 1 filter := bson.D{{"email", email}} - raw, err := utils.PreUserData.FindOne(context.Background(), filter).Raw() - if err != nil { + + raw, err := utils.PreUserData.FindOne(ctx, filter).Raw() + + if errors.Is(err, mongo.ErrClientDisconnected) { + errorChan = errors.New("can't connect to mongodb") + ctxCancel() + wg.Done() + fmt.Println("Internal Log: Can't connect to mongodb") + } + + if errors.Is(err, mongo.ErrNoDocuments) { hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10) if err != nil { + errorChan = errors.New("fail to hash password") + ctxCancel() + wg.Done() fmt.Println("Internal Log :Fail to hash password") - _ = utils.WriteJSONInternalError(w, "Fail to hash password") } registerInfo.HashPassword = string(hashed) _, err = utils.PreUserData.InsertOne(context.Background(), registerInfo) if err != nil { + errorChan = errors.New("can't insert data to PreUser") + ctxCancel() + wg.Done() fmt.Println("Internal Log: Can't Insert Data To PreUser") - _ = utils.WriteJSONInternalError(w, "Can't Insert Data To PreUser") } - return uint64(count), timeCreate - } else { - update := bson.D{ - {"$inc", bson.D{ - {"verify_sent_count", 1}, - }}, - {"$set", bson.D{ - {"update_date", timeCreate}, - }, - }} - - _, err := utils.PreUserData.UpdateOne(context.Background(), filter, update) - if err != nil { - fmt.Println("Internal log: Update document fail") - _ = utils.WriteJSONInternalError(w, "Update document fail") - } - fmt.Println() - count = int(raw.Lookup("verify_sent_count").Int32() + 1) + wg.Done() return uint64(count), timeCreate } + update := bson.D{ + {"$inc", bson.D{ + {"verify_sent_count", 1}, + }}, + {"$set", bson.D{ + {"update_date", timeCreate}, + }, + }} + + _, err = utils.PreUserData.UpdateOne(context.Background(), filter, update) + if err != nil { + errorChan = errors.New("update document fail") + ctxCancel() + wg.Done() + fmt.Println("Internal log: Update document fail") + } + count = int(raw.Lookup("verify_sent_count").Int32() + 1) + wg.Done() + return uint64(count), timeCreate + } //write otp in redis for valid if otp expired -func writeOTPInRedis(counter uint64, registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, w http.ResponseWriter) { +func WriteOTPInRedis(counter uint64, registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, w http.ResponseWriter) { otp := <-otpChan diff --git a/tmp/build-errors.log b/tmp/build-errors.log index bfd43cc..5e540c5 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 93944a9..e923254 100755 Binary files a/tmp/main and b/tmp/main differ diff --git a/utils/crypto.go b/utils/crypto.go index 42f1e1f..0a4b927 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -136,8 +136,8 @@ func DecryptAESMailLink(linkVerifyInfo *models.LinkVerify, w http.ResponseWriter //OTP+++++++++++++OTP// -// Gen Otp and hash otp -func GenOTP(registerInfo *models.PreusersMongo, counter uint64, otpDigits int, w http.ResponseWriter, otpChan chan models.OtpGenerate, wg *sync.WaitGroup) chan models.OtpGenerate { +// Gen OTP and hash OTP +func GenOTP(registerInfo *models.PreusersMongo, counter uint64, otpDigits int, w http.ResponseWriter, otpChan chan models.OtpGenerate, errorChan error, ctxCancel context.CancelFunc, wg *sync.WaitGroup) { serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + registerInfo.UUID + registerInfo.Email)) passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ @@ -146,14 +146,18 @@ func GenOTP(registerInfo *models.PreusersMongo, counter uint64, otpDigits int, w }) if err != nil { + errorChan = errors.New("fail to create OTP") + ctxCancel() + wg.Done() fmt.Println("Internal log: Fail to create OTP") - _ = WriteJSONInternalError(w, "Fail to create OTP") } hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) if err != nil { + errorChan = errors.New("fail to encrypt OTP") + ctxCancel() + wg.Done() fmt.Println("Internal log: Fail to encrypt OTP") - _ = WriteJSONInternalError(w, "Fail to encrypt OTP") } data := models.OtpGenerate{ @@ -162,10 +166,6 @@ func GenOTP(registerInfo *models.PreusersMongo, counter uint64, otpDigits int, w } otpChan <- data - otpChan <- data - wg.Done() - return otpChan - } //Decrypt OTP and verify otp diff --git a/utils/restapiType.go b/utils/restapiType.go index dad1d7e..e2e1398 100644 --- a/utils/restapiType.go +++ b/utils/restapiType.go @@ -19,6 +19,7 @@ func WriteJSON(w http.ResponseWriter, status int, v any) error { func WriteJSONInternalError(w http.ResponseWriter, v any) error { w.Header().Set("Content-Type", "application/json") + w.Header().Set("Access-Control-Allow-Origin", "*") w.WriteHeader(http.StatusInternalServerError) err := json.NewEncoder(w).Encode(v) if err != nil {