Skip to content

Commit a54eca3

Browse files
committed
updated
1 parent ed3e66a commit a54eca3

File tree

5 files changed

+208
-14
lines changed

5 files changed

+208
-14
lines changed

controllers/auth.controller.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package controllers
22

33
import (
4+
"log"
45
"net/http"
56
"strings"
67
"time"
@@ -85,7 +86,7 @@ func (ac *AuthController) SignUpUser(ctx *gin.Context) {
8586
Subject: "Your account verification code",
8687
}
8788

88-
utils.SendEmail(&newUser, &emailData)
89+
utils.SendEmail(&newUser, &emailData, "verificationCode.html")
8990

9091
message := "We sent an email with a verification code to " + newUser.Email
9192
ctx.JSON(http.StatusCreated, gin.H{"status": "success", "message": message})
@@ -161,3 +162,92 @@ func (ac *AuthController) VerifyEmail(ctx *gin.Context) {
161162

162163
ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Email verified successfully"})
163164
}
165+
166+
func (ac *AuthController) ForgotPassword(ctx *gin.Context) {
167+
var payload *models.ForgotPasswordInput
168+
169+
if err := ctx.ShouldBindJSON(&payload); err != nil {
170+
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
171+
return
172+
}
173+
174+
message := "You will receive a reset email if user with that email exist"
175+
176+
var user models.User
177+
result := ac.DB.First(&user, "email = ?", strings.ToLower(payload.Email))
178+
if result.Error != nil {
179+
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Invalid email or Password"})
180+
return
181+
}
182+
183+
if !user.Verified {
184+
ctx.JSON(http.StatusUnauthorized, gin.H{"status": "error", "message": "Account not verified"})
185+
return
186+
}
187+
188+
config, err := initializers.LoadConfig(".")
189+
if err != nil {
190+
log.Fatal("Could not load config", err)
191+
}
192+
193+
// Generate Verification Code
194+
resetToken := randstr.String(20)
195+
196+
passwordResetToken := utils.Encode(resetToken)
197+
user.PasswordResetToken = passwordResetToken
198+
user.PasswordResetAt = time.Now().Add(time.Minute * 15)
199+
ac.DB.Save(&user)
200+
201+
var firstName = user.Name
202+
203+
if strings.Contains(firstName, " ") {
204+
firstName = strings.Split(firstName, " ")[1]
205+
}
206+
207+
// 👇 Send Email
208+
emailData := utils.EmailData{
209+
URL: config.ClientOrigin + "/forgotPassword/" + resetToken,
210+
FirstName: firstName,
211+
Subject: "Your password reset token (valid for 10min)",
212+
}
213+
214+
utils.SendEmail(&user, &emailData, "resetPassword.html")
215+
216+
ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": message})
217+
}
218+
219+
func (ac *AuthController) ResetPassword(ctx *gin.Context) {
220+
var payload *models.ResetPasswordInput
221+
resetToken := ctx.Params.ByName("resetToken")
222+
223+
if err := ctx.ShouldBindJSON(&payload); err != nil {
224+
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": err.Error()})
225+
return
226+
}
227+
228+
if payload.Password != payload.PasswordConfirm {
229+
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Passwords do not match"})
230+
return
231+
}
232+
233+
hashedPassword, _ := utils.HashPassword(payload.Password)
234+
235+
passwordResetToken := utils.Encode(resetToken)
236+
237+
var updatedUser models.User
238+
result := ac.DB.First(&updatedUser, "password_reset_token = ?", passwordResetToken)
239+
if result.Error != nil {
240+
ctx.JSON(http.StatusBadRequest, gin.H{"status": "fail", "message": "Invalid verification code or user doesn't exists"})
241+
return
242+
}
243+
244+
updatedUser.Password = hashedPassword
245+
updatedUser.PasswordResetToken = ""
246+
ac.DB.Save(&updatedUser)
247+
248+
ctx.SetCookie("access_token", "", -1, "/", "localhost", false, true)
249+
ctx.SetCookie("refresh_token", "", -1, "/", "localhost", false, true)
250+
ctx.SetCookie("logged_in", "", -1, "/", "localhost", false, true)
251+
252+
ctx.JSON(http.StatusOK, gin.H{"status": "success", "message": "Password data updated successfully"})
253+
}

models/user.model.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,19 @@ import (
77
)
88

99
type User struct {
10-
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
11-
Name string `gorm:"type:varchar(255);not null"`
12-
Email string `gorm:"uniqueIndex;not null"`
13-
Password string `gorm:"not null"`
14-
Role string `gorm:"type:varchar(255);not null"`
15-
Provider string `gorm:"not null"`
16-
Photo string `gorm:"not null"`
17-
VerificationCode string
18-
Verified bool `gorm:"not null"`
19-
CreatedAt time.Time `gorm:"not null"`
20-
UpdatedAt time.Time `gorm:"not null"`
10+
ID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primary_key"`
11+
Name string `gorm:"type:varchar(255);not null"`
12+
Email string `gorm:"uniqueIndex;not null"`
13+
Password string `gorm:"not null"`
14+
Role string `gorm:"type:varchar(255);not null"`
15+
Provider string `gorm:"not null"`
16+
Photo string `gorm:"not null"`
17+
VerificationCode string
18+
PasswordResetToken string
19+
PasswordResetAt time.Time
20+
Verified bool `gorm:"not null"`
21+
CreatedAt time.Time `gorm:"not null"`
22+
UpdatedAt time.Time `gorm:"not null"`
2123
}
2224

2325
type SignUpInput struct {
@@ -43,3 +45,14 @@ type UserResponse struct {
4345
CreatedAt time.Time `json:"created_at"`
4446
UpdatedAt time.Time `json:"updated_at"`
4547
}
48+
49+
// 👈 ForgotPasswordInput struct
50+
type ForgotPasswordInput struct {
51+
Email string `json:"email" binding:"required"`
52+
}
53+
54+
// 👈 ResetPasswordInput struct
55+
type ResetPasswordInput struct {
56+
Password string `json:"password" binding:"required"`
57+
PasswordConfirm string `json:"passwordConfirm" binding:"required"`
58+
}

routes/auth.routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ func (rc *AuthRouteController) AuthRoute(rg *gin.RouterGroup) {
2121
router.POST("/login", rc.authController.SignInUser)
2222
router.GET("/logout", middleware.DeserializeUser(), rc.authController.LogoutUser)
2323
router.GET("/verifyemail/:verificationCode", rc.authController.VerifyEmail)
24+
router.POST("/forgotpassword", rc.authController.ForgotPassword)
25+
router.PATCH("/resetpassword/:resetToken", rc.authController.ResetPassword)
2426
}

templates/resetPassword.html

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
5+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
6+
{{template "styles" .}}
7+
<title>{{ .Subject}}</title>
8+
</head>
9+
<body>
10+
<table
11+
role="presentation"
12+
border="0"
13+
cellpadding="0"
14+
cellspacing="0"
15+
class="body"
16+
>
17+
<tr>
18+
<td>&nbsp;</td>
19+
<td class="container">
20+
<div class="content">
21+
<!-- START CENTERED WHITE CONTAINER -->
22+
<table role="presentation" class="main">
23+
<!-- START MAIN CONTENT AREA -->
24+
<tr>
25+
<td class="wrapper">
26+
<table
27+
role="presentation"
28+
border="0"
29+
cellpadding="0"
30+
cellspacing="0"
31+
>
32+
<tr>
33+
<td>
34+
<p>Hi {{ .FirstName}},</p>
35+
<p>
36+
Forgot password? Send a PATCH request to with your
37+
password and passwordConfirm to {{.URL}}
38+
</p>
39+
<table
40+
role="presentation"
41+
border="0"
42+
cellpadding="0"
43+
cellspacing="0"
44+
class="btn btn-primary"
45+
>
46+
<tbody>
47+
<tr>
48+
<td align="left">
49+
<table
50+
role="presentation"
51+
border="0"
52+
cellpadding="0"
53+
cellspacing="0"
54+
>
55+
<tbody>
56+
<tr>
57+
<td>
58+
<a href="{{.URL}}" target="_blank"
59+
>Reset password</a
60+
>
61+
</td>
62+
</tr>
63+
</tbody>
64+
</table>
65+
</td>
66+
</tr>
67+
</tbody>
68+
</table>
69+
<p>
70+
If you didn't forget your password, please ignore this
71+
email
72+
</p>
73+
<p>Good luck! Codevo CEO.</p>
74+
</td>
75+
</tr>
76+
</table>
77+
</td>
78+
</tr>
79+
80+
<!-- END MAIN CONTENT AREA -->
81+
</table>
82+
<!-- END CENTERED WHITE CONTAINER -->
83+
</div>
84+
</td>
85+
<td>&nbsp;</td>
86+
</tr>
87+
</table>
88+
</body>
89+
</html>

utils/email.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ func ParseTemplateDir(dir string) (*template.Template, error) {
4141
return template.ParseFiles(paths...)
4242
}
4343

44-
func SendEmail(user *models.User, data *EmailData) {
44+
func SendEmail(user *models.User, data *EmailData, emailTemp string) {
4545
config, err := initializers.LoadConfig(".")
4646

4747
if err != nil {
@@ -63,7 +63,7 @@ func SendEmail(user *models.User, data *EmailData) {
6363
log.Fatal("Could not parse template", err)
6464
}
6565

66-
template.ExecuteTemplate(&body, "verificationCode.html", &data)
66+
template.ExecuteTemplate(&body, emailTemp, &data)
6767

6868
m := gomail.NewMessage()
6969

0 commit comments

Comments
 (0)