Files
oneuptime/Common/Server/Utils/Express.ts

200 lines
5.5 KiB
TypeScript

import logger from "./Logger";
import Dictionary from "../../Types/Dictionary";
import GenericFunction from "../../Types/GenericFunction";
import { JSONObject, JSONObjectOrArray } from "../../Types/JSON";
import JSONWebTokenData from "../../Types/JsonWebTokenData";
import ObjectID from "../../Types/ObjectID";
import {
UserGlobalAccessPermission,
UserTenantAccessPermission,
} from "../../Types/Permission";
import Port from "../../Types/Port";
import UserType from "../../Types/UserType";
import "ejs";
import express from "express";
import { Server, createServer } from "http";
export type RequestHandler = express.RequestHandler;
export type NextFunction = express.NextFunction;
export const ExpressStatic: GenericFunction = express.static;
export const ExpressJson: GenericFunction = express.json;
export const ExpressUrlEncoded: GenericFunction = express.urlencoded;
export const ExpressRaw: GenericFunction = express.raw;
export type ProbeRequest = {
id: ObjectID;
};
export type ExpressRequest = express.Request;
export type ExpressResponse = express.Response;
export type ExpressApplication = express.Application;
export type ExpressRouter = express.Router;
export interface OneUptimeRequest extends express.Request {
bearerTokenData?: JSONObject | string | undefined; // if bearer token is passed then this is populated.
probe?: ProbeRequest;
userType?: UserType;
userAuthorization?: JSONWebTokenData;
tenantId?: ObjectID;
userGlobalAccessPermission?: UserGlobalAccessPermission;
userTenantAccessPermission?: Dictionary<UserTenantAccessPermission>; // tenantId <-> UserTenantAccessPermission;
rawFormUrlEncodedBody?: string;
rawBody?: string; // Raw body for signature verification (JSON or URL-encoded)
}
export interface OneUptimeResponse extends express.Response {
logBody: JSONObjectOrArray;
}
export type RequestDeviceInfo = {
deviceName?: string;
deviceType?: string;
deviceOS?: string;
deviceBrowser?: string;
};
type HeaderValue = string | Array<string> | null | undefined;
class Express {
private static app: express.Application;
private static httpServer: Server;
public static getRouter(): express.Router {
return express.Router();
}
public static setupExpress(): void {
this.app = express();
}
public static getHttpServer(): Server {
return this.httpServer;
}
public static getExpressApp(): express.Application {
if (!this.app) {
this.setupExpress();
}
return this.app;
}
public static async launchApplication(
appName: string,
port?: Port,
): Promise<express.Application> {
if (!this.app) {
this.setupExpress();
}
if (!this.httpServer) {
this.httpServer = createServer(this.app);
}
type ResolveFunction = (app: express.Application) => void;
return new Promise<express.Application>((resolve: ResolveFunction) => {
this.httpServer.listen(port?.toNumber() || this.app.get("port"), () => {
logger.debug(
`${appName} server started on port: ${port?.toNumber() || this.app.get("port")}`,
);
return resolve(this.app);
});
});
}
}
export default Express;
export const headerValueToString: (value: HeaderValue) => string | undefined = (
value: HeaderValue,
): string | undefined => {
if (Array.isArray(value)) {
return value.length > 0 ? value[0] : undefined;
}
if (typeof value === "string" && value.trim().length > 0) {
return value.trim();
}
return undefined;
};
export const extractDeviceInfo: (req: ExpressRequest) => RequestDeviceInfo = (
req: ExpressRequest,
): RequestDeviceInfo => {
const body: JSONObject = (req.body || {}) as JSONObject;
const data: JSONObject = (body["data"] as JSONObject) || {};
const getValue: (key: string) => string | undefined = (
key: string,
): string | undefined => {
const headerKey: string = key.toLowerCase();
const camelKey: string = headerKey
.split("-")
.map((part: string, index: number) => {
if (index === 0) {
return part;
}
return part.charAt(0).toUpperCase() + part.slice(1);
})
.join("");
return (
headerValueToString(req.headers[`x-${headerKey}`]) ||
headerValueToString(body[camelKey] as HeaderValue) ||
headerValueToString(data[camelKey] as HeaderValue) ||
headerValueToString(body[key] as HeaderValue) ||
headerValueToString(data[key] as HeaderValue)
);
};
const result: RequestDeviceInfo = {};
const deviceName: string | undefined = getValue("device-name");
if (deviceName) {
result.deviceName = deviceName;
}
const deviceType: string | undefined = getValue("device-type");
if (deviceType) {
result.deviceType = deviceType;
}
const deviceOS: string | undefined = getValue("device-os");
if (deviceOS) {
result.deviceOS = deviceOS;
}
const deviceBrowser: string | undefined = getValue("device-browser");
if (deviceBrowser) {
result.deviceBrowser = deviceBrowser;
}
return result;
};
export const getClientIp: (req: ExpressRequest) => string | undefined = (
req: ExpressRequest,
): string | undefined => {
const forwarded: string | Array<string> | undefined = req.headers[
"x-forwarded-for"
] as string | Array<string> | undefined;
if (Array.isArray(forwarded) && forwarded.length > 0) {
return forwarded[0]?.split(",")[0]?.trim();
}
if (typeof forwarded === "string" && forwarded.trim().length > 0) {
return forwarded.split(",")[0]?.trim();
}
if (req.socket?.remoteAddress) {
return req.socket.remoteAddress;
}
return req.ip;
};