Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions backend/internal/handlers/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
35 changes: 31 additions & 4 deletions backend/internal/handlers/registration.go
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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,
Expand Down
45 changes: 45 additions & 0 deletions backend/internal/utils/zerobounce.go
Original file line number Diff line number Diff line change
@@ -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
}
Loading