Clone wiki

ac-koa-hipchat / Multi-tenancy

One add-on can be installed with multiple HipChat OAuth2 clients, referred to here as 'tenants'. In practice, a tenant is either a HipChat room or group, depending on the installation scope of the add-on.

Tenant registration and information

Tenant installation and uninstallation is handled automatically by the library, which configures the necessary routes and handlers for each mounted add-on. Each installation results in the registration information for that tenant to be verified and stored for later use, including the tenant's shared secret, used for bi-directional authentication.

Add-on implementations are given access to the tenant information object in every Koa context for routes and webhook listeners in which this library is involved.


Field Description
id This tenant's id.
group This tenant's group id.
secret This tenant's shared secret.
room This tenant's room id, if installed in a single room.
webhookToken A secret token added to all dynamically generated webhooks, as an extra measure of security.
links A collection of this tenant's relevant URLs.
links.capabilities This tenant's capabilities descriptor URL.
links.base This tenant's base URL.
links.api This tenant's base API URL.
links.token This tenant's OAuth2 token generation URL.

Tenant authentication

This library handles bi-direction authentication between tenants and add-ons. It provides the following facilities:

Inbound JWT signature verification

For add-on routes that provide web UI to the tenant, such as the one defined in an add-on's configurable capabilitity, use the addon object's authenticate() middleware to protect your route. This middleware will then verify that requests have a valid JWT signature provided either as the signed_request query parameter or in a standard HTTP Authorization header with the format Authorization: JWT token=<jwt-token-value>.

For example, one would secure an addon's /configure route with this middleware as follows:

  function *() {
    // Normal Koa route handling here...

Requests successfully passing through this middleware will have the following object available on the Koa context:


Field Description
issuer The authenticated tenant's id.
issued The timestamp at which the token was generated.
userId The id of the user making the request.
expiry The time at which the token should be expired.
context An additional request context object sent by the tenant as part of the signed data. This may contain the current user's timezone in a field named tz.
token A refreshed version of the JWT token, suitable for use in subsequent requests over Ajax or as form or link parameters. See the Hearsay example add-on for a demonstration of this technique. We prefer this approach to maintaining state for iframed add-on UI over cookies due to some anti-click-jacking browser security models that prevent cookies from being set in cross-domain iframes.

Outbound request token handling

Outbound requests to the tenant's REST APIs require an current OAuth2 bearer token, which must be refreshed via the tenant's token API when it expires. As long as the add-on uses the ctx.tenantClient or ctx.roomClient APIs, this token management is handled automatically. See below for information about the these services.

Tenant services

For convenient add-on implementation, several service objects are attached to the Koa context that provide tenant-aware operations.

Tenant data storage

In order to support multiple tenants concurrently, a tenant-aware data storage abstraction is used throughout the library to partition tenant data, and a derivative of a tenant's unique storage object is provided with each Koa context, allowing add-ons access to a simple, partioned location to store basic key/value information. Storage of more structured data in alternative data stores is left as an exercise for each add-on, though every Koa context contains the full tenant data model, making such manual data partioning straightforward.


Method Description
get(key) Gets a value for a given key. Returns a promise.
set(key, value) Sets a value for a given key. Returns a promise.
del(key) Deletes a value for a given key. Returns a promise.
all() Gets all values in the current storage scope. Returns a promise.
narrow(scope) Creates and returns a substore narrowed by the given scope.