Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] MSC1956: Integrations API (base) #1956

Draft
wants to merge 8 commits into
base: old_master
Choose a base branch
from
Draft
260 changes: 260 additions & 0 deletions proposals/1956-integrations-api-base.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
# MSC1956: Integrations API
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: reference the following diagrams (if needed):

Also, the dances need better names. Currently we have:

  • OpenID dance - the dance to get a subject from the homeserver
  • OpenID exchange dance - the dance for a widget to get an OpenID subject/authentication token
  • Terms dance - the dance after an authentication token is acquired to check for unaccepted policies
  • Auth dance - the combination of the three dances, plus the integration manager's internal operations dance

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: Add a ready state to widget postMessage API so that widgets don't have to rely on the capabilities request taking an amount of time.


"Integration managers" are applications that help clients bring integrations (bridges, bots, widgets, etc)
to users. This can be for things like setting up an IRC bridge or adding a Giphy bot to the room. Integration
managers have been popularized by Riot, however other clients also have support for concept as well although
to a lesser extent.

Integration managers are important to the Matrix ecosystem as they help abstract away the underlying integration
and allow providers of the integratiosn to manage access to them. For example, there are multiple XMPP bridges
available in the Matrix ecosystem and an integration manager is in the best position to handle the different APIs
each exposes, generalizing the API exposed to clients. Similarly, the integration manager could charge for access
to specific integrations such as sticker packs or per-user access to a bridge.

This proposal introduces a new Matrix API: the "Integrations API" (or "Integrations Specification") which covers
how integration managers and integrations themselves cooperate with clients. Typically these kinds of extensions
to Matrix would be put into the Client-Server API, however the integration manager is not typically the same as
the user's homeserver and is therefore ideal to be specified indepdently.


## Glossary

Due to the introduction of a new API, some common terminology is to be established:

An **Integration Manager** (or *Integrations Manager*, or simply *Manager*) is an application which assists users
and/or clients in setting up *Integrations*.

**Integrations** are something the *Integration Manager* exposes, such as *Bots*, *Bridges*, and *Widgets*.

A **Bot** is typically a Matrix user which provides a utility service, such as Github Notifications or reaction
GIFs.

