openapi: 3.0.3
info:
  title: User Service API
  description: |
    회의록 작성 및 공유 개선 서비스의 사용자 인증 전용 서비스

    **핵심 기능:**
    - LDAP 기반 사용자 인증
    - JWT 토큰 발급 및 검증
    - 세션 관리 (Access Token + Refresh Token)

    **보안:**
    - LDAP 인증 (LDAPS, port 636)
    - JWT Bearer 토큰
    - 계정 잠금 (5회 실패 시 30분)
    - Refresh Token (Redis 저장, 7일 TTL)

  version: 1.0.0
  contact:
    name: Backend Team
    email: backend@company.com

servers:
  - url: https://api.meeting.company.com
    description: Production Server
  - url: https://dev-api.meeting.company.com
    description: Development Server
  - url: http://localhost:8081
    description: Local Development

tags:
  - name: Authentication
    description: 사용자 인증 관리 API

paths:
  /api/v1/auth/login:
    post:
      summary: 사용자 로그인
      description: |
        LDAP 인증을 통한 사용자 로그인 처리

        **처리 흐름:**
        1. LDAP 서버에 사용자 인증 요청
        2. 인증 성공 시 사용자 정보 조회
        3. 신규 사용자일 경우 자동 등록
        4. JWT Access Token 및 Refresh Token 발급
        5. Refresh Token을 Redis에 저장 (7일 TTL)
        6. 최종 로그인 일시 업데이트

        **인증 실패 처리:**
        - 5회 실패 시 계정 30분 잠금
        - 잠금 상태에서는 로그인 불가
      operationId: login
      x-user-story: AFR-USER-010
      x-controller: UserController
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginRequest'
            examples:
              normal:
                summary: 정상 로그인 요청
                value:
                  username: "user001"
                  password: "P@ssw0rd123!"
      responses:
        '200':
          description: 로그인 성공
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LoginResponse'
              examples:
                success:
                  summary: 로그인 성공 응답
                  value:
                    accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                    refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                    tokenType: "Bearer"
                    expiresIn: 3600
                    user:
                      userId: "user001"
                      username: "user001"
                      name: "홍길동"
                      email: "hong@company.com"
                      department: "개발팀"
                      title: "선임"
                      roles:
                        - "USER"
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '403':
          description: 계정 잠금
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              examples:
                accountLocked:
                  summary: 계정 잠금 응답
                  value:
                    error:
                      code: "ACCOUNT_LOCKED"
                      message: "계정이 잠겼습니다"
                      details: "비밀번호 5회 실패로 30분간 계정이 잠겼습니다. 잠금 해제 시간: 2025-10-23T13:30:00Z"
                      timestamp: "2025-10-23T13:00:00Z"
                      path: "/api/v1/auth/login"
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1/auth/refresh:
    post:
      summary: Access Token 갱신
      description: |
        Refresh Token을 사용하여 새로운 Access Token 발급

        **처리 흐름:**
        1. Refresh Token 검증
        2. Redis에서 Refresh Token 존재 확인
        3. 새로운 Access Token 발급
        4. Refresh Token 갱신 (옵션)
      operationId: refreshToken
      x-user-story: AFR-USER-010
      x-controller: UserController
      tags:
        - Authentication
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/RefreshTokenRequest'
            examples:
              normal:
                summary: 토큰 갱신 요청
                value:
                  refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
      responses:
        '200':
          description: 토큰 갱신 성공
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RefreshTokenResponse'
              examples:
                success:
                  summary: 토큰 갱신 성공 응답
                  value:
                    accessToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                    refreshToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
                    tokenType: "Bearer"
                    expiresIn: 3600
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1/auth/logout:
    post:
      summary: 로그아웃
      description: |
        사용자 로그아웃 처리

        **처리 흐름:**
        1. Access Token에서 사용자 정보 추출
        2. Redis에서 Refresh Token 삭제
        3. 로그아웃 완료
      operationId: logout
      x-user-story: AFR-USER-010
      x-controller: UserController
      tags:
        - Authentication
      security:
        - BearerAuth: []
      responses:
        '200':
          description: 로그아웃 성공
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LogoutResponse'
              examples:
                success:
                  summary: 로그아웃 성공 응답
                  value:
                    message: "로그아웃되었습니다"
                    timestamp: "2025-10-23T14:30:00Z"
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '500':
          $ref: '#/components/responses/InternalServerError'

  /api/v1/auth/validate:
    get:
      summary: 토큰 검증
      description: |
        JWT Access Token 유효성 검증

        **검증 항목:**
        - 토큰 서명 검증
        - 토큰 만료 시간 확인
        - 사용자 정보 조회 (옵션)

        **용도:**
        - API Gateway에서 인증 검증
        - 다른 서비스에서 사용자 정보 조회
      operationId: validateToken
      x-user-story: AFR-USER-010
      x-controller: UserController
      tags:
        - Authentication
      security:
        - BearerAuth: []
      parameters:
        - name: includeUserInfo
          in: query
          description: 사용자 정보 포함 여부
          required: false
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: 토큰 유효
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateTokenResponse'
              examples:
                valid:
                  summary: 토큰 유효 응답
                  value:
                    valid: true
                    userId: "user001"
                    username: "user001"
                    roles:
                      - "USER"
                    expiresAt: "2025-10-23T15:00:00Z"
                validWithUserInfo:
                  summary: 사용자 정보 포함 응답
                  value:
                    valid: true
                    userId: "user001"
                    username: "user001"
                    roles:
                      - "USER"
                    expiresAt: "2025-10-23T15:00:00Z"
                    user:
                      userId: "user001"
                      username: "user001"
                      name: "홍길동"
                      email: "hong@company.com"
                      department: "개발팀"
                      title: "선임"
        '401':
          $ref: '#/components/responses/UnauthorizedError'
        '500':
          $ref: '#/components/responses/InternalServerError'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Bearer Token

  schemas:
    LoginRequest:
      type: object
      required:
        - username
        - password
      properties:
        username:
          type: string
          description: 사용자명 (사번)
          minLength: 3
          maxLength: 50
          example: "user001"
        password:
          type: string
          description: 비밀번호
          format: password
          minLength: 8
          maxLength: 100
          example: "P@ssw0rd123!"

    LoginResponse:
      type: object
      required:
        - accessToken
        - refreshToken
        - tokenType
        - expiresIn
        - user
      properties:
        accessToken:
          type: string
          description: JWT Access Token
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMDAxIiwidXNlcm5hbWUiOiJ1c2VyMDAxIiwicm9sZXMiOlsiVVNFUiJdLCJleHAiOjE3MzAwMDAwMDB9.signature"
        refreshToken:
          type: string
          description: Refresh Token (7일 유효)
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMDAxIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3MzA2MDQ4MDB9.signature"
        tokenType:
          type: string
          description: 토큰 타입
          enum:
            - Bearer
          example: "Bearer"
        expiresIn:
          type: integer
          description: Access Token 만료 시간 (초)
          example: 3600
        user:
          $ref: '#/components/schemas/UserInfo'

    RefreshTokenRequest:
      type: object
      required:
        - refreshToken
      properties:
        refreshToken:
          type: string
          description: Refresh Token
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

    RefreshTokenResponse:
      type: object
      required:
        - accessToken
        - refreshToken
        - tokenType
        - expiresIn
      properties:
        accessToken:
          type: string
          description: 새로운 JWT Access Token
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        refreshToken:
          type: string
          description: 새로운 Refresh Token (옵션)
          example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
        tokenType:
          type: string
          description: 토큰 타입
          enum:
            - Bearer
          example: "Bearer"
        expiresIn:
          type: integer
          description: Access Token 만료 시간 (초)
          example: 3600

    LogoutResponse:
      type: object
      required:
        - message
        - timestamp
      properties:
        message:
          type: string
          description: 로그아웃 완료 메시지
          example: "로그아웃되었습니다"
        timestamp:
          type: string
          format: date-time
          description: 로그아웃 시간
          example: "2025-10-23T14:30:00Z"

    ValidateTokenResponse:
      type: object
      required:
        - valid
        - userId
        - username
        - roles
        - expiresAt
      properties:
        valid:
          type: boolean
          description: 토큰 유효 여부
          example: true
        userId:
          type: string
          description: 사용자 ID
          example: "user001"
        username:
          type: string
          description: 사용자명
          example: "user001"
        roles:
          type: array
          description: 사용자 권한 목록
          items:
            type: string
          example:
            - "USER"
        expiresAt:
          type: string
          format: date-time
          description: 토큰 만료 시간
          example: "2025-10-23T15:00:00Z"
        user:
          $ref: '#/components/schemas/UserInfo'
          description: 사용자 정보 (includeUserInfo=true 시)

    UserInfo:
      type: object
      required:
        - userId
        - username
        - name
        - email
      properties:
        userId:
          type: string
          description: 사용자 ID
          example: "user001"
        username:
          type: string
          description: 사용자명 (사번)
          example: "user001"
        name:
          type: string
          description: 이름
          example: "홍길동"
        email:
          type: string
          format: email
          description: 이메일
          example: "hong@company.com"
        department:
          type: string
          description: 부서
          example: "개발팀"
        title:
          type: string
          description: 직급
          example: "선임"
        roles:
          type: array
          description: 권한 목록
          items:
            type: string
          example:
            - "USER"

    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - timestamp
            - path
          properties:
            code:
              type: string
              description: 에러 코드
              example: "AUTHENTICATION_FAILED"
            message:
              type: string
              description: 에러 메시지
              example: "인증에 실패했습니다"
            details:
              type: string
              description: 상세 에러 정보
              example: "사용자명 또는 비밀번호가 올바르지 않습니다"
            timestamp:
              type: string
              format: date-time
              description: 에러 발생 시간
              example: "2025-10-23T12:00:00Z"
            path:
              type: string
              description: 요청 경로
              example: "/api/v1/auth/login"

  responses:
    UnauthorizedError:
      description: 인증 실패
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            authenticationFailed:
              summary: 인증 실패
              value:
                error:
                  code: "AUTHENTICATION_FAILED"
                  message: "인증에 실패했습니다"
                  details: "사용자명 또는 비밀번호가 올바르지 않습니다"
                  timestamp: "2025-10-23T12:00:00Z"
                  path: "/api/v1/auth/login"
            invalidToken:
              summary: 유효하지 않은 토큰
              value:
                error:
                  code: "INVALID_TOKEN"
                  message: "유효하지 않은 토큰입니다"
                  details: "토큰이 만료되었거나 형식이 올바르지 않습니다"
                  timestamp: "2025-10-23T12:00:00Z"
                  path: "/api/v1/auth/validate"

    InternalServerError:
      description: 서버 오류
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          examples:
            serverError:
              summary: 서버 오류
              value:
                error:
                  code: "INTERNAL_SERVER_ERROR"
                  message: "서버 오류가 발생했습니다"
                  details: "일시적인 오류입니다. 잠시 후 다시 시도해주세요"
                  timestamp: "2025-10-23T12:00:00Z"
                  path: "/api/v1/auth/login"
