fix: safe type assertions across OAuth, model Scan, relay, and middleware
- OAuth: safe type assertions for session state and affiliate code (oauth.go, github.go, discord.go, linuxdo.go, oidc.go) - Model: Scan() methods handle string values from DB drivers, not just []byte (channel.go ChannelInfo, task.go Properties/TaskPrivateData) - Model: safe type assertion in CleanupChannelPollingLocks sync.Map iteration - Relay: safe type assertions in audio_handler, AWS InvokeModel, ollama ConvertClaudeRequest, claude stop sequences, zhipu token cache - Service: fix slice bounds panic in Gemini->OpenAI stop sequences conversion - Service: safe type assertion in CleanupFileSources middleware - Middleware: add missing c.Abort() in turnstile session save failure - Middleware: safe type assertion in distributor channelId - Middleware: safe int comparison in auth helper Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -101,7 +101,8 @@ func getDiscordUserInfoByCode(code string) (*DiscordUser, error) {
|
||||
func DiscordOAuth(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
state := c.Query("state")
|
||||
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||
storedState, _ := session.Get("oauth_state").(string)
|
||||
if state == "" || storedState == "" || state != storedState {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "state is empty or not same",
|
||||
|
||||
@@ -81,7 +81,8 @@ func getGitHubUserInfoByCode(code string) (*GitHubUser, error) {
|
||||
func GitHubOAuth(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
state := c.Query("state")
|
||||
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||
storedState, _ := session.Get("oauth_state").(string)
|
||||
if state == "" || storedState == "" || state != storedState {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "state is empty or not same",
|
||||
@@ -140,10 +141,9 @@ func GitHubOAuth(c *gin.Context) {
|
||||
user.Email = githubUser.Email
|
||||
user.Role = common.RoleCommonUser
|
||||
user.Status = common.UserStatusEnabled
|
||||
affCode := session.Get("aff")
|
||||
inviterId := 0
|
||||
if affCode != nil {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
|
||||
if affCodeStr, ok := session.Get("aff").(string); ok && affCodeStr != "" {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCodeStr)
|
||||
}
|
||||
|
||||
if err := user.Insert(inviterId); err != nil {
|
||||
|
||||
@@ -168,7 +168,8 @@ func LinuxdoOAuth(c *gin.Context) {
|
||||
}
|
||||
|
||||
state := c.Query("state")
|
||||
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||
storedState, _ := session.Get("oauth_state").(string)
|
||||
if state == "" || storedState == "" || state != storedState {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "state is empty or not same",
|
||||
@@ -226,10 +227,9 @@ func LinuxdoOAuth(c *gin.Context) {
|
||||
user.Role = common.RoleCommonUser
|
||||
user.Status = common.UserStatusEnabled
|
||||
|
||||
affCode := session.Get("aff")
|
||||
inviterId := 0
|
||||
if affCode != nil {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
|
||||
if affCodeStr, ok := session.Get("aff").(string); ok && affCodeStr != "" {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCodeStr)
|
||||
}
|
||||
|
||||
if err := user.Insert(inviterId); err != nil {
|
||||
|
||||
@@ -56,7 +56,8 @@ func HandleOAuth(c *gin.Context) {
|
||||
|
||||
// 1. Validate state (CSRF protection)
|
||||
state := c.Query("state")
|
||||
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||
storedState, _ := session.Get("oauth_state").(string)
|
||||
if state == "" || storedState == "" || state != storedState {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": i18n.T(c, i18n.MsgOAuthStateInvalid),
|
||||
@@ -263,10 +264,9 @@ func findOrCreateOAuthUser(c *gin.Context, provider oauth.Provider, oauthUser *o
|
||||
user.Status = common.UserStatusEnabled
|
||||
|
||||
// Handle affiliate code
|
||||
affCode := session.Get("aff")
|
||||
inviterId := 0
|
||||
if affCode != nil {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCode.(string))
|
||||
if affCodeStr, ok := session.Get("aff").(string); ok && affCodeStr != "" {
|
||||
inviterId, _ = model.GetUserIdByAffCode(affCodeStr)
|
||||
}
|
||||
|
||||
// Use transaction to ensure user creation and OAuth binding are atomic
|
||||
|
||||
@@ -103,7 +103,8 @@ func getOidcUserInfoByCode(code string) (*OidcUser, error) {
|
||||
func OidcAuth(c *gin.Context) {
|
||||
session := sessions.Default(c)
|
||||
state := c.Query("state")
|
||||
if state == "" || session.Get("oauth_state") == nil || state != session.Get("oauth_state").(string) {
|
||||
storedState, _ := session.Get("oauth_state").(string)
|
||||
if state == "" || storedState == "" || state != storedState {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": "state is empty or not same",
|
||||
|
||||
@@ -112,7 +112,8 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
return
|
||||
|
||||
}
|
||||
if id != apiUserId {
|
||||
idVal, _ := id.(int)
|
||||
if idVal != apiUserId {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgAuthUserIdMismatch),
|
||||
|
||||
@@ -37,7 +37,8 @@ func Distribute() func(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
id, err := strconv.Atoi(channelId.(string))
|
||||
channelIdStr, _ := channelId.(string)
|
||||
id, err := strconv.Atoi(channelIdStr)
|
||||
if err != nil {
|
||||
abortWithOpenAiMessage(c, http.StatusBadRequest, i18n.T(c, i18n.MsgDistributorInvalidChannelId))
|
||||
return
|
||||
|
||||
@@ -72,6 +72,7 @@ func TurnstileCheck() gin.HandlerFunc {
|
||||
"message": "无法保存会话信息,请重试",
|
||||
"success": false,
|
||||
})
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,18 @@ func (c ChannelInfo) Value() (driver.Value, error) {
|
||||
|
||||
// Scan implements sql.Scanner interface
|
||||
func (c *ChannelInfo) Scan(value interface{}) error {
|
||||
bytesValue, _ := value.([]byte)
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
var bytesValue []byte
|
||||
switch v := value.(type) {
|
||||
case []byte:
|
||||
bytesValue = v
|
||||
case string:
|
||||
bytesValue = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("cannot scan %T into ChannelInfo", value)
|
||||
}
|
||||
return common.Unmarshal(bytesValue, c)
|
||||
}
|
||||
|
||||
@@ -562,7 +573,10 @@ func CleanupChannelPollingLocks() {
|
||||
}
|
||||
|
||||
channelPollingLocks.Range(func(key, value interface{}) bool {
|
||||
channelId := key.(int)
|
||||
channelId, ok := key.(int)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
if !activeChannelSet[channelId] {
|
||||
channelPollingLocks.Delete(channelId)
|
||||
}
|
||||
|
||||
+26
-2
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/heicode/manager/common"
|
||||
@@ -81,7 +82,19 @@ type Properties struct {
|
||||
}
|
||||
|
||||
func (m *Properties) Scan(val interface{}) error {
|
||||
bytesValue, _ := val.([]byte)
|
||||
if val == nil {
|
||||
*m = Properties{}
|
||||
return nil
|
||||
}
|
||||
var bytesValue []byte
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
bytesValue = v
|
||||
case string:
|
||||
bytesValue = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("cannot scan %T into Properties", val)
|
||||
}
|
||||
if len(bytesValue) == 0 {
|
||||
*m = Properties{}
|
||||
return nil
|
||||
@@ -142,7 +155,18 @@ func GenerateTaskID() string {
|
||||
}
|
||||
|
||||
func (p *TaskPrivateData) Scan(val interface{}) error {
|
||||
bytesValue, _ := val.([]byte)
|
||||
if val == nil {
|
||||
return nil
|
||||
}
|
||||
var bytesValue []byte
|
||||
switch v := val.(type) {
|
||||
case []byte:
|
||||
bytesValue = v
|
||||
case string:
|
||||
bytesValue = []byte(v)
|
||||
default:
|
||||
return fmt.Errorf("cannot scan %T into TaskPrivateData", val)
|
||||
}
|
||||
if len(bytesValue) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -67,10 +67,14 @@ func AudioHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *type
|
||||
service.ResetStatusCode(newAPIError, statusCodeMappingStr)
|
||||
return newAPIError
|
||||
}
|
||||
if usage.(*dto.Usage).CompletionTokenDetails.AudioTokens > 0 || usage.(*dto.Usage).PromptTokensDetails.AudioTokens > 0 {
|
||||
service.PostAudioConsumeQuota(c, info, usage.(*dto.Usage), "")
|
||||
usageData, ok := usage.(*dto.Usage)
|
||||
if !ok {
|
||||
return types.NewError(fmt.Errorf("invalid usage type"), types.ErrorCodeBadResponseBody)
|
||||
}
|
||||
if usageData.CompletionTokenDetails.AudioTokens > 0 || usageData.PromptTokensDetails.AudioTokens > 0 {
|
||||
service.PostAudioConsumeQuota(c, info, usageData, "")
|
||||
} else {
|
||||
service.PostTextConsumeQuota(c, info, usage.(*dto.Usage), nil)
|
||||
service.PostTextConsumeQuota(c, info, usageData, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -225,7 +225,11 @@ func awsHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (*types
|
||||
ctx, cancel := newAwsInvokeContext()
|
||||
defer cancel()
|
||||
|
||||
awsResp, err := a.AwsClient.InvokeModel(ctx, a.AwsReq.(*bedrockruntime.InvokeModelInput))
|
||||
invokeInput, ok := a.AwsReq.(*bedrockruntime.InvokeModelInput)
|
||||
if !ok {
|
||||
return types.NewOpenAIError(errors.New("invalid AWS request type"), types.ErrorCodeAwsInvokeError, http.StatusInternalServerError), nil
|
||||
}
|
||||
awsResp, err := a.AwsClient.InvokeModel(ctx, invokeInput)
|
||||
if err != nil {
|
||||
statusCode := getAwsErrorStatusCode(err)
|
||||
return types.NewOpenAIError(errors.Wrap(err, "InvokeModel"), types.ErrorCodeAwsInvokeError, statusCode), nil
|
||||
@@ -255,7 +259,11 @@ func awsStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor) (
|
||||
ctx, cancel := newAwsInvokeContext()
|
||||
defer cancel()
|
||||
|
||||
awsResp, err := a.AwsClient.InvokeModelWithResponseStream(ctx, a.AwsReq.(*bedrockruntime.InvokeModelWithResponseStreamInput))
|
||||
streamInput, ok := a.AwsReq.(*bedrockruntime.InvokeModelWithResponseStreamInput)
|
||||
if !ok {
|
||||
return types.NewOpenAIError(errors.New("invalid AWS stream request type"), types.ErrorCodeAwsInvokeError, http.StatusInternalServerError), nil
|
||||
}
|
||||
awsResp, err := a.AwsClient.InvokeModelWithResponseStream(ctx, streamInput)
|
||||
if err != nil {
|
||||
statusCode := getAwsErrorStatusCode(err)
|
||||
return types.NewOpenAIError(errors.Wrap(err, "InvokeModelWithResponseStream"), types.ErrorCodeAwsInvokeError, statusCode), nil
|
||||
@@ -298,7 +306,11 @@ func handleNovaRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor)
|
||||
ctx, cancel := newAwsInvokeContext()
|
||||
defer cancel()
|
||||
|
||||
awsResp, err := a.AwsClient.InvokeModel(ctx, a.AwsReq.(*bedrockruntime.InvokeModelInput))
|
||||
novaInput, ok := a.AwsReq.(*bedrockruntime.InvokeModelInput)
|
||||
if !ok {
|
||||
return types.NewOpenAIError(errors.New("invalid AWS Nova request type"), types.ErrorCodeAwsInvokeError, http.StatusInternalServerError), nil
|
||||
}
|
||||
awsResp, err := a.AwsClient.InvokeModel(ctx, novaInput)
|
||||
if err != nil {
|
||||
statusCode := getAwsErrorStatusCode(err)
|
||||
return types.NewOpenAIError(errors.Wrap(err, "InvokeModel"), types.ErrorCodeAwsInvokeError, statusCode), nil
|
||||
|
||||
@@ -248,7 +248,9 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
||||
case []interface{}:
|
||||
stopSequences := make([]string, 0)
|
||||
for _, stop := range textRequest.Stop.([]interface{}) {
|
||||
stopSequences = append(stopSequences, stop.(string))
|
||||
if s, ok := stop.(string); ok {
|
||||
stopSequences = append(stopSequences, s)
|
||||
}
|
||||
}
|
||||
claudeRequest.StopSequences = stopSequences
|
||||
}
|
||||
|
||||
@@ -29,11 +29,14 @@ func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayIn
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
openaiRequest.(*dto.GeneralOpenAIRequest).StreamOptions = &dto.StreamOptions{
|
||||
oaiReq, ok := openaiRequest.(*dto.GeneralOpenAIRequest)
|
||||
if !ok {
|
||||
return nil, errors.New("unexpected request type from Claude conversion")
|
||||
}
|
||||
oaiReq.StreamOptions = &dto.StreamOptions{
|
||||
IncludeUsage: true,
|
||||
}
|
||||
// map to ollama chat request (Claude -> OpenAI -> Ollama chat)
|
||||
return openAIChatToOllamaChat(c, openaiRequest.(*dto.GeneralOpenAIRequest))
|
||||
return openAIChatToOllamaChat(c, oaiReq)
|
||||
}
|
||||
|
||||
func (a *Adaptor) ConvertAudioRequest(c *gin.Context, info *relaycommon.RelayInfo, request dto.AudioRequest) (io.Reader, error) {
|
||||
|
||||
@@ -32,7 +32,7 @@ var expSeconds int64 = 24 * 3600
|
||||
func getZhipuToken(apikey string) string {
|
||||
data, ok := zhipuTokens.Load(apikey)
|
||||
if ok {
|
||||
tokenData := data.(zhipuTokenData)
|
||||
tokenData, _ := data.(zhipuTokenData)
|
||||
if time.Now().Before(tokenData.ExpiryTime) {
|
||||
return tokenData.Token
|
||||
}
|
||||
|
||||
@@ -753,7 +753,11 @@ func GeminiToOpenAIRequest(geminiRequest *dto.GeminiChatRequest, info *relaycomm
|
||||
}
|
||||
// gemini stop sequences 最多 5 个,openai stop 最多 4 个
|
||||
if len(geminiRequest.GenerationConfig.StopSequences) > 0 {
|
||||
openaiRequest.Stop = geminiRequest.GenerationConfig.StopSequences[:4]
|
||||
maxStop := 4
|
||||
if len(geminiRequest.GenerationConfig.StopSequences) < maxStop {
|
||||
maxStop = len(geminiRequest.GenerationConfig.StopSequences)
|
||||
}
|
||||
openaiRequest.Stop = geminiRequest.GenerationConfig.StopSequences[:maxStop]
|
||||
}
|
||||
if geminiRequest.GenerationConfig.CandidateCount != nil && *geminiRequest.GenerationConfig.CandidateCount > 0 {
|
||||
openaiRequest.N = lo.ToPtr(*geminiRequest.GenerationConfig.CandidateCount)
|
||||
|
||||
@@ -144,9 +144,11 @@ func registerSourceForCleanup(c *gin.Context, source types.FileSource) {
|
||||
func CleanupFileSources(c *gin.Context) {
|
||||
key := string(constant.ContextKeyFileSourcesToCleanup)
|
||||
if sources, exists := c.Get(key); exists {
|
||||
for _, source := range sources.([]types.FileSource) {
|
||||
if cache := source.GetCache(); cache != nil {
|
||||
cache.Close()
|
||||
if sourcesSlice, ok := sources.([]types.FileSource); ok {
|
||||
for _, source := range sourcesSlice {
|
||||
if cache := source.GetCache(); cache != nil {
|
||||
cache.Close()
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Set(key, nil)
|
||||
|
||||
Reference in New Issue
Block a user