diff --git a/src/controller/AssetConfigController.ts b/src/controller/AssetConfigController.ts index bdd2899..aea72a5 100644 --- a/src/controller/AssetConfigController.ts +++ b/src/controller/AssetConfigController.ts @@ -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) => { @@ -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", { diff --git a/src/controller/CameraController.ts b/src/controller/CameraController.ts index edefc72..4c41680 100644 --- a/src/controller/CameraController.ts +++ b/src/controller/CameraController.ts @@ -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: diff --git a/src/controller/ObservationController.ts b/src/controller/ObservationController.ts index d1e6841..7174b91 100644 --- a/src/controller/ObservationController.ts +++ b/src/controller/ObservationController.ts @@ -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; diff --git a/src/controller/StreamAuthApiController.ts b/src/controller/StreamAuthApiController.ts index be860cf..ca7118c 100644 --- a/src/controller/StreamAuthApiController.ts +++ b/src/controller/StreamAuthApiController.ts @@ -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) { @@ -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) { @@ -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 }); } diff --git a/src/lib/jose.ts b/src/lib/jose.ts index 23471eb..1fc91ae 100644 --- a/src/lib/jose.ts +++ b/src/lib/jose.ts @@ -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. @@ -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. @@ -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} 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} The generated JWK pair. @@ -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} 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. @@ -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} 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. diff --git a/src/router/cameraRouter.ts b/src/router/cameraRouter.ts index 8cdff3c..9e7b49f 100644 --- a/src/router/cameraRouter.ts +++ b/src/router/cameraRouter.ts @@ -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 }; diff --git a/src/router/observationRouter.ts b/src/router/observationRouter.ts index 66403e6..8f154a5 100644 --- a/src/router/observationRouter.ts +++ b/src/router/observationRouter.ts @@ -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), @@ -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 }; diff --git a/src/utils/configs.ts b/src/utils/configs.ts index 83ad83b..65ddb4d 100644 --- a/src/utils/configs.ts +++ b/src/utils/configs.ts @@ -1,3 +1,4 @@ +import crypto from "crypto"; import * as dotenv from "dotenv"; dotenv.config(); @@ -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