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; // 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 | 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 { if (!this.app) { this.setupExpress(); } if (!this.httpServer) { this.httpServer = createServer(this.app); } type ResolveFunction = (app: express.Application) => void; return new Promise((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 | undefined = req.headers[ "x-forwarded-for" ] as string | Array | 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; };