Skip to content

Commit

Permalink
forgot password and check username
Browse files Browse the repository at this point in the history
  • Loading branch information
bkawk committed Feb 5, 2023
1 parent 50106d1 commit 72d9f16
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 240 deletions.
4 changes: 3 additions & 1 deletion api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ [email protected]
SMTP_SERVER=smtp.example.com
SMTP_PORT=587
SMTP_PASSWORD=password
JWT_SECRET=jwtSecret
JWT_SECRET=jwtSecret
VERIFY_URL=http://example.com/verify
RESET_EMAIL_URL=http://example.com/reset-email
60 changes: 60 additions & 0 deletions api/emails/resetPassword.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package emails

import (
"fmt"
"net/smtp"
"os"
"strconv"
)

func SendResetPasswordEmail(to, resetPasswordLink string) error {

var (
smtpServer = os.Getenv("SMTP_SERVER")
smtpPort = os.Getenv("SMTP_PORT")
username = os.Getenv("EMAIL_FROM")
password = os.Getenv("SMTP_PASSWORD")
from = os.Getenv("EMAIL_FROM")
)

if smtpServer == "" || smtpPort == "" || username == "" || password == "" || from == "" {
return fmt.Errorf("environment variable not set: SMTP_SERVER, SMTP_PORT, EMAIL_FROM, or SMTP_PASSWORD")
}

port, err := strconv.Atoi(smtpPort)
if err != nil {
return fmt.Errorf("failed to convert smtpPort to int: %v", err)
}

body := fmt.Sprintf(`
<html>
<body>
<p>
Hello!
</p>
<p>
We received a request to reset your password. If you did not make this request, please ignore this email.
</p>
<p>
Please click the following link to reset your password:
<br />
<a href="%s">%s</a>
</p>
<p>
<button style="background-color: #4CAF50; color: white; padding: 14px 20px; margin: 8px 0; border: none; cursor: pointer; width: 100%%;">Verify</button>
</p>
<p>
Best regards,
<br />
The Team
</p>
</body>
</html>
`, resetPasswordLink, resetPasswordLink)
msg := []byte(fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: Welcome\r\nContent-Type: text/html\r\n\r\n%s", from, to, body))
auth := smtp.PlainAuth("", username, password, smtpServer)
if err := smtp.SendMail(fmt.Sprintf("%s:%d", smtpServer, port), auth, from, []string{to}, msg); err != nil {
return fmt.Errorf("failed to send email: %v", err)
}
return nil
}
2 changes: 1 addition & 1 deletion api/email/welcome.go → api/emails/welcome.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package email
package emails

import (
"fmt"
Expand Down
4 changes: 2 additions & 2 deletions api/handlers/checkUsername.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func CheckUsernameGet(c echo.Context) error {
"username": username,
}).Decode(&existingUser)
if err == nil {
return c.JSON(http.StatusOK, echo.Map{"message": "Username not available"})
return c.JSON(http.StatusOK, echo.Map{"message": "Username not available", "available": false})
}

return c.JSON(http.StatusOK, echo.Map{"message": "Username available"})
return c.JSON(http.StatusOK, echo.Map{"message": "Username available", "available": true})
}
4 changes: 4 additions & 0 deletions api/handlers/checkUsername.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Your request headers, e.g.
GET http://localhost:8080/check-username?username=bkawk
Content-Type: application/json

52 changes: 36 additions & 16 deletions api/handlers/forgotPassword.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,54 @@
package handlers

import (
"net/http"

"bkawk/go-echo/api/models"
"context"
"fmt"
"net/http"
"time"

"github.com/labstack/echo/v4"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
)

