feat: publish sanitized custom.97 source
This commit is contained in:
1022 files changed
+69903
-19207
No files matched your search
+228
-61
@@ -23,7 +23,12 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const authIdentityContextKey = "auth_identity"
|
||||
const (
|
||||
authIdentityContextKey = "auth_identity"
|
||||
accessTokenLookupContextKey = "access_token_lookup"
|
||||
accessTokenIdentityContextKey = "access_token_identity"
|
||||
accessTokenSessionRequiredMsg = "a dashboard login session is required"
|
||||
)
|
||||
|
||||
type dashboardCredentialKind int
|
||||
|
||||
@@ -54,18 +59,28 @@ func authHelper(c *gin.Context, minRole int) {
|
||||
return
|
||||
}
|
||||
if user.Status != common.UserStatusEnabled {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "code": "AUTH_USER_DISABLED", "message": common.TranslateMessage(c, i18n.MsgAuthUserBanned)})
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "AUTH_USER_DISABLED", common.NewMessage("User has been banned"))
|
||||
return
|
||||
}
|
||||
if user.Role < minRole {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"success": false, "code": "AUTH_INSUFFICIENT_PRIVILEGE", "message": common.TranslateMessage(c, i18n.MsgAuthInsufficientPrivilege)})
|
||||
abortWithWebMessage(c, http.StatusForbidden, "AUTH_INSUFFICIENT_PRIVILEGE", common.NewMessage("Unauthorized, insufficient privileges"))
|
||||
return
|
||||
}
|
||||
var lookup *accessTokenLookup
|
||||
if useAccessToken {
|
||||
lookup = requestAccessTokenLookup(c)
|
||||
if !enforceAccessTokenRoute(c, lookup, true) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !validUserInfo(user.Username, user.Role) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "code": "AUTH_USER_INVALID", "message": common.TranslateMessage(c, i18n.MsgAuthUserInfoInvalid)})
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "AUTH_USER_INVALID", common.NewMessage("Unauthorized, invalid user info"))
|
||||
return
|
||||
}
|
||||
setDashboardAuthContext(c, user, identity, useAccessToken)
|
||||
if lookup != nil {
|
||||
setAccessTokenContext(c, lookup)
|
||||
}
|
||||
|
||||
// 管理/root 写操作审计兜底:内聚在鉴权链路里,保证任何经过 AdminAuth/RootAuth
|
||||
// 的写接口都会自动留痕(无需在路由上单独挂审计中间件,避免漏挂)。
|
||||
@@ -86,12 +101,29 @@ func TryUserAuth() func(c *gin.Context) {
|
||||
defer finishAccessTokenAudit(c)
|
||||
}
|
||||
user, identity, credentialKind, err := classifyDashboardCredential(c)
|
||||
if errors.Is(err, model.ErrLegacyAccessTokenRetired) {
|
||||
// After the deadline no opaque value is looked up, so a retired
|
||||
// token is indistinguishable from any unknown value: anonymous.
|
||||
credentialKind, err = dashboardCredentialUnmatched, nil
|
||||
}
|
||||
if err != nil {
|
||||
writeDashboardAuthError(c, err)
|
||||
return
|
||||
}
|
||||
// Optional authentication never elevates a token beyond its grant: a
|
||||
// token the route would reject leaves the request anonymous.
|
||||
var lookup *accessTokenLookup
|
||||
if credentialKind == dashboardCredentialPAT {
|
||||
lookup = requestAccessTokenLookup(c)
|
||||
if user.Status != common.UserStatusEnabled || !enforceAccessTokenRoute(c, lookup, false) {
|
||||
credentialKind = dashboardCredentialUnmatched
|
||||
}
|
||||
}
|
||||
if credentialKind != dashboardCredentialUnmatched {
|
||||
setDashboardAuthContext(c, user, identity, credentialKind == dashboardCredentialPAT)
|
||||
if credentialKind == dashboardCredentialPAT {
|
||||
setAccessTokenContext(c, lookup)
|
||||
}
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
@@ -144,6 +176,21 @@ func GetSessionAuthIdentity(c *gin.Context) (service.AuthIdentity, bool) {
|
||||
return identity, true
|
||||
}
|
||||
|
||||
// GetStepUpIdentity returns the identity that may request and consume
|
||||
// security verification proofs: a browser session, or a scoped access token
|
||||
// bound to its own pat:<id> session. Legacy access tokens have neither.
|
||||
func GetStepUpIdentity(c *gin.Context) (service.AuthIdentity, bool) {
|
||||
if identity, ok := GetSessionAuthIdentity(c); ok {
|
||||
return identity, true
|
||||
}
|
||||
value, ok := c.Get(accessTokenIdentityContextKey)
|
||||
if !ok {
|
||||
return service.AuthIdentity{}, false
|
||||
}
|
||||
identity, ok := value.(service.AuthIdentity)
|
||||
return identity, ok && identity.UserID > 0 && identity.SessionID != ""
|
||||
}
|
||||
|
||||
func authenticateDashboardRequest(c *gin.Context) (*model.UserBase, service.AuthIdentity, bool, error) {
|
||||
user, identity, credentialKind, err := classifyDashboardCredential(c)
|
||||
if err != nil {
|
||||
@@ -171,19 +218,128 @@ func classifyDashboardCredential(c *gin.Context) (*model.UserBase, service.AuthI
|
||||
}
|
||||
return user, identity, dashboardCredentialInternal, nil
|
||||
}
|
||||
patUser, err := model.ValidateAccessToken(raw)
|
||||
if err != nil {
|
||||
return nil, service.AuthIdentity{}, dashboardCredentialPAT, err
|
||||
lookup := lookupAccessToken(c, raw)
|
||||
if lookup.user != nil {
|
||||
beginAccessTokenAudit(c, lookup.user, lookup.ref)
|
||||
}
|
||||
if patUser == nil || patUser.Id <= 0 {
|
||||
if lookup.err != nil {
|
||||
if errors.Is(lookup.err, model.ErrAccessTokenExpired) {
|
||||
setAccessTokenAuditParam(c, "failure_reason", "expired")
|
||||
}
|
||||
return nil, service.AuthIdentity{}, dashboardCredentialPAT, lookup.err
|
||||
}
|
||||
if lookup.user == nil {
|
||||
return nil, service.AuthIdentity{}, dashboardCredentialUnmatched, nil
|
||||
}
|
||||
beginAccessTokenAudit(c, patUser, raw)
|
||||
user, err := model.GetUserCache(patUser.Id)
|
||||
if err != nil {
|
||||
return nil, service.AuthIdentity{}, dashboardCredentialPAT, err
|
||||
return lookup.user, service.AuthIdentity{UserID: lookup.user.Id, UserAuthVersion: lookup.user.AuthVersion}, dashboardCredentialPAT, nil
|
||||
}
|
||||
|
||||
// accessTokenLookup is the per-request result of resolving an opaque dashboard
|
||||
// credential. A nil user with a nil err means the value is not a PAT.
|
||||
type accessTokenLookup struct {
|
||||
ref string
|
||||
legacy bool
|
||||
token *model.UserAccessToken
|
||||
user *model.UserBase
|
||||
err error
|
||||
}
|
||||
|
||||
// lookupAccessToken resolves raw once per request, so the access audit and
|
||||
// authentication share one database lookup.
|
||||
func lookupAccessToken(c *gin.Context, raw string) *accessTokenLookup {
|
||||
ref := model.AccessTokenFingerprint(raw)
|
||||
if cached := requestAccessTokenLookup(c); cached != nil && cached.ref == ref {
|
||||
return cached
|
||||
}
|
||||
lookup := &accessTokenLookup{ref: ref}
|
||||
c.Set(accessTokenLookupContextKey, lookup)
|
||||
if !strings.HasPrefix(raw, model.AccessTokenPrefix) {
|
||||
lookup.legacy = true
|
||||
legacyUser, err := model.ValidateAccessToken(raw)
|
||||
if err != nil || legacyUser == nil || legacyUser.Id <= 0 {
|
||||
lookup.err = err
|
||||
return lookup
|
||||
}
|
||||
lookup.user, lookup.err = model.GetUserCache(legacyUser.Id)
|
||||
return lookup
|
||||
}
|
||||
token, err := model.FindUserAccessTokenByHash(ref)
|
||||
if err != nil || token == nil {
|
||||
lookup.err = err
|
||||
return lookup
|
||||
}
|
||||
lookup.token = token
|
||||
lookup.user, lookup.err = model.GetUserCache(token.UserId)
|
||||
if lookup.err == nil && token.Expired(common.GetTimestamp()) {
|
||||
// Keep the owner so the rejected attempt is still audited.
|
||||
lookup.err = model.ErrAccessTokenExpired
|
||||
}
|
||||
return lookup
|
||||
}
|
||||
|
||||
func requestAccessTokenLookup(c *gin.Context) *accessTokenLookup {
|
||||
value, _ := c.Get(accessTokenLookupContextKey)
|
||||
lookup, _ := value.(*accessTokenLookup)
|
||||
return lookup
|
||||
}
|
||||
|
||||
// enforceAccessTokenRoute applies the route's access token rule (fail closed:
|
||||
// scoped tokens cannot call undeclared routes). With abort set it writes the
|
||||
// 403 response and records the reason in the access audit.
|
||||
func enforceAccessTokenRoute(c *gin.Context, lookup *accessTokenLookup, abort bool) bool {
|
||||
key := c.Request.Method + " " + c.FullPath()
|
||||
rule, declared := AccessTokenRouteRule(key)
|
||||
code, reason := "", ""
|
||||
var message *common.Message
|
||||
switch {
|
||||
case declared && rule.kind == accessTokenRuleSession:
|
||||
code, reason, message = "AUTH_SESSION_REQUIRED", "session_required", common.NewMessage(accessTokenSessionRequiredMsg)
|
||||
case lookup.legacy:
|
||||
return true
|
||||
case !declared:
|
||||
common.SysError(common.LogText("access token route is not declared: %s", key))
|
||||
code, reason, message = "ACCESS_TOKEN_ROUTE_UNDECLARED", "route_undeclared", common.NewMessage("Unauthorized, insufficient privileges")
|
||||
case rule.kind == accessTokenRuleAny, service.AccessTokenScopeGranted(lookup.token.GetScopes(), rule.scope):
|
||||
return true
|
||||
default:
|
||||
code, reason, message = "ACCESS_TOKEN_SCOPE_DENIED", "scope_denied", common.NewMessage("The access token lacks the required permission: {{scope}}", map[string]any{"scope": rule.scope})
|
||||
if abort {
|
||||
setAccessTokenAuditParam(c, "required_scope", rule.scope)
|
||||
}
|
||||
}
|
||||
if abort {
|
||||
setAccessTokenAuditParam(c, "failure_reason", reason)
|
||||
abortWithWebMessage(c, http.StatusForbidden, code, message)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setAccessTokenContext exposes the token grant to handlers. Scoped tokens
|
||||
// also get a step-up identity bound to the token instead of a browser session.
|
||||
func setAccessTokenContext(c *gin.Context, lookup *accessTokenLookup) {
|
||||
_, alreadySet := c.Get(accessTokenIdentityContextKey)
|
||||
c.Set("access_token_legacy", lookup.legacy)
|
||||
// Security audit params identify the token the same way the access audit does.
|
||||
c.Set("access_token_ref", lookup.ref)
|
||||
if lookup.legacy {
|
||||
c.Set("access_token_id", 0)
|
||||
c.Set("access_token_scopes", []string{})
|
||||
return
|
||||
}
|
||||
c.Set("access_token_id", lookup.token.Id)
|
||||
c.Set("access_token_scopes", lookup.token.GetScopes())
|
||||
c.Set(accessTokenIdentityContextKey, service.AuthIdentity{
|
||||
UserID: lookup.user.Id,
|
||||
SessionID: model.AccessTokenSessionID(lookup.token.Id),
|
||||
UserAuthVersion: lookup.user.AuthVersion,
|
||||
SessionVersion: model.AccessTokenSessionVersion,
|
||||
})
|
||||
if alreadySet {
|
||||
return
|
||||
}
|
||||
if err := model.TouchUserAccessToken(lookup.token.Id, c.ClientIP(), common.GetTimestamp()); err != nil {
|
||||
logger.LogError(c.Request.Context(), common.LogText("access token last-use update failed (token_id=%d): %v", lookup.token.Id, err))
|
||||
}
|
||||
return user, service.AuthIdentity{UserID: user.Id, UserAuthVersion: user.AuthVersion}, dashboardCredentialPAT, nil
|
||||
}
|
||||
|
||||
func authorizationToken(header string) (string, bool) {
|
||||
@@ -215,21 +371,40 @@ func setDashboardAuthContext(c *gin.Context, user *model.UserBase, identity serv
|
||||
user.WriteContext(c)
|
||||
}
|
||||
|
||||
// abortWithWebMessage answers a web console request; the web console
|
||||
// translates the message, and code, when set, stays a stable error code.
|
||||
func abortWithWebMessage(c *gin.Context, status int, code string, message *common.Message) {
|
||||
var fields []gin.H
|
||||
if code != "" {
|
||||
fields = append(fields, gin.H{"code": code})
|
||||
}
|
||||
common.ApiErrorStatus(c, status, message, fields...)
|
||||
c.Abort()
|
||||
}
|
||||
|
||||
func writeDashboardAuthError(c *gin.Context, err error) {
|
||||
if errors.Is(err, model.ErrAccessTokenExpired) {
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "ACCESS_TOKEN_EXPIRED", common.NewMessage("Unauthorized, the access token has expired"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, model.ErrLegacyAccessTokenRetired) {
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "ACCESS_TOKEN_LEGACY_RETIRED", common.NewMessage("Legacy access tokens have been retired. Create a new access token in security settings"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrAuthTokenExpired) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "code": "AUTH_TOKEN_EXPIRED", "message": common.TranslateMessage(c, i18n.MsgAuthNotLoggedIn)})
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "AUTH_TOKEN_EXPIRED", common.NewMessage("Unauthorized, not logged in and no access token provided"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrLoginSessionRevoked) || errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "code": "AUTH_SESSION_REVOKED", "message": common.TranslateMessage(c, i18n.MsgAuthNotLoggedIn)})
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "AUTH_SESSION_REVOKED", common.NewMessage("Unauthorized, not logged in and no access token provided"))
|
||||
return
|
||||
}
|
||||
if errors.Is(err, service.ErrAuthTokenInvalid) {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"success": false, "code": "AUTH_UNAUTHORIZED", "message": common.TranslateMessage(c, i18n.MsgAuthAccessTokenInvalid)})
|
||||
abortWithWebMessage(c, http.StatusUnauthorized, "AUTH_UNAUTHORIZED", common.NewMessage("Unauthorized, invalid access token"))
|
||||
return
|
||||
}
|
||||
common.SysLog("dashboard authentication error: " + err.Error())
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"success": false, "code": "AUTH_INTERNAL_ERROR", "message": common.TranslateMessage(c, i18n.MsgDatabaseError)})
|
||||
common.SysLog(common.LogText("dashboard authentication error: %s", err.Error()))
|
||||
abortWithWebMessage(c, http.StatusInternalServerError, "AUTH_INTERNAL_ERROR", common.NewMessage("Database error, please contact the administrator"))
|
||||
}
|
||||
|
||||
func RequirePermission(permission authz.Permission) func(c *gin.Context) {
|
||||
@@ -240,11 +415,7 @@ func RequirePermission(permission authz.Permission) func(c *gin.Context) {
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgAuthInsufficientPrivilege),
|
||||
})
|
||||
c.Abort()
|
||||
abortWithWebMessage(c, http.StatusForbidden, "", common.NewMessage("Unauthorized, insufficient privileges"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,17 +453,14 @@ func TokenOrUserAuth() func(c *gin.Context) {
|
||||
}
|
||||
|
||||
// TokenAuthReadOnly 宽松版本的令牌认证中间件,用于只读查询接口。
|
||||
// 只验证令牌 key 是否存在,不检查令牌状态、过期时间和额度。
|
||||
// 即使令牌已过期、已耗尽或已禁用,也允许访问。
|
||||
// 仍然检查用户是否被封禁。
|
||||
// 只验证令牌 key 是否存在,不检查过期时间和额度。
|
||||
// 即使令牌已过期或已耗尽,也允许访问;已禁用的令牌会被拒绝。
|
||||
// 仍然检查令牌 IP 白名单和用户是否被封禁。
|
||||
func TokenAuthReadOnly() func(c *gin.Context) {
|
||||
return func(c *gin.Context) {
|
||||
key := c.Request.Header.Get("Authorization")
|
||||
if key == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgTokenNotProvided),
|
||||
})
|
||||
common.ApiErrorStatus(c, http.StatusUnauthorized, errors.New(common.TranslateMessage(c, i18n.MsgTokenNotProvided)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -306,16 +474,10 @@ func TokenAuthReadOnly() func(c *gin.Context) {
|
||||
token, err := model.GetTokenByKey(key, false)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgTokenInvalid),
|
||||
})
|
||||
common.ApiErrorStatus(c, http.StatusUnauthorized, errors.New(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),
|
||||
})
|
||||
common.SysLog(common.LogText("TokenAuthReadOnly GetTokenByKey database error: %s", err.Error()))
|
||||
common.ApiErrorStatus(c, http.StatusInternalServerError, errors.New(common.TranslateMessage(c, i18n.MsgDatabaseError)))
|
||||
}
|
||||
c.Abort()
|
||||
return
|
||||
@@ -324,29 +486,34 @@ func TokenAuthReadOnly() func(c *gin.Context) {
|
||||
// TokenAuthReadOnly must keep allowing other token states to query read-only
|
||||
// data, such as token usage logs; only explicitly disabled tokens are denied.
|
||||
if token.Status == common.TokenStatusDisabled {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgTokenStatusUnavailable),
|
||||
})
|
||||
common.ApiErrorStatus(c, http.StatusUnauthorized, errors.New(common.TranslateMessage(c, i18n.MsgTokenStatusUnavailable)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if allowIps := token.GetIpLimits(); len(allowIps) > 0 {
|
||||
ip := net.ParseIP(c.ClientIP())
|
||||
if ip == nil {
|
||||
common.ApiErrorStatus(c, http.StatusForbidden, errors.New(common.TranslateMessage(c, i18n.MsgAuthClientIPUnresolved)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if !common.IsIpInCIDRList(ip, allowIps) {
|
||||
common.ApiErrorStatus(c, http.StatusForbidden, errors.New(common.TranslateMessage(c, i18n.MsgAuthTokenIPNotAllowed)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
userCache, err := model.GetUserCache(token.UserId)
|
||||
if err != nil {
|
||||
common.SysLog(fmt.Sprintf("TokenAuthReadOnly GetUserCache error for user %d: %v", token.UserId, err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgDatabaseError),
|
||||
})
|
||||
common.SysLog(common.LogText("TokenAuthReadOnly GetUserCache error for user %d: %v", token.UserId, err))
|
||||
common.ApiErrorStatus(c, http.StatusInternalServerError, errors.New(common.TranslateMessage(c, i18n.MsgDatabaseError)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
if userCache.Status != common.UserStatusEnabled {
|
||||
c.JSON(http.StatusForbidden, gin.H{
|
||||
"success": false,
|
||||
"message": common.TranslateMessage(c, i18n.MsgAuthUserBanned),
|
||||
})
|
||||
common.ApiErrorStatus(c, http.StatusForbidden, errors.New(common.TranslateMessage(c, i18n.MsgAuthUserBanned)))
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
@@ -411,7 +578,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
}
|
||||
if err != nil {
|
||||
if errors.Is(err, model.ErrDatabase) {
|
||||
common.SysLog("TokenAuth ValidateUserToken database error: " + err.Error())
|
||||
common.SysLog(common.LogText("TokenAuth ValidateUserToken database error: %s", err.Error()))
|
||||
abortWithOpenAiMessage(c, http.StatusInternalServerError,
|
||||
common.TranslateMessage(c, i18n.MsgDatabaseError))
|
||||
} else {
|
||||
@@ -427,11 +594,11 @@ func TokenAuth() func(c *gin.Context) {
|
||||
logger.LogDebug(c, "Token has IP restrictions, checking client IP %s", clientIp)
|
||||
ip := net.ParseIP(clientIp)
|
||||
if ip == nil {
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, "无法解析客户端 IP 地址")
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgAuthClientIPUnresolved))
|
||||
return
|
||||
}
|
||||
if common.IsIpInCIDRList(ip, allowIps) == false {
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, "您的 IP 不在令牌允许访问的列表中", types.ErrorCodeAccessDenied)
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgAuthTokenIPNotAllowed), types.ErrorCodeAccessDenied)
|
||||
return
|
||||
}
|
||||
logger.LogDebug(c, "Client IP %s passed the token IP restrictions check", clientIp)
|
||||
@@ -439,7 +606,7 @@ func TokenAuth() func(c *gin.Context) {
|
||||
|
||||
userCache, err := model.GetUserCache(token.UserId)
|
||||
if err != nil {
|
||||
common.SysLog(fmt.Sprintf("TokenAuth GetUserCache error for user %d: %v", token.UserId, err))
|
||||
common.SysLog(common.LogText("TokenAuth GetUserCache error for user %d: %v", token.UserId, err))
|
||||
abortWithOpenAiMessage(c, http.StatusInternalServerError,
|
||||
common.TranslateMessage(c, i18n.MsgDatabaseError))
|
||||
return
|
||||
@@ -462,13 +629,13 @@ func TokenAuth() func(c *gin.Context) {
|
||||
if tokenGroup != "" && token.RoutingPolicy == "" {
|
||||
// check common.UserUsableGroups[userGroup]
|
||||
if _, ok := service.GetUserUsableGroups(userGroup)[tokenGroup]; !ok {
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("无权访问 %s 分组", tokenGroup))
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgAuthTokenGroupForbidden, map[string]any{"Group": tokenGroup}))
|
||||
return
|
||||
}
|
||||
// check group in common.GroupRatio
|
||||
if !ratio_setting.ContainsGroupRatio(tokenGroup) {
|
||||
if tokenGroup != "auto" {
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, fmt.Sprintf("分组 %s 已被弃用", tokenGroup))
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgAuthTokenGroupDeprecated, map[string]any{"Group": tokenGroup}))
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -542,7 +709,7 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
|
||||
if token.AutoGroups != "" {
|
||||
groupPool, err := token.GetGroupPool()
|
||||
if err != nil {
|
||||
common.SysError(fmt.Sprintf("failed to parse auto groups for token %d: %v", token.Id, err))
|
||||
common.SysError(common.LogText("failed to parse auto groups for token %d: %v", token.Id, err))
|
||||
common.SetContextKey(c, constant.ContextKeyTokenAutoGroups, []string{})
|
||||
} else if len(groupPool) > 0 {
|
||||
autoGroups := make([]string, 0, len(groupPool))
|
||||
@@ -568,8 +735,8 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
|
||||
})
|
||||
} else {
|
||||
c.Header("specific_channel_version", "701e3ae1dc3f7975556d354e0675168d004891c8")
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, "普通用户不支持指定渠道")
|
||||
return fmt.Errorf("普通用户不支持指定渠道")
|
||||
abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgAuthChannelSelectForbidden))
|
||||
return errors.New("regular users cannot specify a channel")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
Reference in new issue
Block a user