Compare commits

..

3 Commits

Author SHA1 Message Date
Pine efcba2cef9 chore(env): 内部令牌配置样例与 compose 透传 2026-08-29 00:31:34 +08:00
Pine 502e8b5db5 feat(auth): 受信任内部调用方令牌直通(PINEAGENTS_INTERNAL_TOKEN)
- 命中内部令牌时鉴权层合成 root 身份,供 server-core 作为唯一入口无权限限制管理微服务
- 为空时关闭该能力;constant-time 比较防时序侧信道
2026-08-29 00:31:34 +08:00
Pine 07806a7d60 uimark: 更换品牌 logo.png 为新版品牌标识 2026-08-27 19:44:13 +08:00
6 changed files with 50 additions and 0 deletions
+2
View File
@@ -77,6 +77,8 @@
# 会话密钥
# SESSION_SECRET=random_string
# 云超服内部管理令牌:server-core 作为唯一入口无权限限制地管理微服务;置空关闭该能力(须与 server-core 的 PINEAGENTS_INTERNAL_TOKEN 一致)
# PINEAGENTS_INTERNAL_TOKEN=
# false/未配置:本地 HTTP 模式,关闭 refresh/logout OriginGuard,且不得设置 TRUSTED_URL;兼容本地开发代理。
# true:启用 Secure Refresh Cookie 和严格 OriginGuard,必须同时列出全部可信 HTTPS Origin。
# SESSION_COOKIE_TRUSTED_URL 多项用英文逗号分隔;不支持通配符、路径或域名后缀匹配。
+11
View File
@@ -6,6 +6,17 @@ import (
"strconv"
)
// InternalAccessToken 受信任内部调用方令牌:命中后鉴权层直接合成 root 身份,
// 放开全部管理/root 端点(用于 server-core 作为唯一入口无权限限制地管理微服务)。
// 为空时关闭该能力。
//
// 需在请求时惰性读取(而非包 init 时),因为 main() 里 godotenv.Load(".env")
// 晚于包 init 执行,过早读取会拿到空串。
func InternalAccessToken() string {
return os.Getenv("PINEAGENTS_INTERNAL_TOKEN")
}
func GetEnvOrDefault(env string, defaultValue int) int {
if env == "" || os.Getenv(env) == "" {
return defaultValue
+1
View File
@@ -31,6 +31,7 @@ services:
- REDIS_CONN_STRING=redis://redis
- TZ=Asia/Shanghai
- BATCH_UPDATE_ENABLED=true
- PINEAGENTS_INTERNAL_TOKEN=${PINEAGENTS_INTERNAL_TOKEN:-} # 云超服内部管理令牌:server-core 无权限限制管理;置空关闭
# Local HTTP dev mode: keep Secure=false and leave TRUSTED_URL unset. This disables the refresh/logout OriginGuard so the :5173 -> :3000 dev proxy works.
- SESSION_COOKIE_SECURE=false
# For HTTPS only: set Secure=true and list every exact trusted HTTPS browser Origin. This does not configure relay CORS.
+1
View File
@@ -36,6 +36,7 @@ services:
- ERROR_LOG_ENABLED=true # 是否启用错误日志记录 (Whether to enable error log recording)
- BATCH_UPDATE_ENABLED=true # 是否启用批量更新 (Whether to enable batch update)
- NODE_NAME=new-api-node-1 # 节点名称,用于审计日志中标识节点身份;多节点/容器部署时建议设置 (Node name used in audit logs; recommended when running multiple instances or in containers)
- PINEAGENTS_INTERNAL_TOKEN=${PINEAGENTS_INTERNAL_TOKEN:-} # 云超服内部管理令牌:server-core 作为唯一入口无权限限制地管理微服务;置空则关闭此能力 (Trusted internal token; empty disables it)
# - STREAMING_TIMEOUT=300 # 流模式无响应超时时间,单位秒,默认120秒,如果出现空补全可以尝试改为更大值 (Streaming timeout in seconds, default is 120s. Increase if experiencing empty completions
# - RELAY_IDLE_CONN_TIMEOUT=90 # Relay HTTP 客户端空闲连接超时时间,单位秒,默认跟随 Go 标准库,设置为0表示不限制 (Relay HTTP client idle keep-alive timeout in seconds, defaults to Go standard library; set 0 to disable)
# - SESSION_SECRET=random_string # 多机部署时设置,必须修改这个随机字符串!! (multi-node deployment, set this to a random string!!!!!!!
+35
View File
@@ -7,6 +7,8 @@ import (
"net/http"
"strings"
"crypto/subtle"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/i18n"
@@ -42,7 +44,40 @@ func validUserInfo(username string, role int) bool {
return true
}
// bypassTrustedInternal 受信任内部调用方(server-core):命中 PINEAGENTS_INTERNAL_TOKEN 时
// 不查库、直接合成 root 身份,放开 AdminAuth/RootAuth/RequirePermission 的权限限制。
// 返回 true 表示已处理(含 c.Next())。
func bypassTrustedInternal(c *gin.Context, minRole int) bool {
internalToken := common.InternalAccessToken()
if internalToken == "" {
return false
}
raw, ok := authorizationToken(c.GetHeader("Authorization"))
if !ok || subtle.ConstantTimeCompare([]byte(raw), []byte(internalToken)) != 1 {
return false
}
user := &model.UserBase{
Id: -1,
Group: "default",
Role: common.RoleRootUser,
Status: common.UserStatusEnabled,
Username: "internal-server",
}
identity := service.AuthIdentity{UserID: user.Id, UserAuthVersion: 1}
setDashboardAuthContext(c, user, identity, true)
var auditWriter *auditResponseWriter
if minRole >= common.RoleAdminUser {
auditWriter = beginAdminAudit(c)
}
c.Next()
finishAdminAudit(c, auditWriter)
return true
}
func authHelper(c *gin.Context, minRole int) {
if bypassTrustedInternal(c, minRole) {
return
}
user, identity, useAccessToken, err := authenticateDashboardRequest(c)
if err != nil {
writeDashboardAuthError(c, err)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 2.4 MiB