mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
- Simplified arrow function syntax in MasterPassword.tsx and DashboardAPI.ts - Consolidated logger.debug statements in PublicDashboard.ts and DashboardDomainAPI.ts - Reformatted multi-line statements for better clarity in various files - Updated migration files for consistent naming conventions and formatting - Enhanced code structure in DashboardDomainService.ts and MonitorTelemetryMonitor.ts - Incremented version number to 10.0.40
553 lines
14 KiB
TypeScript
553 lines
14 KiB
TypeScript
import UserMiddleware from "Common/Server/Middleware/UserAuthorization";
|
|
import {
|
|
IsBillingEnabled,
|
|
getFrontendEnvVars,
|
|
} from "Common/Server/EnvironmentConfig";
|
|
import Express, {
|
|
ExpressApplication,
|
|
ExpressRequest,
|
|
ExpressResponse,
|
|
ExpressStatic,
|
|
NextFunction,
|
|
RequestHandler,
|
|
} from "Common/Server/Utils/Express";
|
|
import JSONWebToken from "Common/Server/Utils/JsonWebToken";
|
|
import JSONWebTokenData from "Common/Types/JsonWebTokenData";
|
|
import logger from "Common/Server/Utils/Logger";
|
|
import Response from "Common/Server/Utils/Response";
|
|
import NotAuthorizedException from "Common/Types/Exception/NotAuthorizedException";
|
|
import { PromiseVoidFunction } from "Common/Types/FunctionTypes";
|
|
import { JSONObject } from "Common/Types/JSON";
|
|
import ObjectID from "Common/Types/ObjectID";
|
|
import {
|
|
handleRSS,
|
|
StatusPageData,
|
|
getStatusPageData,
|
|
} from "./Utils/StatusPage";
|
|
import DashboardDomainService from "Common/Server/Services/DashboardDomainService";
|
|
import DashboardDomain from "Common/Models/DatabaseModels/DashboardDomain";
|
|
|
|
const app: ExpressApplication = Express.getExpressApp();
|
|
|
|
const AccountsPublicPath: string = "/usr/src/app/FeatureSet/Accounts/public";
|
|
const AccountsViewPath: string =
|
|
"/usr/src/app/FeatureSet/Accounts/views/index.ejs";
|
|
|
|
const DashboardPublicPath: string = "/usr/src/app/FeatureSet/Dashboard/public";
|
|
const DashboardViewPath: string =
|
|
"/usr/src/app/FeatureSet/Dashboard/views/index.ejs";
|
|
|
|
const AdminPublicPath: string = "/usr/src/app/FeatureSet/AdminDashboard/public";
|
|
const AdminViewPath: string =
|
|
"/usr/src/app/FeatureSet/AdminDashboard/views/index.ejs";
|
|
|
|
const StatusPagePublicPath: string =
|
|
"/usr/src/app/FeatureSet/StatusPage/public";
|
|
const StatusPageViewPath: string =
|
|
"/usr/src/app/FeatureSet/StatusPage/views/index.ejs";
|
|
|
|
const PublicDashboardPublicPath: string =
|
|
"/usr/src/app/FeatureSet/PublicDashboard/public";
|
|
const PublicDashboardViewPath: string =
|
|
"/usr/src/app/FeatureSet/PublicDashboard/views/index.ejs";
|
|
|
|
interface FrontendConfig {
|
|
routePrefix: string;
|
|
publicPath: string;
|
|
indexViewPath: string;
|
|
primaryHostOnly?: boolean;
|
|
getVariablesToRenderIndexPage?: (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
) => Promise<JSONObject>;
|
|
}
|
|
|
|
interface RenderFrontendOptions {
|
|
req: ExpressRequest;
|
|
res: ExpressResponse;
|
|
next: NextFunction;
|
|
frontendConfig: FrontendConfig;
|
|
}
|
|
|
|
const DashboardFallbackRoutePrefixesToSkip: Array<string> = [
|
|
"/status-page",
|
|
"/status-page-api",
|
|
"/status-page-sso-api",
|
|
"/status-page-identity-api",
|
|
"/public-dashboard",
|
|
"/public-dashboard-api",
|
|
"/api",
|
|
"/identity",
|
|
"/notification",
|
|
"/telemetry",
|
|
"/incoming-request-ingest",
|
|
"/otlp",
|
|
"/opentelemetry.proto.collector",
|
|
"/probe-ingest",
|
|
"/ingestor",
|
|
"/server-monitor",
|
|
"/realtime",
|
|
"/workflow",
|
|
"/workers",
|
|
"/mcp",
|
|
"/analytics-api",
|
|
"/heartbeat",
|
|
"/incoming-email",
|
|
"/file",
|
|
"/docs",
|
|
"/reference",
|
|
"/worker",
|
|
"/.well-known",
|
|
"/l",
|
|
"/manifest.json",
|
|
"/service-worker.js",
|
|
"/sw.js",
|
|
"/browserconfig.xml",
|
|
"/rss",
|
|
];
|
|
|
|
const StatusPageDomainFallbackRoutePrefixesToSkip: Array<string> = [
|
|
"/status-page-api",
|
|
"/status-page-sso-api",
|
|
"/status-page-identity-api",
|
|
"/public-dashboard-api",
|
|
"/.well-known",
|
|
"/rss",
|
|
];
|
|
|
|
const StatusPageFrontendConfig: FrontendConfig = {
|
|
routePrefix: "/status-page",
|
|
publicPath: StatusPagePublicPath,
|
|
indexViewPath: StatusPageViewPath,
|
|
getVariablesToRenderIndexPage: async (
|
|
req: ExpressRequest,
|
|
_res: ExpressResponse,
|
|
): Promise<JSONObject> => {
|
|
const statusPageData: StatusPageData | null = await getStatusPageData(req);
|
|
|
|
if (statusPageData) {
|
|
return {
|
|
title: statusPageData.title,
|
|
description: statusPageData.description,
|
|
faviconUrl: statusPageData.faviconUrl,
|
|
};
|
|
}
|
|
|
|
return {
|
|
title: "Status Page",
|
|
description:
|
|
"Status Page lets you see real-time information about the status of our services.",
|
|
faviconUrl:
|
|
"/status-page-api/favicon/" + ObjectID.getZeroObjectID().toString(),
|
|
};
|
|
},
|
|
};
|
|
|
|
const DashboardFrontendConfig: FrontendConfig = {
|
|
routePrefix: "/dashboard",
|
|
publicPath: DashboardPublicPath,
|
|
indexViewPath: DashboardViewPath,
|
|
primaryHostOnly: true,
|
|
};
|
|
|
|
const PublicDashboardFrontendConfig: FrontendConfig = {
|
|
routePrefix: "/public-dashboard",
|
|
publicPath: PublicDashboardPublicPath,
|
|
indexViewPath: PublicDashboardViewPath,
|
|
};
|
|
|
|
const DashboardRootPwaFileMap: Array<{ route: string; file: string }> = [
|
|
{ route: "/manifest.json", file: "manifest.json" },
|
|
{ route: "/sw.js", file: "sw.js" },
|
|
{ route: "/service-worker.js", file: "sw.js" },
|
|
{ route: "/browserconfig.xml", file: "browserconfig.xml" },
|
|
];
|
|
|
|
const normalizeHostname: (host: string) => string = (host: string): string => {
|
|
const hostParts: Array<string> = host.split(":");
|
|
const hostPart: string | undefined = hostParts[0];
|
|
|
|
if (!hostPart) {
|
|
return "";
|
|
}
|
|
|
|
return hostPart.trim().toLowerCase();
|
|
};
|
|
|
|
const getPrimaryHosts: () => Set<string> = (): Set<string> => {
|
|
const hostSet: Set<string> = new Set<string>();
|
|
|
|
const hostCandidates: Array<string> = [
|
|
process.env["HOST"] || "",
|
|
"localhost",
|
|
"ingress",
|
|
];
|
|
|
|
for (const hostCandidate of hostCandidates) {
|
|
const normalizedHost: string = normalizeHostname(hostCandidate);
|
|
if (normalizedHost) {
|
|
hostSet.add(normalizedHost);
|
|
}
|
|
}
|
|
|
|
return hostSet;
|
|
};
|
|
|
|
const PrimaryHosts: Set<string> = getPrimaryHosts();
|
|
|
|
const getRequestHostname: (req: ExpressRequest) => string = (
|
|
req: ExpressRequest,
|
|
): string => {
|
|
if (req.hostname) {
|
|
return normalizeHostname(req.hostname.toString());
|
|
}
|
|
|
|
const hostHeader: string | Array<string> | undefined = req.headers["host"];
|
|
if (typeof hostHeader === "string") {
|
|
return normalizeHostname(hostHeader);
|
|
}
|
|
|
|
if (Array.isArray(hostHeader)) {
|
|
const firstHostHeader: string | undefined = hostHeader[0];
|
|
if (!firstHostHeader) {
|
|
return "";
|
|
}
|
|
|
|
return normalizeHostname(firstHostHeader);
|
|
}
|
|
|
|
return "";
|
|
};
|
|
|
|
const isPrimaryHostRequest: (req: ExpressRequest) => boolean = (
|
|
req: ExpressRequest,
|
|
): boolean => {
|
|
const requestHost: string = getRequestHostname(req);
|
|
if (!requestHost) {
|
|
return true;
|
|
}
|
|
|
|
return PrimaryHosts.has(requestHost);
|
|
};
|
|
|
|
const shouldSkipDashboardFallbackRoute: (path: string) => boolean = (
|
|
path: string,
|
|
): boolean => {
|
|
return DashboardFallbackRoutePrefixesToSkip.some((prefix: string) => {
|
|
if (path === prefix) {
|
|
return true;
|
|
}
|
|
|
|
return path.startsWith(`${prefix}/`);
|
|
});
|
|
};
|
|
|
|
const shouldSkipStatusPageDomainFallbackRoute: (path: string) => boolean = (
|
|
path: string,
|
|
): boolean => {
|
|
return StatusPageDomainFallbackRoutePrefixesToSkip.some((prefix: string) => {
|
|
if (path === prefix) {
|
|
return true;
|
|
}
|
|
|
|
return path.startsWith(`${prefix}/`);
|
|
});
|
|
};
|
|
|
|
const sendFrontendEnvScript: (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
) => Promise<void> = async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
const env: JSONObject = getFrontendEnvVars();
|
|
|
|
const script: string = `
|
|
if(!window.process){
|
|
window.process = {}
|
|
}
|
|
|
|
if(!window.process.env){
|
|
window.process.env = {}
|
|
}
|
|
window.process.env = ${JSON.stringify(env)};
|
|
`;
|
|
|
|
Response.sendJavaScriptResponse(req, res, script);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
const renderFrontendIndexPage: (
|
|
options: RenderFrontendOptions,
|
|
) => Promise<void> = async (options: RenderFrontendOptions): Promise<void> => {
|
|
const { req, res, next, frontendConfig } = options;
|
|
|
|
try {
|
|
let variables: JSONObject = {};
|
|
|
|
if (frontendConfig.getVariablesToRenderIndexPage) {
|
|
try {
|
|
const variablesToRenderIndexPage: JSONObject =
|
|
await frontendConfig.getVariablesToRenderIndexPage(req, res);
|
|
|
|
variables = {
|
|
...variables,
|
|
...variablesToRenderIndexPage,
|
|
};
|
|
} catch (err) {
|
|
logger.error(err);
|
|
}
|
|
}
|
|
|
|
if (res.headersSent) {
|
|
return;
|
|
}
|
|
|
|
res.render(frontendConfig.indexViewPath, {
|
|
enableGoogleTagManager: IsBillingEnabled || false,
|
|
...variables,
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
};
|
|
|
|
const ensureMasterAdminAccess: (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
) => Promise<JSONObject> = async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
): Promise<JSONObject> => {
|
|
try {
|
|
const accessToken: string | undefined =
|
|
UserMiddleware.getAccessTokenFromExpressRequest(req);
|
|
|
|
if (!accessToken) {
|
|
Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new NotAuthorizedException(
|
|
"Unauthorized: Only master admins can access the admin dashboard.",
|
|
),
|
|
);
|
|
return {};
|
|
}
|
|
|
|
const authData: JSONWebTokenData = JSONWebToken.decode(accessToken);
|
|
|
|
if (!authData.isMasterAdmin) {
|
|
Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new NotAuthorizedException(
|
|
"Unauthorized: Only master admins can access the admin dashboard.",
|
|
),
|
|
);
|
|
return {};
|
|
}
|
|
|
|
return {};
|
|
} catch (error) {
|
|
logger.error(error);
|
|
Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new NotAuthorizedException(
|
|
"Unauthorized: Only master admins can access the admin dashboard.",
|
|
),
|
|
);
|
|
return {};
|
|
}
|
|
};
|
|
|
|
const registerFrontendApp: (frontendConfig: FrontendConfig) => void = (
|
|
frontendConfig: FrontendConfig,
|
|
): void => {
|
|
const staticHandler: RequestHandler = ExpressStatic(
|
|
frontendConfig.publicPath,
|
|
) as RequestHandler;
|
|
|
|
app.use(
|
|
frontendConfig.routePrefix,
|
|
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
return staticHandler(req, res, next);
|
|
},
|
|
);
|
|
|
|
app.get(
|
|
`${frontendConfig.routePrefix}/env.js`,
|
|
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
return sendFrontendEnvScript(req, res, next);
|
|
},
|
|
);
|
|
|
|
app.get(
|
|
[frontendConfig.routePrefix, `${frontendConfig.routePrefix}/*`],
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (frontendConfig.primaryHostOnly && !isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
return renderFrontendIndexPage({
|
|
req,
|
|
res,
|
|
next,
|
|
frontendConfig,
|
|
});
|
|
},
|
|
);
|
|
};
|
|
|
|
const isDashboardDomain: (hostname: string) => Promise<boolean> = async (
|
|
hostname: string,
|
|
): Promise<boolean> => {
|
|
try {
|
|
const dashboardDomain: DashboardDomain | null =
|
|
await DashboardDomainService.findOneBy({
|
|
query: {
|
|
fullDomain: hostname,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
return dashboardDomain !== null;
|
|
} catch (err) {
|
|
logger.error("Error checking if domain is a dashboard domain:");
|
|
logger.error(err);
|
|
return false;
|
|
}
|
|
};
|
|
|
|
const registerCustomDomainFallback: () => void = (): void => {
|
|
app.get(
|
|
"*",
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
if (shouldSkipStatusPageDomainFallbackRoute(req.path)) {
|
|
return next();
|
|
}
|
|
|
|
/*
|
|
* Check if this custom domain belongs to a PublicDashboard.
|
|
* If so, serve the PublicDashboard SPA instead of StatusPage.
|
|
*/
|
|
const requestHostname: string = getRequestHostname(req);
|
|
|
|
if (requestHostname && (await isDashboardDomain(requestHostname))) {
|
|
return renderFrontendIndexPage({
|
|
req,
|
|
res,
|
|
next,
|
|
frontendConfig: PublicDashboardFrontendConfig,
|
|
});
|
|
}
|
|
|
|
// Default: serve StatusPage for custom domains
|
|
return renderFrontendIndexPage({
|
|
req,
|
|
res,
|
|
next,
|
|
frontendConfig: StatusPageFrontendConfig,
|
|
});
|
|
},
|
|
);
|
|
};
|
|
|
|
const registerDashboardFallbackForPrimaryHost: () => void = (): void => {
|
|
app.get(
|
|
"*",
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (IsBillingEnabled) {
|
|
return next();
|
|
}
|
|
|
|
if (!isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
if (shouldSkipDashboardFallbackRoute(req.path)) {
|
|
return next();
|
|
}
|
|
|
|
return renderFrontendIndexPage({
|
|
req,
|
|
res,
|
|
next,
|
|
frontendConfig: DashboardFrontendConfig,
|
|
});
|
|
},
|
|
);
|
|
};
|
|
|
|
const registerDashboardRootPwaFiles: () => void = (): void => {
|
|
for (const pwaFileRoute of DashboardRootPwaFileMap) {
|
|
app.get(
|
|
pwaFileRoute.route,
|
|
(req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
if (IsBillingEnabled || !isPrimaryHostRequest(req)) {
|
|
return next();
|
|
}
|
|
|
|
return res.sendFile(`${DashboardPublicPath}/${pwaFileRoute.file}`);
|
|
},
|
|
);
|
|
}
|
|
};
|
|
|
|
const init: PromiseVoidFunction = async (): Promise<void> => {
|
|
app.get("/rss", handleRSS);
|
|
app.get("/status-page/:statusPageId/rss", handleRSS);
|
|
|
|
registerFrontendApp({
|
|
routePrefix: "/accounts",
|
|
publicPath: AccountsPublicPath,
|
|
indexViewPath: AccountsViewPath,
|
|
primaryHostOnly: true,
|
|
});
|
|
|
|
registerFrontendApp(DashboardFrontendConfig);
|
|
|
|
registerFrontendApp({
|
|
routePrefix: "/admin",
|
|
publicPath: AdminPublicPath,
|
|
indexViewPath: AdminViewPath,
|
|
primaryHostOnly: true,
|
|
getVariablesToRenderIndexPage: ensureMasterAdminAccess,
|
|
});
|
|
|
|
registerFrontendApp(StatusPageFrontendConfig);
|
|
|
|
registerFrontendApp(PublicDashboardFrontendConfig);
|
|
|
|
registerDashboardRootPwaFiles();
|
|
registerCustomDomainFallback();
|
|
registerDashboardFallbackForPrimaryHost();
|
|
};
|
|
|
|
export default {
|
|
init,
|
|
};
|