From cdfca613044f40b8280fa1996e046a6a14e6cfa1 Mon Sep 17 00:00:00 2001 From: chengcheng Date: Fri, 17 Jul 2026 12:31:11 +0800 Subject: [PATCH] =?UTF-8?q?docs(identity):=20=E5=8F=91=E5=B8=83=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E8=AE=A4=E8=AF=81=E7=AE=A1=E7=90=86=E5=A5=91=E7=BA=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为配对、策略、验证、激活、回滚、禁用和公开运行时接口补充 OpenAPI 注解,并将通用 Identity 类型加入生成范围。生成契约不包含 Exchange Token、Machine Secret 或 Secret 引用。\n\n验证:pnpm openapi;OpenAPI 敏感字段扫描通过 --- apps/api/docs/swagger.json | 905 ++++++++++++++++++ apps/api/docs/swagger.yaml | 596 ++++++++++++ .../identity_configuration_handlers.go | 124 +++ apps/api/project.json | 2 +- 4 files changed, 1626 insertions(+), 1 deletion(-) diff --git a/apps/api/docs/swagger.json b/apps/api/docs/swagger.json index 5515870..22c082c 100644 --- a/apps/api/docs/swagger.json +++ b/apps/api/docs/swagger.json @@ -2868,6 +2868,575 @@ } } }, + "/api/admin/system/identity/configuration": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "返回 Active、Draft、Previous Revision、配对进度和脱敏运行时健康状态。", + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "获取统一认证配置", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/httpapi.identityConfigurationView" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "503": { + "description": "Service Unavailable", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/disable": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "保留 Superseded Revision 供回滚,清理 BFF Session,并继续允许本地管理登录。", + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "禁用统一认证", + "parameters": [ + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "当前 Active Revision ETag", + "name": "If-Match", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.Revision" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "412": { + "description": "Precondition Failed", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/pairings": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "使用一次性接入码创建 Draft 和异步 Exchange。接入码仅允许出现在请求 Body。", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "启动统一认证配对", + "parameters": [ + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "首次配对固定为 W/\\", + "name": "If-Match", + "in": "header", + "required": true + }, + { + "description": "配对参数", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/identity.PairingInput" + } + } + ], + "responses": { + "202": { + "description": "Accepted", + "schema": { + "$ref": "#/definitions/identity.PairingExchange" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/pairings/{pairingID}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "查询统一认证配对状态", + "parameters": [ + { + "type": "string", + "description": "配对 ID", + "name": "pairingID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.PairingExchange" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "404": { + "description": "Not Found", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/revisions/{revisionID}": { + "patch": { + "security": [ + { + "BearerAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "修改统一认证 Draft 策略", + "parameters": [ + { + "type": "string", + "description": "Revision ID", + "name": "revisionID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "当前 Revision ETag", + "name": "If-Match", + "in": "header", + "required": true + }, + { + "description": "Gateway 本地策略", + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/httpapi.identityPolicyPatch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.Revision" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "412": { + "description": "Precondition Failed", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/revisions/{revisionID}/activate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "在完整候选 Runtime 验证成功且本地 Break-glass Manager 可用后原子热切换。", + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "激活统一认证 Revision", + "parameters": [ + { + "type": "string", + "description": "Revision ID", + "name": "revisionID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "当前 Revision ETag", + "name": "If-Match", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.Revision" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "412": { + "description": "Precondition Failed", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/revisions/{revisionID}/rollback": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "回滚统一认证 Revision", + "parameters": [ + { + "type": "string", + "description": "Revision ID", + "name": "revisionID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "当前 Revision ETag", + "name": "If-Match", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.Revision" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "409": { + "description": "Conflict", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "412": { + "description": "Precondition Failed", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, + "/api/admin/system/identity/revisions/{revisionID}/validate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "验证统一认证 Revision", + "parameters": [ + { + "type": "string", + "description": "Revision ID", + "name": "revisionID", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "幂等键", + "name": "Idempotency-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "当前 Revision ETag", + "name": "If-Match", + "in": "header", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/identity.Revision" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "403": { + "description": "Forbidden", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "412": { + "description": "Precondition Failed", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "428": { + "description": "Precondition Required", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + }, + "502": { + "description": "Bad Gateway", + "schema": { + "$ref": "#/definitions/httpapi.ErrorEnvelope" + } + } + } + } + }, "/api/admin/tenants": { "get": { "security": [ @@ -5320,6 +5889,26 @@ } } }, + "/api/v1/public/identity": { + "get": { + "description": "返回 Web 运行时需要的启用状态和非敏感登录、退出入口,不返回 Client、Secret 或内部标识。", + "produces": [ + "application/json" + ], + "tags": [ + "identity" + ], + "summary": "获取公开统一认证状态", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/httpapi.publicIdentityConfiguration" + } + } + } + } + }, "/api/v1/reranks": { "post": { "security": [ @@ -10742,6 +11331,104 @@ } } }, + "httpapi.identityConfigurationView": { + "type": "object", + "properties": { + "active": { + "$ref": "#/definitions/identity.Revision" + }, + "draft": { + "$ref": "#/definitions/identity.Revision" + }, + "pairing": { + "$ref": "#/definitions/identity.PairingExchange" + }, + "previous": { + "$ref": "#/definitions/identity.Revision" + }, + "runtime": { + "$ref": "#/definitions/httpapi.identityRuntimeStatus" + } + } + }, + "httpapi.identityPolicyPatch": { + "type": "object", + "properties": { + "jitEnabled": { + "type": "boolean" + }, + "legacyJwtEnabled": { + "type": "boolean" + }, + "localTenantKey": { + "type": "string" + }, + "rolePrefix": { + "type": "string" + }, + "sessionAbsoluteSeconds": { + "type": "integer" + }, + "sessionIdleSeconds": { + "type": "integer" + }, + "sessionRefreshSeconds": { + "type": "integer" + } + } + }, + "httpapi.identityRuntimeStatus": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "jit": { + "type": "string" + }, + "lastAuditId": { + "type": "string" + }, + "lastErrorCategory": { + "type": "string" + }, + "lastTraceId": { + "type": "string" + }, + "login": { + "type": "string" + }, + "revisionId": { + "type": "string" + }, + "sessionRevocation": { + "type": "string" + }, + "tokenIntrospection": { + "type": "string" + } + } + }, + "httpapi.publicIdentityConfiguration": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean" + }, + "loginUrl": { + "type": "string" + }, + "logoutUrl": { + "type": "string" + }, + "oidcLogin": { + "type": "boolean" + }, + "status": { + "type": "string" + } + } + }, "httpapi.updatePlatformDynamicPriorityRequest": { "type": "object", "properties": { @@ -10805,6 +11492,224 @@ } } }, + "identity.PairingExchange": { + "type": "object", + "properties": { + "authCenterAuditId": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "expiresAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "lastErrorCategory": { + "type": "string" + }, + "lastTraceId": { + "type": "string" + }, + "remoteExchangeId": { + "type": "string" + }, + "remoteVersion": { + "type": "integer" + }, + "revisionId": { + "type": "string" + }, + "status": { + "$ref": "#/definitions/identity.PairingStatus" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "integer" + } + } + }, + "identity.PairingInput": { + "type": "object", + "properties": { + "authCenterUrl": { + "type": "string" + }, + "legacyJwtEnabled": { + "type": "boolean" + }, + "localTenantKey": { + "type": "string" + }, + "onboardingCode": { + "type": "string" + }, + "publicBaseUrl": { + "type": "string" + }, + "webBaseUrl": { + "type": "string" + } + } + }, + "identity.PairingStatus": { + "type": "string", + "enum": [ + "metadata_pending", + "preparing", + "ready", + "credentials_saved", + "completed", + "failed", + "expired" + ], + "x-enum-varnames": [ + "PairingMetadataPending", + "PairingPreparing", + "PairingReady", + "PairingCredentialsSaved", + "PairingCompleted", + "PairingFailed", + "PairingExpired" + ] + }, + "identity.Revision": { + "type": "object", + "properties": { + "activatedAt": { + "type": "string" + }, + "applicationId": { + "type": "string" + }, + "audience": { + "type": "string" + }, + "authCenterUrl": { + "type": "string" + }, + "browserClientId": { + "type": "string" + }, + "capabilities": { + "type": "array", + "items": { + "type": "string" + } + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "jitEnabled": { + "type": "boolean" + }, + "lastAuditId": { + "type": "string" + }, + "lastErrorCategory": { + "type": "string" + }, + "lastTraceId": { + "type": "string" + }, + "legacyJwtEnabled": { + "type": "boolean" + }, + "localTenantKey": { + "type": "string" + }, + "machineClientId": { + "type": "string" + }, + "publicBaseUrl": { + "type": "string" + }, + "rolePrefix": { + "type": "string" + }, + "schemaVersion": { + "type": "integer" + }, + "scopes": { + "type": "array", + "items": { + "type": "string" + } + }, + "securityEventAudience": { + "type": "string" + }, + "securityEventConfigurationUrl": { + "type": "string" + }, + "securityEventIssuer": { + "type": "string" + }, + "sessionAbsoluteSeconds": { + "type": "integer" + }, + "sessionIdleSeconds": { + "type": "integer" + }, + "sessionRefreshSeconds": { + "type": "integer" + }, + "sessionRevocation": { + "type": "boolean" + }, + "state": { + "$ref": "#/definitions/identity.RevisionState" + }, + "supersededAt": { + "type": "string" + }, + "tenantId": { + "type": "string" + }, + "tokenIntrospection": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "validatedAt": { + "type": "string" + }, + "version": { + "type": "integer" + }, + "webBaseUrl": { + "type": "string" + } + } + }, + "identity.RevisionState": { + "type": "string", + "enum": [ + "draft", + "validated", + "active", + "superseded", + "failed" + ], + "x-enum-varnames": [ + "RevisionDraft", + "RevisionValidated", + "RevisionActive", + "RevisionSuperseded", + "RevisionFailed" + ] + }, "store.APIKey": { "type": "object", "properties": { diff --git a/apps/api/docs/swagger.yaml b/apps/api/docs/swagger.yaml index 1db16ed..9ba8cf4 100644 --- a/apps/api/docs/swagger.yaml +++ b/apps/api/docs/swagger.yaml @@ -823,6 +823,70 @@ definitions: billing: $ref: '#/definitions/httpapi.desktopBillingConfigResponse' type: object + httpapi.identityConfigurationView: + properties: + active: + $ref: '#/definitions/identity.Revision' + draft: + $ref: '#/definitions/identity.Revision' + pairing: + $ref: '#/definitions/identity.PairingExchange' + previous: + $ref: '#/definitions/identity.Revision' + runtime: + $ref: '#/definitions/httpapi.identityRuntimeStatus' + type: object + httpapi.identityPolicyPatch: + properties: + jitEnabled: + type: boolean + legacyJwtEnabled: + type: boolean + localTenantKey: + type: string + rolePrefix: + type: string + sessionAbsoluteSeconds: + type: integer + sessionIdleSeconds: + type: integer + sessionRefreshSeconds: + type: integer + type: object + httpapi.identityRuntimeStatus: + properties: + enabled: + type: boolean + jit: + type: string + lastAuditId: + type: string + lastErrorCategory: + type: string + lastTraceId: + type: string + login: + type: string + revisionId: + type: string + sessionRevocation: + type: string + tokenIntrospection: + type: string + type: object + httpapi.publicIdentityConfiguration: + properties: + enabled: + type: boolean + loginUrl: + type: string + logoutUrl: + type: string + oidcLogin: + type: boolean + status: + type: string + type: object httpapi.updatePlatformDynamicPriorityRequest: properties: dynamicPriority: @@ -868,6 +932,157 @@ definitions: example: manual recharge type: string type: object + identity.PairingExchange: + properties: + authCenterAuditId: + type: string + createdAt: + type: string + expiresAt: + type: string + id: + type: string + lastErrorCategory: + type: string + lastTraceId: + type: string + remoteExchangeId: + type: string + remoteVersion: + type: integer + revisionId: + type: string + status: + $ref: '#/definitions/identity.PairingStatus' + updatedAt: + type: string + version: + type: integer + type: object + identity.PairingInput: + properties: + authCenterUrl: + type: string + legacyJwtEnabled: + type: boolean + localTenantKey: + type: string + onboardingCode: + type: string + publicBaseUrl: + type: string + webBaseUrl: + type: string + type: object + identity.PairingStatus: + enum: + - metadata_pending + - preparing + - ready + - credentials_saved + - completed + - failed + - expired + type: string + x-enum-varnames: + - PairingMetadataPending + - PairingPreparing + - PairingReady + - PairingCredentialsSaved + - PairingCompleted + - PairingFailed + - PairingExpired + identity.Revision: + properties: + activatedAt: + type: string + applicationId: + type: string + audience: + type: string + authCenterUrl: + type: string + browserClientId: + type: string + capabilities: + items: + type: string + type: array + createdAt: + type: string + id: + type: string + issuer: + type: string + jitEnabled: + type: boolean + lastAuditId: + type: string + lastErrorCategory: + type: string + lastTraceId: + type: string + legacyJwtEnabled: + type: boolean + localTenantKey: + type: string + machineClientId: + type: string + publicBaseUrl: + type: string + rolePrefix: + type: string + schemaVersion: + type: integer + scopes: + items: + type: string + type: array + securityEventAudience: + type: string + securityEventConfigurationUrl: + type: string + securityEventIssuer: + type: string + sessionAbsoluteSeconds: + type: integer + sessionIdleSeconds: + type: integer + sessionRefreshSeconds: + type: integer + sessionRevocation: + type: boolean + state: + $ref: '#/definitions/identity.RevisionState' + supersededAt: + type: string + tenantId: + type: string + tokenIntrospection: + type: boolean + updatedAt: + type: string + validatedAt: + type: string + version: + type: integer + webBaseUrl: + type: string + type: object + identity.RevisionState: + enum: + - draft + - validated + - active + - superseded + - failed + type: string + x-enum-varnames: + - RevisionDraft + - RevisionValidated + - RevisionActive + - RevisionSuperseded + - RevisionFailed store.APIKey: properties: createdAt: @@ -4372,6 +4587,374 @@ paths: summary: 更新文件存储设置 tags: - system + /api/admin/system/identity/configuration: + get: + description: 返回 Active、Draft、Previous Revision、配对进度和脱敏运行时健康状态。 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/httpapi.identityConfigurationView' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "503": + description: Service Unavailable + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 获取统一认证配置 + tags: + - identity + /api/admin/system/identity/disable: + post: + description: 保留 Superseded Revision 供回滚,清理 BFF Session,并继续允许本地管理登录。 + parameters: + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 当前 Active Revision ETag + in: header + name: If-Match + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.Revision' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "409": + description: Conflict + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "412": + description: Precondition Failed + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 禁用统一认证 + tags: + - identity + /api/admin/system/identity/pairings: + post: + consumes: + - application/json + description: 使用一次性接入码创建 Draft 和异步 Exchange。接入码仅允许出现在请求 Body。 + parameters: + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 首次配对固定为 W/\ + in: header + name: If-Match + required: true + type: string + - description: 配对参数 + in: body + name: body + required: true + schema: + $ref: '#/definitions/identity.PairingInput' + produces: + - application/json + responses: + "202": + description: Accepted + schema: + $ref: '#/definitions/identity.PairingExchange' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "409": + description: Conflict + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 启动统一认证配对 + tags: + - identity + /api/admin/system/identity/pairings/{pairingID}: + get: + parameters: + - description: 配对 ID + in: path + name: pairingID + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.PairingExchange' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "404": + description: Not Found + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 查询统一认证配对状态 + tags: + - identity + /api/admin/system/identity/revisions/{revisionID}: + patch: + consumes: + - application/json + parameters: + - description: Revision ID + in: path + name: revisionID + required: true + type: string + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 当前 Revision ETag + in: header + name: If-Match + required: true + type: string + - description: Gateway 本地策略 + in: body + name: body + required: true + schema: + $ref: '#/definitions/httpapi.identityPolicyPatch' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.Revision' + "400": + description: Bad Request + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "412": + description: Precondition Failed + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 修改统一认证 Draft 策略 + tags: + - identity + /api/admin/system/identity/revisions/{revisionID}/activate: + post: + description: 在完整候选 Runtime 验证成功且本地 Break-glass Manager 可用后原子热切换。 + parameters: + - description: Revision ID + in: path + name: revisionID + required: true + type: string + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 当前 Revision ETag + in: header + name: If-Match + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.Revision' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "409": + description: Conflict + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "412": + description: Precondition Failed + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 激活统一认证 Revision + tags: + - identity + /api/admin/system/identity/revisions/{revisionID}/rollback: + post: + parameters: + - description: Revision ID + in: path + name: revisionID + required: true + type: string + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 当前 Revision ETag + in: header + name: If-Match + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.Revision' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "409": + description: Conflict + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "412": + description: Precondition Failed + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 回滚统一认证 Revision + tags: + - identity + /api/admin/system/identity/revisions/{revisionID}/validate: + post: + parameters: + - description: Revision ID + in: path + name: revisionID + required: true + type: string + - description: 幂等键 + in: header + name: Idempotency-Key + required: true + type: string + - description: 当前 Revision ETag + in: header + name: If-Match + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/identity.Revision' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "403": + description: Forbidden + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "412": + description: Precondition Failed + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "428": + description: Precondition Required + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + "502": + description: Bad Gateway + schema: + $ref: '#/definitions/httpapi.ErrorEnvelope' + security: + - BearerAuth: [] + summary: 验证统一认证 Revision + tags: + - identity /api/admin/tenants: get: description: 管理端返回网关租户列表。 @@ -5951,6 +6534,19 @@ paths: summary: 获取公开客户端自定义设置 tags: - system + /api/v1/public/identity: + get: + description: 返回 Web 运行时需要的启用状态和非敏感登录、退出入口,不返回 Client、Secret 或内部标识。 + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/httpapi.publicIdentityConfiguration' + summary: 获取公开统一认证状态 + tags: + - identity /api/v1/reranks: post: consumes: diff --git a/apps/api/internal/httpapi/identity_configuration_handlers.go b/apps/api/internal/httpapi/identity_configuration_handlers.go index aedf60a..06d96ae 100644 --- a/apps/api/internal/httpapi/identity_configuration_handlers.go +++ b/apps/api/internal/httpapi/identity_configuration_handlers.go @@ -74,6 +74,13 @@ func (operation *identityWriteOperation) close() { } } +// getPublicIdentityConfiguration godoc +// @Summary 获取公开统一认证状态 +// @Description 返回 Web 运行时需要的启用状态和非敏感登录、退出入口,不返回 Client、Secret 或内部标识。 +// @Tags identity +// @Produce json +// @Success 200 {object} publicIdentityConfiguration +// @Router /api/v1/public/identity [get] func (s *Server) getPublicIdentityConfiguration(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Cache-Control", "no-store") runtime := s.currentIdentityRuntime() @@ -90,6 +97,17 @@ func (s *Server) getPublicIdentityConfiguration(w http.ResponseWriter, _ *http.R writeJSON(w, http.StatusOK, view) } +// getIdentityConfiguration godoc +// @Summary 获取统一认证配置 +// @Description 返回 Active、Draft、Previous Revision、配对进度和脱敏运行时健康状态。 +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Success 200 {object} identityConfigurationView +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 503 {object} ErrorEnvelope +// @Router /api/admin/system/identity/configuration [get] func (s *Server) getIdentityConfiguration(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") view := identityConfigurationView{Runtime: s.identityRuntimeStatus(r)} @@ -117,6 +135,23 @@ func (s *Server) getIdentityConfiguration(w http.ResponseWriter, r *http.Request writeJSON(w, http.StatusOK, view) } +// startIdentityPairing godoc +// @Summary 启动统一认证配对 +// @Description 使用一次性接入码创建 Draft 和异步 Exchange。接入码仅允许出现在请求 Body。 +// @Tags identity +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "首次配对固定为 W/\"0\"" +// @Param body body identity.PairingInput true "配对参数" +// @Success 202 {object} identity.PairingExchange +// @Failure 400 {object} ErrorEnvelope +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 409 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Router /api/admin/system/identity/pairings [post] func (s *Server) startIdentityPairing(w http.ResponseWriter, r *http.Request) { var input identity.PairingInput if !decodeIdentityRequest(w, r, &input) { @@ -139,6 +174,17 @@ func (s *Server) startIdentityPairing(w http.ResponseWriter, r *http.Request) { s.startIdentityPairingWorker(pairing.ID) } +// getIdentityPairing godoc +// @Summary 查询统一认证配对状态 +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Param pairingID path string true "配对 ID" +// @Success 200 {object} identity.PairingExchange +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 404 {object} ErrorEnvelope +// @Router /api/admin/system/identity/pairings/{pairingID} [get] func (s *Server) getIdentityPairing(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") pairing, err := s.store.IdentityPairingExchange(r.Context(), r.PathValue("pairingID")) @@ -150,6 +196,23 @@ func (s *Server) getIdentityPairing(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, pairing) } +// updateIdentityDraftPolicy godoc +// @Summary 修改统一认证 Draft 策略 +// @Tags identity +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param revisionID path string true "Revision ID" +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "当前 Revision ETag" +// @Param body body identityPolicyPatch true "Gateway 本地策略" +// @Success 200 {object} identity.Revision +// @Failure 400 {object} ErrorEnvelope +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 412 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Router /api/admin/system/identity/revisions/{revisionID} [patch] func (s *Server) updateIdentityDraftPolicy(w http.ResponseWriter, r *http.Request) { var patch identityPolicyPatch if !decodeIdentityRequest(w, r, &patch) { @@ -185,24 +248,85 @@ func (s *Server) updateIdentityDraftPolicy(w http.ResponseWriter, r *http.Reques s.completeIdentityWrite(w, r, operation, http.StatusOK, updated, updated.Version, auditID) } +// validateIdentityRevision godoc +// @Summary 验证统一认证 Revision +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Param revisionID path string true "Revision ID" +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "当前 Revision ETag" +// @Success 200 {object} identity.Revision +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 412 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Failure 502 {object} ErrorEnvelope +// @Router /api/admin/system/identity/revisions/{revisionID}/validate [post] func (s *Server) validateIdentityRevision(w http.ResponseWriter, r *http.Request) { s.runIdentityRevisionAction(w, r, "revision.validate", func(expected int64, traceID, auditID string) (identity.Revision, error) { return s.identityRuntime.Validate(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID) }) } +// activateIdentityRevision godoc +// @Summary 激活统一认证 Revision +// @Description 在完整候选 Runtime 验证成功且本地 Break-glass Manager 可用后原子热切换。 +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Param revisionID path string true "Revision ID" +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "当前 Revision ETag" +// @Success 200 {object} identity.Revision +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 409 {object} ErrorEnvelope +// @Failure 412 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Router /api/admin/system/identity/revisions/{revisionID}/activate [post] func (s *Server) activateIdentityRevision(w http.ResponseWriter, r *http.Request) { s.runIdentityRevisionAction(w, r, "revision.activate", func(expected int64, traceID, auditID string) (identity.Revision, error) { return s.identityRuntime.Activate(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID) }) } +// rollbackIdentityRevision godoc +// @Summary 回滚统一认证 Revision +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Param revisionID path string true "Revision ID" +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "当前 Revision ETag" +// @Success 200 {object} identity.Revision +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 409 {object} ErrorEnvelope +// @Failure 412 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Router /api/admin/system/identity/revisions/{revisionID}/rollback [post] func (s *Server) rollbackIdentityRevision(w http.ResponseWriter, r *http.Request) { s.runIdentityRevisionAction(w, r, "revision.rollback", func(expected int64, traceID, auditID string) (identity.Revision, error) { return s.identityRuntime.Rollback(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID) }) } +// disableIdentityConfiguration godoc +// @Summary 禁用统一认证 +// @Description 保留 Superseded Revision 供回滚,清理 BFF Session,并继续允许本地管理登录。 +// @Tags identity +// @Produce json +// @Security BearerAuth +// @Param Idempotency-Key header string true "幂等键" +// @Param If-Match header string true "当前 Active Revision ETag" +// @Success 200 {object} identity.Revision +// @Failure 401 {object} ErrorEnvelope +// @Failure 403 {object} ErrorEnvelope +// @Failure 409 {object} ErrorEnvelope +// @Failure 412 {object} ErrorEnvelope +// @Failure 428 {object} ErrorEnvelope +// @Router /api/admin/system/identity/disable [post] func (s *Server) disableIdentityConfiguration(w http.ResponseWriter, r *http.Request) { expectedVersion, ok := requiredIdentityVersion(w, r) if !ok { diff --git a/apps/api/project.json b/apps/api/project.json index 204346b..2eb2550 100644 --- a/apps/api/project.json +++ b/apps/api/project.json @@ -24,7 +24,7 @@ "outputs": ["{projectRoot}/docs/swagger.json", "{projectRoot}/docs/swagger.yaml"], "options": { "cwd": "apps/api", - "command": "go run github.com/swaggo/swag/cmd/swag@v1.16.4 init --parseInternal -d ./cmd/gateway,./internal/httpapi,./internal/store,./internal/auth -g main.go -o docs --outputTypes json,yaml" + "command": "go run github.com/swaggo/swag/cmd/swag@v1.16.4 init --parseInternal -d ./cmd/gateway,./internal/httpapi,./internal/store,./internal/auth,./internal/identity -g main.go -o docs --outputTypes json,yaml" } }, "test": {