247 lines
6.8 KiB
Go
247 lines
6.8 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/go-mail/mail"
|
|
"github.com/go-playground/validator/v10"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"linhdevtran99/rest-api/models"
|
|
"linhdevtran99/rest-api/utils"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
type ResponseError struct {
|
|
Code int
|
|
Err error
|
|
}
|
|
|
|
// CheckAndValidRegisterFiled Check the user register field is valid or not
|
|
func CheckAndValidRegisterFiled(registerData *models.CreateUser) (bool, *ResponseError) {
|
|
_ = 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("Internal Log: Email không hợp lệ")
|
|
errStack = append(errStack, "Email")
|
|
}
|
|
case "Username":
|
|
{
|
|
fmt.Println("Internal Log: User không hợp lệ")
|
|
errStack = append(errStack, "User")
|
|
}
|
|
case "Password":
|
|
{
|
|
fmt.Println("Internal Log: Password không hợp lệ")
|
|
errStack = append(errStack, "Password")
|
|
}
|
|
case "ConfirmPassword":
|
|
{
|
|
fmt.Println("Internal Log: Nhập lại mật khẩu sai")
|
|
errStack = append(errStack, "ConfirmPassword")
|
|
}
|
|
case "PhoneNumber":
|
|
{
|
|
fmt.Println("Internal Log: SĐT không hợp lệ")
|
|
errStack = append(errStack, "PhoneNumber")
|
|
}
|
|
}
|
|
}
|
|
//handle repose error
|
|
jsonData, _ := json.Marshal(errStack)
|
|
return false, &ResponseError{Code: http.StatusBadRequest, Err: errors.New(string(jsonData))}
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
// CheckAccountExist Checking in db is user input same data in
|
|
func CheckAccountValid(userName string, email string) (bool, *ResponseError) {
|
|
//filter in mongodb
|
|
var isValid bool
|
|
var errAPI *ResponseError
|
|
var dataRedisRetrive models.RedisOTP
|
|
|
|
filter := bson.D{
|
|
{"$or", bson.A{
|
|
bson.D{{"username", userName}},
|
|
bson.D{{"email", email}},
|
|
}},
|
|
}
|
|
|
|
_, err := utils.User.FindOne(context.TODO(), filter).Raw()
|
|
if err != nil {
|
|
isValid = true
|
|
errAPI = nil
|
|
} else {
|
|
fmt.Println("Internal Log: Email or username already had register")
|
|
isValid = false
|
|
errAPI = &ResponseError{
|
|
Code: http.StatusBadRequest,
|
|
Err: errors.New("your username or email had been register before"),
|
|
}
|
|
}
|
|
retrievedValue, err := utils.Redis.Get(context.Background(), "otp:"+email).Result()
|
|
err = json.Unmarshal([]byte(retrievedValue), &dataRedisRetrive)
|
|
if err != nil {
|
|
fmt.Println("Internal Log: Can't get data from redis")
|
|
}
|
|
|
|
timeDiff := time.Now().Unix() - dataRedisRetrive.CreatedDate
|
|
|
|
if timeDiff < 30 {
|
|
isValid = false
|
|
fmt.Println("Internal Log: Rate limit send verify mail")
|
|
errAPI = &ResponseError{
|
|
Code: http.StatusBadRequest,
|
|
Err: errors.New("wait " + strconv.FormatInt(30-timeDiff, 10) + " to send verify mail again"),
|
|
}
|
|
|
|
}
|
|
|
|
return isValid, errAPI
|
|
}
|
|
|
|
// GeneratorOtp Generate OTP Verify Link and Qr Link
|
|
|
|
const (
|
|
otpDigits = 6
|
|
mailValidTime = time.Hour * 24
|
|
)
|
|
|
|
func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWriter) {
|
|
var wg sync.WaitGroup
|
|
|
|
otpChannel := make(chan models.OtpGenerate, 1)
|
|
mailChannel := make(chan models.MailVefiry, 1)
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
// CreateLinkVerify Create a alternative verify link
|
|
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", "admin@wliafdew.dev")
|
|
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("mail.wliafdew.dev", 465, "admin@wliafdew.dev", 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, w http.ResponseWriter) uint64 {
|
|
//checking
|
|
email := registerInfo.Email
|
|
count := 1
|
|
filter := bson.D{{"email", email}}
|
|
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("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("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{
|
|
{"verify_sent_count", 1},
|
|
}},
|
|
{"$set", bson.D{
|
|
{"update_date", time.Now()},
|
|
},
|
|
}}
|
|
|
|
_, 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)
|
|
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
|
|
|
|
dataRedis := &models.RedisOTP{
|
|
Email: registerInfo.Email,
|
|
User: registerInfo.Username,
|
|
CreatedDate: registerInfo.CreatedDate.Unix(),
|
|
HashOTP: otp.HashOTP,
|
|
}
|
|
|
|
jsonData, err := json.Marshal(dataRedis)
|
|
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:"+registerInfo.Email, string(jsonData), time.Hour*24)
|
|
|
|
fmt.Println(status.Err())
|
|
|
|
}
|