Skip to content

Commit

Permalink
Merge pull request #143 from ohcnetwork/develop
Browse files Browse the repository at this point in the history
Production release v24.39.0
  • Loading branch information
gigincg authored Sep 23, 2024
2 parents c32a8df + 0888ff4 commit b8649e6
Show file tree
Hide file tree
Showing 9 changed files with 112 additions and 99 deletions.
4 changes: 2 additions & 2 deletions src/controller/AssetConfigController.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import dayjs from "dayjs";
import { Request, Response } from "express";

import prisma from "@/lib/prisma";
import { retrieveAssetConfig } from "@/cron/retrieveAssetConfig";
import prisma from "@/lib/prisma";

export class AssetConfigController {
static listAssets = async (req: Request, res: Response) => {
Expand Down Expand Up @@ -40,7 +40,7 @@ export class AssetConfigController {
static refreshAssets = async (req: Request, res: Response) => {
await retrieveAssetConfig();
res.redirect("/assets");
}
};

static createAssetForm = async (req: Request, res: Response) => {
res.render("pages/assets/form", {
Expand Down
18 changes: 0 additions & 18 deletions src/controller/CameraController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,24 +353,6 @@ export class CameraController {
});
});

/**
* @swagger
* /get_time:
* get:
* summary: "Get current time"
* description: ""
* tags:
* - BPL
* responses:
* "200":
* description: Return current time
*/
static getTime = catchAsync(async (req: Request, res: Response) => {
res.send({
time: new Date().toISOString(),
});
});

/**
* @swagger
* /preset:
Expand Down
29 changes: 0 additions & 29 deletions src/controller/ObservationController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,35 +175,6 @@ export class ObservationController {

static latestObservation = new ObservationsMap();

static getObservations(req: Request, res: Response) {
const limit = req.query?.limit || DEFAULT_LISTING_LIMIT;
const ip = req.query?.ip;

if (!ip) {
return res.json(staticObservations);
}
// console.log("Filtering");
const filtered = staticObservations.filter(
(observation) => observation.device_id === ip,
);
// Sort the observation by last updated time.
// .sort(
// (a, b) => new Date(a.lastObservationAt) - new Date(b.lastObservationAt)
// )
// // Limit the results
// .slice(0, limit);

return res.json(filtered ?? []);
}

static getLogData(req: Request, res: Response) {
return res.json(logData);
}

static getLastRequestData(req: Request, res: Response) {
return res.json(lastRequestData);
}

static updateObservations = (req: Request, res: Response) => {
// database logic
lastRequestData = req.body;
Expand Down
42 changes: 13 additions & 29 deletions src/controller/StreamAuthApiController.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { Request, Response } from "express";

import { generateJWT, verifyJWT } from "@/lib/jose";
import { generateJWTWithKey, verifyJWTWithKey } from "@/lib/jose";

export class StreamAuthApiController {
static getVideoFeedStreamToken = async (req: Request, res: Response) => {
const { stream, ip, _duration } = req.body;
if (!stream || !ip) {
return res.status(400).json({ message: "stream and ip are required" });
const { stream_id } = req.body;
if (!stream_id) {
return res.status(400).json({ message: "stream_id is required" });
}

try {
const duration = parseInt(_duration ?? "5");
if (duration < 0 || duration > 60) {
return res
.status(400)
.json({ message: "duration must be between 0 and 60" });
}

const token = await generateJWT({ stream, ip }, `${duration}m`);
const token = await generateJWTWithKey({ stream:stream_id }, "60s");

return res.status(200).json({ token });
} catch (error: any) {
Expand All @@ -26,20 +19,13 @@ export class StreamAuthApiController {
};

static getVitalStreamToken = async (req: Request, res: Response) => {
const { asset_id, ip, _duration } = req.body;
const { asset_id, ip } = req.body;
if (!asset_id || !ip) {
return res.status(400).json({ message: "asset_id and ip are required" });
}

try {
const duration = parseInt(_duration ?? "5");
if (duration < 0 || duration > 60) {
return res
.status(400)
.json({ message: "duration must be between 0 and 60" });
}

const token = await generateJWT({ asset_id, ip }, `${duration}m`);
const token = await generateJWTWithKey({ asset_id, ip }, "60s");

return res.status(200).json({ token });
} catch (error: any) {
Expand All @@ -49,19 +35,17 @@ export class StreamAuthApiController {

static validateStreamToken = async (req: Request, res: Response) => {
const { token, ip, stream } = req.body;
if (!token || !ip || !stream) {
return res
.status(400)
.json({ message: "token, stream, and ip are required" });
if (!token || !stream) {
return res.status(400).json({ message: "token and stream are required" });
}

try {
const decoded = await verifyJWT(token);
if (decoded.ip === ip || decoded.stream === stream) {
return res.status(200).json({ status: 1 });
const decoded = await verifyJWTWithKey(token);
if (decoded.stream === stream) {
return res.status(200).json({ status: "1" });
}

return res.status(401).json({ status: 0 });
return res.status(401).json({ status: "0" });
} catch (error: any) {
return res.status(500).json({ message: error.message });
}
Expand Down
15 changes: 10 additions & 5 deletions src/cron/automatedDailyRounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -274,12 +274,17 @@ export function getVitalsFromObservationsForAccuracy(
.reduce((acc, curr) => {
return [...acc, ...curr.data];
}, [] as Observation[][])
.find(
(observation) =>
.find((observation) => {
const observationTime = new Date(observation[0]["date-time"]);
observationTime.setMilliseconds(0);
const imageTime = new Date(time);
imageTime.setMilliseconds(0);

return (
observation[0].device_id === deviceId &&
new Date(observation[0]["date-time"]).toISOString() ===
new Date(time).toISOString(),
);
observationTime.toISOString() === imageTime.toISOString()
);
});

if (!observations) {
return null;
Expand Down
66 changes: 65 additions & 1 deletion src/lib/jose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as jose from "jose";
import { v4 as uuidv4 } from "uuid";

import { randomString } from "@/lib/crypto";
import { careApi } from "@/utils/configs";
import { careApi, jwtSecret } from "@/utils/configs";

/**
* Default JSON Web Key (JWK) configuration.
Expand All @@ -29,6 +29,11 @@ let JWKs: jose.JSONWebKeySet | null = null;
*/
let careJWKs: jose.JSONWebKeySet | null = null;

/**
* JWT secret used to sign JWTs.
*/
let jwtSecretKey: Uint8Array | null = null;

/**
* Retrieves the private key used for encryption.
* If the private key is not already loaded, it attempts to load it from the "keys.json" file.
Expand Down Expand Up @@ -106,6 +111,19 @@ export async function getCareJWKs() {
return careJWKs;
}

/**
* Retrieves the JWT secret used to sign JWTs.
* If the JWT secret is not already loaded, it uses the value from the environment
* variable "
* @returns {Promise<Uint8Array>} The JWT secret used to sign JWTs.
*/
export async function getJWTSecret() {
if (!jwtSecretKey) {
jwtSecretKey = jwtSecret;
}
return jwtSecretKey;
}

/**
* Generates a new JSON Web Key (JWK) pair.
* @returns {Promise<jose.JWK>} The generated JWK pair.
Expand Down Expand Up @@ -151,6 +169,33 @@ export async function generateJWT(
.sign(privateKey);
}

/**
* Generates a JSON Web Token (JWT) with the provided claims and expiration time using the provided key.
* @param claims The payload of the JWT.
* @param expiresIn The expiration time of the JWT. Defaults to "2m" (2 minutes).
* @returns {Promise<string>} The generated JWT.
*/
export async function generateJWTWithKey(
claims: jose.JWTPayload,
expiresIn: string = "2m",
) {
const claimsWithDefaults: jose.JWTPayload = {
// iss: "teleicu-middleware",
// aud: "care",
jti: randomString(20),
...claims,
};

return new jose.SignJWT(claimsWithDefaults)
.setProtectedHeader({
alg: "HS256",
typ: "jwt",
})
.setIssuedAt()
.setExpirationTime(expiresIn)
.sign(await getJWTSecret());
}

/**
* Represents a function that validates a JSON Web Token (JWT).
* @param jwt The JWT to be validated.
Expand Down Expand Up @@ -182,6 +227,25 @@ export async function verifyJWT(
return payload;
}

/**
* Verifies a JSON Web Token (JWT) generated by this server using the provided options.
* @param jwt The JWT to verify.
* @param options The options for JWT verification.
* @returns {Promise<jose.JWTPayload>} The payload of the JWT.
*/
export async function verifyJWTWithKey(
jwt: string,
options: jose.JWTVerifyOptions = {},
) {
const { payload } = await jose.jwtVerify(jwt, await getJWTSecret(), {
// issuer: "teleicu-middleware",
// audience: "care",
...options,
});

return payload;
}

/**
* Verifies a JSON Web Token (JWT) generated by care backend using the provided options.
* @param jwt The JWT to verify.
Expand Down
17 changes: 12 additions & 5 deletions src/router/cameraRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,57 @@ import {
setPresetValidators,
} from "@/Validators/cameraValidators";
import { CameraController } from "@/controller/CameraController";
import { careJwtAuth } from "@/middleware/auth";
import { validate } from "@/middleware/validate";

const router = express.Router();

router.get(
"/presets",
careJwtAuth(),
validate(baseGetCameraParamsValidators),
CameraController.getPresets,
);

router.post(
"/presets",
careJwtAuth(),
validate(setPresetValidators),
CameraController.setPreset,
);

router.get(
"/status",
careJwtAuth(),
validate(baseGetCameraParamsValidators),
CameraController.getStatus,
);

router.post("/cameras/status", CameraController.getCameraStatuses);
router.post(
"/cameras/status",
careJwtAuth(),
CameraController.getCameraStatuses,
);

router.post(
"/gotoPreset",
careJwtAuth(),
validate(gotoPresetValidator),
CameraController.gotoPreset,
);

router.post(
"/absoluteMove",
careJwtAuth(),
validate(camMoveValidator),
CameraController.absoluteMove,
);

router.post(
"/relativeMove",
careJwtAuth(),
validate(camMoveValidator),
CameraController.relativeMove,
);

// BPL Integration

router.get("/get_time", CameraController.getTime);

export { router as cameraRouter };
15 changes: 5 additions & 10 deletions src/router/observationRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import {
vitalsValidator,
} from "@/Validators/observationValidators";
import { ObservationController } from "@/controller/ObservationController";
import { careJwtAuth } from "@/middleware/auth";
import { validate } from "@/middleware/validate";

const router = express.Router();

router.get("/get_observations", ObservationController.getObservations);

// blocked on nginx
router.post(
"/update_observations",
validate(observationsValidators),
Expand All @@ -19,18 +19,13 @@ router.post(

router.get(
"/vitals",
careJwtAuth(),
validate(vitalsValidator),
ObservationController.getLatestVitals,
);

router.get("/get_time", ObservationController.getTime);

router.get("/devices/status", ObservationController.status);
router.get("/devices/status", careJwtAuth(), ObservationController.status);

// Debugging Endpoints

router.get("/get_log_data", ObservationController.getLogData);

router.get("/get_last_request_data", ObservationController.getLastRequestData);
router.get("/get_time", ObservationController.getTime);

export { router as observationRouter };
5 changes: 5 additions & 0 deletions src/utils/configs.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import crypto from "crypto";
import * as dotenv from "dotenv";

dotenv.config();
Expand All @@ -9,6 +10,10 @@ export const facilityID =
process.env.FACILITY_ID ?? "00000000-0000-0000-0000-000000000000";
export const careApi = process.env.CARE_API ?? "http://localhost:9000";

export const jwtSecret = new TextEncoder().encode(
process.env.JWT_SECRET ?? crypto.randomBytes(32).toString("hex"),
);

export const adminUsername = process.env.USERNAME ?? "admin";
export const adminPassword = process.env.PASSWORD + facilityID; // good luck brute-forcing this

Expand Down

0 comments on commit b8649e6

Please sign in to comment.