restruc my codebase

This commit is contained in:
Trần Duy Linh 2023-12-04 12:09:34 +07:00
parent c577c396f4
commit 3869d24200
5 changed files with 247 additions and 236 deletions

4
.env
View File

@ -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"

View File

@ -83,3 +83,8 @@ type EmailTemplate struct {
Otp string `json:"otp"`
AlternativeLink string `json:"alternativeLink"`
}
type OtpGenerate struct {
PureOTP string
HashOTP string
}

View File

@ -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(&registerInfo)
//call function check info user type in
validRegisterInfo := checkAndValidDataFiled(&registerInfo, w)
validRegisterInfo := services.CheckAndValidRegisterFiled(&registerInfo, 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]
}

View File

@ -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
}

View File

@ -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]
}