1.3.0 • Published 2 months ago

@dodi-smart/ttlock-graphql-api v1.3.0

Weekly downloads
-
License
MIT
Repository
github
Last release
2 months ago

This package creates a TTLock GraphQL API, allowing for interaction with data at TTLock. It is unofficial TTLock GraphQL API generated based on documentation, mainly configured to be used as Hasura Remote Schema.

Here's an example of how to use the TTLock GraphQL API to get a list of smart locks of the configured TTLock user:

query MyLocks {
  ttlock {
    smartlocks {
      lockName
      lockId
      details {
        state {
          state
        }
        electricQuantity
        firmwareRevision
        hardwareRevision
      }
    }
  }
}

It's recommended to add the TTLock GraphQL API as a Remote Schema in Hasura and connect data from your database with data in TTLock. By doing so, it's possible to request data from your database and TTLock in a single GraphQL query.

Here's an example of how to use the TTLock GraphQL API to get a list of smart locks for a specific TTLock user. Note that the user data is fetched from your database and the TTLock user data is fetched trough the TTLock API:

query UsersLocks {
  users {
    id
    displayName
    ttlocks {
      # Remote schema relation in users table
      lockName
      lockId
      details {
        state {
          state
        }
        electricQuantity
        firmwareRevision
        hardwareRevision
      }
    }
  }
}

Supported APIs

APIs can be added upon request. PRs are welcome of course!

https://euopen.ttlock.com/document/doc?urlName=userGuide%2FekeyEn.html

ApiSupportedComment
User🔴
Lock🟠
Ekey🔴
Passcode🟢
Gateway🟠
AddressToken🔴
IC Card🔴
Fingerprint🔴
Group🔴
Unlock record🟠
Lock upgrade🔴
Wireless Keyboard🟢
Remote🔴
Door Sensor🔴
NB-IoT🔴
QR Code🔴
WiFi Lock🔴

🟢 - Supported 🟠 - Partial 🔴 - Not supported

Getting Started

Install the package:

pnpm add @dodi-smart/ttlock-graphql-api

Setup serverless function

Create a new Serverless Function: functions/graphql/ttlock.ts:

import { createTTlockGraphQLServer } from '@dodi-smart/ttlock-graphql-api'

const server = createTTlockGraphQLServer({
  graphiql: true,
  graphqlEndpoint: '/graphql/ttlock',
  healthCheckEndpoint: '/graphql/ttlock/health',
  provideAuth(context) {
    return {
      // Should provide authentication
    }
  },
  onUpdateSession(context, auth) {
    // Handle session update
  }
})

export default server

Setup TTLock Management

Go to TTLock Management and obtain OAuth2 API key and secret by creating an application and wait for approval.

  • Add TTLOCK_CLIENT_ID as an environment variable.
  • Add TTLOCK_CLIENT_SECRET as an environment variable.

If you're using Nhost, add TTLOCK_CLIENT_ID, TTLOCK_CLIENT_SECRET to .env.development like this:

TTLOCK_CLIENT_ID=6EYY••••
TTLOCK_CLIENT_SECRET=BPum••••

And add the production key values to environment variables in the Nhost dashboard.

Start Nhost

nhost up

Learn more about the Nhost CLI.

Test

Test the TTLock GraphQL API in the browser:

https://local.functions.nhost.run/v1/graphql/ttlock

Remote Schema

Add the TTLock GraphQL API as a Remote Schema in Hasura.

URL

{{NHOST_FUNCTIONS_URL}}/graphql/ttlock

Headers

x-nhost-webhook-secret: NHOST_WEBHOOK_SECRET (From env var)

The NHOST_WEBHOOK_SECRET is used to verify that the request is coming from Nhost. The environment variable is a system environment variable and is always available.

Authentication

You have several options to authentication your users agains TTLock using this library. All of them depends on the usecase:

Single user usecase (easy to setup)

When the system should work with the locks of a single user (administrator). Permissions and access to the locks are granted to others users via the administrator or using Hasura user roles.

Multi user usecase (advanced)

When system work with locks of multiple users. Each user can authenticate against TTLock using their own credentials via OAuth 2.0. This usecase can be used for multi tenant apps for example.

How to choose

Use caseUsing JWT Token (easy)OAuth 2.0 (advanced)
Single user🟠🟢
Multi user🔴🟢

🟢 - Preferable way 🟠 - Supported 🔴 - Not supported

Using JWT Token

Obtain a JWT token by logging the user via curl, paw or postman. The Refresh token is valid for 10 years.

  • Add TTLOCK_ACCESS_TOKEN as an environment variable.
  • Add TTLOCK_REFRESH_TOKEN as an environment variable.

