From 89913a91ff8ebe6b7afd03e3ea26c0b126c71f45 Mon Sep 17 00:00:00 2001 From: linhdevtran99 Date: Thu, 7 Dec 2023 21:07:05 +0700 Subject: [PATCH] soft finishing register verify with otp, mail link and qr code --- .env | 2 +- Template/email.html | 8 +++ models/models.go | 17 +++-- rest-api/init-api.go | 37 +++------- rest-api/routes/auth.go | 4 +- rest-api/services/register.go | 130 +++++++++++++++++++++++++--------- utils/crypto.go | 80 ++++++++++++++------- utils/mail-template.go | 10 ++- utils/mongodb.go | 2 + 9 files changed, 191 insertions(+), 99 deletions(-) diff --git a/.env b/.env index 7de2f47..70ce67d 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017" API_TEST_PORT=":8080" REDIS_URI="redis://localhost:6379/0" -STMP_PASS="btmp judz ebys pfxw" +SMTP_PASS="btmp judz ebys pfxw" EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812" diff --git a/Template/email.html b/Template/email.html index 8dbd674..d227ca7 100644 --- a/Template/email.html +++ b/Template/email.html @@ -235,6 +235,14 @@ instead.

+
+ qrcode +
diff --git a/models/models.go b/models/models.go index 47d14eb..ea58cfc 100644 --- a/models/models.go +++ b/models/models.go @@ -3,6 +3,7 @@ package models import ( "github.com/go-playground/validator/v10" "go.mongodb.org/mongo-driver/bson/primitive" + "html/template" "strings" "time" "unicode" @@ -81,12 +82,18 @@ func PasswordValidator(fl validator.FieldLevel) bool { //Interal Type -type EmailTemplate struct { - Otp string `json:"otp"` - AlternativeLink string `json:"alternativeLink"` -} - type OtpGenerate struct { PureOTP string HashOTP string } + +type MailVefiry struct { + LinkMail string + ImageBase64 string +} + +type EmailTemplate struct { + Otp string `json:"otp"` + AlternativeLink string `json:"alternativeLink"` + QrCode template.URL +} diff --git a/rest-api/init-api.go b/rest-api/init-api.go index 7a159e3..db07dfa 100644 --- a/rest-api/init-api.go +++ b/rest-api/init-api.go @@ -5,7 +5,6 @@ import ( "github.com/gorilla/mux" "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/rest-api/routes" - "linhdevtran99/rest-api/rest-api/services" "linhdevtran99/rest-api/utils" "log" "net/http" @@ -45,22 +44,6 @@ func (s *APIServer) TestRoute(w http.ResponseWriter, r *http.Request) error { if r.Method == http.MethodGet { fmt.Println("hit") - //m := mail.NewMessage() - // - //emailBody := utils.BuildEmail() - //m.SetHeader("From", "kotomi.poro1@gmail.com") - //m.SetHeader("To", "nhocdl.poro1@gmail.com") - //m.SetHeader("Subject", "Hello!") - //m.SetBody("text/html", emailBody) - // - //d := mail.NewDialer("smtp.gmail.com", 587, "kotomi.poro1@gmail.com", "btmpjudzebyspfxw") - //d.StartTLSPolicy = mail.MandatoryStartTLS - // - //// Send the email to Bob, Cora and Dan. - //if err := d.DialAndSend(m); err != nil { - // panic(err) - //} - //serect := os.Getenv("EMAIL_VERIFY_SECRET") //_, otp := services.GeneratorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) @@ -68,23 +51,19 @@ func (s *APIServer) TestRoute(w http.ResponseWriter, r *http.Request) error { //fmt.Println(otp.PureOTP) //utils.EncryptAESMailLink("nhocdl.poro2@gmail.com", serect, w) - - //jsonData, _ := json.Marshal(map[string]string{"email": "nhocdl.poro1@gmail.com", "user": "thewind121212"}) - //// - //utils.Redis.Set(context.Background(), "otp:nhocdl.poro1@gmail.com", string(jsonData), time.Minute) - // - //utils.CheckAndWriteRedis("nhocdl.poro1@gmail.com", "thewind121212", "lasdjflasdjlfj") - - services.CheckAndWritePreuser(&models.PreusersMongo{ + preUserData := &models.PreusersMongo{ Username: "thewind121212", - Email: "nhocdl.poro2@gmail.com", + Email: "nhocdl.poro1@gmail.com", PhoneNumber: "0918327132", - VerifySentCount: 0, + HashPassword: "it ok now ", CreatedDate: time.Now(), UpdateDate: time.Now(), - }) + VerifySentCount: 1, + } - // + _ = preUserData + + //services.WriteOTPInRedis(preUserData, "tranduy linh ", w) } diff --git a/rest-api/routes/auth.go b/rest-api/routes/auth.go index a18e996..1bf4ec3 100644 --- a/rest-api/routes/auth.go +++ b/rest-api/routes/auth.go @@ -21,7 +21,7 @@ func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error { return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) } //call function check data user use in past or not - isValidData, responseAPI := services.CheckAccountExist(utils.MongoDB, registerInfo.Username, registerInfo.Email) + isValidData, responseAPI := services.CheckAccountExist(registerInfo.Username, registerInfo.Email) if responseAPI != nil { return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) } @@ -36,7 +36,7 @@ func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error { VerifySentCount: 1, } - services.CheckAndWritePreuser(preUserData) + services.GenerateVerifyAccount(preUserData, w) //debug if isValidData == true || validRegisterInfo == true { diff --git a/rest-api/services/register.go b/rest-api/services/register.go index 4fe5e57..86df1e1 100644 --- a/rest-api/services/register.go +++ b/rest-api/services/register.go @@ -2,19 +2,20 @@ package services import ( "context" - "encoding/base32" + "encoding/base64" "encoding/json" "errors" "fmt" + "github.com/go-mail/mail" "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" + "log" "net/http" + "os" + "sync" "time" ) @@ -68,7 +69,7 @@ func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *Respons } // CheckAccountExist Checking in db is user input same data in -func CheckAccountExist(mongoClient *mongo.Client, userName string, email string) (bool, *ResponseError) { +func CheckAccountExist(userName string, email string) (bool, *ResponseError) { //filter in mongodb var isValid bool var errAPI *ResponseError @@ -80,7 +81,7 @@ func CheckAccountExist(mongoClient *mongo.Client, userName string, email string) }}, } - _, err := utils.PreUserData.FindOne(context.TODO(), filter).Raw() + _, err := utils.User.FindOne(context.TODO(), filter).Raw() if err != nil { isValid = true errAPI = nil @@ -96,55 +97,89 @@ func CheckAccountExist(mongoClient *mongo.Client, userName string, email string) return isValid, errAPI } -// GeneratorOtp Generate HOtp for confirm information +// GeneratorOtp Generate OTP Verify Link and Qr Link -func GeneratorOtp(userName string, email string, counter uint64, serect string) (bool, *models.OtpGenerate) { +const ( + otpDigits = 6 + mailValidTime = time.Hour * 24 +) - serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + userName + email)) - passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ - Digits: 6, - Algorithm: otp.AlgorithmSHA256, - }) +func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWriter) { + var wg sync.WaitGroup - if err != nil { - fmt.Println("Fail to create OTP") - return false, nil - } + otpChannel := make(chan models.OtpGenerate, 1) + mailChannel := make(chan models.MailVefiry, 1) - hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) - if err != nil { - fmt.Println("Fail to create OTP") - return false, nil - } + counter := 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) + go createMailVerify(registerInfo, otpChannel, mailChannel, w) + go writeOTPInRedis(registerInfo, otpChannel, w) + + wg.Wait() - return true, &models.OtpGenerate{ - PureOTP: passCode, - HashOTP: string(hashed), - } } // CreateLinkVerify Create a alternative verify link -func CreateLinkVerify(registerInfo string, secrect string) string { - return "linh" +func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, mailChan chan models.MailVefiry, w http.ResponseWriter) { + smtpPass := os.Getenv("SMTP_PASS") + m := mail.NewMessage() + opt := <-otpChan + mailVerify := <-mailChan + + decodedImage, err := base64.StdEncoding.DecodeString(mailVerify.ImageBase64) + if err != nil { + log.Println("Error decoding base64 image:", err) + } + + qrFileName := registerInfo.Email + registerInfo.Username + ".png" + os.WriteFile("./temp/"+qrFileName, decodedImage, 0666) + emailBody := utils.BuildEmail(opt.PureOTP, mailVerify.LinkMail, qrFileName) + + m.SetHeader("From", "kotomi.poro1@gmail.com") + m.SetHeader("To", registerInfo.Email) + m.SetHeader("Subject", "Thanks For Join My Business") + m.SetBody("text/html", emailBody) + m.Embed("./temp/" + qrFileName) + + d := mail.NewDialer("smtp.gmail.com", 587, "kotomi.poro1@gmail.com", smtpPass) + d.StartTLSPolicy = mail.MandatoryStartTLS + + // 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) + } + + defer func() { + os.Remove("./temp/" + qrFileName) + }() } // check and write pre use into mongo db -func CheckAndWritePreuser(registerInfo *models.PreusersMongo) { +func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWriter) uint64 { //checking email := registerInfo.Email + count := 1 filter := bson.D{{"email", email}} - _, err := utils.PreUserData.FindOne(context.Background(), filter).Raw() + raw, err := utils.PreUserData.FindOne(context.Background(), filter).Raw() if err != nil { hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10) if err != nil { - fmt.Println("Fail to create OTP") + fmt.Println("Internal Log :Fail to create OTP") + _ = utils.WriteJSONInternalError(w, "Fail to create OTP") } registerInfo.HashPassword = string(hashed) _, err = utils.PreUserData.InsertOne(context.Background(), registerInfo) if err != nil { - fmt.Println("Something Went Wrong") + fmt.Println("Internal Log: Can't Insert Data To PreUser") + _ = utils.WriteJSONInternalError(w, "Can't Insert Data To PreUser") } + return uint64(count) } else { update := bson.D{ {"$inc", bson.D{ @@ -157,9 +192,36 @@ func CheckAndWritePreuser(registerInfo *models.PreusersMongo) { _, err := utils.PreUserData.UpdateOne(context.Background(), filter, update) if err != nil { - fmt.Println("Internal log: update document fail") + fmt.Println("Internal log: Update document fail") + _ = utils.WriteJSONInternalError(w, "Update document fail") } - + fmt.Println() + count = int(raw.Lookup("verify_sent_count").Int32() + 1) + return uint64(count) } } + +//write otp in redis for valid if otp expired + +func writeOTPInRedis(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, w http.ResponseWriter) { + + otp := <-otpChan + + data := map[string]string{ + "email": registerInfo.Email, + "user": registerInfo.Username, + "create_date": registerInfo.CreatedDate.String(), + "hashOTP": otp.HashOTP, + } + + jsonData, err := json.Marshal(data) + if err != nil { + fmt.Println("Internal log: Can't stringfy json data") + _ = utils.WriteJSONInternalError(w, "Can't stringfy json data") + } + status := utils.Redis.Set(context.Background(), "otp:nhocdl.poro1@gmail.com", string(jsonData), time.Minute*2) + + fmt.Println(status.Err()) + +} diff --git a/utils/crypto.go b/utils/crypto.go index 1ba2527..527c9c7 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -1,15 +1,26 @@ package utils import ( + "encoding/base32" "encoding/base64" "fmt" "github.com/golang-jwt/jwt/v5" + "github.com/pquerna/otp" + "github.com/pquerna/otp/hotp" "github.com/skip2/go-qrcode" + "golang.org/x/crypto/bcrypt" + "linhdevtran99/rest-api/models" "net/http" + "os" "strings" + "sync" "time" ) +//const + +var serect = os.Getenv("EMAIL_VERIFY_SECRET") + //Mail type MyCustomClaims struct { @@ -20,21 +31,19 @@ type MyCustomClaims struct { func buildTokenLink(token string, w http.ResponseWriter) (string, string) { tokenCustomTrim := strings.ReplaceAll(token, ".", "&") emailVerifyLink := "http://www.totoday.com/?p=" + tokenCustomTrim - fmt.Println(emailVerifyLink) png, err := qrcode.Encode(emailVerifyLink, qrcode.Low, 200) if err != nil { fmt.Println("Internal log: error create qr ") _ = WriteJSONInternalError(w, "error create QR code") } base64Image := base64.StdEncoding.EncodeToString(png) - dataURL := "data:image/png;base64," + base64Image - return emailVerifyLink, dataURL + return emailVerifyLink, base64Image } -func EncryptAESMailLink(data string, key string, w http.ResponseWriter) (string, string) { +func EncryptAESMailLink(registerInfo *models.PreusersMongo, w http.ResponseWriter, mailChan chan models.MailVefiry, wg *sync.WaitGroup) chan models.MailVefiry { claims := MyCustomClaims{ - data, + registerInfo.Email, jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), IssuedAt: jwt.NewNumericDate(time.Now()), @@ -46,13 +55,24 @@ func EncryptAESMailLink(data string, key string, w http.ResponseWriter) (string, token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token.Header["purpose"] = "Email_Verify" - tokenString, err := token.SignedString([]byte(key)) + tokenString, err := token.SignedString([]byte(serect)) if err != nil { fmt.Println("Internal Log: Error signed token fail", err.Error()) _ = WriteJSONInternalError(w, "Error signing token fail") } - return buildTokenLink(tokenString, w) + linkMail, imageB64 := buildTokenLink(tokenString, w) + + data := models.MailVefiry{ + LinkMail: linkMail, + ImageBase64: imageB64, + } + + mailChan <- data + + wg.Done() + return mailChan + } // @@ -60,24 +80,34 @@ func EncryptAESMailLink(data string, key string, w http.ResponseWriter) (string, //} //OTP -//Write preuser to mongo db -//func WriteToMongo(registerInfo *models.PreusersMongo) { -// -// -//} +func GenOTP(registerInfo *models.PreusersMongo, counter uint64, otpDigits int, w http.ResponseWriter, otpChan chan models.OtpGenerate, wg *sync.WaitGroup) chan models.OtpGenerate { -//Write otp to redis + serectBase32 := base32.StdEncoding.EncodeToString([]byte(serect + registerInfo.Username + registerInfo.Email)) + passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ + Digits: otp.Digits(otpDigits), + Algorithm: otp.AlgorithmSHA256, + }) -//func CheckAndWriteRedis(email string, username string, hashOTP string) { -// //checking does it valid or have in redis or not -// //var count int -// exists, err := Redis.Exists(context.Background(), "otp:nhocdl.poro1@gmail.com").Result() -// if err != nil { -// fmt.Println("something went wrong") -// } -// if exists == 0 { -// //count = 0 -// -// } -//} + if err != nil { + fmt.Println("Internal log: Fail to create OTP") + _ = WriteJSONInternalError(w, "Fail to create OTP") + } + + hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) + if err != nil { + fmt.Println("Internal log: Fail to encrypt OTP") + _ = WriteJSONInternalError(w, "Fail to encrypt OTP") + } + + data := models.OtpGenerate{ + PureOTP: passCode, + HashOTP: string(hashed), + } + + otpChan <- data + otpChan <- data + wg.Done() + return otpChan + +} diff --git a/utils/mail-template.go b/utils/mail-template.go index 1062835..1826cd9 100644 --- a/utils/mail-template.go +++ b/utils/mail-template.go @@ -7,13 +7,17 @@ import ( "linhdevtran99/rest-api/models" ) -func BuildEmail() string { +func BuildEmail(otp string, mailLink string, fileName string) string { + + qrcodeURL := template.URL("cid:" + fileName) data := models.EmailTemplate{ - Otp: "11017", - AlternativeLink: "https://www.google.com.vn", + Otp: otp, + AlternativeLink: mailLink, + QrCode: qrcodeURL, } + //tmpl, err := template.ParseFiles("./Template/email.html") tmpl, err := template.ParseFiles("./Template/email.html") if err != nil { diff --git a/utils/mongodb.go b/utils/mongodb.go index 1bccfea..b54dfd8 100644 --- a/utils/mongodb.go +++ b/utils/mongodb.go @@ -12,6 +12,7 @@ import ( var MongoDB *mongo.Client var PreUserData *mongo.Collection +var User *mongo.Collection func InitMongoDriver(mongoUri string) *mongo.Client { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -32,6 +33,7 @@ func InitMongoDriver(mongoUri string) *mongo.Client { MongoDB = client PreUserData = MongoDB.Database("Totoday-shop").Collection("preusers") + User = MongoDB.Database("Totoday-shop").Collection("users") return client }