fix: purge authentication data on hard user deletion (#6168)
* fix: purge authentication data on hard user deletion * fix: fail closed when 2FA status lookup fails * fix: reject stale Telegram login callbacks * fix(twofa): prevent concurrent backup code and lockout bypasses * fix(auth): harden user deletion and Telegram verification
This commit is contained in:
+52
-21
@@ -4,9 +4,13 @@ import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/QuantumNous/new-api/common"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
@@ -15,6 +19,13 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
const (
|
||||
// The legacy Telegram widget has no nonce. Keep its signed assertion short-lived
|
||||
// so captured callbacks cannot be reused indefinitely.
|
||||
telegramAuthorizationMaxAge = 5 * time.Minute
|
||||
telegramAuthorizationFutureSkew = 2 * time.Minute
|
||||
)
|
||||
|
||||
func TelegramBind(c *gin.Context) {
|
||||
if !common.TelegramOAuthEnabled {
|
||||
c.JSON(200, gin.H{
|
||||
@@ -24,14 +35,15 @@ func TelegramBind(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
params := c.Request.URL.Query()
|
||||
if !checkTelegramAuthorization(params, common.TelegramBotToken) {
|
||||
telegramId, err := verifyTelegramAuthorization(params, common.TelegramBotToken, time.Now())
|
||||
if err != nil {
|
||||
common.SysLog("TelegramBind authorization failed: " + err.Error())
|
||||
c.JSON(200, gin.H{
|
||||
"message": "无效的请求",
|
||||
"success": false,
|
||||
})
|
||||
return
|
||||
}
|
||||
telegramId := params["id"][0]
|
||||
if model.IsTelegramIdAlreadyTaken(telegramId) {
|
||||
c.JSON(200, gin.H{
|
||||
"message": "该 Telegram 账户已被绑定",
|
||||
@@ -78,7 +90,9 @@ func TelegramLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
params := c.Request.URL.Query()
|
||||
if !checkTelegramAuthorization(params, common.TelegramBotToken) {
|
||||
telegramId, err := verifyTelegramAuthorization(params, common.TelegramBotToken, time.Now())
|
||||
if err != nil {
|
||||
common.SysLog("TelegramLogin authorization failed: " + err.Error())
|
||||
c.JSON(200, gin.H{
|
||||
"message": "无效的请求",
|
||||
"success": false,
|
||||
@@ -86,7 +100,6 @@ func TelegramLogin(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
telegramId := params["id"][0]
|
||||
user := model.User{TelegramId: telegramId}
|
||||
if err := user.FillUserByTelegramId(); err != nil {
|
||||
c.JSON(200, gin.H{
|
||||
@@ -98,28 +111,46 @@ func TelegramLogin(c *gin.Context) {
|
||||
setupLogin(&user, c)
|
||||
}
|
||||
|
||||
func checkTelegramAuthorization(params map[string][]string, token string) bool {
|
||||
strs := []string{}
|
||||
var hash = ""
|
||||
func verifyTelegramAuthorization(params url.Values, token string, now time.Time) (string, error) {
|
||||
if token == "" {
|
||||
return "", errors.New("telegram bot token is empty")
|
||||
}
|
||||
for _, values := range params {
|
||||
if len(values) != 1 {
|
||||
return "", errors.New("telegram authorization contains duplicate parameters")
|
||||
}
|
||||
}
|
||||
|
||||
telegramID := params.Get("id")
|
||||
hash := params.Get("hash")
|
||||
authDateText := params.Get("auth_date")
|
||||
if telegramID == "" || hash == "" || authDateText == "" {
|
||||
return "", errors.New("telegram authorization is incomplete")
|
||||
}
|
||||
authDate, err := strconv.ParseInt(authDateText, 10, 64)
|
||||
if err != nil {
|
||||
return "", errors.New("telegram authorization date is invalid")
|
||||
}
|
||||
if authDate < now.Add(-telegramAuthorizationMaxAge).Unix() ||
|
||||
authDate > now.Add(telegramAuthorizationFutureSkew).Unix() {
|
||||
return "", errors.New("telegram authorization has expired")
|
||||
}
|
||||
|
||||
strs := make([]string, 0, len(params)-1)
|
||||
for k, v := range params {
|
||||
if k == "hash" {
|
||||
hash = v[0]
|
||||
continue
|
||||
}
|
||||
strs = append(strs, k+"="+v[0])
|
||||
}
|
||||
sort.Strings(strs)
|
||||
var imploded = ""
|
||||
for _, s := range strs {
|
||||
if imploded != "" {
|
||||
imploded += "\n"
|
||||
}
|
||||
imploded += s
|
||||
secret := sha256.Sum256([]byte(token))
|
||||
mac := hmac.New(sha256.New, secret[:])
|
||||
_, _ = mac.Write([]byte(strings.Join(strs, "\n")))
|
||||
providedHash, err := hex.DecodeString(hash)
|
||||
if err != nil || !hmac.Equal(providedHash, mac.Sum(nil)) {
|
||||
return "", errors.New("telegram authorization signature is invalid")
|
||||
}
|
||||
sha256hash := sha256.New()
|
||||
io.WriteString(sha256hash, token)
|
||||
hmachash := hmac.New(sha256.New, sha256hash.Sum(nil))
|
||||
io.WriteString(hmachash, imploded)
|
||||
ss := hex.EncodeToString(hmachash.Sum(nil))
|
||||
return hash == ss
|
||||
|
||||
return telegramID, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user