openapi: 3.1.0 info: title: Blah Chatserver Proto version: 0.0.1 paths: # OAPI does not support WebSocket interface definitions. # See: https://github.com/OAI/OpenAPI-Specification/issues/55#issuecomment-929382279 /_blah/ws: get: summary: WebSocket endpoint description: | This endpoint is for server-side-event dispatching. Once connected, client must send a JSON text message of type `Signed-Auth` for authentication. If server does not close it immediately, it means success. Since OAPI does not support WebSocket interface, we use request and response types documented here mean outgoing and incoming JSON text messages. parameters: - name: Connection in: header required: true - name: Upgrade in: header required: true requestBody: content: application/json: schema: $ref: '#/components/schemas/WSClientToServer' responses: 101: headers: Connection: required: true Upgrade: required: true content: application/json: schema: $ref: '#/components/schemas/WSServerToClient' /_blah/user/me: get: summary: Check registration status of the current user parameters: - name: Authorization in: header description: Optional user authentication token. schema: $ref: '#/components/schemas/Signed-Auth' responses: 204: description: The user is already registered on the server. 404: description: | The user is not registered, or no token is not provided. headers: x-blah-nonce: description: The challenge nonce for registration. schema: type: integer format: uint32 x-blah-difficulty: description: The challenge difficulty for registration. schema: type: integer format: uint32 post: summary: Register or update user identity description: | Register or update a user identity description. To prevent misuse and DOS of this endpoint, the request must pass the server-specific Proof of Work (PoW) challenge as below: 1. The request payload must include `challenge_nonce` with the value of `x-blah-nonce` header from a recent enough GET response of `/user/me`. Server will rotate it and a nonce will expire after a server-specific time period. 2. The SHA256 of the canonical serialization (JCS) of `signee` must have at least `x-blah-difficulty` (from a recent response) number of leading zero bits. The `id_url` should be a HTTPS domain name without path. A fixed well-known path `/.well-known/blah.identity.json` will be fetched. It should return status 200, with a JSON response of type `UserIdentityDescription`. requestBody: content: application/json: schema: $ref: '#/components/schemas/Signed-UserRegister' responses: 204: description: User successfully registered. 400: description: Invalid request format, or invalid challenge. content: application/json: schema: $ref: '#/components/schemas/ApiError' 401: description: | Unable to verify user identity. May caused by connection failure when fetching id_url, malformed identity description, and etc. content: application/json: schema: $ref: '#/components/schemas/ApiError' 409: description: | User state changed during the operation. Could retry later. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room: get: summary: List rooms parameters: - name: filter in: query required: true schema: enum: - public - joined - unseen description: | Must be one of following values: - "public": list all public rooms on the server. - "joined": list rooms the user have joined. Requires `Authorization`. - "unseen": list rooms the user have joined and have unseen messages. Requires `Authorization`. - name: top in: query schema: type: string description: The maximum count of rooms returned in a single response. This is only an advice and server can clamp it to a smaller value. - name: skipToken in: query schema: type: string description: The page token returned from a previous list response to fetch the next page. NB. Other parameters (eg. `joined` and `page_len`) should be included (as the same value) for each page fetch. - name: Authorization in: header description: Optional proof of membership for private rooms. schema: $ref: '#/components/schemas/Signed-Auth' responses: 200: description: Filtered and paged rooms. content: application/json: schema: $ref: '#/components/schemas/RoomList' 401: description: Missing or invalid Authorization header. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/create: post: summary: Create a room description: When `typ="create_room"`, create a multi-user room. When `typ="create_peer_chat"`, create a peer-to-peer room between two users. There can be at most one peer room for each given user pair. requestBody: content: application/json: schema: $ref: '#/components/schemas/Signed-CreateRoom' responses: 200: description: Room created. content: application/json: schema: type: string description: Newly created room `rid`. 403: description: The user does not have permission to create room. content: application/json: schema: $ref: '#/components/schemas/ApiError' 404: description: | The current user does not exists, the peer user does not exist or they disallows peer chat. content: application/json: schema: $ref: '#/components/schemas/ApiError' 409: description: There is already a peer chat room between the user pair. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/{rid}: get: summary: Get room metadata parameters: - name: Authorization in: header description: Optional proof of membership for private rooms. schema: $ref: '#/components/schemas/Signed-Auth' responses: 200: description: The metadata of the specified room. content: application/json: schema: $ref: '#/components/schemas/RoomMetadata' 404: description: | Room does not exist or the user does not have permission to get metadata of it. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/{rid}/admin: post: summary: Room management requestBody: content: application/json: schema: $ref: '#/components/schemas/Signed-RoomAdmin' responses: 204: description: Operation completed. 404: description: | Room does not exist or the user does not have permission for the operation. content: application/json: schema: $ref: '#/components/schemas/ApiError' 409: description: Operation is already done, eg. joining an already joined room. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/{rid}/feed.json: get: summary: Get JSON feed of room description: | Get room {rid}'s content in JSON feed v1.1 format. The room must be public. For human and feed reader consumption only. responses: 200: description: The JSON feed. content: text/feed+json: schema: $ref: 'https://www.jsonfeed.org/version/1.1/' 404: description: Room does not exist or is private. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/{rid}/msg: get: summary: List messages in a room description: | Return a list of messages in reversed server time order, up to length `top` in a single response, from room {rid}. The last (oldest) message's `cid` will be returned as `skipToken` in response, which can be used as query parameter for the next GET, to repeatedly fetch more history. parameters: - name: Authorization in: header description: Optional proof of membership for private rooms. schema: $ref: '#/components/schemas/Signed-Auth' - name: top in: query schema: type: integer description: | The number of items returned in a single response. This is an advice and may be further clamped by the server. It must not be zero. - name: skipToken in: query schema: type: string description: | Return items after (older than) an existing `cid`. Useful for pagination. responses: 200: content: application/json: schema: $ref: '#/components/schemas/RoomMsgs' 404: description: | Room does not exist or the user does not have permission to read it. content: application/json: schema: $ref: '#/components/schemas/ApiError' post: summary: Post a `Msg` into a room requestBody: content: application/json: schema: $ref: '#/components/schemas/Signed-Chat' responses: 200: content: application/json: schema: type: string description: Newly created message id `cid`. 403: description: The user does not have permission to post in this room. content: application/json: schema: $ref: '#/components/schemas/ApiError' 404: description: The room does not exist or the user is not a room member. content: application/json: schema: $ref: '#/components/schemas/ApiError' /_blah/room/{rid}/msg/{cid}/seen: post: summary: Mark a message seen description: | Mark message {cid} and everything before it in room {rid} seen by the current user. Server may enforce that last seen message does not go backward. Marking an older message seen or sending the same request multiple times can be a no-op. parameters: - name: Authorization in: header required: true description: Proof of membership for private rooms. schema: $ref: '#/components/schemas/Signed-Auth' responses: 204: description: Operation completed. 404: description: | Room does not exist or the user is not in the room. content: application/json: schema: $ref: '#/components/schemas/ApiError' # Ideally we should generate these from src, but we need to # WAIT: https://github.com/juhaku/utoipa/pull/1034 components: schemas: WSClientToServer: anyOf: - $ref: '#/components/schemas/Signed-Auth' WSServerToClient: anyOf: - type: object properties: chat: $ref: '#/components/schemas/Signed-Chat' - type: object properties: lagged: type: object const: {} ApiError: type: object properties: error: type: object properties: code: type: string description: A machine-readable error code string. example: invalid_signature message: type: string description: A human-readable error message. example: signature verification failed RoomList: type: object required: - rooms properties: rooms: type: array items: $ref: '#/components/schemas/RoomMetadataForList' next_token: type: string description: An opaque token to fetch the next page. RoomMetadataForList: type: object required: ['rid', 'title', 'attrs'] properties: rid: type: string title: type: string attrs: description: Room attributes bitset, see `RoomAttrs`. type: integer format: int64 last_msg: $ref: '#/components/schemas/WithMsgId-Signed-Chat' last_seen_cid: description: The `cid` of the last chat being marked as seen. type: string unseen_cnt: description: | The number of unseen messages. Only available for GET `/room?filter=unseen`. type: integer format: uint64 member_permission: type: integer format: int64 peer_user: type: string description: | For peer chat room, this gives the identity of the peer user. RoomMetadata: type: object required: ['rid', 'title', 'attrs'] properties: rid: type: string title: type: string attrs: type: integer format: int64 RoomMsgs: type: object required: - msgs properties: msgs: description: Room messages in reversed server-received time order. type: array items: $ref: '#/components/schemas/WithMsgId-Signed-Chat' skip_token: description: The token for fetching the next page. type: string RichText: type: array items: anyOf: - type: string description: Unstyled text piece. - type: array items: false prefixItems: - type: string description: The text piece to apply styles on. - type: object properties: b: type: boolean description: Bold. m: type: boolean description: Monospace. i: type: boolean description: Italic. s: type: boolean description: Strikethrough. u: type: boolean description: Underline. hashtag: type: boolean description: Hashtag. link: type: string description: Link target. Signed-Auth: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: type: object properties: typ: type: string const: 'auth' Signed-RoomAdmin: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: oneOf: - description: Add member to the room. type: object properties: typ: type: string const: 'add_member' room: type: string permission: type: integer format: uint64 user: type: string - description: Remove member from the room. type: object properties: typ: type: string const: 'remove_member' room: type: string user: type: string Signed-Chat: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: type: object properties: typ: type: string const: 'chat' room: type: string rich_text: $ref: '$/components/schemas/RichText' WithMsgId-Signed-Chat: allOf: - $ref: '#/components/schemas/Signed-Chat' - type: object properties: cid: type: string description: An opaque server-specific identifier. Signed-CreateRoom: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: oneOf: - type: object properties: typ: type: string const: 'create_room' title: type: string - type: object properties: typ: type: string const: 'create_peer_chat' peer: type: string Signed-UserRegister: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: type: object properties: typ: type: string const: 'user_register' server_url: type: string description: | The server URL to register on. Must matches chat server's base_url. It's path segment must be normalized, eg. always contains a `/` path for top-level. id_url: type: string description: | The identity server URL. Must be in form `https:///`. It's path segment must be normalized, eg. always contains a `/` path for top-level. id_key: type: string description: Hex encoded user primary key `id_key`. challenge_nonce: type: integer format: uint32 description: The challenge nonce retrieved from a recent GET response of `/user/me`. UserIdentityDescription: type: object properties: id_key: type: string act_keys: type: array items: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: type: object properties: typ: type: string const: 'user_act_key' act_key: type: string expire_time: type: integer format: uint64 comment: type: string profile: type: object properties: sig: type: string signee: type: object properties: nonce: type: integer format: uint32 timestamp: type: integer format: uint64 id_key: type: string act_key: type: string payload: type: object properties: typ: type: string const: 'user_profile' preferred_chat_server_urls: type: array items: type: string format: url id_urls: type: array items: type: string format: url