Register and authorize devices

Before the IoT Center allows a device to communicate with its server or InfluxDB, it must validate the device’s configuration and distribute credentials in the form of a token.

In the context of the IoT Center, a device being registered is meant to be coupled with the permissions model of InfluxDB. A device is registered if it is in the database and has been issued credentials.

InfluxDB 2 requires authentication by default. Each request to the API must provide a properly-scoped token. For instance, a write request must provide a token that has write permissions to the particular bucket.

The IoT Center automatically creates an authorization token for every device using the InfluxDB authorizations API. A device, identified by a unique deviceId, uses HTTP GET to receive a InfluxDB URL, organization, bucket, and token. The device uses this information to start writing data to InfluxDB.

The code used by IoT Center to interact with the InfluxDB authorization API is in app/server/influxdb/authorizations.js.

Importing the InfluxDB authorizations API

The file that defines the authorization-related parts of the IoT Center API begins with several import statements. The first import brings in the InfluxDB authorizations API.

const {AuthorizationsAPI} = require('@influxdata/influxdb-client-apis')
const influxdb = require('./influxdb')
const {getOrganization} = require('./organizations')
const {getBucket} = require('./buckets')
const {INFLUX_BUCKET} = require('../env')

const authorizationsAPI = new AuthorizationsAPI(influxdb)
const DESC_PREFIX = 'IoT Center: '
const CREATE_BUCKET_SPECIFIC_AUTHORIZATIONS = false

(Source)

const influxdb refers to the InfluxDB object. The influxdb object is passed to new AuthorizationsAPI(influxdb) to get an authorizationsAPI.

The Authorization type is an important structure. It contains the permissions information for reading and writing to InfluxDB. These data are passed as an array of Permissions.

Define device functions with the InfluxDB API

In this section we write functions for creating authorizations, getting various data about devices, deleting authorizations, and checking permissions.

Create an authorization for a device

This function creates an authorization for a device, given an ID. It returns a promise with an authorization or an error.

async function createIoTAuthorization(deviceId) {
  const {id: orgID} = await getOrganization()
  let bucketID = undefined
  if (CREATE_BUCKET_SPECIFIC_AUTHORIZATIONS) {
    bucketID = await getBucket(INFLUX_BUCKET).id
  }
  console.log(
    `createIoTAuthorization: deviceId=${deviceId} orgID=${orgID} bucketID=${bucketID}`
  )
  return await authorizationsAPI.postAuthorizations({
    body: {
      orgID,
      description: DESC_PREFIX + deviceId,
      permissions: [
        {
          action: 'read',
          resource: {type: 'buckets', id: bucketID, orgID},
        },
        {
          action: 'write',
          resource: {type: 'buckets', id: bucketID, orgID},
        },
      ],
    },
  })
}

(Source)

This function is a good example of the interaction between the IoTCenter API, represented by HTTP paths, and the InfluxDB API, wrapped in the JavaScript client library

(See https://influxdata.github.io/influxdb-client-js/influxdb-client-apis.authorizationsapi.postauthorizations.html .)

The createIoTAuthorization function is called by the /env/:deviceid API path. (We discuss REST HTTP paths in the next section.)

Get information about authorizations

Gets IoT Center device authorization.

async function getIoTAuthorization(deviceId) {
  const authorizations = await getAuthorizations()
  const descriptionPrefix = DESC_PREFIX + deviceId
  const {id: bucketId} = await getBucket()
  const retVal = authorizations.reduce((acc, val) => {
    if (val.description.startsWith(descriptionPrefix)) {
      // does the authorization allow access to the bucket
      if (!isBucketRWAuthorized(val, bucketId)) {
        return acc // this grant does not allow R/W to the bucket
      }
      // if there are more tokens, use the one that was lastly updated
      if (!acc || String(val.updatedAt) > String(acc.updatedAt)) {
        return val
      }
    }
    return acc
  }, undefined)
  // console.log('getIoTAuthorization: ', retVal ? retVal.description : undefined)
  return retVal
}

(Source)

This calls: getAuthorizations. Let’s look at the definition of getAuthorizations:

async function getAuthorizations() {
  const {id: orgID} = await getOrganization()
  const authorizations = await authorizationsAPI.getAuthorizations({orgID})
  return authorizations.authorizations || []
}

(Source)

The functions gets all authorizations, returning (a promise with) an array of authorizations. It does this with the InfluxDB client library: authorizationsAPI.getAuthorizations.

getDeviceId is another helper function that returns a device ID from a given Authorization object.

function getDeviceId(authorization) {
  const description = String(authorization.description)
  if (description.startsWith(DESC_PREFIX)) {
    return description.substring(DESC_PREFIX.length)
  }
  return description
}

(Source)

This is called here. so that getIoTAuthorization can have a deviceID to pass.

Check authorization permissions

The getIoTAuthorization function above also calls isBucketRWAuthorized. This is an important helper function in the authorization code. isBucketRWAuthorized checks if the supplied authorization has permissions to read and write to the INFLUX_BUCKET. If it can, this returns true.

function isBucketRWAuthorized(authorization, bucketId) {
  const bucketRW = authorization.permissions.reduce((acc, val) => {
    if (
      val.resource.type === 'buckets' &&
      (!val.resource.id || val.resource.id === bucketId)
    ) {
      return acc | (val.action === 'read' ? 1 : 2)
    }
    return acc
  }, 0)
  return bucketRW === 3
}

(Source)

Delete an authorization

Finally, we need a way to delete authorizations. Deletes authorization for a supplied InfluxDB key (the ID of the authorization).

async function deleteAuthorization(key) {
  return authorizationsAPI.deleteAuthorizationsID({authID: key})
}

(Source)

Called here.