From c74329b5d3d89e993a0f019bc731200441e54e0a Mon Sep 17 00:00:00 2001 From: Jordan Ibrahim Date: Thu, 28 Nov 2024 22:40:14 -0800 Subject: [PATCH] Updates for registration, login, and new zerobounce.go file --- backend/internal/handlers/login.go | 26 ++++++++----- backend/internal/handlers/registration.go | 35 ++++++++++++++++-- backend/internal/utils/zerobounce.go | 45 +++++++++++++++++++++++ 3 files changed, 93 insertions(+), 13 deletions(-) create mode 100644 backend/internal/utils/zerobounce.go diff --git a/backend/internal/handlers/login.go b/backend/internal/handlers/login.go index 2ee6f42..3b058d7 100644 --- a/backend/internal/handlers/login.go +++ b/backend/internal/handlers/login.go @@ -7,31 +7,39 @@ import ( "github.com/sandbox-science/online-learning-platform/internal/utils" ) -// Login auths a user by checking email and password, and generate a token +// Login authenticates a user by checking email and password, and generates a token func Login(c *fiber.Ctx) error { var data entity.Login + // Parse request body if err := c.BodyParser(&data); err != nil { - - return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"message": "Invalid input"}) + return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ + "message": "Invalid input", + }) } var user entity.Account - // Check if email exist + // Check if email exists in the database if err := database.DB.Where("email = ?", data.Email).First(&user).Error; err != nil { - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "There is no account with this email. Please register."}) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "There is no account with this email. Please register.", + }) } - // Check if the password matches + // Check if the password matches the hash if err := utils.CheckPasswordHash(data.Password, user.Password); err != nil { - return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"message": "Incorrect password"}) + return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{ + "message": "Incorrect password", + }) } - // Generate token upon successful login + // Generate JWT token on successful login token, err := utils.GenerateJWT(user.Email) if err != nil { - return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "Could not generate token"}) + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "message": "Could not generate token", + }) } return c.JSON(fiber.Map{ diff --git a/backend/internal/handlers/registration.go b/backend/internal/handlers/registration.go index 3213468..8748f67 100644 --- a/backend/internal/handlers/registration.go +++ b/backend/internal/handlers/registration.go @@ -1,6 +1,8 @@ package handlers import ( + "log" + "github.com/gofiber/fiber/v2" "github.com/sandbox-science/online-learning-platform/configs/database" "github.com/sandbox-science/online-learning-platform/internal/entity" @@ -33,22 +35,47 @@ func Register(c *fiber.Ctx) error { // Create user account user := entity.Account{ - Username: encryptedUsername, - Email: data["email"], - Password: data["password"], - Role: data["role"], + Username: encryptedUsername, + Email: data["email"], + Password: data["password"], + Role: data["role"], + EmailVerified: false, } // Hash password + if err := utils.HashPassword(&user); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": "Couldn't hash password"}) } // Add user to database + if err := database.DB.Create(&user).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()}) } + // Trigger email verification asynchronously + + go func(email string, userID uint) { + + status, err := utils.VerifyEmailWithZeroBounce(email) + if err != nil { + log.Printf("[ERROR] Failed to verify email for user ID %d (%s): %v\n", userID, email, err) + return + } + + if status == "valid" { + // Update EmailVerified to true + if err := database.DB.Model(&entity.Account{}).Where("id = ?", userID).Update("EmailVerified", true).Error; err != nil { + log.Printf("[ERROR] Failed to update EmailVerified for user ID %d (%s): %v\n", userID, email, err) + } else { + log.Printf("[INFO] Email verified successfully for user ID %d (%s)\n", userID, email) + } + } else { + log.Printf("[WARN] Invalid email detected for user ID %d (%s): %s\n", userID, email, status) + } + }(user.Email, user.ID) + return c.JSON(fiber.Map{ "message": "User registered successfully", "user": user, diff --git a/backend/internal/utils/zerobounce.go b/backend/internal/utils/zerobounce.go new file mode 100644 index 0000000..5707f1f --- /dev/null +++ b/backend/internal/utils/zerobounce.go @@ -0,0 +1,45 @@ +package utils + +import ( + "encoding/json" + "fmt" + "net/http" + "os" +) + +type ZeroBounceResponse struct { + Status string `json:"status"` +} + +// VerifyEmailWithZeroBounce checks if an email address is valid using the ZeroBounce API +func VerifyEmailWithZeroBounce(email string) (string, error) { + // Retrieve the API key from environment variables + apiKey := os.Getenv("ZEROBOUNCE_API_KEY") + if apiKey == "" { + return "", fmt.Errorf("ZeroBounce API key not set in environment variables") + } + + // Build the API request URL + url := fmt.Sprintf("https://api.zerobounce.net/v2/validate?email=%s&api_key=%s", email, apiKey) + + // Send the HTTP GET request + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to connect to ZeroBounce API: %v", err) + } + defer resp.Body.Close() + + // Check if the response status code is 200 OK + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("ZeroBounce API returned status: %s", resp.Status) + } + + // Parse the response JSON + var result ZeroBounceResponse + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", fmt.Errorf("error decoding ZeroBounce API response: %v", err) + } + + // Return the email status + return result.Status, nil +}