Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup unused routes and convert auth response to string #142

Merged
merged 1 commit into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading