@dodi-smart/nuki-graphql-api
v1.2.0
Published
Nuki GraphQL API
Downloads
4
Maintainers
Readme
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 validx-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 userquery
- the DocumentNode that was parsed from the GraphQL query stringoperationName
- the operation name selected from the incoming queryvariables
- the variables that were defined in the queryextensions
- the extensions that were received from the client
Read more about the default context from GraphQL Yoga.