soft finishing register verify with otp, mail link and qr code

This commit is contained in:
Trần Duy Linh 2023-12-07 21:07:05 +07:00
parent 47816debd3
commit 89913a91ff
9 changed files with 191 additions and 99 deletions

2
.env
View File

@ -1,5 +1,5 @@
MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017" MONGO_URI="mongodb://adminLinh:linhporo1@localhost:27017"
API_TEST_PORT=":8080" API_TEST_PORT=":8080"
REDIS_URI="redis://localhost:6379/0" 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" EMAIL_VERIFY_SECRET="WDc&4+&vYP(n'}?LHNE#5M?IE|g(c812"

View File

@ -235,6 +235,14 @@
instead. instead.
</p> </p>
<div style="text-align: center;">
<img
src="{{ .QrCode }}"
height="200"
width="200"
alt="qrcode"
/>
</div>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -3,6 +3,7 @@ package models
import ( import (
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/bson/primitive"
"html/template"
"strings" "strings"
"time" "time"
"unicode" "unicode"
@ -81,12 +82,18 @@ func PasswordValidator(fl validator.FieldLevel) bool {
//Interal Type //Interal Type
type EmailTemplate struct {
Otp string `json:"otp"`
AlternativeLink string `json:"alternativeLink"`
}
type OtpGenerate struct { type OtpGenerate struct {
PureOTP string PureOTP string
HashOTP string HashOTP string
} }
type MailVefiry struct {
LinkMail string
ImageBase64 string
}
type EmailTemplate struct {
Otp string `json:"otp"`
AlternativeLink string `json:"alternativeLink"`
QrCode template.URL
}

View File

@ -5,7 +5,6 @@ import (
"github.com/gorilla/mux" "github.com/gorilla/mux"
"linhdevtran99/rest-api/models" "linhdevtran99/rest-api/models"
"linhdevtran99/rest-api/rest-api/routes" "linhdevtran99/rest-api/rest-api/routes"
"linhdevtran99/rest-api/rest-api/services"
"linhdevtran99/rest-api/utils" "linhdevtran99/rest-api/utils"
"log" "log"
"net/http" "net/http"
@ -45,22 +44,6 @@ func (s *APIServer) TestRoute(w http.ResponseWriter, r *http.Request) error {
if r.Method == http.MethodGet { if r.Method == http.MethodGet {
fmt.Println("hit") 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") //serect := os.Getenv("EMAIL_VERIFY_SECRET")
//_, otp := services.GeneratorOtp("hello", "nhocdl.poro1@gmail.com", 12, serect) //_, 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) //fmt.Println(otp.PureOTP)
//utils.EncryptAESMailLink("nhocdl.poro2@gmail.com", serect, w) //utils.EncryptAESMailLink("nhocdl.poro2@gmail.com", serect, w)
preUserData := &models.PreusersMongo{
//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{
Username: "thewind121212", Username: "thewind121212",
Email: "nhocdl.poro2@gmail.com", Email: "nhocdl.poro1@gmail.com",
PhoneNumber: "0918327132", PhoneNumber: "0918327132",
VerifySentCount: 0, HashPassword: "it ok now ",
CreatedDate: time.Now(), CreatedDate: time.Now(),
UpdateDate: time.Now(), UpdateDate: time.Now(),
}) VerifySentCount: 1,
}
// _ = preUserData
//services.WriteOTPInRedis(preUserData, "tranduy linh ", w)
} }

View File

@ -21,7 +21,7 @@ func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error {
return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error())
} }
//call function check data user use in past or not //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 { if responseAPI != nil {
return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error()) return utils.WriteJSON(w, responseAPI.Code, responseAPI.Err.Error())
} }
@ -36,7 +36,7 @@ func RegisterNewAccount(w http.ResponseWriter, r *http.Request) error {
VerifySentCount: 1, VerifySentCount: 1,
} }
services.CheckAndWritePreuser(preUserData) services.GenerateVerifyAccount(preUserData, w)
//debug //debug
if isValidData == true || validRegisterInfo == true { if isValidData == true || validRegisterInfo == true {

View File

@ -2,19 +2,20 @@ package services
import ( import (
"context" "context"
"encoding/base32" "encoding/base64"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"github.com/go-mail/mail"
"github.com/go-playground/validator/v10" "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/bson"
"go.mongodb.org/mongo-driver/mongo"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"linhdevtran99/rest-api/models" "linhdevtran99/rest-api/models"
"linhdevtran99/rest-api/utils" "linhdevtran99/rest-api/utils"
"log"
"net/http" "net/http"
"os"
"sync"
"time" "time"
) )
@ -68,7 +69,7 @@ func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *Respons
} }
// CheckAccountExist Checking in db is user input same data in // 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 //filter in mongodb
var isValid bool var isValid bool
var errAPI *ResponseError 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 { if err != nil {
isValid = true isValid = true
errAPI = nil errAPI = nil
@ -96,55 +97,89 @@ func CheckAccountExist(mongoClient *mongo.Client, userName string, email string)
return isValid, errAPI 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)) func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWriter) {
passCode, err := hotp.GenerateCodeCustom(serectBase32, counter, hotp.ValidateOpts{ var wg sync.WaitGroup
Digits: 6,
Algorithm: otp.AlgorithmSHA256,
})
if err != nil { otpChannel := make(chan models.OtpGenerate, 1)
fmt.Println("Fail to create OTP") mailChannel := make(chan models.MailVefiry, 1)
return false, nil
}
hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5) counter := CheckAndWritePreuser(registerInfo, w)
if err != nil { wg.Add(1)
fmt.Println("Fail to create OTP") go utils.GenOTP(registerInfo, counter, otpDigits, w, otpChannel, &wg)
return false, nil 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 // CreateLinkVerify Create a alternative verify link
func CreateLinkVerify(registerInfo string, secrect string) string { func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.OtpGenerate, mailChan chan models.MailVefiry, w http.ResponseWriter) {
return "linh" 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 // check and write pre use into mongo db
func CheckAndWritePreuser(registerInfo *models.PreusersMongo) { func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWriter) uint64 {
//checking //checking
email := registerInfo.Email email := registerInfo.Email
count := 1
filter := bson.D{{"email", email}} 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 { if err != nil {
hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10) hashed, err := bcrypt.GenerateFromPassword([]byte(registerInfo.HashPassword), 10)
if err != nil { 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) registerInfo.HashPassword = string(hashed)
_, err = utils.PreUserData.InsertOne(context.Background(), registerInfo) _, err = utils.PreUserData.InsertOne(context.Background(), registerInfo)
if err != nil { 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 { } else {
update := bson.D{ update := bson.D{
{"$inc", bson.D{ {"$inc", bson.D{
@ -157,9 +192,36 @@ func CheckAndWritePreuser(registerInfo *models.PreusersMongo) {
_, err := utils.PreUserData.UpdateOne(context.Background(), filter, update) _, err := utils.PreUserData.UpdateOne(context.Background(), filter, update)
if err != nil { 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())
}

View File

@ -1,15 +1,26 @@
package utils package utils
import ( import (
"encoding/base32"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/pquerna/otp"
"github.com/pquerna/otp/hotp"
"github.com/skip2/go-qrcode" "github.com/skip2/go-qrcode"
"golang.org/x/crypto/bcrypt"
"linhdevtran99/rest-api/models"
"net/http" "net/http"
"os"
"strings" "strings"
"sync"
"time" "time"
) )
//const
var serect = os.Getenv("EMAIL_VERIFY_SECRET")
//Mail //Mail
type MyCustomClaims struct { type MyCustomClaims struct {
@ -20,21 +31,19 @@ type MyCustomClaims struct {
func buildTokenLink(token string, w http.ResponseWriter) (string, string) { func buildTokenLink(token string, w http.ResponseWriter) (string, string) {
tokenCustomTrim := strings.ReplaceAll(token, ".", "&") tokenCustomTrim := strings.ReplaceAll(token, ".", "&")
emailVerifyLink := "http://www.totoday.com/?p=" + tokenCustomTrim emailVerifyLink := "http://www.totoday.com/?p=" + tokenCustomTrim
fmt.Println(emailVerifyLink)
png, err := qrcode.Encode(emailVerifyLink, qrcode.Low, 200) png, err := qrcode.Encode(emailVerifyLink, qrcode.Low, 200)
if err != nil { if err != nil {
fmt.Println("Internal log: error create qr ") fmt.Println("Internal log: error create qr ")
_ = WriteJSONInternalError(w, "error create QR code") _ = WriteJSONInternalError(w, "error create QR code")
} }
base64Image := base64.StdEncoding.EncodeToString(png) 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{ claims := MyCustomClaims{
data, registerInfo.Email,
jwt.RegisteredClaims{ jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()), 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 := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token.Header["purpose"] = "Email_Verify" token.Header["purpose"] = "Email_Verify"
tokenString, err := token.SignedString([]byte(key)) tokenString, err := token.SignedString([]byte(serect))
if err != nil { if err != nil {
fmt.Println("Internal Log: Error signed token fail", err.Error()) fmt.Println("Internal Log: Error signed token fail", err.Error())
_ = WriteJSONInternalError(w, "Error signing token fail") _ = 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 //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) { if err != nil {
// //checking does it valid or have in redis or not fmt.Println("Internal log: Fail to create OTP")
// //var count int _ = WriteJSONInternalError(w, "Fail to create OTP")
// exists, err := Redis.Exists(context.Background(), "otp:nhocdl.poro1@gmail.com").Result() }
// if err != nil {
// fmt.Println("something went wrong") hashed, err := bcrypt.GenerateFromPassword([]byte(passCode), 5)
// } if err != nil {
// if exists == 0 { fmt.Println("Internal log: Fail to encrypt OTP")
// //count = 0 _ = WriteJSONInternalError(w, "Fail to encrypt OTP")
// }
// }
//} data := models.OtpGenerate{
PureOTP: passCode,
HashOTP: string(hashed),
}
otpChan <- data
otpChan <- data
wg.Done()
return otpChan
}

View File

@ -7,13 +7,17 @@ import (
"linhdevtran99/rest-api/models" "linhdevtran99/rest-api/models"
) )
func BuildEmail() string { func BuildEmail(otp string, mailLink string, fileName string) string {
qrcodeURL := template.URL("cid:" + fileName)
data := models.EmailTemplate{ data := models.EmailTemplate{
Otp: "11017", Otp: otp,
AlternativeLink: "https://www.google.com.vn", AlternativeLink: mailLink,
QrCode: qrcodeURL,
} }
//tmpl, err := template.ParseFiles("./Template/email.html")
tmpl, err := template.ParseFiles("./Template/email.html") tmpl, err := template.ParseFiles("./Template/email.html")
if err != nil { if err != nil {

View File

@ -12,6 +12,7 @@ import (
var MongoDB *mongo.Client var MongoDB *mongo.Client
var PreUserData *mongo.Collection var PreUserData *mongo.Collection
var User *mongo.Collection
func InitMongoDriver(mongoUri string) *mongo.Client { func InitMongoDriver(mongoUri string) *mongo.Client {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
@ -32,6 +33,7 @@ func InitMongoDriver(mongoUri string) *mongo.Client {
MongoDB = client MongoDB = client
PreUserData = MongoDB.Database("Totoday-shop").Collection("preusers") PreUserData = MongoDB.Database("Totoday-shop").Collection("preusers")
User = MongoDB.Database("Totoday-shop").Collection("users")
return client return client
} }