Skip to content

Commit c8bc0b3

Browse files
committed
Lots of work on user password reset, grafana#1456
1 parent aa4d60c commit c8bc0b3

File tree

8 files changed

+129
-50
lines changed

8 files changed

+129
-50
lines changed

pkg/api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func Register(r *macaron.Macaron) {
4646
r.Get("/user/password/reset", Index)
4747

4848
r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
49-
r.Post("/api/user/password/reset", wrap(ViewResetPasswordForm))
49+
r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
5050

5151
// dashboard snapshots
5252
r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)

pkg/api/dtos/user.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,9 @@ type AdminUserListItem struct {
3131
type SendResetPasswordEmailForm struct {
3232
UserOrEmail string `json:"userOrEmail" binding:"Required"`
3333
}
34+
35+
type ResetUserPasswordForm struct {
36+
Code string `json:"code"`
37+
NewPassword string `json:"newPassword"`
38+
ConfirmPassword string `json:"confirmPassword"`
39+
}

pkg/api/password.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"github.com/grafana/grafana/pkg/bus"
66
"github.com/grafana/grafana/pkg/middleware"
77
m "github.com/grafana/grafana/pkg/models"
8+
"github.com/grafana/grafana/pkg/util"
89
)
910

1011
func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEmailForm) Response {
@@ -22,6 +23,27 @@ func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEm
2223
return ApiSuccess("Email sent")
2324
}
2425

