mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
ac84069071
* add password service interface * add password service implementation * add tests for password service * add password service wiring * add feature toggle * Rework from service interface to static function * Replace previous password validations * Add codeowners to password service * add error logs * update config files --------- Co-authored-by: Karl Persson <kalle.persson@grafana.com>
116 lines
3.1 KiB
Go
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(secretKey string, 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(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 + string(user.Password) + user.Rands
|
|
expectedCode, err := createTimeLimitCode(cfg.SecretKey, 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 + string(user.Password) + user.Rands
|
|
code, err := createTimeLimitCode(cfg.SecretKey, payload, minutes, startStr)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// add tail hex username
|
|
code += hex.EncodeToString([]byte(user.Login))
|
|
return code, nil
|
|
}
|