From 3869d24200159fff5f310f842fe1a7d9d6a67988 Mon Sep 17 00:00:00 2001 From: Linh Date: Mon, 4 Dec 2023 12:09:34 +0700 Subject: [PATCH] restruc my codebase --- .env | 4 +- models/models.go | 5 + rest-api/init-api.go | 253 +++------------------------------- rest-api/services/register.go | 143 +++++++++++++++++++ utils/crypto.go | 78 +++++++++++ 5 files changed, 247 insertions(+), 236 deletions(-) diff --git a/.env b/.env index b7451ca..0c1b390 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ -MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017/linhporo1?authSource=admin" +MONGO_URI="mongodb://adminLinh:linhporo1@10.10.0.216:27017/linhporo1?authSource=admin" API_TEST_PORT=":8080" -REDIS_URI="redis://localhost:6379/0" +REDIS_URI="redis://10.10.0.216:6379/0" STMP_PASS="btmp judz ebys pfxw" EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812" \ No newline at end of file diff --git a/models/models.go b/models/models.go index f3789c4..2a1baac 100644 --- a/models/models.go +++ b/models/models.go @@ -83,3 +83,8 @@ type EmailTemplate struct { Otp string `json:"otp"` AlternativeLink string `json:"alternativeLink"` } + +type OtpGenerate struct { + PureOTP string + HashOTP string +} diff --git a/rest-api/init-api.go b/rest-api/init-api.go index b5eb0de..c1ffab6 100644 --- a/rest-api/init-api.go +++ b/rest-api/init-api.go @@ -1,28 +1,16 @@ package rest_api import ( - "bytes" "context" - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base32" - "encoding/base64" "encoding/json" "errors" "fmt" - "github.com/go-playground/validator/v10" "github.com/gorilla/mux" - "github.com/pquerna/otp" - "github.com/pquerna/otp/hotp" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "golang.org/x/crypto/bcrypt" "linhdevtran99/rest-api/models" + "linhdevtran99/rest-api/rest-api/services" "linhdevtran99/rest-api/utils" "log" "net/http" - "os" ) type APIServer struct { @@ -108,24 +96,24 @@ func (s *APIServer) testCheckUserAndPass(w http.ResponseWriter, r *http.Request) // panic(err) //} - serect := os.Getenv("EMAIL_VERIFY_SECRET") + //serect := os.Getenv("EMAIL_VERIFY_SECRET") //isPass, _ := generatorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) //fmt.Println(isPass) - cipherBase64 := createLinkVerify(&models.CreateUser{ - Username: "thewind121212", - Email: "nhocdl.poro1@gmail.com", - Password: "linhporoQ1@", - ConfirmPassword: "linhporoQ1@", - }, serect) - - key := []byte(serect) - - i, err := decrypt(cipherBase64, key) - if err != nil { - return err - } - - fmt.Println(string(i)) + //cipherBase64 := createLinkVerify(&models.CreateUser{ + // Username: "thewind121212", + // Email: "nhocdl.poro1@gmail.com", + // Password: "linhporoQ1@", + // ConfirmPassword: "linhporoQ1@", + //}, serect) + // + //key := []byte(serect) + // + //i, err := utils.DecryptAES(cipherBase64, key) + //if err != nil { + // return err + //} + // + //fmt.Println(string(i)) } @@ -134,9 +122,9 @@ func (s *APIServer) testCheckUserAndPass(w http.ResponseWriter, r *http.Request) _ = json.NewDecoder(r.Body).Decode(®isterInfo) //call function check info user type in - validRegisterInfo := checkAndValidDataFiled(®isterInfo, w) + validRegisterInfo := services.CheckAndValidRegisterFiled(®isterInfo, w) //call function check data user use in past or not - isValidData := checkAccountExist(client, registerInfo.Username, registerInfo.Email, w) + isValidData := services.CheckAccountExist(client, registerInfo.Username, registerInfo.Email, w) if isValidData == false || validRegisterInfo == false { return errors.New("USER DON'T HAVE VALID INFO FOR REGISTER ACCOUNT") @@ -175,206 +163,3 @@ func (s *APIServer) handleAccount(w http.ResponseWriter, r *http.Request) error return nil } - -//register group-func - -//checking is user create that have account before - -func checkAndValidDataFiled(registerData *models.CreateUser, w http.ResponseWriter) bool { - _ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator) - errs := models.Validate.Struct(registerData) - var errStack []string - if errs != nil { - for _, err := range errs.(validator.ValidationErrors) { - switch err.Field() { - case "Email": - { - fmt.Println("Email không hợp lệ") - errStack = append(errStack, "Email") - } - case "Username": - { - fmt.Println("User không hợp lệ") - errStack = append(errStack, "User") - } - case "Password": - { - fmt.Println("Password không hợp lệ") - errStack = append(errStack, "Password") - } - case "ConfirmPassword": - { - fmt.Println("Nhập lại mật khẩu sai") - errStack = append(errStack, "ConfirmPassword") - } - case "PhoneNumber": - { - fmt.Println("SĐT không hợp lệ") - errStack = append(errStack, "PhoneNumber") - } - } - } - //handle repose error - w.WriteHeader(http.StatusBadRequest) - err := json.NewEncoder(w).Encode(errStack) - - if err != nil { - fmt.Println("There is error in write reponse at checking info user") - } - return false - } - - return true - -} - -func checkAccountExist(mongoClient *mongo.Client, userName string, email string, w http.ResponseWriter) bool { - //filter in mongodb - var isValid bool - - filter := bson.D{ - {"$or", bson.A{ - bson.D{{"username", userName}}, - bson.D{{"email", email}}, - }}, - } - - userData := mongoClient.Database("Totoday-shop").Collection("users") - - var result models.UsersMongo - err := userData.FindOne(context.TODO(), filter).Decode(&result) - if err != nil { - isValid = true - } - if err == nil { - fmt.Println("Email or username already had register") - w.WriteHeader(http.StatusBadRequest) - err := json.NewEncoder(w).Encode("Your username or email had been register before") - if err != nil { - fmt.Println("There is error in write reponse at checking data user register") - } - isValid = false - } - - return isValid -} - -//after cheking register data generate otp and send email write valid time to confirm in redis -// noice this will do concurency for speed reason - -type otpGenerate struct { - pureOTP string - hashOTP string -} - -func generatorOtp(userName string, email string, counter uint64, serect string) (bool, *otpGenerate) { - - serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + userName + email)) - passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ - Digits: 6, - Algorithm: otp.AlgorithmSHA256, - }) - - if err != nil { - fmt.Println("Fail to create OTP") - return false, &otpGenerate{ - pureOTP: "none", - hashOTP: "none", - } - } - - hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) - if err != nil { - fmt.Println("Fail to create OTP") - return false, &otpGenerate{ - pureOTP: "none", - hashOTP: "none", - } - } - - return true, &otpGenerate{ - pureOTP: passCode, - hashOTP: string(hashed), - } -} - -func createLinkVerify(registerInfo *models.CreateUser, secrect string) string { - - data, _ := json.Marshal(registerInfo) - key := []byte(secrect) - - ciphertext, _ := encrypt(data, key) - - cipherTextBase64 := base64.StdEncoding.EncodeToString(ciphertext) - - return cipherTextBase64 -} - -func encrypt(data []byte, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - // Generate a random IV (Initialization Vector) - iv := make([]byte, aes.BlockSize) - if _, err := rand.Read(iv); err != nil { - fmt.Println("error generating IV:", err) - } - - // Pad the data to a multiple of the block size - data = pkcs7Pad(data, aes.BlockSize) - - // Create a CBC mode cipher block - mode := cipher.NewCBCEncrypter(block, iv) - - // Encrypt the data - ciphertext := make([]byte, len(data)) - mode.CryptBlocks(ciphertext, data) - - // Prepend the IV to the ciphertext - ciphertext = append(iv, ciphertext...) - - return ciphertext, nil -} - -func decrypt(base64Ciphertext string, key []byte) ([]byte, error) { - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - // Decode base64 - ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) - if err != nil { - fmt.Println("error decoding base64:", err) - } - - // Extract the IV from the ciphertext - iv := ciphertext[:aes.BlockSize] - ciphertext = ciphertext[aes.BlockSize:] - - // Create a CBC mode cipher block - mode := cipher.NewCBCDecrypter(block, iv) - - // Decrypt the data - mode.CryptBlocks(ciphertext, ciphertext) - - // Remove padding - ciphertext = pkcs7Unpad(ciphertext) - - return ciphertext, nil -} - -// pkcs7Pad pads the input to a multiple of blockSize using PKCS#7 padding -func pkcs7Pad(data []byte, blockSize int) []byte { - padding := blockSize - len(data)%blockSize - padText := bytes.Repeat([]byte{byte(padding)}, padding) - return append(data, padText...) -} - -// pkcs7Unpad removes PKCS#7 padding from the input -func pkcs7Unpad(data []byte) []byte { - padding := int(data[len(data)-1]) - return data[:len(data)-padding] -} diff --git a/rest-api/services/register.go b/rest-api/services/register.go index 5e568ea..556d2ac 100644 --- a/rest-api/services/register.go +++ b/rest-api/services/register.go @@ -1 +1,144 @@ package services + +import ( + "context" + "encoding/base32" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/go-playground/validator/v10" + "github.com/pquerna/otp" + "github.com/pquerna/otp/hotp" + "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" + "net/http" +) + +// Check the user register field is valid or not +func CheckAndValidRegisterFiled(registerData *models.CreateUser, w http.ResponseWriter) bool { + _ = models.Validate.RegisterValidation("customPassword", models.PasswordValidator) + errs := models.Validate.Struct(registerData) + var errStack []string + if errs != nil { + for _, err := range errs.(validator.ValidationErrors) { + switch err.Field() { + case "Email": + { + fmt.Println("Email không hợp lệ") + errStack = append(errStack, "Email") + } + case "Username": + { + fmt.Println("User không hợp lệ") + errStack = append(errStack, "User") + } + case "Password": + { + fmt.Println("Password không hợp lệ") + errStack = append(errStack, "Password") + } + case "ConfirmPassword": + { + fmt.Println("Nhập lại mật khẩu sai") + errStack = append(errStack, "ConfirmPassword") + } + case "PhoneNumber": + { + fmt.Println("SĐT không hợp lệ") + errStack = append(errStack, "PhoneNumber") + } + } + } + //handle repose error + w.WriteHeader(http.StatusBadRequest) + err := json.NewEncoder(w).Encode(errStack) + + if err != nil { + fmt.Println("There is error in write reponse at checking info user") + } + return false + } + + return true + +} + +// Checking in db is user input same data in +func CheckAccountExist(mongoClient *mongo.Client, userName string, email string, w http.ResponseWriter) bool { + //filter in mongodb + var isValid bool + + filter := bson.D{ + {"$or", bson.A{ + bson.D{{"username", userName}}, + bson.D{{"email", email}}, + }}, + } + + userData := mongoClient.Database("Totoday-shop").Collection("users") + + var result models.UsersMongo + err := userData.FindOne(context.TODO(), filter).Decode(&result) + if err != nil { + isValid = true + } + if err == nil { + fmt.Println("Email or username already had register") + w.WriteHeader(http.StatusBadRequest) + err := json.NewEncoder(w).Encode("Your username or email had been register before") + if err != nil { + fmt.Println("There is error in write reponse at checking data user register") + } + isValid = false + } + + return isValid +} + +// Generate HOtp for confirm infomation +func GeneratorOtp(userName string, email string, counter uint64, serect string) (bool, *models.OtpGenerate) { + + serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + userName + email)) + passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ + Digits: 6, + Algorithm: otp.AlgorithmSHA256, + }) + + if err != nil { + fmt.Println("Fail to create OTP") + return false, &models.OtpGenerate{ + PureOTP: "none", + HashOTP: "none", + } + } + + hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) + if err != nil { + fmt.Println("Fail to create OTP") + return false, &models.OtpGenerate{ + PureOTP: "none", + HashOTP: "none", + } + } + + return true, &models.OtpGenerate{ + PureOTP: passCode, + HashOTP: string(hashed), + } +} + +// Create a alternative verify link +func CreateLinkVerify(registerInfo *models.CreateUser, secrect string) string { + + data, _ := json.Marshal(registerInfo) + key := []byte(secrect) + + ciphertext, _ := utils.EncryptAES(data, key) + + cipherTextBase64 := base64.StdEncoding.EncodeToString(ciphertext) + + return cipherTextBase64 +} diff --git a/utils/crypto.go b/utils/crypto.go index d4b585b..c2d8e7a 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -1 +1,79 @@ package utils + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" +) + +func EncryptAES(data []byte, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Generate a random IV (Initialization Vector) + iv := make([]byte, aes.BlockSize) + if _, err := rand.Read(iv); err != nil { + fmt.Println("error generating IV:", err) + } + + // Pad the data to a multiple of the block size + data = pkcs7Pad(data, aes.BlockSize) + + // Create a CBC mode cipher block + mode := cipher.NewCBCEncrypter(block, iv) + + // Encrypt the data + ciphertext := make([]byte, len(data)) + mode.CryptBlocks(ciphertext, data) + + // Prepend the IV to the ciphertext + ciphertext = append(iv, ciphertext...) + + return ciphertext, nil +} + +func DecryptAES(base64Ciphertext string, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // Decode base64 + ciphertext, err := base64.StdEncoding.DecodeString(base64Ciphertext) + if err != nil { + fmt.Println("error decoding base64:", err) + } + + // Extract the IV from the ciphertext + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + + // Create a CBC mode cipher block + mode := cipher.NewCBCDecrypter(block, iv) + + // Decrypt the data + mode.CryptBlocks(ciphertext, ciphertext) + + // Remove padding + ciphertext = pkcs7Unpad(ciphertext) + + return ciphertext, nil +} + +// pkcs7Pad pads the input to a multiple of blockSize using PKCS#7 padding +func pkcs7Pad(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padText...) +} + +// pkcs7Unpad removes PKCS#7 padding from the input +func pkcs7Unpad(data []byte) []byte { + padding := int(data[len(data)-1]) + return data[:len(data)-padding] +}