grafana/pkg/services/notifications/codes.go
Andreas Gerstmayr 8b22481aec
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>
2022-08-18 16:01:09 +02:00

116 lines
3.1 KiB
Go

package notifications
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"strconv"
"time"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
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 + 64 HMAC-SHA256 encoded string
func createTimeLimitCode(payload string, minutes int, startStr string) (string, error) {
format := "200601021504"
var start, end time.Time
var endStr string
if startStr == "" {
// Use now time create code
start = time.Now()
startStr = start.Format(format)
} else {
// use start string create code
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 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(h.Sum(nil))
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
return code, nil
}
// verify time limit code
func validateUserEmailCode(cfg *setting.Cfg, user *user.User, code string) (bool, error) {
if len(code) <= 18 {
return false, nil
}
code = code[:timeLimitCodeLength]
// split code
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
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 hmac.Equal([]byte(code), []byte(expectedCode)) && minutes > 0 {
// check time is expired or not
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
}
}
return false, nil
}
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(cfg *setting.Cfg, user *user.User, startStr string) (string, error) {
minutes := cfg.EmailCodeValidMinutes
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(user.Login))
return code, nil
}