If you're using Nhost, add TTLOCK_ACCESS_TOKEN and TTLOCK_REFRESH_TOKEN to .env.development like this:

TTLOCK_ACCESS_TOKEN=59f6d••••
TTLOCK_REFRESH_TOKEN=BPum••••

And add the production key values to environment variables in the Nhost dashboard.

Configure the TTLock GraphQL API

import { createTTLockGraphQLServer } from '@dodi-smart/ttlock-graphql-api'

const server = createTTLockGraphQLServer({
  graphqlEndpoint: '/graphql/ttlock',
  healthCheckEndpoint: '/graphql/ttlock/health',
  provideAuth(context) {
    const { request } = context
    return {
      accessToken: process.env["TTLOCK_ACCESS_TOKEN"]
      refreshToken: process.env["TTLOCK_REFRESH_TOKEN"]
    }
  },
  onUpdateSession(context, auth) {
    // Can be empty as TTLOCK_REFRESH_TOKEN is valid for 10 years
  }
})

export default server

OAuth 2.0

From the TTLock Management page get client id and secret.

  • Add TTLOCK_CLIENT_ID as an environment variable.
  • Add TTLOCK_CLIENT_SECRET as an environment variable.

If you're using Nhost, add TTLOCK_CLIENT_ID and TTLOCK_CLIENT_SECRET to .env.development like this:

TTLOCK_CLIENT_ID=59f6d••••
TTLOCK_CLIENT_SECRET=BPum••••

Configure the TTLock GraphQL API

import { createTTLockGraphQLServer } from '@dodi-smart/ttlock-graphql-api'

const server = createTTLockGraphQLServer({
  graphqlEndpoint: '/graphql/ttlock',
  healthCheckEndpoint: '/graphql/ttlock/health',
  provideAuth(context) {
    const { userClaims } = context

    const { user } = await gqlSDK.getUser({
      id: userId
    })

    return {
      // Example if ttlock creds are saved in user's metadata, can differ
      accessToken: user.metadata.ttlockAccessToken,
      refreshToken: user.metadata.ttlockRefreshToken,
    }
  },
  onUpdateSession(context, auth) {
    const { userClaims } = context
    const { accessToken, refreshToken } = auth

    await gqlSDK.updateUserTTLockAuth({
      id: userId,
      accessToken,
      refreshToken
    })
  }
})

export default server

Permissions

Here's a minimal example without any custom permissions. Only requests using the x-hasura-admin-secret header will work:

import { createTTLockGraphQLServer } from '@dodi-smart/ttlock-graphql-api'

const server = createTTLockGraphQLServer({
  graphqlEndpoint: '/graphql/ttlock',
  healthCheckEndpoint: '/graphql/ttlock/health',
  provideAuth(context) {
    /* ... */
  },
  onUpdateSession(context, auth) {
    /* ... */
  }
})

export default server

For more granular permissions, you can pass an isAllowed function to the createTTLockGraphQLServer. The isAllowed function takes a context as parameter and runs every time the GraphQL server makes a request to TTLock to get or modify data for a specific TTLock user.

Here is an example of an isAllowed function:

import { createTTLockGraphQLServer } from '@dodi-smart/ttlock-graphql-api';

const isAllowed = (context: Context) => {
  const { isAdmin, userClaims } = context

  // allow all requests if they have a valid `x-hasura-admin-secret`
  if (isAdmin) {
    return true
  }

  // get user id
  const userId = userClaims['x-hasura-user-id']

  // check if the user is signed in
  if (!userId) {
    return false
  }

  // get more user information from the database
  const { user } = await gqlSDK.getUser({
    id: userId
  })

  if (!user) {
    return false
  }

  // other checks
}

const server = createTTLockGraphQLServer({
  graphqlEndpoint: '/graphql/ttlock',
  healthCheckEndpoint: '/graphql/ttlock/health',
  isAllowed,
  provideAuth(context) {
    /* ... */
  },
  onUpdateSession(context, auth) {
    /* ... */
  }
});

export default server;

Context

The context object contains:

  • userClaims - verified JWT claims from the user's access token.
  • isAdmin - true if the request was made using a valid x-hasura-admin-secret header.
  • request - Fetch API Request object that represents the incoming HTTP request in platform-independent way. It can be useful for accessing headers to authenticate a user
  • query - the DocumentNode that was parsed from the GraphQL query string
  • operationName - the operation name selected from the incoming query
  • variables - the variables that were defined in the query
  • extensions - the extensions that were received from the client

Read more about the default context from GraphQL Yoga.