A **Bridge** is typically an Application Service which proxies events between Matrix and a 3rd party platform,
such as IRC. Bridges may also operate in a single direction and have other features like *Puppeting*, which are
not covered here. For information on the various types of bridging, please see the
[reference guide](https://matrix.org/docs/guides/types-of-bridging.html).

**Widgets** are embedded web applications in a Matrix client. These are split into two types: **Account Widgets**
and **Room Widgets**. *Account Widgets* are specific to a particular Matrix user and not shared with other users
whereas *Room Widgets* are shared with the members of the room they reside in.

A **Trusted Integration Manager** is an *Integration Manager* which the client has deemed trustworthy by its own
criteria. For example, this may be defined as being known as an account widget, discovered by .well-known, or
being set in the client's configuration. By proxy, an **Untrusted Integration Manager** is the opposite.

The **Integration Manager API** is the set of HTTP endpoints which clients can use to interact with a given
*Integration Manager*.

The **Widget API** is the set of `postMessage` APIs which are used for clients and *Widgets* to communicate with
each other when embedded.

The **Integrations API** is the set of both the *Integration Manager API* and *Widget API* as well as any other
details which affect *Widgets* or *Integration Managers* and their interactions with clients.


## Proposal

A new Integrations API be introduced into Matrix which consists of the components listed in this proposal.

**Components**:
turt2live marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Include #2192 (if it stays relevant)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add #2315


* API Standards (see later section of this proposal)
* Discovery - [MSC1957](https://github.com/matrix-org/matrix-doc/pull/1957)
* Widgets
* Includes [MSC1236](https://github.com/matrix-org/matrix-doc/issues/1236)
* Includes [MSC1958](https://github.com/matrix-org/matrix-doc/pull/1958)
* Includes [MSC1960](https://github.com/matrix-org/matrix-doc/pull/1960)
* MSC for extensions and alterations not yet defined
* ***TODO: MSC for each or group them by subject?***
* The first version of the Widget API is to be `0.1.0`, with the existing `0.0.x` versions being flagged as
development versions which are otherwise unspecified.
* Sticker picker and custom emoji
* Includes [MSC1951](https://github.com/matrix-org/matrix-doc/pull/1951)
* Includes [MSC1959](https://github.com/matrix-org/matrix-doc/pull/1959)
* Authentication in integration managers - [MSC1961](https://github.com/matrix-org/matrix-doc/pull/1961)
* Terms of service / policies - [MSC2140](https://github.com/matrix-org/matrix-doc/pull/2140)
* Integration manager specific APIs
* MSC and scope not yet defined

***TODO: Define where paid integrations, OAuth, etc all fit if at all.***
***TODO: Finalize what goes into this spec.***


## Proposed API standards

All HTTP APIs in this specification must consume and produce JSON unless otherwise indicated. Errors emitted by
these APIs should follow the standard error responses defined by other APIs in Matrix. Some common error responses
implementations may encounter are:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From reading the code of https://github.com/matrix-org/matrix-widget-api, it seems like these errors have only a message field that contains human-readable text. This could be problematic since the error message is not defined in the spec, so it's impossible for a widget to know what the error is. For example, the string Invalid request - missing event type isn't standardized anywhere, so it can't be differentiated from Cannot send state events of this type unless the widget knows exactly what these strings will be ahead of time (which are specific to that particular library).

It may be helpful to define standard error types (like the error codes in the C2S API) to categorize these errors. Ex, M_WIDGET_MISSING_PARAM or M_WIDGET_DENIED for the above errors.

* `403` `{"errcode":"M_UNKNOWN_TOKEN","error":"Invalid token"}`
* `400` `{"errcode":"M_MISSING_PARAM","error":"Missing 'matrix_server_name'"}`
* `400` `{"errcode":"M_INVALID_PARAM","error":"'matrix_server_name' must be a string"}`
* `400` `{"errcode":"M_NOT_FOUND","error":"The sharable URL did not resolve to a pack"}`


## Tradeoffs

Specifying a whole new set of APIs means introducing another complex system into Matrix, potentially causing
confusion or concerns about the architecture of Matrix or its implementations. It is believed by the author that
having a dedicated set of APIs for this system within Matrix is important to reduce the burden on homeserver
authors and to support client authors in their ambitions to grow Matrix through bridges, bots, etc.

It is also questionable if integration managers should be a concept within Matrix as the APIs which clients would
be interacting with can be generically exposed by the integrations themselves without the need for a manager. The
manager's role in this specification is to provide clients with the opportunity to quickly get themselves connected
with the larger Matrix ecossytem without adding the burden of designing their own interface. The majority of the
APIs are defined such that a client is able to put their own UI on top of the manager instead of embedding that
manager if desired by the client.


## Potential issues

Clients which support today's ecosystem of integration managers have a lot of work to do to support this proposal
in all its parts. In addition, integration managers themselves have a decent amount of work in order to become
compliant. The exercise of this proposal is not to make development harder for the projects in the ecosystem, but
to standardize on a set of APIs which bridge the gap between managers while also patching inadequacies in the current
systems.


## Security considerations

Each proposal which branches off this proposal has its own set of security considerations. As a whole, clients are
expected to make decisions on which integration managers are trusted, and integration managers are encouraged to do
the same with clients and integrations.

----

## How all of this is meant to work (versus before)

This portion of the proposal is not required reading - it is supplemental information to help explain how integration
managers work in a world with this API and prior. This is also meant to be moderately high level and doesn't go into
the specific API endpoints one would call.

### Integration managers today (pre-API)

Integration managers are very much a Riot concept that has lead to enough utility to make it worth putting the idea
in the spec. An integration manager is typically just a UI for interacting with bridges/bots/widgets instead of having
to do all the bits manually or through a command line-like interface. Riot defines an integration manager as a set
of URLs: one for the UI (`ui_url`) and one for its API (`rest_url`). There are other URLs that also take place, but
they're covered in more detail later on in this text.

Before an integration manager can even be shown to the user, an authentication dance must take place. This is to
acquire an authentication token (wrongly named a `scalar_token` in Riot) - this token is then provided to the integration
manager where applicable via a `scalar_token` query string parameter. The auth dance involves getting an OpenID token
from the homeserver for the user, passing that to the integration manager via its API URL, and getting back a token
it can then use for future requests. The integration manager internally takes the OpenID token and verifies it by
doing a federated request to the homeserver - this is how it claims the token and gets a user ID back. This is also
why homeservers generally need working federation in order to use integrations, at least until recently when Synapse
could support exposing the OpenID claim endpoint without the rest of federation (Modular and similar deployments use
this).

The entire auth dance is done in the background, so the user doesn't see this happening. If any of the steps go wrong,
the user is warned that the integration manager is offline/inaccessible. After the auth dance, Riot does a terms of
service check to ensure the user has accepted all applicable policies. It does this by using the API URL to get the
authentication token owner's information (`/account`) which can return an error if there are unaccepted policies. If
there are policies to accept, Riot prompts before continuing. Provided the user accepts all the policies, Riot moves
on to rendering the integration manager window using the UI URL as a base. Depending on what the user clicked depends
on how the URL is constructed. Typically users would click the 4/9 squares in the context of the room, so a generic
URL referencing the "homepage" of the manager and the room ID is supplied to the manager. The user's authentication
token for the manager is also included in the URL.

The integration manager then takes all the information from the URL and starts to render the UI. In this case, it would
be rendering the homepage so it asks its backend for information about integrations it supports and starts trying to
figure out which ones are feasible. In the process, it queries Riot itself for some information (bot membership, room
publicity, room member count, etc) - this is done over a special `postMessage` API named `ScalarMessaging` (which is
not really Scalar-specific). Note that this API is different from the Widget `postMessage` API - more on that in a bit.

The ScalarMessaging API acts in the background and allows the integration manager to check the state of things as well
as perform actions. For example, when the user wants to add a bot to the room the manager will use the ScalarMessaging
API to determine if the bot is already in the room, and if it isn't it will use ScalarMessaging to cause the user to
invite the bot to the room. The expectation is generally that the bot will auto-accept invites without needing the
integration manager or Riot to interfere. When the bot is being removed from the room, it is not kicked through the API.
Instead, the integration manager calls its backend which impersonates the bot to leave the room.

The ScalarMessaging API is locked down enough to ensure only the integration manager can interact with it.

When the user finally wants to close the integration manager, they can click outside of it to have Riot dismiss the dialog
or they can click a close button within the integration manager which uses the ScalarMessaging API to close itself.

The other thing the ScalarMessaging class lets integration managers do is add widgets into the room and onto the user's
account. Widgets added by an integration manager are almost always wrapped by the integration manager itself. Widgets
are simply URLs that are rendered in iframes, so when the integration manager wraps a widget it means there's the top
level iframe supplied by Riot and another iframe supplied by the integration manager's wrapper. The wrapper usually
provides some features like being able to talk the Widget `postMessage` API (here on out known as the Widget API)
and a fullscreen button. Dimension, Scalar's opensource "competitor", doesn't require any authentication in order to
load this widget wrapper however Scalar does. Riot solves this by having a "widget URL whitelist" in the configuration
for when to provide an authentication token to the widget through its URL.

Scalar has two methods of checking for an authentication token, both of which are used to try and avoid the dreaded
"Forbidden" error page. The first is simply using the authentication token it was provided by the client. The second
involves cookies that are scoped to the Scalar domain. Typically the cookie is set after a token has been successfully
used (as provided by the client). The cookies helps avoid the forbidden error page when Riot decides it can't send
the authentication token to the widget/manager (typically when people change to Dimension or another integration manager).

Dimension uses a similar scheme for ensuring it has a token available, though it uses localstorage in place of cookies.
When both the provided and stored token fail, Dimension uses Riot's OpenID exchange API to acquire a new token by asking
the user for permission to share their identity. This is supposed to be used as a last resort, given Dimension only needs
a token to determine the user's stickerpacks and nothing more (for widgets).

It is worth noting that widgets are designed such that the integration manager gets no special treatment, however in
practice integration manager widgets are very difficult to manage from a client perspective, primarily for authentication
reasons. Widgets are simply supposed to be iframes with no dealing of tokens from a spec perspective.

The sticker picker is a particularly interesting widget in that it's at the user's account level and not in a room. By
nature of being a widget, it is supposed to be independent of the integration manager however in practice users find it
confusing when their integration manager changes and their sticker picker is the "old" one. This is something that can
usually be mitigated by the client (removing/replacing the sticker picker widget when the user asks to change their
integration manager), but so far no client has opted to do so.

The sticker picker widget uses a capabilities subsystem in the Widget API to get permission to send events (stickers)
on behalf of the user. Sticker pickers are one of the widgets which require authentication so the widget can show the
user the sticker packs they have enabled. Riot currently has the capabilities exchange happen in the background, though
it could expose it as a dialog to the user.

The final aspect of integration managers in the current ecosystem is conference calls: Riot uses a Jitsi widget to make
a conference call. Because different integration managers have different opinions on how the Jitsi widget should be declared,
Riot supports a configuration option to change the base URL of the Jitsi widget before it gets added to the room state.
The Jitsi URL in the config almost always points to an integration manager which wraps the widget.

### Integration managers in a post-API world

This proposal defines an API for how integration managers are supposed to work, taking into consideration user expectations,
technical limitations, and other issues with the current state of affairs. Most critically, this new API brings forth a
new authentication scheme for integration managers (and therefore widgets) by making somewhat radical changes to the way
things work.

The first major change is that integration managers become widgets. This is confusing at first, but if we run the thought
experiment for a bit it starts to make some sense. Widgets already have a well-specified API they can use to talk to the
client, and with some modifications (proposed here) the same API can be used to run an integration manager. This proposal
builds on the capabilities system to add additional actions needed by integration managers, such as adding readonly views
of the room membership and ability to invite users. This does away with the existing API which is not as well specified.

Widgets under this proposal also lose their authentication tokens, which means integration managers also lose an authentication
token. Authentication tokens are not completely gone though: the APIs to get them still exist for reasons described a bit
later on, and widgets can use the now-specified OpenID exchange API to acquire a token if it needs one. There's additionally
a provision within the proposal to allow clients to skip the prompt under some circumstances, such as when dealing with a
sticker picker or integration manager - these kinds of widgets are likely to need/request OpenID information for good reason
so the client can do so quietly in the background without nagging the user. Other widgets, like custom widgets, are expected
to prompt for confirmation from the user.

With this new authentication system, clients no longer need to track a whitelist of widget URLs to append tokens to and
generally don't need to track the tokens for themselves. Terms of service acceptance is also expected to be done by the
widgets (including integration manager) themselves, though smarter clients like Riot can try and optimize for it by still
doing the auth&check dance.

This proposal also defines a set of APIs for clients to interact with the integration manager directly, avoiding the use
of iframes where applicable. The theory is that clients wanting to control the integrations experience can do so by invoking
the integration manager's API on top of the client's native UI/UX. An example of where this might be worthwhile for Riot is
the sticker picker: instead of rendering an iframe and having to deal with a lot of complicated APIs, Riot could instead
just do the auth dance and get all the information it needs from the widget. A similar approach could be taken for adding
an IRC bridge to a room: if the client were driving the manager's API, it could offer a button instead of rendering a whole
iframe.

To handle the Jitsi use case, this proposal introduces a "make me a widget" API. The API exists on integration managers to
construct a widget for the client, doing away with Riot's Jitsi URL in the config.

Integration managers like Scalar can still require an authentication token to render the actual widget by having a small
wrapper which uses the OpenID exchange API. The wrapper should also persist the token somewhere (cookies/localstorage) so
it can load the next time unobtrusively.

Fundamentally the power an integration manager has is unchanged by this proposal, though how they perform actions is vastly
different, hopefully for the better.