@@ -10,6 +10,7 @@ import (
10
10
"fmt"
11
11
"log"
12
12
"net/http"
13
+ "net/smtp"
13
14
"time"
14
15
15
16
"os"
@@ -30,6 +31,11 @@ var collection *mongo.Collection
30
31
var encryptionKey []byte
31
32
var jwtSecret []byte
32
33
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
+
33
39
func init () {
34
40
err := godotenv .Load () // Load .env file
35
41
if err != nil {
@@ -73,6 +79,10 @@ func main() {
73
79
r .POST ("/api/v1/register" , registerUser )
74
80
r .POST ("/api/v1/login" , loginUser )
75
81
82
+ // Password reset routes
83
+ r .POST ("/api/v1/forgot-password" , requestPasswordReset )
84
+ r .POST ("/api/v1/reset-password" , resetPassword )
85
+
76
86
// protected routes
77
87
auth := r .Group ("/api/v1" )
78
88
auth .Use (authMiddleware ())
@@ -312,7 +322,7 @@ func getCurrentUser(c *gin.Context) {
312
322
})
313
323
}
314
324
315
- // Delete a Key
325
+ // Delete a Key->new way
316
326
func deleteKey (c * gin.Context ) {
317
327
userID , exists := c .Get ("userID" )
318
328
if ! exists {
@@ -594,8 +604,152 @@ func storeVariablesBulk(c *gin.Context) {
594
604
c .JSON (http .StatusOK , gin.H {"message" : "Variables stored successfully" })
595
605
}
596
606
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 \n Click the link below to reset your password:\n %s\n \n If you didn't request this, please ignore it.The link will expire in 1 hour.\n \n Thanks,\n SafeEnv" ,
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