瀏覽代碼

Lots of work on user password reset, #1456

Torkel Ödegaard 10 年之前
父節點
當前提交
c8bc0b3bf8

+ 1 - 1
pkg/api/api.go

@@ -46,7 +46,7 @@ func Register(r *macaron.Macaron) {
 	r.Get("/user/password/reset", Index)
 
 	r.Post("/api/user/password/send-reset-email", bind(dtos.SendResetPasswordEmailForm{}), wrap(SendResetPasswordEmail))
-	r.Post("/api/user/password/reset", wrap(ViewResetPasswordForm))
+	r.Post("/api/user/password/reset", bind(dtos.ResetUserPasswordForm{}), wrap(ResetPassword))
 
 	// dashboard snapshots
 	r.Post("/api/snapshots/", bind(m.CreateDashboardSnapshotCommand{}), CreateDashboardSnapshot)

+ 6 - 0
pkg/api/dtos/user.go

@@ -31,3 +31,9 @@ type AdminUserListItem struct {
 type SendResetPasswordEmailForm struct {
 	UserOrEmail string `json:"userOrEmail" binding:"Required"`
 }
+
+type ResetUserPasswordForm struct {
+	Code            string `json:"code"`
+	NewPassword     string `json:"newPassword"`
+	ConfirmPassword string `json:"confirmPassword"`
+}

+ 24 - 2
pkg/api/password.go

@@ -5,6 +5,7 @@ import (
 	"github.com/grafana/grafana/pkg/bus"
 	"github.com/grafana/grafana/pkg/middleware"
 	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/util"
 )
 
 func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEmailForm) Response {
@@ -22,6 +23,27 @@ func SendResetPasswordEmail(c *middleware.Context, form dtos.SendResetPasswordEm
 	return ApiSuccess("Email sent")
 }
 
-func ViewResetPasswordForm(c *middleware.Context) Response {
-	return ApiSuccess("Email sent")
+func ResetPassword(c *middleware.Context, form dtos.ResetUserPasswordForm) Response {
+	query := m.ValidateResetPasswordCodeQuery{Code: form.Code}
+
+	if err := bus.Dispatch(&query); err != nil {
+		if err == m.ErrInvalidEmailCode {
+			return ApiError(400, "Invalid or expired reset password code", nil)
+		}
+		return ApiError(500, "Unknown error validating email code", err)
+	}
+
+	if form.NewPassword != form.ConfirmPassword {
+		return ApiError(400, "Passwords do not match", nil)
+	}
+
+	cmd := m.ChangeUserPasswordCommand{}
+	cmd.UserId = query.Result.Id
+	cmd.NewPassword = util.EncodePassword(form.NewPassword, query.Result.Salt)
+
+	if err := bus.Dispatch(&cmd); err != nil {
+		return ApiError(500, "Failed to change user password", err)
+	}
+
+	return ApiSuccess("User password changed")
 }

+ 9 - 0
pkg/models/emails.go

@@ -1,5 +1,9 @@
 package models
 
+import "errors"
+
+var ErrInvalidEmailCode = errors.New("Invalid or expired email code")
+
 type SendEmailCommand struct {
 	To      []string
 	From    string
@@ -13,6 +17,11 @@ type SendResetPasswordEmailCommand struct {
 	User *User
 }
 
+type ValidateResetPasswordCodeQuery struct {
+	Code   string
+	Result *User
+}
+
 // create mail content
 func (m *SendEmailCommand) Content() string {
 	contentType := "text/html; charset=UTF-8"

+ 32 - 3
pkg/services/notifications/codes.go

@@ -7,12 +7,15 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
+	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
 )
 
+const timeLimitCodeLength = 12 + 6 + 40
+
 // create a time limit code
 // code format: 12 length date time string + 6 minutes string + 40 sha1 encoded string
-func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string {
+func createTimeLimitCode(data string, minutes int, startInf interface{}) string {
 	format := "200601021504"
 
 	var start, end time.Time
@@ -42,11 +45,14 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
 }
 
 // verify time limit code
-func VerifyTimeLimitCode(data string, minutes int, code string) bool {
+func validateUserEmailCode(user *m.User, code string) bool {
 	if len(code) <= 18 {
 		return false
 	}
 
+	minutes := setting.EmailCodeValidMinutes
+	code = code[:timeLimitCodeLength]
+
 	// split code
 	start := code[:12]
 	lives := code[12:18]
@@ -55,7 +61,9 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool {
 	}
 
 	// right active code
-	retCode := CreateTimeLimitCode(data, minutes, start)
+	data := com.ToStr(user.Id) + user.Email + user.Login + user.Password + user.Rands
+	retCode := createTimeLimitCode(data, minutes, start)
+	fmt.Printf("code : %s\ncode2: %s", retCode, code)
 	if retCode == code && minutes > 0 {
 		// check time is expired or not
 		before, _ := time.ParseInLocation("200601021504", start, time.Local)
@@ -67,3 +75,24 @@ func VerifyTimeLimitCode(data string, minutes int, code string) bool {
 
 	return false
 }
+
+func getLoginForEmailCode(code string) string {
+	if len(code) <= timeLimitCodeLength {
+		return ""
+	}
+
+	// use tail hex username query user
+	hexStr := code[timeLimitCodeLength:]
+	b, _ := hex.DecodeString(hexStr)
+	return string(b)
+}
+
+func createUserEmailCode(u *m.User, startInf interface{}) string {
+	minutes := setting.EmailCodeValidMinutes
+	data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
+	code := createTimeLimitCode(data, minutes, startInf)
+
+	// add tail hex username
+	code += hex.EncodeToString([]byte(u.Login))
+	return code
+}

+ 35 - 0
pkg/services/notifications/codes_test.go

@@ -0,0 +1,35 @@
+package notifications
+
+import (
+	"testing"
+
+	m "github.com/grafana/grafana/pkg/models"
+	"github.com/grafana/grafana/pkg/setting"
+	. "github.com/smartystreets/goconvey/convey"
+)
+
+func TestEmailCodes(t *testing.T) {
+
+	Convey("When generating code", t, func() {
+		setting.EmailCodeValidMinutes = 120
+
+		user := &m.User{Id: 10, Email: "t@a.com", Login: "asd", Password: "1", Rands: "2"}
+		code := createUserEmailCode(user, nil)
+
+		Convey("getLoginForCode should return login", func() {
+			login := getLoginForEmailCode(code)
+			So(login, ShouldEqual, "asd")
+		})
+
+		Convey("Can verify valid code", func() {
+			So(validateUserEmailCode(user, code), ShouldBeTrue)
+		})
+
+		Convey("Cannot verify in-valid code", func() {
+			code = "ASD"
+			So(validateUserEmailCode(user, code), ShouldBeFalse)
+		})
+
+	})
+
+}

+ 18 - 42
pkg/services/notifications/notifications.go

@@ -2,12 +2,10 @@ package notifications
 
 import (
 	"bytes"
-	"encoding/hex"
 	"errors"
 	"html/template"
 	"path/filepath"
 
-	"github.com/Unknwon/com"
 	"github.com/grafana/grafana/pkg/bus"
 	m "github.com/grafana/grafana/pkg/models"
 	"github.com/grafana/grafana/pkg/setting"
@@ -19,6 +17,7 @@ var tmplResetPassword = "reset_password.html"
 
 func Init() error {
 	bus.AddHandler("email", sendResetPasswordEmail)
+	bus.AddHandler("email", validateResetPasswordCode)
 
 	mailTemplates = template.New("name")
 	mailTemplates.Funcs(template.FuncMap{
@@ -55,7 +54,7 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
 	var buffer bytes.Buffer
 
 	var data = getMailTmplData(cmd.User)
-	code := CreateUserActiveCode(cmd.User, nil)
+	code := createUserEmailCode(cmd.User, nil)
 	data["Code"] = code
 
 	mailTemplates.ExecuteTemplate(&buffer, tmplResetPassword, data)
@@ -70,44 +69,21 @@ func sendResetPasswordEmail(cmd *m.SendResetPasswordEmailCommand) error {
 	return nil
 }
 
-func CreateUserActiveCode(u *m.User, startInf interface{}) string {
-	minutes := setting.EmailCodeValidMinutes
-	data := com.ToStr(u.Id) + u.Email + u.Login + u.Password + u.Rands
-	code := CreateTimeLimitCode(data, minutes, startInf)
+func validateResetPasswordCode(query *m.ValidateResetPasswordCodeQuery) error {
+	login := getLoginForEmailCode(query.Code)
+	if login == "" {
+		return m.ErrInvalidEmailCode
+	}
 
-	// add tail hex username
-	code += hex.EncodeToString([]byte(u.Login))
-	return code
-}
+	userQuery := m.GetUserByLoginQuery{LoginOrEmail: login}
+	if err := bus.Dispatch(&userQuery); err != nil {
+		return err
+	}
 
-// // verify active code when active account
-// func VerifyUserActiveCode(code string) (user *User) {
-// 	minutes := setting.Service.ActiveCodeLives
-//
-// 	if user = getVerifyUser(code); user != nil {
-// 		// time limit code
-// 		prefix := code[:base.TimeLimitCodeLength]
-// 		data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
-//
-// 		if base.VerifyTimeLimitCode(data, minutes, prefix) {
-// 			return user
-// 		}
-// 	}
-// 	return nil
-// }
-//
-// // verify active code when active account
-// func VerifyUserActiveCode(code string) (user *User) {
-// 	minutes := setting.Service.ActiveCodeLives
-//
-// 	if user = getVerifyUser(code); user != nil {
-// 		// time limit code
-// 		prefix := code[:base.TimeLimitCodeLength]
-// 		data := com.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
-//
-// 		if base.VerifyTimeLimitCode(data, minutes, prefix) {
-// 			return user
-// 		}
-// 	}
-// 	return nil
-// }
+	if !validateUserEmailCode(userQuery.Result, query.Code) {
+		return m.ErrInvalidEmailCode
+	}
+
+	query.Result = userQuery.Result
+	return nil
+}

+ 4 - 2
public/app/controllers/resetPasswordCtrl.js

@@ -12,8 +12,10 @@ function (angular) {
     $scope.formModel = {};
     $scope.mode = 'send';
 
-    if ($location.search().code) {
+    var params = $location.search();
+    if (params.code) {
       $scope.mode = 'reset';
+      $scope.formModel.code = params.code;
     }
 
     $scope.sendResetEmail = function() {
@@ -33,7 +35,7 @@ function (angular) {
         return;
       }
 
-      backendSrv.post('/api/user/password/send-reset-email', $scope.formModel).then(function() {
+      backendSrv.post('/api/user/password/reset', $scope.formModel).then(function() {
         $location.path('login');
       });
     };