fix: harden token auth error handling to prevent info leakage
- Create model/errors.go to centralize all sentinel errors - ValidateAccessToken now returns error to distinguish DB failures - ValidateUserToken uses unified ErrTokenInvalid for all auth failures (expired/exhausted/disabled/not-found) to prevent token enumeration - authHelper and TokenAuthReadOnly use i18n messages instead of hardcoded Chinese strings - All err.Error() removed from user-facing responses; DB errors logged server-side and return generic "contact admin" message (HTTP 500) - Migrate ErrRedeemFailed, ErrTwoFANotEnabled to model/errors.go
This commit is contained in:
@@ -28,6 +28,18 @@ const (
|
|||||||
MsgBatchTooMany = "common.batch_too_many"
|
MsgBatchTooMany = "common.batch_too_many"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Auth middleware messages
|
||||||
|
const (
|
||||||
|
MsgAuthNotLoggedIn = "auth.not_logged_in"
|
||||||
|
MsgAuthAccessTokenInvalid = "auth.access_token_invalid"
|
||||||
|
MsgAuthUserInfoInvalid = "auth.user_info_invalid"
|
||||||
|
MsgAuthUserIdNotProvided = "auth.user_id_not_provided"
|
||||||
|
MsgAuthUserIdFormatError = "auth.user_id_format_error"
|
||||||
|
MsgAuthUserIdMismatch = "auth.user_id_mismatch"
|
||||||
|
MsgAuthUserBanned = "auth.user_banned"
|
||||||
|
MsgAuthInsufficientPrivilege = "auth.insufficient_privilege"
|
||||||
|
)
|
||||||
|
|
||||||
// Token related messages
|
// Token related messages
|
||||||
const (
|
const (
|
||||||
MsgTokenNameTooLong = "token.name_too_long"
|
MsgTokenNameTooLong = "token.name_too_long"
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ common.already_exists: "Already exists"
|
|||||||
common.name_cannot_be_empty: "Name cannot be empty"
|
common.name_cannot_be_empty: "Name cannot be empty"
|
||||||
common.batch_too_many: "Too many items in batch request, maximum is {{.Max}}"
|
common.batch_too_many: "Too many items in batch request, maximum is {{.Max}}"
|
||||||
|
|
||||||
|
# Auth middleware messages
|
||||||
|
auth.not_logged_in: "Unauthorized, not logged in and no access token provided"
|
||||||
|
auth.access_token_invalid: "Unauthorized, invalid access token"
|
||||||
|
auth.user_info_invalid: "Unauthorized, invalid user info"
|
||||||
|
auth.user_id_not_provided: "Unauthorized, New-Api-User header not provided"
|
||||||
|
auth.user_id_format_error: "Unauthorized, New-Api-User header format error"
|
||||||
|
auth.user_id_mismatch: "Unauthorized, New-Api-User does not match logged in user"
|
||||||
|
auth.user_banned: "User has been banned"
|
||||||
|
auth.insufficient_privilege: "Unauthorized, insufficient privileges"
|
||||||
|
|
||||||
# Token messages
|
# Token messages
|
||||||
token.name_too_long: "Token name is too long"
|
token.name_too_long: "Token name is too long"
|
||||||
token.quota_negative: "Quota value cannot be negative"
|
token.quota_negative: "Quota value cannot be negative"
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ common.already_exists: "已存在"
|
|||||||
common.name_cannot_be_empty: "名称不能为空"
|
common.name_cannot_be_empty: "名称不能为空"
|
||||||
common.batch_too_many: "批量请求数量过多,最多 {{.Max}} 条"
|
common.batch_too_many: "批量请求数量过多,最多 {{.Max}} 条"
|
||||||
|
|
||||||
|
# Auth middleware messages
|
||||||
|
auth.not_logged_in: "无权进行此操作,未登录且未提供 access token"
|
||||||
|
auth.access_token_invalid: "无权进行此操作,access token 无效"
|
||||||
|
auth.user_info_invalid: "无权进行此操作,用户信息无效"
|
||||||
|
auth.user_id_not_provided: "无权进行此操作,未提供 New-Api-User"
|
||||||
|
auth.user_id_format_error: "无权进行此操作,New-Api-User 格式错误"
|
||||||
|
auth.user_id_mismatch: "无权进行此操作,New-Api-User 与登录用户不匹配"
|
||||||
|
auth.user_banned: "用户已被封禁"
|
||||||
|
auth.insufficient_privilege: "无权进行此操作,权限不足"
|
||||||
|
|
||||||
# Token messages
|
# Token messages
|
||||||
token.name_too_long: "令牌名称过长"
|
token.name_too_long: "令牌名称过长"
|
||||||
token.quota_negative: "额度值不能为负数"
|
token.quota_negative: "额度值不能为负数"
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ common.already_exists: "已存在"
|
|||||||
common.name_cannot_be_empty: "名稱不能為空"
|
common.name_cannot_be_empty: "名稱不能為空"
|
||||||
common.batch_too_many: "批次請求數量過多,最多 {{.Max}} 條"
|
common.batch_too_many: "批次請求數量過多,最多 {{.Max}} 條"
|
||||||
|
|
||||||
|
# Auth middleware messages
|
||||||
|
auth.not_logged_in: "無權進行此操作,未登入且未提供 access token"
|
||||||
|
auth.access_token_invalid: "無權進行此操作,access token 無效"
|
||||||
|
auth.user_info_invalid: "無權進行此操作,使用者資訊無效"
|
||||||
|
auth.user_id_not_provided: "無權進行此操作,未提供 New-Api-User"
|
||||||
|
auth.user_id_format_error: "無權進行此操作,New-Api-User 格式錯誤"
|
||||||
|
auth.user_id_mismatch: "無權進行此操作,New-Api-User 與登入使用者不匹配"
|
||||||
|
auth.user_banned: "使用者已被封禁"
|
||||||
|
auth.insufficient_privilege: "無權進行此操作,權限不足"
|
||||||
|
|
||||||
# Token messages
|
# Token messages
|
||||||
token.name_too_long: "令牌名稱過長"
|
token.name_too_long: "令牌名稱過長"
|
||||||
token.quota_negative: "額度值不能為負數"
|
token.quota_negative: "額度值不能為負數"
|
||||||
|
|||||||
+57
-20
@@ -1,6 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/QuantumNous/new-api/common"
|
"github.com/QuantumNous/new-api/common"
|
||||||
"github.com/QuantumNous/new-api/constant"
|
"github.com/QuantumNous/new-api/constant"
|
||||||
|
"github.com/QuantumNous/new-api/i18n"
|
||||||
"github.com/QuantumNous/new-api/logger"
|
"github.com/QuantumNous/new-api/logger"
|
||||||
"github.com/QuantumNous/new-api/model"
|
"github.com/QuantumNous/new-api/model"
|
||||||
"github.com/QuantumNous/new-api/service"
|
"github.com/QuantumNous/new-api/service"
|
||||||
@@ -17,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/gin-contrib/sessions"
|
"github.com/gin-contrib/sessions"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
func validUserInfo(username string, role int) bool {
|
func validUserInfo(username string, role int) bool {
|
||||||
@@ -43,17 +46,33 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if accessToken == "" {
|
if accessToken == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,未登录且未提供 access token",
|
"message": common.TranslateMessage(c, i18n.MsgAuthNotLoggedIn),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user := model.ValidateAccessToken(accessToken)
|
user, authErr := model.ValidateAccessToken(accessToken)
|
||||||
|
if authErr != nil {
|
||||||
|
if errors.Is(authErr, model.ErrDatabase) {
|
||||||
|
common.SysLog("ValidateAccessToken database error: " + authErr.Error())
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": common.TranslateMessage(c, i18n.MsgDatabaseError),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": common.TranslateMessage(c, i18n.MsgAuthAccessTokenInvalid),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.Abort()
|
||||||
|
return
|
||||||
|
}
|
||||||
if user != nil && user.Username != "" {
|
if user != nil && user.Username != "" {
|
||||||
if !validUserInfo(user.Username, user.Role) {
|
if !validUserInfo(user.Username, user.Role) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,用户信息无效",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserInfoInvalid),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -67,7 +86,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
} else {
|
} else {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,access token 无效",
|
"message": common.TranslateMessage(c, i18n.MsgAuthAccessTokenInvalid),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -78,7 +97,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if apiUserIdStr == "" {
|
if apiUserIdStr == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,未提供 New-Api-User",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserIdNotProvided),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -87,7 +106,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,New-Api-User 格式错误",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserIdFormatError),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -96,7 +115,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if id != apiUserId {
|
if id != apiUserId {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,New-Api-User 与登录用户不匹配",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserIdMismatch),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -104,7 +123,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if status.(int) == common.UserStatusDisabled {
|
if status.(int) == common.UserStatusDisabled {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "用户已被封禁",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserBanned),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -112,7 +131,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if role.(int) < minRole {
|
if role.(int) < minRole {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,权限不足",
|
"message": common.TranslateMessage(c, i18n.MsgAuthInsufficientPrivilege),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -120,7 +139,7 @@ func authHelper(c *gin.Context, minRole int) {
|
|||||||
if !validUserInfo(username.(string), role.(int)) {
|
if !validUserInfo(username.(string), role.(int)) {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "无权进行此操作,用户信息无效",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserInfoInvalid),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -198,7 +217,7 @@ func TokenAuthReadOnly() func(c *gin.Context) {
|
|||||||
if key == "" {
|
if key == "" {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "未提供 Authorization 请求头",
|
"message": common.TranslateMessage(c, i18n.MsgTokenNotProvided),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -212,19 +231,28 @@ func TokenAuthReadOnly() func(c *gin.Context) {
|
|||||||
|
|
||||||
token, err := model.GetTokenByKey(key, false)
|
token, err := model.GetTokenByKey(key, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
"success": false,
|
c.JSON(http.StatusUnauthorized, gin.H{
|
||||||
"message": "无效的令牌",
|
"success": false,
|
||||||
})
|
"message": common.TranslateMessage(c, i18n.MsgTokenInvalid),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
common.SysLog("TokenAuthReadOnly GetTokenByKey database error: " + err.Error())
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
|
"success": false,
|
||||||
|
"message": common.TranslateMessage(c, i18n.MsgDatabaseError),
|
||||||
|
})
|
||||||
|
}
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userCache, err := model.GetUserCache(token.UserId)
|
userCache, err := model.GetUserCache(token.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
common.SysLog(fmt.Sprintf("TokenAuthReadOnly GetUserCache error for user %d: %v", token.UserId, err))
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": err.Error(),
|
"message": common.TranslateMessage(c, i18n.MsgDatabaseError),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -232,7 +260,7 @@ func TokenAuthReadOnly() func(c *gin.Context) {
|
|||||||
if userCache.Status != common.UserStatusEnabled {
|
if userCache.Status != common.UserStatusEnabled {
|
||||||
c.JSON(http.StatusForbidden, gin.H{
|
c.JSON(http.StatusForbidden, gin.H{
|
||||||
"success": false,
|
"success": false,
|
||||||
"message": "用户已被封禁",
|
"message": common.TranslateMessage(c, i18n.MsgAuthUserBanned),
|
||||||
})
|
})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
@@ -309,7 +337,14 @@ func TokenAuth() func(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
abortWithOpenAiMessage(c, http.StatusUnauthorized, err.Error())
|
if errors.Is(err, model.ErrDatabase) {
|
||||||
|
common.SysLog("TokenAuth ValidateUserToken database error: " + err.Error())
|
||||||
|
abortWithOpenAiMessage(c, http.StatusInternalServerError,
|
||||||
|
common.TranslateMessage(c, i18n.MsgDatabaseError))
|
||||||
|
} else {
|
||||||
|
abortWithOpenAiMessage(c, http.StatusUnauthorized,
|
||||||
|
common.TranslateMessage(c, i18n.MsgTokenInvalid))
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,12 +366,14 @@ func TokenAuth() func(c *gin.Context) {
|
|||||||
|
|
||||||
userCache, err := model.GetUserCache(token.UserId)
|
userCache, err := model.GetUserCache(token.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
abortWithOpenAiMessage(c, http.StatusInternalServerError, err.Error())
|
common.SysLog(fmt.Sprintf("TokenAuth GetUserCache error for user %d: %v", token.UserId, err))
|
||||||
|
abortWithOpenAiMessage(c, http.StatusInternalServerError,
|
||||||
|
common.TranslateMessage(c, i18n.MsgDatabaseError))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userEnabled := userCache.Status == common.UserStatusEnabled
|
userEnabled := userCache.Status == common.UserStatusEnabled
|
||||||
if !userEnabled {
|
if !userEnabled {
|
||||||
abortWithOpenAiMessage(c, http.StatusForbidden, "用户已被封禁")
|
abortWithOpenAiMessage(c, http.StatusForbidden, common.TranslateMessage(c, i18n.MsgAuthUserBanned))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
// Common errors
|
||||||
|
var (
|
||||||
|
ErrDatabase = errors.New("database error")
|
||||||
|
)
|
||||||
|
|
||||||
|
// User auth errors
|
||||||
|
var (
|
||||||
|
ErrInvalidCredentials = errors.New("invalid credentials")
|
||||||
|
ErrUserEmptyCredentials = errors.New("empty credentials")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token auth errors
|
||||||
|
var (
|
||||||
|
ErrTokenNotProvided = errors.New("token not provided")
|
||||||
|
ErrTokenInvalid = errors.New("token invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
// Redemption errors
|
||||||
|
var ErrRedeemFailed = errors.New("redeem.failed")
|
||||||
|
|
||||||
|
// 2FA errors
|
||||||
|
var ErrTwoFANotEnabled = errors.New("2fa not enabled")
|
||||||
@@ -11,9 +11,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrRedeemFailed is returned when redemption fails due to database error
|
|
||||||
var ErrRedeemFailed = errors.New("redeem.failed")
|
|
||||||
|
|
||||||
type Redemption struct {
|
type Redemption struct {
|
||||||
Id int `json:"id"`
|
Id int `json:"id"`
|
||||||
UserId int `json:"user_id"`
|
UserId int `json:"user_id"`
|
||||||
|
|||||||
+9
-18
@@ -187,19 +187,14 @@ func SearchUserTokens(userId int, keyword string, token string, offset int, limi
|
|||||||
|
|
||||||
func ValidateUserToken(key string) (token *Token, err error) {
|
func ValidateUserToken(key string) (token *Token, err error) {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, errors.New("未提供令牌")
|
return nil, ErrTokenNotProvided
|
||||||
}
|
}
|
||||||
token, err = GetTokenByKey(key, false)
|
token, err = GetTokenByKey(key, false)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if token.Status == common.TokenStatusExhausted {
|
if token.Status == common.TokenStatusExhausted ||
|
||||||
keyPrefix := key[:3]
|
token.Status == common.TokenStatusExpired ||
|
||||||
keySuffix := key[len(key)-3:]
|
token.Status != common.TokenStatusEnabled {
|
||||||
return token, errors.New("该令牌额度已用尽 TokenStatusExhausted[sk-" + keyPrefix + "***" + keySuffix + "]")
|
return token, ErrTokenInvalid
|
||||||
} else if token.Status == common.TokenStatusExpired {
|
|
||||||
return token, errors.New("该令牌已过期")
|
|
||||||
}
|
|
||||||
if token.Status != common.TokenStatusEnabled {
|
|
||||||
return token, errors.New("该令牌状态不可用")
|
|
||||||
}
|
}
|
||||||
if token.ExpiredTime != -1 && token.ExpiredTime < common.GetTimestamp() {
|
if token.ExpiredTime != -1 && token.ExpiredTime < common.GetTimestamp() {
|
||||||
if !common.RedisEnabled {
|
if !common.RedisEnabled {
|
||||||
@@ -209,29 +204,25 @@ func ValidateUserToken(key string) (token *Token, err error) {
|
|||||||
common.SysLog("failed to update token status" + err.Error())
|
common.SysLog("failed to update token status" + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return token, errors.New("该令牌已过期")
|
return token, ErrTokenInvalid
|
||||||
}
|
}
|
||||||
if !token.UnlimitedQuota && token.RemainQuota <= 0 {
|
if !token.UnlimitedQuota && token.RemainQuota <= 0 {
|
||||||
if !common.RedisEnabled {
|
if !common.RedisEnabled {
|
||||||
// in this case, we can make sure the token is exhausted
|
|
||||||
token.Status = common.TokenStatusExhausted
|
token.Status = common.TokenStatusExhausted
|
||||||
err := token.SelectUpdate()
|
err := token.SelectUpdate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.SysLog("failed to update token status" + err.Error())
|
common.SysLog("failed to update token status" + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyPrefix := key[:3]
|
return token, ErrTokenInvalid
|
||||||
keySuffix := key[len(key)-3:]
|
|
||||||
return token, fmt.Errorf("[sk-%s***%s] 该令牌额度已用尽 !token.UnlimitedQuota && token.RemainQuota = %d", keyPrefix, keySuffix, token.RemainQuota)
|
|
||||||
}
|
}
|
||||||
return token, nil
|
return token, nil
|
||||||
}
|
}
|
||||||
common.SysLog("ValidateUserToken: failed to get token: " + err.Error())
|
common.SysLog("ValidateUserToken: failed to get token: " + err.Error())
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, errors.New("无效的令牌")
|
return nil, ErrTokenInvalid
|
||||||
} else {
|
|
||||||
return nil, errors.New("无效的令牌,数据库查询出错,请联系管理员")
|
|
||||||
}
|
}
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrDatabase, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTokenByIds(id int, userId int) (*Token, error) {
|
func GetTokenByIds(id int, userId int) (*Token, error) {
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import (
|
|||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTwoFANotEnabled = errors.New("用户未启用2FA")
|
|
||||||
|
|
||||||
// TwoFA 用户2FA设置表
|
// TwoFA 用户2FA设置表
|
||||||
type TwoFA struct {
|
type TwoFA struct {
|
||||||
Id int `json:"id" gorm:"primaryKey"`
|
Id int `json:"id" gorm:"primaryKey"`
|
||||||
|
|||||||
+10
-12
@@ -18,12 +18,6 @@ import (
|
|||||||
|
|
||||||
const UserNameMaxLength = 20
|
const UserNameMaxLength = 20
|
||||||
|
|
||||||
var (
|
|
||||||
ErrDatabase = errors.New("database error")
|
|
||||||
ErrInvalidCredentials = errors.New("invalid credentials")
|
|
||||||
ErrUserEmptyCredentials = errors.New("empty credentials")
|
|
||||||
)
|
|
||||||
|
|
||||||
// User if you add sensitive fields, don't forget to clean them in setupLogin function.
|
// User if you add sensitive fields, don't forget to clean them in setupLogin function.
|
||||||
// Otherwise, the sensitive information will be saved on local storage in plain text!
|
// Otherwise, the sensitive information will be saved on local storage in plain text!
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -766,16 +760,20 @@ func IsAdmin(userId int) bool {
|
|||||||
// return user.Status == common.UserStatusEnabled, nil
|
// return user.Status == common.UserStatusEnabled, nil
|
||||||
//}
|
//}
|
||||||
|
|
||||||
func ValidateAccessToken(token string) (user *User) {
|
func ValidateAccessToken(token string) (*User, error) {
|
||||||
if token == "" {
|
if token == "" {
|
||||||
return nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
token = strings.Replace(token, "Bearer ", "", 1)
|
token = strings.Replace(token, "Bearer ", "", 1)
|
||||||
user = &User{}
|
user := &User{}
|
||||||
if DB.Where("access_token = ?", token).First(user).RowsAffected == 1 {
|
err := DB.Where("access_token = ?", token).First(user).Error
|
||||||
return user
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%w: %v", ErrDatabase, err)
|
||||||
}
|
}
|
||||||
return nil
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserQuota gets quota from Redis first, falls back to DB if needed
|
// GetUserQuota gets quota from Redis first, falls back to DB if needed
|
||||||
|
|||||||
Reference in New Issue
Block a user