Skip to content

Commit 87b43ef

Browse files
committed
ADD(CORE): reset,forget password, send email->go net/smtp
1 parent 1f82d8c commit 87b43ef

File tree

2 files changed

+160
-6
lines changed

2 files changed

+160
-6
lines changed

asssets/safeenv.png

2.14 KB
Loading

main.go

+160-6
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"log"
1212
"net/http"
13+
"net/smtp"
1314
"time"
1415

1516
"os"
@@ -30,6 +31,11 @@ var collection *mongo.Collection
3031
var encryptionKey []byte
3132
var jwtSecret []byte
3233

34+
// var smtpEmail = os.Getenv("SMTP_EMAIL")
35+
// var smtpPassword = os.Getenv("SMTP_PASSWORD")
36+
// var smtpHost = os.Getenv("SMTP_HOST")
37+
// var smtpPort = os.Getenv("SMTP_PORT")
38+
3339
func init() {
3440
err := godotenv.Load() // Load .env file
3541
if err != nil {
@@ -73,6 +79,10 @@ func main() {
7379
r.POST("/api/v1/register", registerUser)
7480
r.POST("/api/v1/login", loginUser)
7581

82+
// Password reset routes
83+
r.POST("/api/v1/forgot-password", requestPasswordReset)
84+
r.POST("/api/v1/reset-password", resetPassword)
85+
7686
// protected routes
7787
auth := r.Group("/api/v1")
7888
auth.Use(authMiddleware())
@@ -312,7 +322,7 @@ func getCurrentUser(c *gin.Context) {
312322
})
313323
}
314324

315-
// Delete a Key
325+
// Delete a Key->new way
316326
func deleteKey(c *gin.Context) {
317327
userID, exists := c.Get("userID")
318328
if !exists {
@@ -594,8 +604,152 @@ func storeVariablesBulk(c *gin.Context) {
594604
c.JSON(http.StatusOK, gin.H{"message": "Variables stored successfully"})
595605
}
596606

597-
// func auditLogs(c *gin.Context) {
598-
// // Dummy audit log response for now
599-
// logs := []string{"User1 stored KEY1", "User2 retrieved KEY2"}
600-
// c.JSON(http.StatusOK, gin.H{"logs": logs})
601-
// }
607+
// send-reset-email
608+
func sendResetEmail(to, resetToken string) error {
609+
smtpHost := os.Getenv("SMTP_HOST")
610+
smtpPort := os.Getenv("SMTP_PORT")
611+
smtpEmail := os.Getenv("SMTP_EMAIL")
612+
smtpPassword := os.Getenv("SMTP_PASSWORD")
613+
frontendURL := os.Getenv("SAFEENV_FRONTEND_URL")
614+
615+
// Debugging logs
616+
fmt.Println("SMTP Configs:", smtpHost, smtpPort, smtpEmail)
617+
618+
if smtpHost == "" || smtpPort == "" || smtpEmail == "" || smtpPassword == "" || frontendURL == "" {
619+
return fmt.Errorf("SMTP credentials are not set properly")
620+
}
621+
622+
addr := fmt.Sprintf("%s:%s", smtpHost, smtpPort)
623+
auth := smtp.PlainAuth("", smtpEmail, smtpPassword, smtpHost)
624+
625+
// Generate the reset link with the token
626+
resetLink := fmt.Sprintf("%s/reset-password?token=%s", frontendURL, resetToken)
627+
628+
subject := "Password Reset Request"
629+
body := fmt.Sprintf(
630+
"Hello,\n\nClick the link below to reset your password:\n%s\n\nIf you didn't request this, please ignore it.The link will expire in 1 hour.\n\nThanks,\nSafeEnv",
631+
resetLink,
632+
)
633+
634+
msg := []byte("Subject: " + subject + "\r\n\r\n" + body)
635+
636+
err := smtp.SendMail(addr, auth, smtpEmail, []string{to}, msg)
637+
if err != nil {
638+
return fmt.Errorf("failed to send email: %v", err)
639+
}
640+
return nil
641+
}
642+
643+
func requestPasswordReset(c *gin.Context) {
644+
var request struct {
645+
Email string `json:"email"`
646+
}
647+
648+
if err := c.ShouldBindJSON(&request); err != nil {
649+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
650+
return
651+
}
652+
653+
// Check if user exists
654+
var user struct {
655+
ID primitive.ObjectID `bson:"_id"`
656+
Email string `bson:"email"`
657+
}
658+
err := collection.Database().Collection("users").FindOne(context.TODO(), bson.M{"email": request.Email}).Decode(&user)
659+
if err != nil {
660+
c.JSON(http.StatusNotFound, gin.H{"error": "User not found"})
661+
return
662+
}
663+
664+
// Generate reset token (JWT)
665+
resetToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
666+
"email": user.Email,
667+
"exp": time.Now().Add(time.Hour * 1).Unix(), // Expires in 1 hour
668+
})
669+
tokenString, err := resetToken.SignedString(jwtSecret)
670+
if err != nil {
671+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
672+
return
673+
}
674+
675+
// Store token in DB (optional)
676+
_, err = collection.Database().Collection("password_resets").InsertOne(context.TODO(), bson.M{
677+
"email": user.Email,
678+
"token": tokenString,
679+
"createdAt": time.Now(),
680+
"expiresAt": time.Now().Add(time.Hour),
681+
"used": false,
682+
})
683+
684+
if err != nil {
685+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to store reset token"})
686+
return
687+
}
688+
689+
// Send Reset Email
690+
err = sendResetEmail(user.Email, tokenString)
691+
if err != nil {
692+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send email" + err.Error()})
693+
return
694+
}
695+
696+
c.JSON(http.StatusOK, gin.H{"message": "Password reset email sent!"})
697+
}
698+
699+
func resetPassword(c *gin.Context) {
700+
var request struct {
701+
Token string `json:"token"`
702+
NewPassword string `json:"newPassword"`
703+
}
704+
705+
if err := c.ShouldBindJSON(&request); err != nil {
706+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request"})
707+
return
708+
}
709+
710+
// Parse the token
711+
token, err := jwt.Parse(request.Token, func(token *jwt.Token) (interface{}, error) {
712+
return jwtSecret, nil
713+
})
714+
715+
if err != nil || !token.Valid {
716+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
717+
return
718+
}
719+
720+
claims, ok := token.Claims.(jwt.MapClaims)
721+
if !ok {
722+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"})
723+
return
724+
}
725+
726+
email, ok := claims["email"].(string)
727+
if !ok {
728+
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email in token"})
729+
return
730+
}
731+
732+
// Hash the new password
733+
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(request.NewPassword), bcrypt.DefaultCost)
734+
if err != nil {
735+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"})
736+
return
737+
}
738+
739+
// Update the user's password
740+
_, err = collection.Database().Collection("users").UpdateOne(
741+
context.TODO(),
742+
bson.M{"email": email},
743+
bson.M{"$set": bson.M{"passwordHash": string(hashedPassword)}},
744+
)
745+
746+
if err != nil {
747+
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update password"})
748+
return
749+
}
750+
751+
// Invalidate the reset token
752+
collection.Database().Collection("password_resets").DeleteOne(context.TODO(), bson.M{"email": email})
753+
754+
c.JSON(http.StatusOK, gin.H{"message": "Password updated successfully!"})
755+
}

0 commit comments

Comments
 (0)