diff --git a/models/models.go b/models/models.go index 076ad9b..4e8cfc1 100644 --- a/models/models.go +++ b/models/models.go @@ -130,3 +130,8 @@ type SuccessAPI struct { Message string `json:"message"` Type string `json:"type"` } + +type LinkVerify struct { + JWT string `json:"jwt"` + UUID string `json:"uuid"` +} diff --git a/rest-api/init-api.go b/rest-api/init-api.go index a1593f6..236c4b2 100644 --- a/rest-api/init-api.go +++ b/rest-api/init-api.go @@ -1,9 +1,11 @@ package rest_api import ( + "encoding/json" "fmt" "github.com/gorilla/handlers" "github.com/gorilla/mux" + "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/rest-api/routes" "linhdevtran99/rest-api/utils" "log" @@ -23,7 +25,7 @@ func NewAPIServer(listenAddr string) *APIServer { func startMuxServer(s *APIServer, router *mux.Router) { log.Println("Listening on", s.listenAddr) headersOk := handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"}) - originsOk := handlers.AllowedOrigins([]string{"https://app.wliafdew.dev"}) + originsOk := handlers.AllowedOrigins([]string{"https://app.wliafdew.dev", "http://localhost:4200"}) methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"}) if err := http.ListenAndServe(s.listenAddr, handlers.CORS(originsOk, headersOk, methodsOk)(router)); err != nil { log.Fatal(err) @@ -42,8 +44,12 @@ func (s *APIServer) Run() { func (s *APIServer) TestRoute(w http.ResponseWriter, r *http.Request) error { - if r.Method == http.MethodGet { - //utils.VerifyOTP() + if r.Method == http.MethodPost { + var linkVerifyInfo models.LinkVerify + + _ = json.NewDecoder(r.Body).Decode(&linkVerifyInfo) + + utils.DecryptAESMailLink(&linkVerifyInfo, w) fmt.Println("hello") } diff --git a/rest-api/routes/auth.go b/rest-api/routes/auth.go index 8c1b0dd..104eab4 100644 --- a/rest-api/routes/auth.go +++ b/rest-api/routes/auth.go @@ -3,6 +3,7 @@ package routes import ( "encoding/json" "fmt" + "github.com/google/uuid" "github.com/gorilla/mux" "linhdevtran99/rest-api/models" "linhdevtran99/rest-api/rest-api/services" @@ -75,8 +76,34 @@ func VerifyWithOTP(w http.ResponseWriter, r *http.Request) error { return nil } +func VerifyWithJWT(w http.ResponseWriter, r *http.Request) error { + fmt.Println("hello") + if r.Method == http.MethodPost { + var jwtInfo models.LinkVerify + _ = json.NewDecoder(r.Body).Decode(&jwtInfo) + _, err := uuid.Parse(jwtInfo.UUID) + //checking uuid + if err != nil { + fmt.Println("Internal log: UUID not valid") + _ = utils.WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{ + Errors: []string{"UUID not valid"}, + Message: "UUID not valid", + Type: "UUIDNotValid", + }) + return err + } + //call function check jwt + isValid, email := utils.DecryptAESMailLink(&jwtInfo, w) + + if isValid == true { + services.VerifyWithJWT(email, w) + } + } + return nil +} func AuthRouterSetup(router *mux.Router) { authRouter := router.PathPrefix("/account").Subrouter() authRouter.Handle("/register", utils.MakeHTTPHandlerFn(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 825aa06..5fb97f6 100644 --- a/rest-api/services/register.go +++ b/rest-api/services/register.go @@ -126,8 +126,7 @@ func CheckAccountValid(userName string, email string) (bool, *models.ResponseErr // GeneratorOtp Generate OTP Verify Link and Qr Link const ( - otpDigits = 6 - mailValidTime = time.Minute * 15 + otpDigits = 6 ) func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWriter) error { @@ -136,11 +135,11 @@ func GenerateVerifyAccount(registerInfo *models.PreusersMongo, w http.ResponseWr otpChannel := make(chan models.OtpGenerate, 1) mailChannel := make(chan models.MailVefiry, 1) - counter := CheckAndWritePreuser(registerInfo, w) + 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) + go utils.EncryptAESMailLink(registerInfo, w, mailChannel, &wg, timerCreate) go createMailVerify(registerInfo, otpChannel, mailChannel, w) go writeOTPInRedis(counter, registerInfo, otpChannel, w) @@ -166,7 +165,7 @@ func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.Ot } qrFileName := registerInfo.Email + registerInfo.Username + ".png" - os.WriteFile("./temp/"+qrFileName, decodedImage, 0666) + _ = os.WriteFile("./temp/"+qrFileName, decodedImage, 0666) emailBody := utils.BuildEmail(opt.PureOTP, mailVerify.LinkMail, qrFileName) m.SetHeader("From", "admin@wliafdew.dev") @@ -186,12 +185,15 @@ func createMailVerify(registerInfo *models.PreusersMongo, otpChan chan models.Ot } defer func() { - os.Remove("./temp/" + qrFileName) + _ = os.Remove("./temp/" + qrFileName) }() } // check and write pre use into mongo db -func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWriter) uint64 { +func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWriter) (uint64, time.Time) { + //generate timestamp + timeCreate := time.Now() + registerInfo.CreatedDate = timeCreate //checking email := registerInfo.Email count := 1 @@ -200,8 +202,8 @@ func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWri 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") + 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) @@ -209,14 +211,14 @@ func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWri fmt.Println("Internal Log: Can't Insert Data To PreUser") _ = utils.WriteJSONInternalError(w, "Can't Insert Data To PreUser") } - return uint64(count) + return uint64(count), timeCreate } else { update := bson.D{ {"$inc", bson.D{ {"verify_sent_count", 1}, }}, {"$set", bson.D{ - {"update_date", time.Now()}, + {"update_date", timeCreate}, }, }} @@ -227,7 +229,7 @@ func CheckAndWritePreuser(registerInfo *models.PreusersMongo, w http.ResponseWri } fmt.Println() count = int(raw.Lookup("verify_sent_count").Int32() + 1) - return uint64(count) + return uint64(count), timeCreate } } @@ -258,10 +260,10 @@ func writeOTPInRedis(counter uint64, registerInfo *models.PreusersMongo, otpChan } // ////////////////////////Create User Complete////////////////////////// -func CreateUserAfterVerify(otpInfo *models.OTPVerify, redis *models.RedisOTP, w http.ResponseWriter) bool { +func CreateUserAfterVerify(email string, w http.ResponseWriter) bool { isValid := true var preUserData models.PreusersMongo - filter := bson.D{{"email", otpInfo.Email}} + filter := bson.D{{"email", email}} err := utils.PreUserData.FindOne(context.Background(), filter).Decode(&preUserData) if err != nil { @@ -278,7 +280,7 @@ func CreateUserAfterVerify(otpInfo *models.OTPVerify, redis *models.RedisOTP, w Active: true, CreatedDate: time.Now(), UpdateDate: time.Now(), - VerifySentCount: int(redis.Counter), + VerifySentCount: preUserData.VerifySentCount, } _, err = utils.User.InsertOne(context.Background(), user) @@ -303,9 +305,9 @@ func CreateUserAfterVerify(otpInfo *models.OTPVerify, redis *models.RedisOTP, w //////////////////////////Verify OTP////////////////////////// -func CheckUserVerify(otpInfo *models.OTPVerify, w http.ResponseWriter) bool { +func CheckUserVerify(email string, w http.ResponseWriter) bool { isVerified := false - filter := bson.D{{"email", otpInfo.Email}} + filter := bson.D{{"email", email}} var user models.UsersMongo _ = utils.User.FindOne(context.Background(), filter).Decode(&user) @@ -353,14 +355,31 @@ func CheckOTPIsValid(otpInfo *models.OTPVerify, w http.ResponseWriter) bool { isValid = false } - if isVerified := CheckUserVerify(otpInfo, w); isVerified == true { + if isVerified := CheckUserVerify(otpInfo.Email, w); isVerified == true { return false } isValid = utils.VerifyOTP(otpInfo, dataRedis, w) if isValid == true { - CreateUserAfterVerify(otpInfo, &dataRedis, w) + CreateUserAfterVerify(otpInfo.Email, w) + } + return true +} + +//////////////////////////Verify JWT////////////////////////// + +func VerifyWithJWT(email string, w http.ResponseWriter) bool { + var isValid bool + isValid = true + //verify uuid and email + + if isVerified := CheckUserVerify(email, w); isVerified == true { + return false + } + + if isValid == true { + CreateUserAfterVerify(email, w) } return true } diff --git a/tmp/build-errors.log b/tmp/build-errors.log index 370d558..bfd43cc 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 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 1 \ No newline at end of file diff --git a/tmp/main b/tmp/main index 8950cb2..93944a9 100755 Binary files a/tmp/main and b/tmp/main differ diff --git a/utils/crypto.go b/utils/crypto.go index 29d6b8d..42f1e1f 100644 --- a/utils/crypto.go +++ b/utils/crypto.go @@ -1,18 +1,20 @@ package utils import ( + "context" "encoding/base32" "encoding/base64" + "errors" "fmt" "github.com/golang-jwt/jwt/v5" "github.com/pquerna/otp" "github.com/pquerna/otp/hotp" "github.com/skip2/go-qrcode" + "go.mongodb.org/mongo-driver/bson" "golang.org/x/crypto/bcrypt" "linhdevtran99/rest-api/models" "net/http" "os" - "strings" "sync" "time" ) @@ -28,9 +30,8 @@ type MyCustomClaims struct { jwt.RegisteredClaims } -func buildTokenLink(token string, w http.ResponseWriter) (string, string) { - tokenCustomTrim := strings.ReplaceAll(token, ".", "&") - emailVerifyLink := "https://api.wliafdew.dev/?p=" + tokenCustomTrim +func buildTokenLink(registerInfo *models.PreusersMongo, token string, w http.ResponseWriter) (string, string) { + emailVerifyLink := "http://localhost:4200/register?uuid=" + registerInfo.UUID + "®ister_step=verify_with_jwt" + "&p=" + token png, err := qrcode.Encode(emailVerifyLink, qrcode.Low, 200) if err != nil { fmt.Println("Internal log: error create qr ") @@ -41,27 +42,28 @@ func buildTokenLink(token string, w http.ResponseWriter) (string, string) { return emailVerifyLink, base64Image } -func EncryptAESMailLink(registerInfo *models.PreusersMongo, w http.ResponseWriter, mailChan chan models.MailVefiry, wg *sync.WaitGroup) chan models.MailVefiry { +func EncryptAESMailLink(registerInfo *models.PreusersMongo, w http.ResponseWriter, mailChan chan models.MailVefiry, wg *sync.WaitGroup, timeCreate time.Time) chan models.MailVefiry { claims := MyCustomClaims{ registerInfo.Email, jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)), - IssuedAt: jwt.NewNumericDate(time.Now()), + ExpiresAt: jwt.NewNumericDate(time.Now().Add(5 * time.Hour)), + IssuedAt: jwt.NewNumericDate(timeCreate), NotBefore: jwt.NewNumericDate(time.Now()), - Issuer: "totodayShop", + Issuer: "totodayShopRegister", }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token.Header["purpose"] = "Email_Verify" - tokenString, err := token.SignedString([]byte(serect)) + tokenString, err := token.SignedString([]byte(serect + registerInfo.UUID)) if err != nil { fmt.Println("Internal Log: Error signed token fail", err.Error()) _ = WriteJSONInternalError(w, "Error signing token fail") } + tokenBS64 := base64.StdEncoding.EncodeToString([]byte(tokenString)) - linkMail, imageB64 := buildTokenLink(tokenString, w) + linkMail, imageB64 := buildTokenLink(registerInfo, tokenBS64, w) data := models.MailVefiry{ LinkMail: linkMail, @@ -75,9 +77,62 @@ func EncryptAESMailLink(registerInfo *models.PreusersMongo, w http.ResponseWrite } -// -//func DecryptAESMailLink(data string, key string) string { -//} +func DecryptAESMailLink(linkVerifyInfo *models.LinkVerify, w http.ResponseWriter) (bool, string) { + decodeBase64, err := base64.StdEncoding.DecodeString(linkVerifyInfo.JWT) + token, err := jwt.ParseWithClaims(string(decodeBase64), &MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(serect + linkVerifyInfo.UUID), nil + }) + if err != nil { + fmt.Println("Internal log: Error decrypt token fail", err.Error()) + if errors.Is(err, jwt.ErrTokenMalformed) { + _ = WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{ + Errors: []string{"Token is malformed"}, + Message: "Token is invalid", + Type: "TokenMalformed", + }) + } + if errors.Is(err, jwt.ErrSignatureInvalid) { + _ = WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{ + Errors: []string{"UUID is invalid"}, + Message: "UUID is invalid", + Type: "UUIDInvalid", + }) + } + + if errors.Is(err, jwt.ErrTokenExpired) { + _ = WriteJSON(w, http.StatusBadRequest, models.ErrorAPI{ + Errors: []string{"Token is expired"}, + Message: "Link is expired please register again", + Type: "TokenExpired", + }) + } + + return false, "" + } + + if token.Valid && token.Header["purpose"] == "Email_Verify" && err == nil { + //get update date from preuser + var preUser models.PreusersMongo + email := token.Claims.(*MyCustomClaims).Email + err := PreUserData.FindOne(context.TODO(), bson.M{"email": email}).Decode(&preUser) + if err != nil { + //handler erorr later + fmt.Println("Internal log: Error find preuser fail", err.Error()) + return false, "" + } + timeRegisterToken := preUser.UpdateDate.Unix() + timeIssueToken, _ := token.Claims.GetIssuedAt() + if timeRegisterToken != timeIssueToken.Unix() { + //handler erorr later + fmt.Println("Internal log: Error token is not valid to use") + return false, "" + } + return true, email + } + + return false, "" + +} //OTP+++++++++++++OTP// diff --git a/utils/mail-template.go b/utils/mail-template.go index e58f562..757f4a8 100644 --- a/utils/mail-template.go +++ b/utils/mail-template.go @@ -17,6 +17,8 @@ func BuildEmail(otp string, mailLink string, fileName string) string { QrCode: qrcodeURL, } + fmt.Println(data.AlternativeLink) + //tmpl, err := template.ParseFiles("./Template/email.html") tmpl, err := template.ParseFiles("./Template/email.html")