25-
func ViewResetPasswordForm(c *middleware.Context) Response {
26-
return ApiSuccess("Email sent")
26+
func ResetPassword(c *middleware.Context, form dtos.ResetUserPasswordForm) Response {
27+
query := m.ValidateResetPasswordCodeQuery{Code: form.Code}
28+
29+
if err := bus.Dispatch(&query); err != nil {
30+
if err == m.ErrInvalidEmailCode {
31+
return ApiError(400, "Invalid or expired reset password code", nil)
32+
}
33+
return ApiError(500, "Unknown error validating email code", err)
34+
}
35+
36+
if form.NewPassword != form.ConfirmPassword {
37+
return ApiError(400, "Passwords do not match", nil)
38+
}
39+
40+
cmd := m.ChangeUserPasswordCommand{}
41+
cmd.UserId = query.Result.Id
42+
cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
43+
44+
if err := bus.Dispatch(&cmd); err != nil {
45+
return ApiError(500, "Failed to change user password", err)
46+
}
47+
48+
return ApiSuccess("User password changed")
2749
}

pkg/models/emails.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package models
22

3+
import "errors"
4+
5+
var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
6+
37
type SendEmailCommand struct {
48
To []string
59
From string
@@ -13,6 +17,11 @@ type SendResetPasswordEmailCommand struct {
1317
User *User
1418
}
1519

20+
type ValidateResetPasswordCodeQuery struct {
21+
Code string
22+
Result *User
23+
}
24+
1625
// create mail content
1726
func (m *SendEmailCommand) Content() string {
1827
contentType := "text/html; charset=UTF-8"

pkg/services/notifications/codes.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import (
77
"time"
88

99
"github.com/Unknwon/com"
10+
m "github.com/grafana/grafana/pkg/models"
1011
"github.com/grafana/grafana/pkg/setting"
1112
)
1213

14+
const timeLimitCodeLength = 12 + 6 + 40
15+
1316
// create a time limit code
1417
// code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
15-
func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
18+
func createTimeLimitCode(data string, minutes int, startInf interface{}) string {
1619
format := "200601021504"
1720

1821
var start, end time.Time
@@ -42,11 +45,14 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
4245
}
4346

4447
// verify time limit code
45-
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
48+
func validateUserEmailCode(user *m.User, code string) bool {
4649
if len(code) <= 18 {
4750
return false
4851
}
4952

53+
minutes := setting.EmailCodeValidMinutes
54+
code = code[:timeLimitCodeLength]
55+
5056
// split code
5157
start := code[:12]
5258
lives := code[12:18]
@@ -55,7 +61,9 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool {
5561
}
5662

5763
// right active code
58-
retCode := CreateTimeLimitCode(data, minutes, start)
64+
data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands
65+
retCode := createTimeLimitCode(data, minutes, start)
66+
fmt.Printf("code : %s\ncode2: %s", retCode, code)
5967
if retCode == code && minutes > 0 {
6068
// check time is expired or not
6169
before, _ := time.ParseInLocation("200601021504", start, time.Local)
@@ -67,3 +75,24 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool {
6775

6876
return false
6977
}
78+
79+
func getLoginForEmailCode(code string) string {
80+
if len(code) <= timeLimitCodeLength {
81+
return ""
82+
}
83+
84+
// use tail hex username query user
85+
hexStr := code[timeLimitCodeLength:]
86+
b, _ := hex.DecodeString(hexStr)
87+
return string(b)
88+
}
89+
90+
func createUserEmailCode(u *m.User, startInf interface{}) string {
91+
minutes := setting.EmailCodeValidMinutes
92+
data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
93+
code := createTimeLimitCode(data, minutes, startInf)
94+
95+
// add tail hex username
96+
code += hex.EncodeToString([]byte(u.Login))
97+
return code
98+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package notifications
2+
3+
import (
4+
"testing"
5+
6+
m "github.com/grafana/grafana/pkg/models"
7+
"github.com/grafana/grafana/pkg/setting"
8+
. "github.com/smartystreets/goconvey/convey"
9+
)
10+
11+
func TestEmailCodes(t *testing.T) {
12+
13+
Convey("When generating code", t, func() {
14+
setting.EmailCodeValidMinutes = 120
15+
16+
user := &m.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"}
17+
code := createUserEmailCode(user, nil)
18+
19+
Convey("getLoginForCode should return login", func() {
20+
login := getLoginForEmailCode(code)
21+
So(login, ShouldEqual, "asd")
22+
})
23+
24+
Convey("Can verify valid code", func() {
25+
So(validateUserEmailCode(user, code), ShouldBeTrue)
26+
})
27+
28+
Convey("Cannot verify in-valid code", func() {
29+
code = "ASD"
30+
So(validateUserEmailCode(user, code), ShouldBeFalse)
31+
})
32+
33+
})
34+
35+
}

pkg/services/notifications/notifications.go

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ package notifications
22

33
import (
44
"bytes"
5-
"encoding/hex"
65
"errors"
76
"html/template"
87
"path/filepath"
98

10-
"github.com/Unknwon/com"
119
"github.com/grafana/grafana/pkg/bus"
1210
m "github.com/grafana/grafana/pkg/models"
1311
"github.com/grafana/grafana/pkg/setting"
@@ -19,6 +17,7 @@ var tmplResetPassword = "reset_password.html"
1917

2018
func Init() error {
2119
bus.AddHandler("email", sendResetPasswordEmail)
20+
bus.AddHandler("email", validateResetPasswordCode)
2221

2322
mailTemplates = template.New("name")
2423
mailTemplates.Funcs(template.FuncMap{
@@ -55,7 +54,7 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
5554
var buffer bytes.Buffer
5655

5756
var data = getMailTmplData(cmd.User)
58-
code := CreateUserActiveCode(cmd.User, nil)
57+
code := createUserEmailCode(cmd.User, nil)
5958
data["Code"] = code
6059

6160
mailTemplates.ExecuteTemplate(&buffer, tmplResetPassword, data)
@@ -70,44 +69,21 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
7069
return nil
7170
}
7271

73-
func CreateUserActiveCode(u *m.User, startInf interface{}) string {
74-
minutes := setting.EmailCodeValidMinutes
75-
data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
76-
code := CreateTimeLimitCode(data, minutes, startInf)
72+
func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
73+
login := getLoginForEmailCode(query.Code)
74+
if login == "" {
75+
return m.ErrInvalidEmailCode
76+
}
7777

78-
// add tail hex username
79-
code += hex.EncodeToString([]byte(u.Login))
80-
return code
81-
}
78+
userQuery := m.GetUserByLoginQuery{LoginOrEmail: login}
79+
if err := bus.Dispatch(&userQuery); err != nil {
80+
return err
81+
}
8282

83-
// // verify active code when active account
84-
// func VerifyUserActiveCode(code string) (user *User) {
85-
// minutes := setting.Service.ActiveCodeLives
86-
//
87-
// if user = getVerifyUser(code); user != nil {
88-
// // time limit code
89-
// prefix := code[:base.TimeLimitCodeLength]
90-
// data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
91-
//
92-
// if base.VerifyTimeLimitCode(data, minutes, prefix) {
93-
// return user
94-
// }
95-
// }
96-
// return nil
97-
// }
98-
//
99-
// // verify active code when active account
100-
// func VerifyUserActiveCode(code string) (user *User) {
101-
// minutes := setting.Service.ActiveCodeLives
102-
//
103-
// if user = getVerifyUser(code); user != nil {
104-
// // time limit code
105-
// prefix := code[:base.TimeLimitCodeLength]
106-
// data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
107-
//
108-
// if base.VerifyTimeLimitCode(data, minutes, prefix) {
109-
// return user
110-
// }
111-
// }
112-
// return nil
113-
// }
83+
if !validateUserEmailCode(userQuery.Result, query.Code) {
84+
return m.ErrInvalidEmailCode
85+
}
86+
87+
query.Result = userQuery.Result
88+
return nil
89+
}

public/app/controllers/resetPasswordCtrl.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ function (angular) {
1212
$scope.formModel = {};
1313
$scope.mode = 'send';
1414

15-
if ($location.search().code) {
15+
var params = $location.search();
16+
if (params.code) {
1617
$scope.mode = 'reset';
18+
$scope.formModel.code = params.code;
1719
}
1820

1921
$scope.sendResetEmail = function() {
@@ -33,7 +35,7 @@ function (angular) {
3335
return;
3436
}
3537

36-
backendSrv.post('/api/user/password/send-reset-email', $scope.formModel).then(function() {
38+
backendSrv.post('/api/user/password/reset', $scope.formModel).then(function() {
3739
$location.path('login');
3840
});
3941
};

0 commit comments

Comments
 (0)