fix: integrate with signature

This commit is contained in:
2025-12-04 02:09:33 +07:00
parent 5fe9b48f2d
commit 3ad93441b2
5 changed files with 32 additions and 47 deletions
+20
View File
@@ -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:
+6 -26
View File
@@ -1,42 +1,22 @@
import { verify } from '~/helpers/signature'
import { DeviceCommandBody } from '../schema'
import { sendCommandToThirdParty } from './command'
export default abstract class CommandService {
static async sendCommand(body: DeviceCommandBody, headers: Record<string, string | undefined>): Promise<{
static async sendCommand(body: DeviceCommandBody): Promise<{
status: 'success'
message: 'Command accepted'
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 {
const topicData = `${body.merchantName}/${body.floorName}_${body.unitNumber}-${body.deviceName}-${body.roomName}-${body.deviceType}-${body.commandType}-${body.towerNumber}`
await sendCommandToThirdParty(topicData, body.payload.action)
} catch {
throw new Error('internal_error')
}
return result
return {
status: 'success' as const,
message: 'Command accepted' as const,
receivedAt: new Date().toISOString(),
}
}
}
-13
View File
@@ -17,19 +17,6 @@ export default abstract class QueryService {
payload: string
}
} | 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
const topic = `${query.merchantName}/${query.floorName}_${query.unitNumber}-${query.deviceName}-${query.roomName}-${query.deviceType}-S-${query.towerNumber}`
+4 -4
View File
@@ -1,19 +1,19 @@
import { Elysia } from 'elysia'
import { deviceGuard } from '~/middlewares/deviceGuard'
import { deviceCommandBody, deviceCommandResponseSchema, deviceQueryParams, deviceQueryResponseSchema } from './schema'
import CommandService from './commands/service'
import QueryService from './queries/service'
import { deviceErrorResponseMap } from '~/helpers/errors'
import { apiKeyAuthMacro } from '~/middlewares/apiKeyAuth'
export const router = new Elysia({
name: 'modules.device',
detail: { tags: ['Device'] },
prefix: '/device/v1',
})
.guard(deviceGuard())
.post('/command', async ({ body, headers, set }) => {
.use(apiKeyAuthMacro)
.post('/command', async ({ body, set }) => {
try {
const result = await CommandService.sendCommand(body, headers)
const result = await CommandService.sendCommand(body)
return result
} catch (error) {
if (error instanceof Error && error.message in deviceErrorResponseMap) {
+2 -4
View File
@@ -10,12 +10,11 @@ export const deviceCommandBody = z.object({
deviceType: z.string(),
commandType: z.string(),
towerNumber: z.string(),
timestamp: z.string().datetime(), // ISO8601
timestamp: z.string(),
nonce: z.string(),
payload: z.object({
action: z.string(),
}),
signature: z.string(),
})
export type DeviceCommandBody = z.infer<typeof deviceCommandBody>
@@ -29,9 +28,8 @@ export const deviceQueryParams = z.object({
deviceType: z.string(),
commandType: z.string(),
towerNumber: z.string(),
timestamp: z.string().datetime(), // ISO8601
timestamp: z.string(),
nonce: z.string(),
signature: z.string(),
})
export type DeviceQueryParams = z.infer<typeof deviceQueryParams>