mirror of
https://github.com/songquanpeng/one-api.git
synced 2025-09-17 17:16:38 +08:00
- Improve error handling across multiple middleware and adapter components, ensuring consistent error response formats in JSON. - Enhance the functionality of request conversion functions by including context parameters and robust error wrapping. - Introduce new features related to reasoning content in the messaging model, providing better customization and explanations in the documentation.
176 lines
4.7 KiB
Go
176 lines
4.7 KiB
Go
package middleware
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-contrib/sessions"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/pkg/errors"
|
|
"github.com/songquanpeng/one-api/common/blacklist"
|
|
"github.com/songquanpeng/one-api/common/ctxkey"
|
|
"github.com/songquanpeng/one-api/common/logger"
|
|
"github.com/songquanpeng/one-api/common/network"
|
|
"github.com/songquanpeng/one-api/model"
|
|
)
|
|
|
|
func authHelper(c *gin.Context, minRole int) {
|
|
session := sessions.Default(c)
|
|
username := session.Get("username")
|
|
role := session.Get("role")
|
|
id := session.Get("id")
|
|
status := session.Get("status")
|
|
if username == nil {
|
|
logger.SysLog("no user session found, try to use access token")
|
|
// Check access token
|
|
accessToken := c.Request.Header.Get("Authorization")
|
|
if accessToken == "" {
|
|
c.JSON(http.StatusUnauthorized, gin.H{
|
|
"success": false,
|
|
"message": "No permission to perform this operation, not logged in and no access token provided",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
user := model.ValidateAccessToken(accessToken)
|
|
if user != nil && user.Username != "" {
|
|
// Token is valid
|
|
username = user.Username
|
|
role = user.Role
|
|
id = user.Id
|
|
status = user.Status
|
|
} else {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "No permission to perform this operation, access token is invalid",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
}
|
|
if status.(int) == model.UserStatusDisabled || blacklist.IsUserBanned(id.(int)) {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "User has been banned",
|
|
})
|
|
session := sessions.Default(c)
|
|
session.Clear()
|
|
_ = session.Save()
|
|
c.Abort()
|
|
return
|
|
}
|
|
if role.(int) < minRole {
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"success": false,
|
|
"message": "No permission to perform this operation, insufficient permissions",
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
c.Set("username", username)
|
|
c.Set("role", role)
|
|
c.Set("id", id)
|
|
c.Next()
|
|
}
|
|
|
|
func UserAuth() func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
authHelper(c, model.RoleCommonUser)
|
|
}
|
|
}
|
|
|
|
func AdminAuth() func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
authHelper(c, model.RoleAdminUser)
|
|
}
|
|
}
|
|
|
|
func RootAuth() func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
authHelper(c, model.RoleRootUser)
|
|
}
|
|
}
|
|
|
|
func TokenAuth() func(c *gin.Context) {
|
|
return func(c *gin.Context) {
|
|
ctx := c.Request.Context()
|
|
key := c.Request.Header.Get("Authorization")
|
|
key = strings.TrimPrefix(key, "Bearer ")
|
|
key = strings.TrimPrefix(strings.TrimPrefix(key, "sk-"), "laisky-")
|
|
parts := strings.Split(key, "-")
|
|
key = parts[0]
|
|
token, err := model.ValidateUserToken(key)
|
|
if err != nil {
|
|
abortWithError(c, http.StatusUnauthorized, err)
|
|
return
|
|
}
|
|
if token.Subnet != nil && *token.Subnet != "" {
|
|
if !network.IsIpInSubnets(ctx, c.ClientIP(), *token.Subnet) {
|
|
abortWithError(c, http.StatusForbidden, errors.Errorf("This API key can only be used in the specified subnet: %s, current IP: %s", *token.Subnet, c.ClientIP()))
|
|
return
|
|
}
|
|
}
|
|
userEnabled, err := model.CacheIsUserEnabled(token.UserId)
|
|
if err != nil {
|
|
abortWithError(c, http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
if !userEnabled || blacklist.IsUserBanned(token.UserId) {
|
|
abortWithError(c, http.StatusForbidden, errors.New("User has been banned"))
|
|
return
|
|
}
|
|
requestModel, err := getRequestModel(c)
|
|
if err != nil && shouldCheckModel(c) {
|
|
abortWithError(c, http.StatusBadRequest, err)
|
|
return
|
|
}
|
|
c.Set(ctxkey.RequestModel, requestModel)
|
|
if token.Models != nil && *token.Models != "" {
|
|
c.Set(ctxkey.AvailableModels, *token.Models)
|
|
if requestModel != "" && !isModelInList(requestModel, *token.Models) {
|
|
abortWithError(c, http.StatusForbidden, errors.Errorf("This API key does not have permission to use the model: %s", requestModel))
|
|
return
|
|
}
|
|
}
|
|
|
|
c.Set(ctxkey.Id, token.UserId)
|
|
c.Set(ctxkey.TokenId, token.Id)
|
|
c.Set(ctxkey.TokenName, token.Name)
|
|
c.Set(ctxkey.TokenQuota, token.RemainQuota)
|
|
c.Set(ctxkey.TokenQuotaUnlimited, token.UnlimitedQuota)
|
|
|
|
if len(parts) > 1 {
|
|
if model.IsAdmin(token.UserId) {
|
|
c.Set(ctxkey.SpecificChannelId, parts[1])
|
|
} else {
|
|
abortWithError(c, http.StatusForbidden, errors.New("Ordinary users do not support specifying channels"))
|
|
return
|
|
}
|
|
}
|
|
|
|
// set channel id for proxy relay
|
|
if channelId := c.Param("channelid"); channelId != "" {
|
|
c.Set(ctxkey.SpecificChannelId, channelId)
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func shouldCheckModel(c *gin.Context) bool {
|
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/completions") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/chat/completions") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/images") {
|
|
return true
|
|
}
|
|
if strings.HasPrefix(c.Request.URL.Path, "/v1/audio") {
|
|
return true
|
|
}
|
|
return false
|
|
}
|