openapi: 3.1.0
info:
  title: LycianAuth
  version: "1.0"
  description: >
    Self-hosted, passwordless, multi-tenant OpenID Provider. Apps integrate via
    standard OIDC (Authorization Code + PKCE). Identity is (app_id, email); the
    app_id is the OAuth client_id and the JWT `aud`.

    Most endpoints are standard OIDC and are described by the discovery document
    at /.well-known/openid-configuration. This file documents the request shapes
    relevant to integrators.
servers:
  - url: https://auth.lycian.app

paths:
  /.well-known/openid-configuration:
    get:
      summary: OIDC discovery document
      responses:
        "200": { description: Provider metadata (endpoints, supported algs) }

  /keys:
    get:
      summary: JWKS (EdDSA public keys) for verifying tokens
      responses:
        "200": { description: JSON Web Key Set }

  /authorize:
    get:
      summary: Authorization endpoint (start login). Redirects to the hosted login UI.
      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, required: true, schema: { type: string }, example: "openid email profile offline_access" }
        - { name: state, in: query, required: true, schema: { type: string } }
        - { name: nonce, in: query, schema: { type: string } }
        - { name: code_challenge, in: query, required: true, schema: { type: string } }
        - { name: code_challenge_method, in: query, required: true, schema: { type: string, enum: [S256] } }
      responses:
        "302": { description: Redirect to login UI, then back to redirect_uri with ?code&state }

  /oauth/token:
    post:
      summary: Token endpoint (exchange code, or refresh)
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              properties:
                grant_type: { type: string, enum: [authorization_code, refresh_token] }
                code: { type: string }
                redirect_uri: { type: string }
                client_id: { type: string }
                code_verifier: { type: string, description: "PKCE verifier (public clients)" }
                refresh_token: { type: string }
      responses:
        "200":
          description: Token set
          content:
            application/json:
              schema:
                type: object
                properties:
                  access_token: { type: string, description: "EdDSA JWT" }
                  id_token: { type: string }
                  refresh_token: { type: string }
                  token_type: { type: string, example: Bearer }
                  expires_in: { type: integer }

  /userinfo:
    get:
      summary: Userinfo (requires Bearer access token)
      security: [{ bearerAuth: [] }]
      responses:
        "200":
          description: Claims for the authenticated user
          content:
            application/json:
              schema:
                type: object
                properties:
                  sub: { type: string }
                  email: { type: string }
                  email_verified: { type: boolean }
                  name: { type: string }

  /end_session:
    get:
      summary: RP-initiated logout
      responses:
        "302": { description: Redirect to post_logout_redirect_uri }

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
