fix: integrate with signature
This commit is contained in:
@@ -0,0 +1,20 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
service:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- "9000:9000"
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
@@ -1,42 +1,22 @@
|
|||||||
import { verify } from '~/helpers/signature'
|
|
||||||
import { DeviceCommandBody } from '../schema'
|
import { DeviceCommandBody } from '../schema'
|
||||||
import { sendCommandToThirdParty } from './command'
|
import { sendCommandToThirdParty } from './command'
|
||||||
|
|
||||||
export default abstract class CommandService {
|
export default abstract class CommandService {
|
||||||
static async sendCommand(body: DeviceCommandBody, headers: Record<string, string | undefined>): Promise<{
|
static async sendCommand(body: DeviceCommandBody): Promise<{
|
||||||
status: 'success'
|
status: 'success'
|
||||||
message: 'Command accepted'
|
message: 'Command accepted'
|
||||||
receivedAt: string
|
receivedAt: string
|
||||||
}> {
|
}> {
|
||||||
const apiKey = headers['x-api-key']
|
|
||||||
|
|
||||||
// For now, use apiKey as secretKey since DB validation is skipped
|
|
||||||
const secretKey = apiKey
|
|
||||||
|
|
||||||
// Remove signature from body for verification
|
|
||||||
const { signature, ...bodyWithoutSignature } = body
|
|
||||||
|
|
||||||
const isValid = verify(bodyWithoutSignature, signature, secretKey)
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error('invalid_signature')
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check nonce for replay attack when DB is implemented
|
|
||||||
|
|
||||||
const result = {
|
|
||||||
status: 'success' as const,
|
|
||||||
message: 'Command accepted' as const,
|
|
||||||
receivedAt: new Date().toISOString(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate topicData and send to third-party service
|
|
||||||
try {
|
try {
|
||||||
const topicData = `${body.merchantName}/${body.floorName}_${body.unitNumber}-${body.deviceName}-${body.roomName}-${body.deviceType}-${body.commandType}-${body.towerNumber}`
|
const topicData = `${body.merchantName}/${body.floorName}_${body.unitNumber}-${body.deviceName}-${body.roomName}-${body.deviceType}-${body.commandType}-${body.towerNumber}`
|
||||||
await sendCommandToThirdParty(topicData, body.payload.action)
|
await sendCommandToThirdParty(topicData, body.payload.action)
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error('internal_error')
|
throw new Error('internal_error')
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
return result
|
status: 'success' as const,
|
||||||
|
message: 'Command accepted' as const,
|
||||||
|
receivedAt: new Date().toISOString(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,19 +17,6 @@ export default abstract class QueryService {
|
|||||||
payload: string
|
payload: string
|
||||||
}
|
}
|
||||||
} | null> {
|
} | null> {
|
||||||
const apiKey = headers['x-api-key']
|
|
||||||
|
|
||||||
// For now, use apiKey as secretKey since DB validation is skipped
|
|
||||||
const secretKey = apiKey
|
|
||||||
|
|
||||||
// Remove signature from query for verification
|
|
||||||
const { signature, ...queryWithoutSignature } = query
|
|
||||||
|
|
||||||
const isValid = verify(queryWithoutSignature, signature, secretKey)
|
|
||||||
if (!isValid) {
|
|
||||||
throw new Error('invalid_signature')
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check nonce for replay attack when DB is implemented
|
// TODO: Check nonce for replay attack when DB is implemented
|
||||||
|
|
||||||
const topic = `${query.merchantName}/${query.floorName}_${query.unitNumber}-${query.deviceName}-${query.roomName}-${query.deviceType}-S-${query.towerNumber}`
|
const topic = `${query.merchantName}/${query.floorName}_${query.unitNumber}-${query.deviceName}-${query.roomName}-${query.deviceType}-S-${query.towerNumber}`
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import { Elysia } from 'elysia'
|
import { Elysia } from 'elysia'
|
||||||
import { deviceGuard } from '~/middlewares/deviceGuard'
|
|
||||||
import { deviceCommandBody, deviceCommandResponseSchema, deviceQueryParams, deviceQueryResponseSchema } from './schema'
|
import { deviceCommandBody, deviceCommandResponseSchema, deviceQueryParams, deviceQueryResponseSchema } from './schema'
|
||||||
import CommandService from './commands/service'
|
import CommandService from './commands/service'
|
||||||
import QueryService from './queries/service'
|
import QueryService from './queries/service'
|
||||||
import { deviceErrorResponseMap } from '~/helpers/errors'
|
import { deviceErrorResponseMap } from '~/helpers/errors'
|
||||||
|
import { apiKeyAuthMacro } from '~/middlewares/apiKeyAuth'
|
||||||
|
|
||||||
export const router = new Elysia({
|
export const router = new Elysia({
|
||||||
name: 'modules.device',
|
name: 'modules.device',
|
||||||
detail: { tags: ['Device'] },
|
detail: { tags: ['Device'] },
|
||||||
prefix: '/device/v1',
|
prefix: '/device/v1',
|
||||||
})
|
})
|
||||||
.guard(deviceGuard())
|
.use(apiKeyAuthMacro)
|
||||||
.post('/command', async ({ body, headers, set }) => {
|
.post('/command', async ({ body, set }) => {
|
||||||
try {
|
try {
|
||||||
const result = await CommandService.sendCommand(body, headers)
|
const result = await CommandService.sendCommand(body)
|
||||||
return result
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.message in deviceErrorResponseMap) {
|
if (error instanceof Error && error.message in deviceErrorResponseMap) {
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ export const deviceCommandBody = z.object({
|
|||||||
deviceType: z.string(),
|
deviceType: z.string(),
|
||||||
commandType: z.string(),
|
commandType: z.string(),
|
||||||
towerNumber: z.string(),
|
towerNumber: z.string(),
|
||||||
timestamp: z.string().datetime(), // ISO8601
|
timestamp: z.string(),
|
||||||
nonce: z.string(),
|
nonce: z.string(),
|
||||||
payload: z.object({
|
payload: z.object({
|
||||||
action: z.string(),
|
action: z.string(),
|
||||||
}),
|
}),
|
||||||
signature: z.string(),
|
|
||||||
})
|
})
|
||||||
export type DeviceCommandBody = z.infer<typeof deviceCommandBody>
|
export type DeviceCommandBody = z.infer<typeof deviceCommandBody>
|
||||||
|
|
||||||
@@ -29,9 +28,8 @@ export const deviceQueryParams = z.object({
|
|||||||
deviceType: z.string(),
|
deviceType: z.string(),
|
||||||
commandType: z.string(),
|
commandType: z.string(),
|
||||||
towerNumber: z.string(),
|
towerNumber: z.string(),
|
||||||
timestamp: z.string().datetime(), // ISO8601
|
timestamp: z.string(),
|
||||||
nonce: z.string(),
|
nonce: z.string(),
|
||||||
signature: z.string(),
|
|
||||||
})
|
})
|
||||||
export type DeviceQueryParams = z.infer<typeof deviceQueryParams>
|
export type DeviceQueryParams = z.infer<typeof deviceQueryParams>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user