fix(access): 统一 API Key 模型权限与列表契约

将全局启用、用户组基线、API Key 专属或排除规则及 scope 按固定顺序求值,避免 Key 越过所属用户组权限,并让运行时候选与模型列表共用同一权限链。

新增 Key 级可分配模型与失效规则诊断接口、OpenAI 兼容 /v1/models 及 rich 列表迁移路径;前端权限弹窗改为按当前 Key 实时加载并支持清理失效规则。

验证:Go 全量测试与 go vet 通过;Web 22 个测试文件共 142 项通过;pnpm lint、pnpm openapi、pnpm build、Compose 配置、gofmt、ShellCheck 和 git diff --check 通过;独立 PostgreSQL 真实配置验收通过。
This commit is contained in:
2026-08-03 09:17:15 +08:00
parent c28bf74230
commit cc97e6649c
26 changed files with 2249 additions and 220 deletions
+244 -26
View File
@@ -5726,6 +5726,7 @@
"api-keys"
],
"summary": "列出 API Key 可分配模型",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
@@ -5809,6 +5810,64 @@
}
}
},
"/api/v1/api-keys/{apiKeyID}/assignable-models": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "返回指定 API Key 所属用户组允许、全局启用且符合 KEY scope 的平台来源,并附带已有规则有效性诊断。",
"produces": [
"application/json"
],
"tags": [
"api-keys"
],
"summary": "列出指定 API Key 可分配模型",
"parameters": [
{
"type": "string",
"description": "API Key ID",
"name": "apiKeyID",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httpapi.APIKeyAssignableModelsResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"404": {
"description": "Not Found",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"503": {
"description": "Service Unavailable",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
}
}
}
},
"/api/v1/api-keys/{apiKeyID}/disable": {
"patch": {
"security": [
@@ -7318,26 +7377,15 @@
"BearerAuth": []
}
],
"description": "按当前用户权限返回可用于 Playground 或 API 调用的模型列表。",
"description": "兼容期 rich 平台来源明细;新客户端应改用 /api/v1/platform-modelsOpenAI 客户端使用 /v1/models。",
"produces": [
"application/json"
],
"tags": [
"playground"
],
"summary": "列出可调用模型",
"parameters": [
{
"enum": [
"canvas_model_node",
"desktop"
],
"type": "string",
"description": "模型可选场景;不传时保持原有行为",
"name": "usage_scene",
"in": "query"
}
],
"summary": "列出可调用平台模型(已弃用)",
"deprecated": true,
"responses": {
"200": {
"description": "OK",
@@ -7345,12 +7393,6 @@
"$ref": "#/definitions/httpapi.PlatformModelListResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"401": {
"description": "Unauthorized",
"schema": {
@@ -7362,12 +7404,6 @@
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"502": {
"description": "Bad Gateway",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
}
}
}
@@ -7682,6 +7718,67 @@
}
}
},
"/api/v1/platform-models": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "按当前用户权限返回可用于 Playground 或 API 调用的模型列表。",
"produces": [
"application/json"
],
"tags": [
"playground"
],
"summary": "列出可调用模型",
"parameters": [
{
"enum": [
"canvas_model_node",
"desktop"
],
"type": "string",
"description": "模型可选场景;不传时保持原有行为",
"name": "usage_scene",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httpapi.PlatformModelListResponse"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"502": {
"description": "Bad Gateway",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
}
}
}
},
"/api/v1/platforms": {
"get": {
"security": [
@@ -10240,6 +10337,43 @@
}
}
}
},
"/v1/models": {
"get": {
"security": [
{
"BearerAuth": []
}
],
"description": "按当前身份、API Key 访问规则及 scope 返回去重后的逻辑模型列表。",
"produces": [
"application/json"
],
"tags": [
"openai-compatible"
],
"summary": "列出 OpenAI 兼容模型",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/httpapi.OpenAIModelListResponse"
}
},
"401": {
"description": "Unauthorized",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/httpapi.ErrorEnvelope"
}
}
}
}
}
},
"definitions": {
@@ -10314,6 +10448,23 @@
}
}
},
"httpapi.APIKeyAssignableModelsResponse": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/store.PlatformModel"
}
},
"ruleDiagnostics": {
"type": "array",
"items": {
"$ref": "#/definitions/store.APIKeyAccessRuleDiagnostic"
}
}
}
},
"httpapi.APIKeyListResponse": {
"type": "object",
"properties": {
@@ -11674,6 +11825,42 @@
}
}
},
"httpapi.OpenAIModel": {
"type": "object",
"properties": {
"created": {
"type": "integer",
"example": 1710000000
},
"id": {
"type": "string",
"example": "gpt-4o-mini"
},
"object": {
"type": "string",
"example": "model"
},
"owned_by": {
"type": "string",
"example": "easyai"
}
}
},
"httpapi.OpenAIModelListResponse": {
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/httpapi.OpenAIModel"
}
},
"object": {
"type": "string",
"example": "list"
}
}
},
"httpapi.PlatformListResponse": {
"type": "object",
"properties": {
@@ -13123,6 +13310,37 @@
}
}
},
"store.APIKeyAccessRuleDiagnostic": {
"type": "object",
"properties": {
"effect": {
"type": "string"
},
"effective": {
"type": "boolean"
},
"reason": {
"type": "string",
"enum": [
"resource_unavailable",
"owner_access_revoked",
"scope_not_allowed"
]
},
"resourceId": {
"type": "string"
},
"resourceName": {
"type": "string"
},
"resourceType": {
"type": "string"
},
"ruleId": {
"type": "string"
}
}
},
"store.AcceptanceRun": {
"type": "object",
"properties": {
+160 -18
View File
@@ -47,6 +47,17 @@ definitions:
username:
type: string
type: object
httpapi.APIKeyAssignableModelsResponse:
properties:
items:
items:
$ref: '#/definitions/store.PlatformModel'
type: array
ruleDiagnostics:
items:
$ref: '#/definitions/store.APIKeyAccessRuleDiagnostic'
type: array
type: object
httpapi.APIKeyListResponse:
properties:
items:
@@ -989,6 +1000,31 @@ definitions:
$ref: '#/definitions/httpapi.OpenAIImageData'
type: array
type: object
httpapi.OpenAIModel:
properties:
created:
example: 1710000000
type: integer
id:
example: gpt-4o-mini
type: string
object:
example: model
type: string
owned_by:
example: easyai
type: string
type: object
httpapi.OpenAIModelListResponse:
properties:
data:
items:
$ref: '#/definitions/httpapi.OpenAIModel'
type: array
object:
example: list
type: string
type: object
httpapi.PlatformListResponse:
properties:
items:
@@ -1999,6 +2035,27 @@ definitions:
userId:
type: string
type: object
store.APIKeyAccessRuleDiagnostic:
properties:
effect:
type: string
effective:
type: boolean
reason:
enum:
- resource_unavailable
- owner_access_revoked
- scope_not_allowed
type: string
resourceId:
type: string
resourceName:
type: string
resourceType:
type: string
ruleId:
type: string
type: object
store.AcceptanceRun:
properties:
apiImageDigest:
@@ -7879,6 +7936,43 @@ paths:
summary: 删除 API Key
tags:
- api-keys
/api/v1/api-keys/{apiKeyID}/assignable-models:
get:
description: 返回指定 API Key 所属用户组允许、全局启用且符合 KEY scope 的平台来源,并附带已有规则有效性诊断。
parameters:
- description: API Key ID
in: path
name: apiKeyID
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.APIKeyAssignableModelsResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"404":
description: Not Found
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"503":
description: Service Unavailable
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出指定 API Key 可分配模型
tags:
- api-keys
/api/v1/api-keys/{apiKeyID}/disable:
patch:
description: 禁用当前用户拥有的 API Key,保留记录但不再允许调用。
@@ -8038,6 +8132,7 @@ paths:
- api-keys
/api/v1/api-keys/assignable-models:
get:
deprecated: true
description: 按当前用户自身的用户、租户和用户组权限返回可分配给 API Key 的启用模型,不受任何 API Key 权限规则影响。
produces:
- application/json
@@ -8954,15 +9049,8 @@ paths:
- model-catalog
/api/v1/models:
get:
description: 按当前用户权限返回可用于 Playground 或 API 调用的模型列表。
parameters:
- description: 模型可选场景;不传时保持原有行为
enum:
- canvas_model_node
- desktop
in: query
name: usage_scene
type: string
deprecated: true
description: 兼容期 rich 平台来源明细;新客户端应改用 /api/v1/platform-modelsOpenAI 客户端使用 /v1/models。
produces:
- application/json
responses:
@@ -8970,10 +9058,6 @@ paths:
description: OK
schema:
$ref: '#/definitions/httpapi.PlatformModelListResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
@@ -8982,13 +9066,9 @@ paths:
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出可调用模型
summary: 列出可调用平台模型(已弃用)
tags:
- playground
/api/v1/models/{model}:generateContent:
@@ -9195,6 +9275,45 @@ paths:
summary: 获取 AI Gateway Swagger YAML
tags:
- agent-resources
/api/v1/platform-models:
get:
description: 按当前用户权限返回可用于 Playground 或 API 调用的模型列表。
parameters:
- description: 模型可选场景;不传时保持原有行为
enum:
- canvas_model_node
- desktop
in: query
name: usage_scene
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.PlatformModelListResponse'
"400":
description: Bad Request
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"502":
description: Bad Gateway
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出可调用模型
tags:
- playground
/api/v1/platforms:
get:
description: 按当前用户可访问模型过滤平台,仅返回启用且存在可访问模型的平台。
@@ -10853,6 +10972,29 @@ paths:
summary: 获取本地上传资源
tags:
- static
/v1/models:
get:
description: 按当前身份、API Key 访问规则及 scope 返回去重后的逻辑模型列表。
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/httpapi.OpenAIModelListResponse'
"401":
description: Unauthorized
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/httpapi.ErrorEnvelope'
security:
- BearerAuth: []
summary: 列出 OpenAI 兼容模型
tags:
- openai-compatible
schemes:
- http
- https