openapi: 3.1.0 info: title: Cmine ID version: '1.0.0' summary: Universal OIDC identity provider for Cmine products and partners. description: | OIDC + OAuth 2.1 provider. Authorize-code-with-PKCE is the only supported grant for new integrations; refresh-token rotation with family-based replay detection is built-in. JWTs signed with EdDSA (primary) and RS256 (compatibility). **Quickstart** (5 steps): see `/developers`. **Discovery**: `/.well-known/openid-configuration` contact: name: Cmine ID support email: support@cmine.local license: name: Proprietary servers: - url: https://identity.chwjh.com description: Production tags: - name: OIDC description: Standard OIDC 1.0 + OAuth 2.1 endpoints - name: Federated description: Sign-in-with-google / apple / wechat / ... - name: Account description: Session-protected self-service for the authenticated user - name: 2FA description: TOTP enrollment and verification - name: Clients description: OAuth client lifecycle (developer + admin) - name: Entitlements description: Cross-product subscription read-model paths: /.well-known/openid-configuration: get: tags: [OIDC] summary: OIDC discovery document responses: '200': { description: OK, content: { application/json: { schema: { type: object } } } } /.well-known/jwks.json: get: tags: [OIDC] summary: JSON Web Key Set (active + next public keys) responses: '200': description: OK content: { application/json: { schema: { type: object, properties: { keys: { type: array } } } } } /oauth/authorize: get: tags: [OIDC] summary: Start the authorization code (PKCE) flow description: | Redirects the user to /signin if no session, otherwise issues a code and redirects back to `redirect_uri?code=…&state=…`. parameters: - { name: response_type, in: query, required: true, schema: { type: string, enum: [code] } } - { name: client_id, in: query, required: true, schema: { type: string } } - { name: redirect_uri, in: query, required: true, schema: { type: string, format: uri } } - { name: scope, in: query, schema: { type: string, default: 'openid email profile' } } - { name: state, in: query, 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 redirect_uri with `code` or `error`. } '400': { description: Validation error } /oauth/token: post: tags: [OIDC] summary: Exchange auth code or refresh token for access + refresh + id_token requestBody: required: true content: application/x-www-form-urlencoded: schema: type: object required: [grant_type, client_id] properties: grant_type: { type: string, enum: [authorization_code, refresh_token] } client_id: { type: string } client_secret: { type: string, description: 'Confidential clients only; can be in Basic auth instead' } code: { type: string } redirect_uri: { type: string } code_verifier: { type: string } refresh_token: { type: string } responses: '200': description: OK content: application/json: schema: type: object properties: access_token: { type: string } token_type: { type: string, enum: [Bearer] } expires_in: { type: integer, example: 3600 } scope: { type: string } refresh_token: { type: string } id_token: { type: string } '400': { description: invalid_grant / invalid_request } '401': { description: invalid_client } /oauth/userinfo: get: tags: [OIDC] summary: OIDC userinfo security: [{ BearerAuth: [] }] responses: '200': description: OK content: application/json: schema: type: object properties: sub: { type: string } email: { type: string } email_verified: { type: boolean } name: { type: string } role: { type: string } '401': { description: invalid_token } /oauth/introspect: post: tags: [OIDC] summary: RFC 7662 token introspection security: [{ BasicAuth: [] }] requestBody: required: true content: application/x-www-form-urlencoded: schema: { type: object, properties: { token: { type: string } } } responses: '200': description: OK content: { application/json: { schema: { type: object, properties: { active: { type: boolean } } } } } '401': { description: invalid_client } /oauth/revoke: post: tags: [OIDC] summary: RFC 7009 token revocation requestBody: required: true content: application/x-www-form-urlencoded: schema: { type: object, properties: { token: { type: string } } } responses: '200': { description: OK } /v1/auth/providers: get: tags: [Federated] summary: List federated identity providers currently enabled responses: '200': description: OK content: application/json: schema: type: object properties: providers: type: array items: type: object properties: id: { type: string } label: { type: string } loginUrl: { type: string, format: uri } /auth/{provider}/login: get: tags: [Federated] summary: Begin federated sign-in parameters: - { name: provider, in: path, required: true, schema: { type: string, enum: [google] } } - { name: next, in: query, schema: { type: string } } responses: '302': { description: Redirects to provider. } /v1/me: get: tags: [Account] summary: Current user (session) security: [{ SessionCookie: [] }] responses: '200': { description: OK } /v1/me/entitlements: get: tags: [Entitlements] summary: User's subscriptions across products security: [{ SessionCookie: [] }] responses: '200': description: OK content: application/json: schema: type: object properties: entitlements: type: array items: type: object properties: product: { type: string } plan: { type: string } status: { type: string, enum: [active, past_due, canceled, expired] } current_period_end: { type: integer, nullable: true } source: { type: string } /v1/entitlements: post: tags: [Entitlements] summary: Upsert entitlement (server-to-server) security: [{ BasicAuth: [] }] requestBody: required: true content: application/json: schema: type: object required: [user_id, product, plan, status, source, version] properties: user_id: { type: string } product: { type: string, example: 'harmony' } plan: { type: string, example: 'pro' } status: { type: string, enum: [active, past_due, canceled, expired] } current_period_end: { type: integer, description: 'unix seconds' } source: { type: string, example: 'stripe' } external_customer_id: { type: string, nullable: true } external_subscription_id: { type: string, nullable: true } event_id: { type: string, nullable: true, description: 'For idempotency' } version: { type: integer, minimum: 1 } metadata: { type: object, additionalProperties: true, nullable: true } responses: '200': { description: OK } '401': { description: invalid_client } '409': description: stale_version content: { application/json: { schema: { type: object, properties: { stored_version: { type: integer } } } } } /v1/me/2fa: get: tags: [2FA] summary: Current 2FA state security: [{ SessionCookie: [] }] responses: '200': { description: OK } /v1/me/2fa/totp/enroll: post: tags: [2FA] summary: Generate a fresh TOTP secret + QR security: [{ SessionCookie: [] }] responses: '200': description: OK content: application/json: schema: type: object properties: otpauth_uri: { type: string, example: 'otpauth://totp/Cmine ID:foo@bar?secret=…' } secret_base32: { type: string } qr_svg: { type: string, description: 'inline SVG' } /v1/me/2fa/totp/confirm: post: tags: [2FA] summary: Activate TOTP by verifying the first code security: [{ SessionCookie: [] }] requestBody: required: true content: application/json: schema: { type: object, properties: { code: { type: string } } } responses: '200': description: OK content: application/json: schema: type: object properties: backup_codes: type: array items: { type: string } /v1/oauth/clients: get: tags: [Clients] summary: List my OAuth clients security: [{ SessionCookie: [] }] responses: { '200': { description: OK } } post: tags: [Clients] summary: Register a new OAuth client (lands in pending_review) security: [{ SessionCookie: [] }] requestBody: required: true content: application/json: schema: type: object required: [name, client_type, redirect_uris] properties: name: { type: string } client_type: { type: string, enum: [public, confidential] } redirect_uris: { type: array, items: { type: string, format: uri } } allowed_scopes: type: array items: { type: string } default: [openid, profile, email] logo_url: { type: string, format: uri } homepage_url: { type: string, format: uri } responses: '200': description: OK content: application/json: schema: type: object properties: client_id: { type: string } client_secret: type: string nullable: true description: 'Confidential clients only. Returned exactly once.' components: securitySchemes: BearerAuth: type: http scheme: bearer bearerFormat: JWT BasicAuth: type: http scheme: basic SessionCookie: type: apiKey in: cookie name: hh_identity_session