// RegisterEndpoint handles user registration requests
func ForgotPasswordPost(c echo.Context) error {
// bind the incoming request body to a User struct

// Get database connection from context
db := c.Get("db").(*mongo.Database)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

// Validate input
u := new(models.User)
if err := c.Bind(u); err != nil {
return err
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Failed to bind request body"})
}

// validate user input
if u.Username == "" || u.Password == "" || u.Email == "" {
return c.JSON(http.StatusBadRequest, map[string]string{
"error": "invalid request body",
})
var user models.User
collection := db.Collection("users")
filter := bson.M{"email": u.Email}

if err := collection.FindOne(ctx, filter).Decode(&user); err != nil {
if err == mongo.ErrNoDocuments {
return echo.NewHTTPError(http.StatusNotFound, "Email not found")
}
return echo.NewHTTPError(http.StatusInternalServerError, "Error fetching user")
}

if time.Since(time.Unix(user.ForgotPassword, 0)) < (5 * time.Minute) {
waitTime := 5*time.Minute - time.Since(time.Unix(user.ForgotPassword, 0))
return echo.NewHTTPError(http.StatusTooManyRequests, fmt.Sprintf("Try again in %d minutes and %d seconds", int(waitTime.Minutes()), int(waitTime.Seconds())%60))
}

// add the new user to the database
// (this is a dummy implementation and would be replaced in a real application)
// ...
// send email logic
fmt.Println("Sending forgot password email to", u.Email)

user.ForgotPassword = time.Now().Unix()
if _, err := collection.ReplaceOne(ctx, filter, user); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, "Error updating user")
}

// return a success response
return c.JSON(http.StatusOK, map[string]string{
"message": "user registered successfully",
})
return c.JSON(http.StatusOK, "Forgot password email sent!")
}
8 changes: 8 additions & 0 deletions api/handlers/forgotPassword.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Your request headers, e.g.
POST http://localhost:8080/forgot-password
Content-Type: application/json

# The request body, if any
{
"email": "[email protected]"
}
10 changes: 8 additions & 2 deletions api/handlers/register.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package handlers

import (
"bkawk/go-echo/api/email"
"bkawk/go-echo/api/emails"
"bkawk/go-echo/api/models"
"bkawk/go-echo/api/utils"
"context"
Expand Down Expand Up @@ -82,8 +82,14 @@ func RegisterPost(c echo.Context) error {
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "Failed to save user"})
}

// Get the verification URL from the environment
verifyUrl := os.Getenv("VERIFY_URL")
if verifyUrl == "" {
return fmt.Errorf("environment variable not set: VERIFY_URL")
}

// Send welcome email
emailError := email.SendWelcomeEmail(u.Email, "http://example.com/verify?verificationCode="+u.VerificationCode)
emailError := emails.SendWelcomeEmail(u.Email, verifyUrl+"?verificationCode="+u.VerificationCode)
if emailError != nil {
fmt.Println(emailError)
return c.JSON(http.StatusInternalServerError, echo.Map{"error": emailError})
Expand Down
20 changes: 11 additions & 9 deletions api/models/user.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package models

type User struct {
ID string `json:"id" bson:"_id" validate:"required"`
Email string `json:"email" bson:"email" validate:"required,email,max=100"`
Username string `json:"username" bson:"username" validate:"required,min=4,max=12"`
Password string `json:"password" bson:"password" validate:"required,max=64,min=8"`
RefreshToken string `json:"refreshToken,omitempty" bson:"refreshToken,omitempty"`
CreatedAt int64 `json:"createdAt" bson:"createdAt"`
VerificationCode string `json:"verificationCode,omitempty" bson:"verificationCode,omitempty"`
LastSeen int64 `json:"lastSeen,omitempty" bson:"lastSeen,omitempty"`
IsVerified bool `bson:"isVerified"`
ID string `json:"id" bson:"_id" validate:"required"`
Email string `json:"email" bson:"email" validate:"required,email,max=100"`
Username string `json:"username" bson:"username" validate:"min=4,max=12"`
Password string `json:"password" bson:"password" validate:"max=64,min=8"`
RefreshToken string `json:"refreshToken,omitempty" bson:"refreshToken,omitempty"`
CreatedAt int64 `json:"createdAt" bson:"createdAt"`
VerificationCode string `json:"verificationCode,omitempty" bson:"verificationCode,omitempty"`
PasswordResetToken string `json:"passwordResetToken,omitempty" bson:"passwordResetToken,omitempty"`
LastSeen int64 `json:"lastSeen,omitempty" bson:"lastSeen,omitempty"`
IsVerified bool `bson:"isVerified"`
ForgotPassword int64 `json:"forgotPassword,omitempty" bson:"forgotPassword,omitempty"`
}
Loading

0 comments on commit 72d9f16

Please sign in to comment.