@dodi-smart/nuki-graphql-api
TypeScript icon, indicating that this package has built-in type declarations

1.2.0 • Public • Published

nuki

Nuki GraphQL API

@dodi-smart/nuki-graphql-api

npm npm NPM

This package creates a Nuki GraphQL API, allowing for interaction with data at Nuki. It is unofficial Nuki GraphQL API generated based on Nuki's Swagger OpenAPI specification, mainly configured to be used as Hasura Remote Schema.

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

query MyLocks {
  nuki {
    smartlocks {
      name
      smartlockId
      state {
        batteryCharge
        state
      }
      firmwareVersion
      hardwareVersion
    }
  }
}

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

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

query UsersLocks {
  users {
    id
    displayName
    nukies {
      # Remote schema relation in users table
      name
      smartlockId
      state {
        batteryCharge
        state
      }
      firmwareVersion
      hardwareVersion
    }
  }
}

Supported APIs

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

https://api.nuki.io

Api Supported Comment
Account 🟠
AccountSubscription 🔴
AccountUser 🔴
Address 🔴
AddressReservation 🔴
AddressToken 🔴
AdvancedApi 🟠
ApiKey 🔴
Company 🔴
Notification 🔴
Opener 🔴
Service 🔴
Smartlock 🟠
SmartlockAuth 🔴
SmartlockLog 🟢 Available nested inside smartlock query
Subscription 🔴

🟢 - Supported 🟠 - Partial 🔴 - Not supported

Getting Started

Install the package:

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

Setup serverless function

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

import { createNukiGraphQLServer } from '@dodi-smart/nuki-graphql-api'

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

export default server

Configure Nuki Web

Go to Nuki Web and obtain OAuth2 API key and secret. Click on Generate API token, give the token name and check the scopes you want to use. Click save and copy the token value.

  • Add NUKI_CLIENT_ID as an environment variable.
  • Add NUKI_CLIENT_SECERT as an environment variable.
  • Add NUKI_API_KEY as an environment variable.

If you're using Nhost, add NUKI_CLIENT_ID, NUKI_CLIENT_SECERT and NUKI_API_KEY to .env.development like this:

NUKI_CLIENT_ID=6EYY••••
NUKI_CLIENT_SECERT=BPum••••
NUKI_API_KEY=59f6d••••

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 Nuki GraphQL API in the browser:

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

Remote Schema

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

URL

{{NHOST_FUNCTIONS_URL}}/graphql/nuki

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 Nuki 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 Nuki using their own credentials via OAuth 2.0. This usecase can be used for multi tenant apps for example.

How to choose

Use case Using API_KEY (easy) OAuth 2.0 (advanced) OAuth 2.0 + ApiKeyToken (advanced)
Single user 🟢 🟠 🟠
Multi user 🔴 🟠 🟢

🟢 - Preferable way 🟠 - Supported 🔴 - Not supported

Using API_KEY

Go to Nuki Web and click on API on side navigation

Click on Generate API token, give the token name and check the scopes you want to use. Click save and copy the token value. ApiKeyToken is long-living token and does not require refresh.

  • Add NUKI_API_KEY as an environment variable.

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

NUKI_API_KEY=59f6d••••

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

(Optional) Modify Remote Schema

If you need to pass the API key from Hasura instead of having it as environment variable, then:

Add to the Hasura Remote Schema the API Key

x-nuki-api-key: NUKI_API_KEY (From env var)

The environment variable is a system environment variable and is always available.

Configure the Nuki GraphQL API

import { createNukiGraphQLServer } from '@dodi-smart/nuki-graphql-api'

const server = createNukiGraphQLServer({
  graphqlEndpoint: '/graphql/nuki',
  healthCheckEndpoint: '/graphql/nuki/health',
  provideAuth(context) {
    const { request } = context
    return {
      // Is enough to pass just the accessToken
      accessToken: request.headers.get('x-nuki-api-key')
    }
  },
  onUpdateSession(context, auth) {
    // Can be empty as NUKI_API_KEY are long-living tokens
  }
})

export default server

OAuth 2.0

Go to Nuki Web and click on API on side navigation

Point OAuth2 Redirect URL to a Nhost serverless function:

https://local.functions.nhost.run/v1/callback/nuki

Use callback helper function to get session

Create a new Serverless Function: functions/callback/nuki.ts:

import { createNukiOAuthCallback } from '@dodi-smart/nuki-graphql-api'

const handler = (req, res) => {
  const {userId, accessToken, refreshToken} = createNukiOAuthCallback(req, res)
  await gqlSDK.updateUserNukiAuth({
    id: userId,
    accessToken,
    refreshToken
  })
}

export default handler

Configure the Nuki GraphQL API

import { createNukiGraphQLServer } from '@dodi-smart/nuki-graphql-api'

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

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

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

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

export default server

OAuth 2.0 + ApiKeyToken creation

This is very similar to previous option with the different that after first login, an ApiKeyToken is automatically created and can be used instead of refreshing user session. ApiKeyToken is long-living token and does not require refresh or anything.

Go to Nuki Web and click on API on side navigation

Point OAuth2 Redirect URL to a Nhost serverless function:

https://local.functions.nhost.run/v1/callback/nuki

(Optional) Enable key creation via env

Api key creation can be enabled via environment variable or via code. If you like to use env variable follow the steps below, skip the section and check how to enable it via the function code.

  • Add NUKI_API_KEY_CREATE as an environment variable.

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

NUKI_API_KEY_CREATE=true

Create API token using the callback

Create a new Serverless Function: functions/callback/nuki.ts:

import { createNukiOAuthCallback } from '@dodi-smart/nuki-graphql-api'

const handler = (req, res) => {
  const {userId, apiKeyToken} = createNukiOAuthCallback(req, res, true)
  await gqlSDK.updateUserNukiAuth({
    id: userId,
    apiKey: apiKeyToken
  })
}

export default handler

Configure the Nuki GraphQL API

import { createNukiGraphQLServer } from '@dodi-smart/nuki-graphql-api'

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

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

    return {
      // Example if nukiApiKey is saved in user's metadata, can differ
      accessToken: user.metadata.nukiApiKey,
    }
  },
  onUpdateSession(context, auth) {
    // Can be empty as API Keys are long-living tokens
  }
})

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 { createNukiGraphQLServer } from '@dodi-smart/nuki-graphql-api'

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

export default server

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

Here is an example of an isAllowed function:

import { createNukiGraphQLServer } from '@dodi-smart/nuki-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 = createNukiGraphQLServer({
  graphqlEndpoint: '/graphql/nuki',
  healthCheckEndpoint: '/graphql/nuki/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.

Package Sidebar

Install

npm i @dodi-smart/nuki-graphql-api

Homepage

dodi.li

Weekly Downloads

2

Version

1.2.0

License

MIT

Unpacked Size

626 kB

Total Files

831

Last publish

Collaborators

  • vokela