User management: Use HMAC-SHA256 to generate time limit codes (password reset tokens) (#42334)
Signed-off-by: bergquist <carl.bergquist@gmail.com> Co-authored-by: bergquist <carl.bergquist@gmail.com>
This commit is contained in:
co-authored by
bergquist
parent
9f90a7b54d
commit
8b22481aec
@@ -1,48 +1,53 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"crypto/sha1" // #nosec
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/unknwon/com"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
const timeLimitCodeLength = 12 + 6 + 40
|
||||
const timeLimitStartDateLength = 12
|
||||
const timeLimitMinutesLength = 6
|
||||
const timeLimitHmacLength = 64
|
||||
const timeLimitCodeLength = timeLimitStartDateLength + timeLimitMinutesLength + timeLimitHmacLength
|
||||
|
||||
// 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, error) {
|
||||
// code format: 12 length date time string + 6 minutes string + 64 HMAC-SHA256 encoded string
|
||||
func createTimeLimitCode(payload string, minutes int, startStr string) (string, error) {
|
||||
format := "200601021504"
|
||||
|
||||
var start, end time.Time
|
||||
var startStr, endStr string
|
||||
var endStr string
|
||||
|
||||
if startInf == nil {
|
||||
if startStr == "" {
|
||||
// Use now time create code
|
||||
start = time.Now()
|
||||
startStr = start.Format(format)
|
||||
} else {
|
||||
// use start string create code
|
||||
startStr = startInf.(string)
|
||||
start, _ = time.ParseInLocation(format, startStr, time.Local)
|
||||
startStr = start.Format(format)
|
||||
var err error
|
||||
start, err = time.ParseInLocation(format, startStr, time.Local)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
end = start.Add(time.Minute * time.Duration(minutes))
|
||||
endStr = end.Format(format)
|
||||
|
||||
// create sha1 encode string
|
||||
sh := sha1.New()
|
||||
if _, err := sh.Write([]byte(data + setting.SecretKey + startStr + endStr +
|
||||
com.ToStr(minutes))); err != nil {
|
||||
return "", err
|
||||
// create HMAC-SHA256 encoded string
|
||||
key := []byte(setting.SecretKey)
|
||||
h := hmac.New(sha256.New, key)
|
||||
if _, err := h.Write([]byte(payload + startStr + endStr)); err != nil {
|
||||
return "", fmt.Errorf("cannot create hmac: %v", err)
|
||||
}
|
||||
encoded := hex.EncodeToString(sh.Sum(nil))
|
||||
encoded := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
||||
return code, nil
|
||||
@@ -54,25 +59,28 @@ func validateUserEmailCode(cfg *setting.Cfg, user *user.User, code string) (bool
|
||||
return false, nil
|
||||
}
|
||||
|
||||
minutes := cfg.EmailCodeValidMinutes
|
||||
code = code[:timeLimitCodeLength]
|
||||
|
||||
// split code
|
||||
start := code[:12]
|
||||
lives := code[12:18]
|
||||
if d, err := com.StrTo(lives).Int(); err == nil {
|
||||
minutes = d
|
||||
startStr := code[:timeLimitStartDateLength]
|
||||
minutesStr := code[timeLimitStartDateLength : timeLimitStartDateLength+timeLimitMinutesLength]
|
||||
minutes, err := strconv.Atoi(minutesStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("invalid time limit code: %v", err)
|
||||
}
|
||||
|
||||
// right active code
|
||||
data := com.ToStr(user.ID) + user.Email + user.Login + user.Password + user.Rands
|
||||
retCode, err := createTimeLimitCode(data, minutes, start)
|
||||
payload := strconv.FormatInt(user.ID, 10) + user.Email + user.Login + user.Password + user.Rands
|
||||
expectedCode, err := createTimeLimitCode(payload, minutes, startStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if retCode == code && minutes > 0 {
|
||||
if hmac.Equal([]byte(code), []byte(expectedCode)) && minutes > 0 {
|
||||
// check time is expired or not
|
||||
before, _ := time.ParseInLocation("200601021504", start, time.Local)
|
||||
before, err := time.ParseInLocation("200601021504", startStr, time.Local)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
now := time.Now()
|
||||
if before.Add(time.Minute*time.Duration(minutes)).Unix() > now.Unix() {
|
||||
return true, nil
|
||||
@@ -93,15 +101,15 @@ func getLoginForEmailCode(code string) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func createUserEmailCode(cfg *setting.Cfg, u *user.User, startInf interface{}) (string, error) {
|
||||
func createUserEmailCode(cfg *setting.Cfg, user *user.User, startStr string) (string, error) {
|
||||
minutes := cfg.EmailCodeValidMinutes
|
||||
data := com.ToStr(u.ID) + u.Email + u.Login + u.Password + u.Rands
|
||||
code, err := createTimeLimitCode(data, minutes, startInf)
|
||||
payload := strconv.FormatInt(user.ID, 10) + user.Email + user.Login + user.Password + user.Rands
|
||||
code, err := createTimeLimitCode(payload, minutes, startStr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// add tail hex username
|
||||
code += hex.EncodeToString([]byte(u.Login))
|
||||
code += hex.EncodeToString([]byte(user.Login))
|
||||
return code, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user