docs(auth): document CLI token failure semantics (#605)

Signed-off-by: dongmucat <1127093059@qq.com>
This commit is contained in:
dongmucat 2026-07-28 12:55:35 +08:00
parent 06cecd4237
commit 5805e0f1d3
2 changed files with 297 additions and 4 deletions

View file

@ -621,10 +621,15 @@ window.location.href = '/oauth2/authorization/github'
### 10.3 CLI API
| 接口 | 所需凭证 | 额外判定 |
|------|---------|---------|
| `GET /api/v1/whoami` | 任意有效 Bearer Token | 无 |
| `POST /api/v1/publish` | Bearer Token + `skill:publish` | 普通用户要求目标 namespace 成员;`SUPER_ADMIN` 可绕过 |
| 接口 | 凭证规则 | 授权与错误语义 |
|------|---------|---------------|
| `GET /api/cli/v1/auth/whoami` | 必须提供有效 Bearer Token | 缺失、未知、过期、撤销或 malformed token 返回 401 |
| `GET /api/cli/v1/skills/search` | 可匿名;提供 Bearer 时必须有效 | 匿名仅返回公开可安装 skill坏凭证返回 401不得降级匿名 |
| `GET /api/cli/v1/skills/{namespace}/{slug}/resolve` | 可匿名读取公开资源;提供 Bearer 时必须有效 | 坏凭证返回 401有效身份无资源权限返回 403 |
| `GET /api/cli/v1/skills/{namespace}/{slug}/download` | 可匿名下载公开资源;提供 Bearer 时必须有效 | 坏凭证返回 401有效身份无资源权限返回 403 |
| `GET /api/cli/v1/skills/{namespace}/{slug}/versions/{version}/download` | 可匿名下载公开资源;提供 Bearer 时必须有效 | 坏凭证返回 401有效身份无资源权限返回 403 |
公共读接口仅在完全缺少 `Authorization` 头时允许匿名访问。请求一旦携带 Bearer 凭证,空值、格式错误、未知、过期、已撤销、用户缺失或用户禁用均由共享认证过滤器返回 401。身份已验证但 token scope 或资源可见性不足时返回 403服务端不向客户端区分 token 不存在、过期或已撤销。
### 10.4 Admin API

View file

@ -0,0 +1,288 @@
openapi: 3.0.3
info:
title: SkillHub CLI Authentication API
version: 1.0.0
description: >-
Authentication contract for CLI identity and public skill reads. Public read
operations permit a request with no Authorization header, but any supplied
Bearer credential must be valid; malformed, unknown, expired, or revoked
credentials return HTTP 401 and never fall back to anonymous access.
servers:
- url: /
tags:
- name: CLI Authentication
- name: CLI Skills
paths:
/api/cli/v1/auth/whoami:
get:
tags: [CLI Authentication]
summary: Return the current CLI identity
operationId: cliWhoAmI
security:
- bearerAuth: []
responses:
'200':
description: Authenticated CLI identity
content:
application/json:
schema:
$ref: '#/components/schemas/CliWhoAmIEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
/api/cli/v1/skills/search:
get:
tags: [CLI Skills]
summary: Search CLI-installable skills
operationId: cliSearchSkills
description: No Authorization header uses anonymous public visibility. A supplied invalid Bearer credential returns 401.
security:
- {}
- bearerAuth: []
parameters:
- name: q
in: query
required: false
schema: {type: string}
example: pdf
description: Optional search text.
- name: limit
in: query
required: false
schema: {type: integer, format: int32, default: 20}
example: 20
description: Maximum number of results.
responses:
'200':
description: Search result
content:
application/json:
schema:
$ref: '#/components/schemas/CliSearchEnvelope'
'401':
$ref: '#/components/responses/Unauthorized'
/api/cli/v1/skills/{namespace}/{slug}/resolve:
get:
tags: [CLI Skills]
summary: Resolve a skill version
operationId: cliResolveSkill
security:
- {}
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/Namespace'
- $ref: '#/components/parameters/Slug'
- name: version
in: query
required: false
schema: {type: string}
example: 1.0.0
description: Optional exact version; omitted resolves latest.
responses:
'200':
description: Resolved version
content:
application/json:
schema:
$ref: '#/components/schemas/CliResolveEnvelope'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/cli/v1/skills/{namespace}/{slug}/download:
get:
tags: [CLI Skills]
summary: Download the latest installable skill version
operationId: cliDownloadLatestSkill
security:
- {}
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/Namespace'
- $ref: '#/components/parameters/Slug'
responses:
'200':
$ref: '#/components/responses/Download'
'302':
$ref: '#/components/responses/DownloadRedirect'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'503':
$ref: '#/components/responses/StorageUnavailable'
/api/cli/v1/skills/{namespace}/{slug}/versions/{version}/download:
get:
tags: [CLI Skills]
summary: Download an exact installable skill version
operationId: cliDownloadSkillVersion
security:
- {}
- bearerAuth: []
parameters:
- $ref: '#/components/parameters/Namespace'
- $ref: '#/components/parameters/Slug'
- $ref: '#/components/parameters/Version'
responses:
'200':
$ref: '#/components/responses/Download'
'302':
$ref: '#/components/responses/DownloadRedirect'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'503':
$ref: '#/components/responses/StorageUnavailable'
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: SkillHub API token
description: API token issued by SkillHub. Invalid lifecycle states all return the same 401 response.
parameters:
Namespace:
name: namespace
in: path
required: true
schema: {type: string}
example: global
description: Namespace slug.
Slug:
name: slug
in: path
required: true
schema: {type: string}
example: pdf-parser
description: Skill slug.
Version:
name: version
in: path
required: true
schema: {type: string}
example: 1.0.0
description: Exact semantic version.
responses:
Download:
description: ZIP package stream
headers:
Content-Disposition:
schema: {type: string}
description: Attachment filename.
content:
application/zip:
schema: {type: string, format: binary}
DownloadRedirect:
description: Redirect to a presigned object-storage URL
headers:
Location:
schema: {type: string, format: uri}
BadRequest:
description: Namespace, skill, or version cannot be resolved.
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
Unauthorized:
description: Bearer credential is missing where required, malformed, unknown, expired, revoked, or belongs to an unavailable user.
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
example:
code: 401
msg: Authentication required
data: null
timestamp: '2026-07-28T00:00:00Z'
requestId: req-123
Forbidden:
description: Credential is valid but token scope or resource permission is insufficient.
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
example:
code: 403
msg: Forbidden
data: null
timestamp: '2026-07-28T00:00:00Z'
requestId: req-123
StorageUnavailable:
description: Object storage is unavailable.
content:
application/json:
schema: {$ref: '#/components/schemas/ErrorEnvelope'}
schemas:
Envelope:
type: object
required: [code, msg, timestamp]
properties:
code: {type: integer, format: int32}
msg: {type: string}
data: {type: object, nullable: true}
timestamp: {type: string, format: date-time}
requestId: {type: string, nullable: true}
ErrorEnvelope:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data: {type: object, nullable: true, example: null}
CliWhoAmIEnvelope:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
$ref: '#/components/schemas/CliWhoAmI'
CliWhoAmI:
type: object
required: [handle, displayName, email]
properties:
handle: {type: string, example: user-123}
displayName: {type: string, example: CLI User}
email: {type: string, format: email, example: cli@example.com}
CliSearchEnvelope:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
$ref: '#/components/schemas/CliSearchResult'
CliSearchResult:
type: object
required: [items, total, limit]
properties:
items:
type: array
items: {$ref: '#/components/schemas/CliSearchItem'}
total: {type: integer, format: int64, example: 1}
limit: {type: integer, format: int32, example: 20}
CliSearchItem:
type: object
required: [namespace, slug, latestVersion]
properties:
namespace: {type: string, example: global}
slug: {type: string, example: pdf-parser}
latestVersion: {type: string, example: 1.2.0}
summary: {type: string, nullable: true, example: Parse PDF files}
CliResolveEnvelope:
allOf:
- $ref: '#/components/schemas/Envelope'
- type: object
properties:
data:
$ref: '#/components/schemas/CliResolveResult'
CliResolveResult:
type: object
required: [namespace, slug, version, versionId, fingerprint, downloadUrl]
properties:
namespace: {type: string, example: global}
slug: {type: string, example: pdf-parser}
version: {type: string, example: 1.2.0}
versionId: {type: integer, format: int64, example: 42}
fingerprint: {type: string, example: 'sha256:abc123'}
downloadUrl: {type: string, example: /api/v1/skills/global/pdf-parser/versions/1.2.0/download}