Skip to content

Commit

Permalink
feat: add API to delete shared device
Browse files Browse the repository at this point in the history
  • Loading branch information
coderbyheart committed Dec 5, 2024
1 parent dfed1a8 commit 8cd72b4
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 45 deletions.
1 change: 1 addition & 0 deletions cdk/BackendStack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ export class BackendStack extends Stack {
'PUT /user/device/{id}/sharing',
shareAPI.extendDeviceSharingFn,
)
api.addRoute('DELETE /user/device/{id}', shareAPI.stopDeviceSharingFn)

const devicesAPI = new DevicesAPI(this, {
baseLayer,
Expand Down
2 changes: 2 additions & 0 deletions cdk/packBackendLambdas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export type BackendLambdas = {
createDevice: PackedLambda
listUserDevices: PackedLambda
extendDeviceSharing: PackedLambda
stopDeviceSharing: PackedLambda
openSSL: PackedLambda
apiHealthCheck: PackedLambda
createCNAMERecord: PackedLambda
Expand All @@ -30,6 +31,7 @@ export const packBackendLambdas = async (): Promise<BackendLambdas> => ({
createDevice: await pack('createDevice'),
listUserDevices: await pack('listUserDevices'),
extendDeviceSharing: await pack('extendDeviceSharing'),
stopDeviceSharing: await pack('stopDeviceSharing'),
openSSL: await pack('openSSL'),
apiHealthCheck: await pack('apiHealthCheck'),
createCNAMERecord: await packLambdaFromPath({
Expand Down
19 changes: 19 additions & 0 deletions cdk/resources/ShareAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export class ShareAPI extends Construct {
public readonly sharingStatusFingerprintFn: Lambda.IFunction
public readonly listUserDevicesFn: Lambda.IFunction
public readonly extendDeviceSharingFn: Lambda.IFunction
public readonly stopDeviceSharingFn: Lambda.IFunction
constructor(
parent: Construct,
{
Expand All @@ -30,6 +31,7 @@ export class ShareAPI extends Construct {
| 'deviceJwt'
| 'listUserDevices'
| 'extendDeviceSharing'
| 'stopDeviceSharing'
>
},
) {
Expand Down Expand Up @@ -132,5 +134,22 @@ export class ShareAPI extends Construct {
publicDevices.publicDevicesTable.grantReadWriteData(
this.extendDeviceSharingFn,
)

this.stopDeviceSharingFn = new PackedLambdaFn(
this,
'stopDeviceSharingFn',
lambdaSources.stopDeviceSharing,
{
description: 'Stop sharing a device',
layers: [baseLayer],
environment: {
PUBLIC_DEVICES_TABLE_NAME: publicDevices.publicDevicesTable.tableName,
PUBLIC_DEVICES_ID_INDEX_NAME: publicDevices.publicDevicesTableIdIndex,
},
},
).fn
publicDevices.publicDevicesTable.grantReadWriteData(
this.stopDeviceSharingFn,
)
}
}
48 changes: 48 additions & 0 deletions features/StopSharing.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
exampleContext:
API: https://api.nordicsemi.world/2024-04-15
jwt: eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6Ijg1NDdiNWIyLTdiNDctNDFlNC1iZjJkLTdjZGZmNDhiM2VhNCJ9.eyJAY29udGV4dCI6Imh0dHBzOi8vZ2l0aHViLmNvbS9oZWxsby1ucmZjbG91ZC9wcm90by1tYXAvdXNlci1qd3QiLCJlbWFpbCI6ImVkYjJiZDM3QGV4YW1wbGUuY29tIiwiaWF0IjoxNzIyODcxNTYyLCJleHAiOjE3MjI5NTc5NjIsImF1ZCI6ImhlbGxvLm5yZmNsb3VkLmNvbSJ9.ALiHjxR7HIjuYQBvPVh5-GMs-2f-pMGs_FTz-x0HGzQ4amLASeUGEZ7X_y-_mgZpYu8VKGm6be0LtIIx9DgYBff1ASfmQH327rub0a2-DjXW-JUJQn_6t6H6_JhvPZ9jWBSzy3Tbpp9NmTUNmHgEwzyoctnmgp0oo26VEwc4r6YGQWkZ
publicDeviceId: outfling-swanherd-attaghan
oldExpiry: "2024-08-13T08:56:51.280Z"
needs:
- Extend sharing
---

# Stop sharing

> Users can stop sharing their device.
## List devices

Given the `Authorization` header of the next request is `Bearer ${jwt}`

When I `GET` `${API}/user/devices`

Then I store `$.devices[0].id` of the last response into `publicDeviceId`

## Extend sharing

Given the `Authorization` header of the next request is `Bearer ${jwt}`

When I `DELETE` to `${API}/user/device/${publicDeviceId}`

Then the status code of the last response should be `202`

## List devices again

> The device should have been deleted
Given the `Authorization` header of the next request is `Bearer ${jwt}`

When I `GET` `${API}/user/devices`

Then the status code of the last response should be `200`

And I should receive a
`https://github.com/hello-nrfcloud/proto-map/user-devices` response

And `{"len": $count($.devices)}` of the last response should match

```json
{ "len": 0 }
```
87 changes: 87 additions & 0 deletions lambda/stopDeviceSharing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { DeleteItemCommand, DynamoDBClient } from '@aws-sdk/client-dynamodb'
import { SSMClient } from '@aws-sdk/client-ssm'
import { marshall } from '@aws-sdk/util-dynamodb'
import { fromEnv } from '@bifravst/from-env'
import { addVersionHeader } from '@hello.nrfcloud.com/lambda-helpers/addVersionHeader'
import { aResponse } from '@hello.nrfcloud.com/lambda-helpers/aResponse'
import { corsOPTIONS } from '@hello.nrfcloud.com/lambda-helpers/corsOPTIONS'
import {
ProblemDetailError,
problemResponse,
} from '@hello.nrfcloud.com/lambda-helpers/problemResponse'
import { requestLogger } from '@hello.nrfcloud.com/lambda-helpers/requestLogger'
import {
validateInput,
type ValidInput,
} from '@hello.nrfcloud.com/lambda-helpers/validateInput'
import { PublicDeviceId } from '@hello.nrfcloud.com/proto-map/api'
import middy from '@middy/core'
import { Type } from '@sinclair/typebox'
import type {
APIGatewayProxyEventV2,
APIGatewayProxyResultV2,
Context as LambdaContext,
} from 'aws-lambda'
import { STACK_NAME } from '../cdk/stackConfig.js'
import { getDeviceId } from '../devices/getDeviceId.js'
import { verifyUserToken } from '../jwt/verifyUserToken.js'
import { getSettings } from '../settings/jwt.js'
import { withUser, type WithUser } from './middleware/withUser.js'

const { stackName, TableName, idIndex, version } = fromEnv({
stackName: 'STACK_NAME',
TableName: 'PUBLIC_DEVICES_TABLE_NAME',
idIndex: 'PUBLIC_DEVICES_ID_INDEX_NAME',
version: 'VERSION',
})({
STACK_NAME,
...process.env,
})
const db = new DynamoDBClient({})
const byId = getDeviceId({ db, TableName, idIndex })
const jwtSettings = await getSettings({ ssm: new SSMClient({}), stackName })

const InputSchema = Type.Object({
id: PublicDeviceId,
})

/**
* Stop sharing a device
*/
const h = async (
event: APIGatewayProxyEventV2,
context: ValidInput<typeof InputSchema> & WithUser & LambdaContext,
): Promise<APIGatewayProxyResultV2> => {
const maybeDevice = await byId(context.validInput.id)

if ('error' in maybeDevice) {
throw new ProblemDetailError({
title: `Device ${context.validInput.id} not shared: ${maybeDevice.error}`,
status: 404,
})
}

await db.send(
new DeleteItemCommand({
TableName,
Key: marshall({ deviceId: maybeDevice.deviceId }),
}),
)

return aResponse(202)
}

export const handler = middy()
.use(addVersionHeader(version))
.use(corsOPTIONS('DELETE'))
.use(requestLogger())
.use(validateInput(InputSchema))
.use(
withUser({
verify: verifyUserToken(
new Map([[jwtSettings.keyId, jwtSettings.publicKey]]),
),
}),
)
.use(problemResponse())
.handler(h)
76 changes: 38 additions & 38 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 8cd72b4

Please sign in to comment.