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": {