) => {
+ e.preventDefault();
+
+ const verificationCode = code.join("");
+
+ mutate(verificationCode);
+ };
+
+ useEffect(() => {
+ if (code.every((digit) => digit !== "")) {
+ const event = new Event("submit", { bubbles: true });
+ formRef.current?.dispatchEvent(event);
+ }
+ }, [code]);
+
+ return (
+
+ );
+};
+
+export { VerifyEmailForm };
diff --git a/client/src/pages/verify-email/page.tsx b/client/src/pages/verify-email/page.tsx
new file mode 100644
index 0000000..344d660
--- /dev/null
+++ b/client/src/pages/verify-email/page.tsx
@@ -0,0 +1,20 @@
+import { VerifyEmailForm } from "./_components/verify-email-form";
+import { ResendCode } from "./_components/resend-code";
+
+const VerifyEmailPage = () => {
+ return (
+
+
+
+ Verify Email
+
+
+
+
+
+
+
+ );
+};
+
+export default VerifyEmailPage;
diff --git a/controllers/auth_controller.go b/controllers/auth_controller.go
index fdecab8..0a66b35 100644
--- a/controllers/auth_controller.go
+++ b/controllers/auth_controller.go
@@ -5,6 +5,7 @@ import (
"strconv"
"time"
+ dto "github.com/Fingertips18/go-auth/DTO"
"github.com/Fingertips18/go-auth/database"
"github.com/Fingertips18/go-auth/models"
"github.com/Fingertips18/go-auth/utils"
@@ -19,7 +20,7 @@ func SignUp(c fiber.Ctx) error {
Password string `json:"password"`
}
if err := c.Bind().JSON(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Fields are either invalid or missing"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Fields are either invalid or missing"})
}
user := models.User{
@@ -30,7 +31,7 @@ func SignUp(c fiber.Ctx) error {
password, err := utils.HashPassword(user.Password)
if err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: err.Error()})
}
user.Password = password
@@ -44,14 +45,19 @@ func SignUp(c fiber.Ctx) error {
res := database.Instance.Create(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to create user"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to create user"})
}
if err := utils.SendEmailVerification(user.Email, user.Username, *user.VerificationToken); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
- return c.Status(fiber.StatusCreated).JSON(fiber.Map{"message": "User created successfully", "user": user})
+ return c.Status(fiber.StatusCreated).JSON(
+ dto.UserDTO{
+ Message: "User created successfully",
+ User: user,
+ },
+ )
}
func SignIn(c fiber.Ctx) error {
@@ -60,26 +66,26 @@ func SignIn(c fiber.Ctx) error {
Password string `json:"password"`
}
if err := c.Bind().JSON(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Either email or/and password is/are empty"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either email or/and password is/are empty"})
}
var user models.User
res := database.Instance.Where("email_address = ?", data.Email).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "User not found"})
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: res.Error.Error()})
}
if err := utils.VerifyPassword([]byte(user.Password), []byte(data.Password)); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid credentials"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Invalid credentials"})
}
if user.IsVerified {
token, err := utils.GenerateJWTToken(user.ID.String(), user.Username)
if err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
utils.SetCookieToken(c, token)
@@ -88,18 +94,18 @@ func SignIn(c fiber.Ctx) error {
res = database.Instance.Save(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save sign in credentials"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to save sign in credentials"})
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Sign in successful", "user": user})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Sign in successful"})
} else {
- return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "User is not verified"})
+ return c.Status(fiber.StatusForbidden).JSON(dto.ErrorDTO{Error: "User is not verified"})
}
}
func SignOut(c fiber.Ctx) error {
utils.ClearCookieToken(c)
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Sign out successful"})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Sign out successful"})
}
func VerifyEmail(c fiber.Ctx) error {
@@ -108,7 +114,7 @@ func VerifyEmail(c fiber.Ctx) error {
}
if err := c.Bind().JSON(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Either token is invalid or empty"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either token is invalid or empty"})
}
var user models.User
@@ -116,9 +122,9 @@ func VerifyEmail(c fiber.Ctx) error {
res := database.Instance.Where("verification_token = ?", data.Token).Where("verification_token_exp > ?", time.Now()).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Invalid or expired verification token"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "Invalid or expired verification token"})
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: res.Error.Error()})
}
user.IsVerified = true
@@ -127,14 +133,14 @@ func VerifyEmail(c fiber.Ctx) error {
res = database.Instance.Save(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save email verification credentials"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to save email verification credentials"})
}
if err := utils.SendWelcomeEmail(user.Email, user.Username); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Welcome message has been sent to your email"})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Welcome message has been sent to your email"})
}
func ResendVerify(c fiber.Ctx) error {
@@ -143,7 +149,7 @@ func ResendVerify(c fiber.Ctx) error {
}
if err := c.Bind().Body(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Either email is invalid or empty"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either email is invalid or empty"})
}
var user models.User
@@ -151,9 +157,9 @@ func ResendVerify(c fiber.Ctx) error {
res := database.Instance.Where("email_address = ?", data.Email).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "User not found"})
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: res.Error.Error()})
}
if time.Now().After(*user.VerificationTokenExpiration) {
@@ -161,24 +167,27 @@ func ResendVerify(c fiber.Ctx) error {
tokenString := strconv.Itoa(verificationToken)
user.VerificationToken = &tokenString
+ exp := time.Now().Add(time.Hour * 24)
+ user.VerificationTokenExpiration = &exp
+
res = database.Instance.Save(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save email verification credentials"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to save email verification credentials"})
}
}
if err := utils.SendEmailVerification(user.Email, user.Username, *user.VerificationToken); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Verification code has been sent to your email"})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Verification code has been sent to your email"})
}
func VerifyToken(c fiber.Ctx) error {
id := c.Locals("id").(string)
if id == "" {
- return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid id"})
+ return c.Status(fiber.StatusUnauthorized).JSON(dto.ErrorDTO{Error: "ID is invalid or missing"})
}
var user models.User
@@ -186,12 +195,19 @@ func VerifyToken(c fiber.Ctx) error {
res := database.Instance.Where("id = ?", id).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{
+ Error: "User not found",
+ })
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{
+ Error: res.Error.Error(),
+ })
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Session valid", "user": user})
+ return c.Status(fiber.StatusOK).JSON(dto.UserDTO{
+ Message: "Session valid",
+ User: user,
+ })
}
func ForgotPassword(c fiber.Ctx) error {
@@ -199,21 +215,21 @@ func ForgotPassword(c fiber.Ctx) error {
Email string `json:"email"`
}
if err := c.Bind().JSON(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Either email is invalid or empty"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either email is invalid or empty"})
}
var user models.User
res := database.Instance.Where("email_address = ?", data.Email).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "User not found"})
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: res.Error.Error()})
}
resetPasswordToken, err := utils.GenerateResetToken()
if err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: err.Error()})
}
resetPasswordTokenExp := time.Now().Add(time.Minute * 15)
@@ -222,14 +238,14 @@ func ForgotPassword(c fiber.Ctx) error {
res = database.Instance.Save(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save forgot password credentials"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to save forgot password credentials"})
}
if err := utils.SendEmailRequestResetPassword(user.Email, *resetPasswordToken); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Password reset request link has been sent to your email"})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Password reset request link has been sent to your email"})
}
func ResetPassword(c fiber.Ctx) error {
@@ -237,12 +253,12 @@ func ResetPassword(c fiber.Ctx) error {
Password string `json:"password"`
}
if err := c.Bind().JSON(&data); err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Either password is invalid or empty"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Either password is invalid or empty"})
}
id := c.Params("token")
if id == "" {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Missing token"})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: "Missing token"})
}
var user models.User
@@ -250,14 +266,14 @@ func ResetPassword(c fiber.Ctx) error {
res := database.Instance.Where("reset_password_token = ?", id).First(&user)
if res.Error != nil {
if res.Error == gorm.ErrRecordNotFound {
- return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
+ return c.Status(fiber.StatusNotFound).JSON(dto.ErrorDTO{Error: "User not found"})
}
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": res.Error.Error()})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: res.Error.Error()})
}
password, err := utils.HashPassword(data.Password)
if err != nil {
- return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()})
+ return c.Status(fiber.StatusBadRequest).JSON(dto.ErrorDTO{Error: err.Error()})
}
user.Password = password
@@ -266,12 +282,12 @@ func ResetPassword(c fiber.Ctx) error {
res = database.Instance.Save(&user)
if res.Error != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Unable to save reset password credentials"})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: "Unable to save reset password credentials"})
}
if err := utils.SendEmailResetPasswordSuccess(user.Email, user.Username); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err})
+ return c.Status(fiber.StatusInternalServerError).JSON(dto.ErrorDTO{Error: err.Error()})
}
- return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Password reset success details has been sent to your email"})
+ return c.Status(fiber.StatusOK).JSON(dto.GenericDTO{Message: "Password reset success details has been sent to your email"})
}
diff --git a/middlewares/verify-token.go b/middlewares/verify-token.go
index a38958b..2b28199 100644
--- a/middlewares/verify-token.go
+++ b/middlewares/verify-token.go
@@ -5,6 +5,7 @@ import (
"os"
"time"
+ dto "github.com/Fingertips18/go-auth/DTO"
"github.com/gofiber/fiber/v3"
"github.com/golang-jwt/jwt/v5"
)
@@ -13,7 +14,7 @@ func VerifyToken(c fiber.Ctx) error {
tokenString := c.Cookies("token")
if tokenString == "" {
- return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "No token found"})
+ return c.Status(fiber.StatusUnauthorized).JSON(dto.ErrorDTO{Error: "No token found"})
}
SECRET_KEY := os.Getenv("SECRET_KEY")
@@ -31,11 +32,11 @@ func VerifyToken(c fiber.Ctx) error {
claims, ok := token.Claims.(*jwt.RegisteredClaims)
if !ok {
- return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid token claims"})
+ return c.Status(fiber.StatusUnauthorized).JSON(dto.ErrorDTO{Error: "Invalid token claims"})
}
if time.Now().After(claims.ExpiresAt.Time) {
- return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"error": "Invalid or expired token"})
+ return c.Status(fiber.StatusUnauthorized).JSON(dto.ErrorDTO{Error: "Invalid or expired token"})
}
c.Locals("id", claims.Issuer)
diff --git a/models/user_model.go b/models/user_model.go
index d70cacb..4371082 100644
--- a/models/user_model.go
+++ b/models/user_model.go
@@ -9,8 +9,8 @@ type User struct {
Password string `json:"-" gorm:"not null"`
LastSignedIn time.Time `json:"last_signed_in" gorm:"column:last_signed_in;default:now()"`
IsVerified bool `json:"is_verified" gorm:"column:is_verified;default:false"`
- VerificationToken *string `json:"verification_token,omitempty" gorm:"column:verification_token"`
- VerificationTokenExpiration *time.Time `json:"verification_token_exp,omitempty" gorm:"column:verification_token_exp"`
- ResetPasswordToken *string `json:"reset_password_token,omitempty" gorm:"column:reset_password_token"`
- ResetPasswordTokenExpiration *time.Time `json:"reset_password_token_exp,omitempty" gorm:"column:reset_password_token_exp"`
+ VerificationToken *string `json:"-" gorm:"column:verification_token"`
+ VerificationTokenExpiration *time.Time `json:"-" gorm:"column:verification_token_exp"`
+ ResetPasswordToken *string `json:"-" gorm:"column:reset_password_token"`
+ ResetPasswordTokenExpiration *time.Time `json:"-" gorm:"column:reset_password_token_exp"`
}