mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-04-06 00:32:12 +02:00
345 lines
11 KiB
TypeScript
345 lines
11 KiB
TypeScript
import UserMiddleware from "../Middleware/UserAuthorization";
|
|
import DashboardService, {
|
|
Service as DashboardServiceType,
|
|
} from "../Services/DashboardService";
|
|
import DashboardDomainService from "../Services/DashboardDomainService";
|
|
import CookieUtil from "../Utils/Cookie";
|
|
import logger from "../Utils/Logger";
|
|
import {
|
|
ExpressRequest,
|
|
ExpressResponse,
|
|
NextFunction,
|
|
} from "../Utils/Express";
|
|
import Response from "../Utils/Response";
|
|
import BaseAPI from "./BaseAPI";
|
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
import NotFoundException from "../../Types/Exception/NotFoundException";
|
|
import HashedString from "../../Types/HashedString";
|
|
import ObjectID from "../../Types/ObjectID";
|
|
import Dashboard from "../../Models/DatabaseModels/Dashboard";
|
|
import DashboardDomain from "../../Models/DatabaseModels/DashboardDomain";
|
|
import { EncryptionSecret } from "../EnvironmentConfig";
|
|
import { DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE } from "../../Types/Dashboard/MasterPassword";
|
|
import NotAuthenticatedException from "../../Types/Exception/NotAuthenticatedException";
|
|
import ForbiddenException from "../../Types/Exception/ForbiddenException";
|
|
import JSONFunctions from "../../Types/JSONFunctions";
|
|
|
|
export default class DashboardAPI extends BaseAPI<
|
|
Dashboard,
|
|
DashboardServiceType
|
|
> {
|
|
public constructor() {
|
|
super(Dashboard, DashboardService);
|
|
|
|
// SEO endpoint - resolve dashboard by ID or domain
|
|
this.router.get(
|
|
`${new this.entityType()
|
|
.getCrudApiPath()
|
|
?.toString()}/seo/:dashboardIdOrDomain`,
|
|
UserMiddleware.getUserMiddleware,
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
try {
|
|
const dashboardIdOrDomain: string = req.params[
|
|
"dashboardIdOrDomain"
|
|
] as string;
|
|
|
|
let dashboardId: ObjectID | null = null;
|
|
|
|
if (dashboardIdOrDomain && dashboardIdOrDomain.includes(".")) {
|
|
// This is a domain - resolve to dashboard ID
|
|
const dashboardDomain: DashboardDomain | null =
|
|
await DashboardDomainService.findOneBy({
|
|
query: {
|
|
fullDomain: dashboardIdOrDomain,
|
|
domain: {
|
|
isVerified: true,
|
|
} as any,
|
|
},
|
|
select: {
|
|
dashboardId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboardDomain || !dashboardDomain.dashboardId) {
|
|
return Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new NotFoundException("Dashboard not found"),
|
|
);
|
|
}
|
|
|
|
dashboardId = dashboardDomain.dashboardId;
|
|
} else {
|
|
try {
|
|
dashboardId = new ObjectID(dashboardIdOrDomain);
|
|
} catch (err) {
|
|
logger.error(err);
|
|
return Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new BadDataException("Invalid dashboard ID"),
|
|
);
|
|
}
|
|
}
|
|
|
|
const dashboard: Dashboard | null =
|
|
await DashboardService.findOneById({
|
|
id: dashboardId,
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
description: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboard) {
|
|
return Response.sendErrorResponse(
|
|
req,
|
|
res,
|
|
new NotFoundException("Dashboard not found"),
|
|
);
|
|
}
|
|
|
|
return Response.sendJsonObjectResponse(req, res, {
|
|
_id: dashboard._id?.toString() || "",
|
|
title: dashboard.name || "Dashboard",
|
|
description:
|
|
dashboard.description || "View dashboard metrics and insights.",
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Domain resolution endpoint
|
|
this.router.post(
|
|
`${new this.entityType().getCrudApiPath()?.toString()}/domain`,
|
|
UserMiddleware.getUserMiddleware,
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
try {
|
|
if (!req.body["domain"]) {
|
|
throw new BadDataException("domain is required in request body");
|
|
}
|
|
|
|
const domain: string = req.body["domain"] as string;
|
|
|
|
const dashboardDomain: DashboardDomain | null =
|
|
await DashboardDomainService.findOneBy({
|
|
query: {
|
|
fullDomain: domain,
|
|
domain: {
|
|
isVerified: true,
|
|
} as any,
|
|
},
|
|
select: {
|
|
dashboardId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboardDomain) {
|
|
throw new BadDataException("No dashboard found with this domain");
|
|
}
|
|
|
|
const objectId: ObjectID = dashboardDomain.dashboardId!;
|
|
|
|
return Response.sendJsonObjectResponse(req, res, {
|
|
dashboardId: objectId.toString(),
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Metadata endpoint - returns dashboard info for the public viewer
|
|
this.router.post(
|
|
`${new this.entityType()
|
|
.getCrudApiPath()
|
|
?.toString()}/metadata/:dashboardId`,
|
|
UserMiddleware.getUserMiddleware,
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
try {
|
|
const dashboardId: ObjectID = new ObjectID(
|
|
req.params["dashboardId"] as string,
|
|
);
|
|
|
|
const dashboard: Dashboard | null =
|
|
await DashboardService.findOneById({
|
|
id: dashboardId,
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
description: true,
|
|
isPublicDashboard: true,
|
|
enableMasterPassword: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboard) {
|
|
throw new NotFoundException("Dashboard not found");
|
|
}
|
|
|
|
return Response.sendJsonObjectResponse(req, res, {
|
|
_id: dashboard._id?.toString() || "",
|
|
name: dashboard.name || "Dashboard",
|
|
description: dashboard.description || "",
|
|
isPublicDashboard: dashboard.isPublicDashboard || false,
|
|
enableMasterPassword: dashboard.enableMasterPassword || false,
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
// Public view-config endpoint - returns dashboard view config for the public viewer
|
|
this.router.post(
|
|
`${new this.entityType()
|
|
.getCrudApiPath()
|
|
?.toString()}/view-config/:dashboardId`,
|
|
UserMiddleware.getUserMiddleware,
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
try {
|
|
const dashboardId: ObjectID = new ObjectID(
|
|
req.params["dashboardId"] as string,
|
|
);
|
|
|
|
// Check read access (handles public check, IP whitelist, master password)
|
|
const accessResult: {
|
|
hasReadAccess: boolean;
|
|
error?: NotAuthenticatedException | ForbiddenException;
|
|
} = await DashboardService.hasReadAccess({
|
|
dashboardId,
|
|
req,
|
|
});
|
|
|
|
if (!accessResult.hasReadAccess) {
|
|
throw (
|
|
accessResult.error ||
|
|
new BadDataException("Access denied to this dashboard.")
|
|
);
|
|
}
|
|
|
|
const dashboard: Dashboard | null =
|
|
await DashboardService.findOneById({
|
|
id: dashboardId,
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
description: true,
|
|
dashboardViewConfig: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboard) {
|
|
throw new NotFoundException("Dashboard not found");
|
|
}
|
|
|
|
return Response.sendJsonObjectResponse(req, res, {
|
|
_id: dashboard._id?.toString() || "",
|
|
name: dashboard.name || "Dashboard",
|
|
description: dashboard.description || "",
|
|
dashboardViewConfig: dashboard.dashboardViewConfig
|
|
? JSONFunctions.serialize(dashboard.dashboardViewConfig as any)
|
|
: null,
|
|
});
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
this.router.post(
|
|
`${new this.entityType()
|
|
.getCrudApiPath()
|
|
?.toString()}/master-password/:dashboardId`,
|
|
UserMiddleware.getUserMiddleware,
|
|
async (req: ExpressRequest, res: ExpressResponse, next: NextFunction) => {
|
|
try {
|
|
if (!req.params["dashboardId"]) {
|
|
throw new BadDataException("Dashboard ID not found");
|
|
}
|
|
|
|
const dashboardId: ObjectID = new ObjectID(
|
|
req.params["dashboardId"] as string,
|
|
);
|
|
|
|
const password: string | undefined =
|
|
req.body && (req.body["password"] as string);
|
|
|
|
if (!password) {
|
|
throw new BadDataException("Master password is required.");
|
|
}
|
|
|
|
const dashboard: Dashboard | null =
|
|
await DashboardService.findOneById({
|
|
id: dashboardId,
|
|
select: {
|
|
_id: true,
|
|
projectId: true,
|
|
enableMasterPassword: true,
|
|
masterPassword: true,
|
|
isPublicDashboard: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!dashboard) {
|
|
throw new NotFoundException("Dashboard not found");
|
|
}
|
|
|
|
if (!dashboard.isPublicDashboard) {
|
|
throw new BadDataException(
|
|
"This dashboard is not publicly accessible.",
|
|
);
|
|
}
|
|
|
|
if (!dashboard.enableMasterPassword || !dashboard.masterPassword) {
|
|
throw new BadDataException(
|
|
"Master password has not been configured for this dashboard.",
|
|
);
|
|
}
|
|
|
|
const hashedInput: string = await HashedString.hashValue(
|
|
password,
|
|
EncryptionSecret,
|
|
);
|
|
|
|
if (hashedInput !== dashboard.masterPassword.toString()) {
|
|
throw new BadDataException(
|
|
DASHBOARD_MASTER_PASSWORD_INVALID_MESSAGE,
|
|
);
|
|
}
|
|
|
|
CookieUtil.setDashboardMasterPasswordCookie({
|
|
expressResponse: res,
|
|
dashboardId,
|
|
});
|
|
|
|
return Response.sendEmptySuccessResponse(req, res);
|
|
} catch (err) {
|
|
next(err);
|
|
}
|
|
},
|
|
);
|
|
}
|
|
}
|