openapi: "3.0.3"
info:
  title: "IdP OAuth2 · 第三方应用接入"
  version: "0.1.0"
  description: "第三方应用接入统一认证（OAuth 2.0 授权码）的 OpenAPI 3.0.3 子集。

    路径：授权、同意、取消、换票、补绑邮箱、GET /api/auth/me。

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


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

    OAuth token 成功为扁平 access_token 等；会话 Cookie auth_token 或 Bearer。"
servers:
  - url: "/"
tags:
  - name: "OAuth2_RP"
    description: "第三方应用授权码 + 换票"
  - name: "Auth"
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/authorize:
    get:
      tags:
        - "OAuth2_RP"
      parameters:
        - name: "client_id"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "redirect_uri"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "response_type"
          in: "query"
          required: true
          schema:
            type: "string"
            enum:
              - "code"
        - name: "scope"
          in: "query"
          schema:
            type: "string"
        - name: "state"
          in: "query"
          schema:
            type: "string"
        - name: "code_challenge"
          in: "query"
          schema:
            type: "string"
        - name: "code_challenge_method"
          in: "query"
          schema:
            type: "string"
      responses:
        "302":
          description: "登录/同意/错误页"
  /api/oauth/authorize/confirm:
    get:
      tags:
        - "OAuth2_RP"
      security: &a2
        - cookieAuth: []
        - bearerAuth: []
      parameters:
        - name: "client_id"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "redirect_uri"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "response_type"
          in: "query"
          required: true
          schema:
            type: "string"
            enum:
              - "code"
        - name: "scope"
          in: "query"
          schema:
            type: "string"
        - name: "state"
          in: "query"
          schema:
            type: "string"
        - name: "code_challenge"
          in: "query"
          schema:
            type: "string"
        - name: "code_challenge_method"
          in: "query"
          schema:
            type: "string"
      responses:
        "302":
          description: "带 code"
        "400": &a1
          description: "错误 JSON"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiErrorBody"
        "401": *a1
  /api/oauth/authorize/cancel:
    get:
      tags:
        - "OAuth2_RP"
      parameters:
        - name: "client_id"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "redirect_uri"
          in: "query"
          required: true
          schema:
            type: "string"
        - name: "state"
          in: "query"
          schema:
            type: "string"
      responses:
        "302":
          description: "error"
        "303":
          description: "oauth-cancelled"
  /api/oauth/token:
    post:
      tags:
        - "OAuth2_RP"
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: "object"
              required:
                - "grant_type"
                - "code"
                - "client_id"
                - "redirect_uri"
              properties:
                grant_type:
                  type: "string"
                  enum:
                    - "authorization_code"
                code:
                  type: "string"
                client_id:
                  type: "string"
                client_secret:
                  type: "string"
                redirect_uri:
                  type: "string"
                code_verifier:
                  type: "string"
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuth2TokenSuccess"
        "400": *a1
        "401": *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
  /api/auth/me:
    get:
      tags:
        - "Auth"
      security: *a2
      responses:
        "200":
          content:
            application/json:
              schema:
                type: "object"
                properties:
                  data:
                    $ref: "#/components/schemas/CurrentUserProfile"
        "401": *a1
