openapi: "3.0.3"
info:
  title: "上游 IdP · 社交登录与绑定"
  version: "0.1.0"
  description: "用户在统一认证站使用 GitHub、Google 等登录或绑定时的浏览器侧 HTTP 接口（OpenAPI 子集）。

    路径：providers、发起登录/回调、绑定，以及站内补绑邮箱 send/verify。

    Swagger 见 /docs/upstream-idp-api；Markdown 见 /docs/upstream-idp-api.md；本文件供工具导入。


    成功多为 { data, message? }；错误为 { error }；

    绑定接口需本站会话 Cookie auth_token 或 Bearer。"
servers:
  - url: "/"
tags:
  - name: "OAuth_Upstream"
    description: "站内 GitHub/Google 等"
components:
  securitySchemes:
    bearerAuth:
      type: "http"
      scheme: "bearer"
      bearerFormat: "JWT"
    cookieAuth:
      type: "apiKey"
      in: "cookie"
      name: "auth_token"
  schemas:
    ApiErrorBody:
      type: "object"
      required:
        - "error"
      properties:
        error:
          type: "object"
          required:
            - "code"
            - "message"
          properties:
            code:
              type: "string"
            message:
              type: "string"
            details:
              type: "object"
              additionalProperties: true
    OAuth2TokenSuccess:
      type: "object"
      required:
        - "access_token"
        - "token_type"
        - "expires_in"
        - "scope"
      properties:
        access_token:
          type: "string"
        token_type:
          type: "string"
        expires_in:
          type: "integer"
        scope:
          type: "string"
    HealthResponse:
      type: "object"
      properties:
        status:
          type: "string"
          enum:
            - "ok"
            - "error"
        timestamp:
          type: "string"
          format: "date-time"
        version:
          type: "string"
        checks:
          type: "object"
        message:
          type: "string"
    CurrentUserProfile:
      type: "object"
      properties:
        id:
          type: "string"
        uni_id:
          type: "string"
          nullable: true
        email:
          type: "string"
          nullable: true
        name:
          type: "string"
          nullable: true
        avatar:
          type: "string"
          nullable: true
        emailVerified:
          type: "boolean"
        mfaEnabled:
          type: "boolean"
        loginEmailCodeEnabled:
          type: "boolean"
        createdAt:
          type: "string"
          format: "date-time"
          nullable: true
    WebAuthnOptions:
      type: "object"
      additionalProperties: true
    OAuthClientRow:
      type: "object"
      properties:
        id:
          type: "string"
        clientId:
          type: "string"
        clientSecret:
          type: "string"
          nullable: true
        name:
          type: "string"
        redirectUris:
          type: "string"
        scopes:
          type: "string"
        isPublic:
          type: "boolean"
        status:
          type: "string"
        authorizationCount:
          type: "integer"
paths:
  /api/oauth/providers:
    get:
      tags:
        - "OAuth_Upstream"
      summary: "已配置的上游 OAuth 提供方"
      responses:
        "200":
          description: "data: string[]"
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  data:
                    type: "array"
                    items:
                      type: "string"
  /api/oauth/{provider}:
    get:
      tags:
        - "OAuth_Upstream"
      summary: "发起上游登录"
      parameters:
        - name: "provider"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "302":
          description: "重定向第三方"
        "400": &a1
          description: "错误 JSON"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorBody"
        "503": *a1
  /api/oauth/callback/{provider}:
    get:
      tags:
        - "OAuth_Upstream"
      parameters:
        - name: "provider"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "302":
          description: "站内跳转"
  /api/oauth/bind/{provider}:
    get:
      tags:
        - "OAuth_Upstream"
      security:
        - cookieAuth: []
        - bearerAuth: []
      parameters:
        - name: "provider"
          in: "path"
          required: true
          schema:
            type: "string"
      responses:
        "302":
          description: "重定向"
        "400": *a1
  /api/oauth/email/send:
    post:
      tags:
        - "OAuth2_RP"
        - "OAuth_Upstream"
      requestBody:
        content:
          application/json:
            schema:
              type: "object"
              required:
                - "email"
              properties:
                email:
                  type: "string"
                  format: "email"
      responses:
        "200":
          description: "已发送"
        "400": *a1
        "401": *a1
        "403": *a1
        "429": *a1
        "500": *a1
  /api/oauth/email/verify:
    post:
      tags:
        - "OAuth2_RP"
        - "OAuth_Upstream"
      requestBody:
        content:
          application/json:
            schema:
              type: "object"
              required:
                - "code"
              properties:
                code:
                  type: "string"
                deviceFingerprint:
                  type: "string"
                  maxLength: 255
      responses:
        "200":
          description: "绑定成功 Set-Cookie"
        "400": *a1
        "401": *a1
        "429": *a1
