You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
127 lines
3.5 KiB
127 lines
3.5 KiB
import { Request } from "express"; |
|
import PromiseRouter from "express-promise-router"; |
|
import { serialize } from "serializr"; |
|
|
|
import ApiError from "@common/ApiError"; |
|
import { ErrorCode } from "@common/ErrorCode"; |
|
import * as schema from "@common/sprinklersRpc/schema"; |
|
import { DeviceToken } from "@common/TokenClaims"; |
|
import { generateDeviceToken } from "@server/authentication"; |
|
import { SprinklersDevice } from "@server/entities"; |
|
import { verifyAuthorization } from "@server/express/verifyAuthorization"; |
|
import { ServerState } from "@server/state"; |
|
|
|
const DEVICE_ID_LEN = 20; |
|
|
|
function randomDeviceId(): string { |
|
let deviceId = ""; |
|
for (let i = 0; i < DEVICE_ID_LEN; i++) { |
|
const j = Math.floor(Math.random() * 36); |
|
let ch; // tslint:disable-next-line |
|
if (j < 10) { |
|
// 0-9 |
|
ch = String.fromCharCode(48 + j); |
|
} else { |
|
// a-z |
|
ch = String.fromCharCode(97 + (j - 10)); |
|
} |
|
deviceId += ch; |
|
} |
|
return deviceId; |
|
} |
|
|
|
export function devices(state: ServerState) { |
|
const router = PromiseRouter(); |
|
|
|
async function verifyUserDevice(req: Request): Promise<SprinklersDevice> { |
|
const token = req.token!; |
|
const userId = token.aud; |
|
const deviceId = req.params.deviceId; |
|
const userDevice = await state.database.sprinklersDevices.findUserDevice( |
|
userId, |
|
deviceId |
|
); |
|
if (!userDevice) { |
|
throw new ApiError( |
|
"User does not have access to the specified device", |
|
ErrorCode.NoPermission |
|
); |
|
} |
|
return userDevice; |
|
} |
|
|
|
router.get("/:deviceId", verifyAuthorization(), async (req, res) => { |
|
const deviceInfo = await verifyUserDevice(req); |
|
res.send({ |
|
id: deviceInfo.id, deviceId: deviceInfo.deviceId, name: deviceInfo.name |
|
}) |
|
}); |
|
|
|
router.get("/:deviceId/data", verifyAuthorization(), async (req, res) => { |
|
await verifyUserDevice(req); |
|
const device = state.mqttClient.acquireDevice(req.params.deviceId); |
|
const j = serialize(schema.sprinklersDevice, device); |
|
res.send(j); |
|
device.release(); |
|
}); |
|
|
|
router.post("/:deviceId/generate_token", |
|
verifyAuthorization(), async (req, res) => { |
|
const device = await verifyUserDevice(req); |
|
if (!device.deviceId) { |
|
throw new ApiError( |
|
"A token cannot be granted for a device with no id", |
|
ErrorCode.BadRequest, |
|
) |
|
} |
|
const token = await generateDeviceToken(device.id, device.deviceId); |
|
res.send({ |
|
token, |
|
}); |
|
}); |
|
|
|
router.post( |
|
"/register", |
|
verifyAuthorization({ |
|
type: "device_reg" |
|
}), |
|
async (req, res) => { |
|
const deviceId = randomDeviceId(); |
|
const newDevice = state.database.sprinklersDevices.create({ |
|
name: "Sprinklers Device", |
|
deviceId |
|
}); |
|
await state.database.sprinklersDevices.save(newDevice); |
|
const token = await generateDeviceToken(newDevice.id, deviceId); |
|
res.send({ |
|
data: newDevice, |
|
token |
|
}); |
|
} |
|
); |
|
|
|
router.post( |
|
"/connect", |
|
verifyAuthorization({ |
|
type: "device" |
|
}), |
|
async (req, res) => { |
|
const token: DeviceToken = req.token! as any; |
|
const deviceId = token.aud; |
|
const devs = await state.database.sprinklersDevices.count({ |
|
deviceId |
|
}); |
|
if (devs === 0) { |
|
throw new ApiError("deviceId not found", ErrorCode.NotFound); |
|
} |
|
const clientId = `device-${deviceId}`; |
|
res.send({ |
|
mqttUrl: state.mqttUrl, |
|
deviceId, |
|
clientId, |
|
}); |
|
} |
|
); |
|
|
|
return router; |
|
}
|
|
|