Added support for registering devices
This commit is contained in:
parent
7da098a070
commit
4f7a6202a4
@ -19,4 +19,9 @@ export interface DeviceRegistrationToken extends BaseClaims {
|
|||||||
type: "device_reg";
|
type: "device_reg";
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken;
|
export interface DeviceToken extends BaseClaims {
|
||||||
|
type: "device";
|
||||||
|
aud: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TokenClaims = AccessToken | RefreshToken | DeviceRegistrationToken | DeviceToken;
|
||||||
|
@ -4,10 +4,26 @@ import { serialize} from "serializr";
|
|||||||
import ApiError from "@common/ApiError";
|
import ApiError from "@common/ApiError";
|
||||||
import { ErrorCode } from "@common/ErrorCode";
|
import { ErrorCode } from "@common/ErrorCode";
|
||||||
import * as schema from "@common/sprinklersRpc/schema";
|
import * as schema from "@common/sprinklersRpc/schema";
|
||||||
import { AccessToken } from "@common/TokenClaims";
|
import { generateDeviceToken, verifyAuthorization } from "@server/express/authentication";
|
||||||
import { verifyAuthorization } from "@server/express/authentication";
|
|
||||||
import { ServerState } from "@server/state";
|
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) {
|
export function devices(state: ServerState) {
|
||||||
const router = PromiseRouter();
|
const router = PromiseRouter();
|
||||||
|
|
||||||
@ -28,7 +44,23 @@ export function devices(state: ServerState) {
|
|||||||
router.post("/register", verifyAuthorization({
|
router.post("/register", verifyAuthorization({
|
||||||
type: "device_reg",
|
type: "device_reg",
|
||||||
}), async (req, res) => {
|
}), async (req, res) => {
|
||||||
// TODO: Implement device registration
|
const deviceId = randomDeviceId();
|
||||||
|
const newDevice = state.database.sprinklersDevices.create({
|
||||||
|
name: "Sprinklers Device", deviceId,
|
||||||
|
});
|
||||||
|
await state.database.sprinklersDevices.save(newDevice);
|
||||||
|
const token = await generateDeviceToken(deviceId);
|
||||||
|
res.send({
|
||||||
|
data: newDevice, token,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post("/connect", verifyAuthorization({
|
||||||
|
type: "device",
|
||||||
|
}), async (req, res) => {
|
||||||
|
res.send({
|
||||||
|
url: state.mqttUrl,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
|
@ -16,6 +16,6 @@ export function mosquitto(state: ServerState) {
|
|||||||
router.post("/acl", async (req, res) => {
|
router.post("/acl", async (req, res) => {
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
});
|
});
|
||||||
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
TokenGrantRequest,
|
TokenGrantRequest,
|
||||||
TokenGrantResponse,
|
TokenGrantResponse,
|
||||||
} from "@common/httpApi";
|
} from "@common/httpApi";
|
||||||
import { AccessToken, DeviceRegistrationToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
|
import { AccessToken, DeviceRegistrationToken, DeviceToken, RefreshToken, TokenClaims } from "@common/TokenClaims";
|
||||||
import { User } from "../entities";
|
import { User } from "../entities";
|
||||||
import { ServerState } from "../state";
|
import { ServerState } from "../state";
|
||||||
|
|
||||||
@ -69,7 +69,8 @@ export function verifyToken<TClaims extends TokenClaims = TokenClaims>(
|
|||||||
} else {
|
} else {
|
||||||
const claims: TokenClaims = decoded as any;
|
const claims: TokenClaims = decoded as any;
|
||||||
if (type != null && claims.type !== type) {
|
if (type != null && claims.type !== type) {
|
||||||
reject(new ApiError(`Expected a "${type} token, received a "${claims.type}" token`));
|
reject(new ApiError(`Expected a "${type}" token, received a "${claims.type}" token`,
|
||||||
|
ErrorCode.BadToken));
|
||||||
}
|
}
|
||||||
resolve(claims as TClaims);
|
resolve(claims as TClaims);
|
||||||
}
|
}
|
||||||
@ -109,6 +110,15 @@ function generateDeviceRegistrationToken(secret: string): Promise<string> {
|
|||||||
return signToken(device_reg_token_claims);
|
return signToken(device_reg_token_claims);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function generateDeviceToken(deviceId: string): Promise<string> {
|
||||||
|
const device_token_claims: DeviceToken = {
|
||||||
|
iss: ISSUER,
|
||||||
|
type: "device",
|
||||||
|
aud: deviceId,
|
||||||
|
};
|
||||||
|
return signToken(device_token_claims);
|
||||||
|
}
|
||||||
|
|
||||||
export function authentication(state: ServerState) {
|
export function authentication(state: ServerState) {
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
@ -158,7 +168,7 @@ export function authentication(state: ServerState) {
|
|||||||
}
|
}
|
||||||
const [access_token, refresh_token] = await Promise.all(
|
const [access_token, refresh_token] = await Promise.all(
|
||||||
[await generateAccessToken(user, JWT_SECRET),
|
[await generateAccessToken(user, JWT_SECRET),
|
||||||
await generateRefreshToken(user, JWT_SECRET)]);
|
await generateRefreshToken(user, JWT_SECRET)]);
|
||||||
const response: TokenGrantResponse = {
|
const response: TokenGrantResponse = {
|
||||||
access_token, refresh_token,
|
access_token, refresh_token,
|
||||||
};
|
};
|
||||||
@ -188,7 +198,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>):
|
|||||||
const opts: VerifyAuthorizationOpts = {
|
const opts: VerifyAuthorizationOpts = {
|
||||||
type: "access",
|
type: "access",
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
const fun = async () => {
|
const fun = async () => {
|
||||||
const bearer = req.headers.authorization;
|
const bearer = req.headers.authorization;
|
||||||
@ -201,12 +211,7 @@ export function verifyAuthorization(options?: Partial<VerifyAuthorizationOpts>):
|
|||||||
}
|
}
|
||||||
const token = matches[1];
|
const token = matches[1];
|
||||||
|
|
||||||
req.token = await verifyToken<AccessToken>(token, "access");
|
req.token = await verifyToken(token, opts.type) as any;
|
||||||
|
|
||||||
if (req.token.type !== opts.type) {
|
|
||||||
throw new ApiError(`Invalid token type "${req.token.type}", must be "${opts.type}"`,
|
|
||||||
ErrorCode.BadToken);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
fun().then(() => next(null), (err) => next(err));
|
fun().then(() => next(null), (err) => next(err));
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import * as mqtt from "@common/sprinklersRpc/mqtt";
|
|||||||
import { Database } from "./Database";
|
import { Database } from "./Database";
|
||||||
|
|
||||||
export class ServerState {
|
export class ServerState {
|
||||||
|
mqttUrl: string;
|
||||||
mqttClient: mqtt.MqttRpcClient;
|
mqttClient: mqtt.MqttRpcClient;
|
||||||
database: Database;
|
database: Database;
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ export class ServerState {
|
|||||||
if (!mqttUrl) {
|
if (!mqttUrl) {
|
||||||
throw new Error("Must specify a MQTT_URL to connect to");
|
throw new Error("Must specify a MQTT_URL to connect to");
|
||||||
}
|
}
|
||||||
|
this.mqttUrl = mqttUrl;
|
||||||
this.mqttClient = new mqtt.MqttRpcClient(mqttUrl);
|
this.mqttClient = new mqtt.MqttRpcClient(mqttUrl);
|
||||||
this.database = new Database();
|
this.database = new Database();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user