mirror of
https://github.com/OneUptime/oneuptime.git
synced 2026-01-11 19:56:44 +00:00
422 lines
12 KiB
TypeScript
422 lines
12 KiB
TypeScript
import BaseModel from "Common/Models/BaseModel";
|
|
import { FileRoute } from "Common/ServiceRoute";
|
|
import Hostname from "Common/Types/API/Hostname";
|
|
import Protocol from "Common/Types/API/Protocol";
|
|
import URL from "Common/Types/API/URL";
|
|
import OneUptimeDate from "Common/Types/Date";
|
|
import EmailTemplateType from "Common/Types/Email/EmailTemplateType";
|
|
import BadDataException from "Common/Types/Exception/BadDataException";
|
|
import { JSONObject } from "Common/Types/JSON";
|
|
import JSONFunctions from "Common/Types/JSONFunctions";
|
|
import ObjectID from "Common/Types/ObjectID";
|
|
import PositiveNumber from "Common/Types/PositiveNumber";
|
|
import DatabaseConfig from "CommonServer/DatabaseConfig";
|
|
import { EncryptionSecret } from "CommonServer/EnvironmentConfig";
|
|
import MailService from "CommonServer/Services/MailService";
|
|
import StatusPagePrivateUserService from "CommonServer/Services/StatusPagePrivateUserService";
|
|
import StatusPageService from "CommonServer/Services/StatusPageService";
|
|
import CookieUtil from "CommonServer/Utils/Cookie";
|
|
import Express, {
|
|
ExpressRequest,
|
|
ExpressResponse,
|
|
ExpressRouter,
|
|
NextFunction,
|
|
} from "CommonServer/Utils/Express";
|
|
import JSONWebToken from "CommonServer/Utils/JsonWebToken";
|
|
import logger from "CommonServer/Utils/Logger";
|
|
import Response from "CommonServer/Utils/Response";
|
|
import StatusPage from "Model/Models/StatusPage";
|
|
import StatusPagePrivateUser from "Model/Models/StatusPagePrivateUser";
|
|
|
|
const router: ExpressRouter = Express.getRouter();
|
|
|
|
router.post(
|
|
"/logout/:statuspageid",
|
|
async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
if (!req.params["statuspageid"]) {
|
|
throw new BadDataException("Status Page ID is required.");
|
|
}
|
|
|
|
const statusPageId: ObjectID = new ObjectID(
|
|
req.params["statuspageid"].toString(),
|
|
);
|
|
|
|
CookieUtil.removeCookie(res, CookieUtil.getUserTokenKey(statusPageId)); // remove the cookie.
|
|
|
|
return Response.sendEmptySuccessResponse(req, res);
|
|
} catch (err) {
|
|
return next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
router.post(
|
|
"/forgot-password",
|
|
async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
const data: JSONObject = req.body["data"];
|
|
|
|
if (!data["email"]) {
|
|
throw new BadDataException("Email is required.");
|
|
}
|
|
|
|
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
|
data as JSONObject,
|
|
StatusPagePrivateUser,
|
|
) as StatusPagePrivateUser;
|
|
|
|
if (!user.statusPageId) {
|
|
throw new BadDataException("Status Page ID is required.");
|
|
}
|
|
|
|
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
|
{
|
|
id: user.statusPageId!,
|
|
props: {
|
|
isRoot: true,
|
|
ignoreHooks: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
pageTitle: true,
|
|
logoFileId: true,
|
|
requireSsoForLogin: true,
|
|
projectId: true,
|
|
},
|
|
},
|
|
);
|
|
|
|
if (!statusPage) {
|
|
throw new BadDataException("Status Page not found");
|
|
}
|
|
|
|
if (statusPage.requireSsoForLogin) {
|
|
throw new BadDataException(
|
|
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
|
);
|
|
}
|
|
|
|
const statusPageName: string | undefined =
|
|
statusPage.pageTitle || statusPage.name;
|
|
|
|
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
statusPage.id!,
|
|
);
|
|
|
|
const alreadySavedUser: StatusPagePrivateUser | null =
|
|
await StatusPagePrivateUserService.findOneBy({
|
|
query: {
|
|
email: user.email!,
|
|
statusPageId: user.statusPageId!,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
password: true,
|
|
email: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (alreadySavedUser) {
|
|
const token: string = ObjectID.generate().toString();
|
|
await StatusPagePrivateUserService.updateOneBy({
|
|
query: {
|
|
_id: alreadySavedUser._id!,
|
|
},
|
|
data: {
|
|
resetPasswordToken: token,
|
|
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
MailService.sendMail(
|
|
{
|
|
toEmail: user.email!,
|
|
subject: "Password Reset Request for " + statusPageName,
|
|
templateType: EmailTemplateType.StatusPageForgotPassword,
|
|
vars: {
|
|
statusPageName: statusPageName!,
|
|
logoUrl: statusPage.logoFileId
|
|
? new URL(httpProtocol, host)
|
|
.addRoute(FileRoute)
|
|
.addRoute("/image/" + statusPage.logoFileId)
|
|
.toString()
|
|
: "",
|
|
homeURL: statusPageURL,
|
|
tokenVerifyUrl: URL.fromString(statusPageURL)
|
|
.addRoute("/reset-password/" + token)
|
|
.toString(),
|
|
},
|
|
},
|
|
{
|
|
projectId: statusPage.projectId!,
|
|
},
|
|
).catch((err: Error) => {
|
|
logger.error(err);
|
|
});
|
|
|
|
return Response.sendEmptySuccessResponse(req, res);
|
|
}
|
|
|
|
throw new BadDataException(
|
|
`No user is registered with ${user.email?.toString()}`,
|
|
);
|
|
} catch (err) {
|
|
return next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
router.post(
|
|
"/reset-password",
|
|
async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
const data: JSONObject = JSONFunctions.deserialize(req.body["data"]);
|
|
|
|
if (!data["statusPageId"]) {
|
|
throw new BadDataException("Status Page ID is required.");
|
|
}
|
|
|
|
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
|
data as JSONObject,
|
|
StatusPagePrivateUser,
|
|
) as StatusPagePrivateUser;
|
|
|
|
await user.password?.hashValue(EncryptionSecret);
|
|
|
|
const alreadySavedUser: StatusPagePrivateUser | null =
|
|
await StatusPagePrivateUserService.findOneBy({
|
|
query: {
|
|
statusPageId: new ObjectID(data["statusPageId"].toString()),
|
|
resetPasswordToken: (user.resetPasswordToken as string) || "",
|
|
},
|
|
select: {
|
|
_id: true,
|
|
password: true,
|
|
email: true,
|
|
resetPasswordExpires: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (!alreadySavedUser) {
|
|
throw new BadDataException(
|
|
"Invalid link. Please go to forgot password page again and request a new link.",
|
|
);
|
|
}
|
|
|
|
if (
|
|
alreadySavedUser &&
|
|
OneUptimeDate.hasExpired(alreadySavedUser.resetPasswordExpires!)
|
|
) {
|
|
throw new BadDataException(
|
|
"Expired link. Please go to forgot password page again and request a new link.",
|
|
);
|
|
}
|
|
|
|
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
|
{
|
|
id: new ObjectID(data["statusPageId"].toString()),
|
|
props: {
|
|
isRoot: true,
|
|
ignoreHooks: true,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
name: true,
|
|
pageTitle: true,
|
|
logoFileId: true,
|
|
requireSsoForLogin: true,
|
|
projectId: true,
|
|
},
|
|
},
|
|
);
|
|
|
|
if (!statusPage) {
|
|
throw new BadDataException("Status Page not found");
|
|
}
|
|
|
|
if (statusPage.requireSsoForLogin) {
|
|
throw new BadDataException(
|
|
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
|
);
|
|
}
|
|
|
|
const statusPageName: string | undefined =
|
|
statusPage.pageTitle || statusPage.name;
|
|
|
|
const statusPageURL: string = await StatusPageService.getStatusPageURL(
|
|
statusPage.id!,
|
|
);
|
|
|
|
await StatusPagePrivateUserService.updateOneById({
|
|
id: alreadySavedUser.id!,
|
|
data: {
|
|
password: user.password!,
|
|
resetPasswordToken: null!,
|
|
resetPasswordExpires: null!,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
const host: Hostname = await DatabaseConfig.getHost();
|
|
const httpProtocol: Protocol = await DatabaseConfig.getHttpProtocol();
|
|
|
|
MailService.sendMail(
|
|
{
|
|
toEmail: alreadySavedUser.email!,
|
|
subject: "Password Changed.",
|
|
templateType: EmailTemplateType.StatusPagePasswordChanged,
|
|
vars: {
|
|
homeURL: statusPageURL,
|
|
statusPageName: statusPageName || "",
|
|
logoUrl: statusPage.logoFileId
|
|
? new URL(httpProtocol, host)
|
|
.addRoute(FileRoute)
|
|
.addRoute("/image/" + statusPage.logoFileId)
|
|
.toString()
|
|
: "",
|
|
},
|
|
},
|
|
{
|
|
projectId: statusPage.projectId!,
|
|
},
|
|
).catch((err: Error) => {
|
|
logger.error(err);
|
|
});
|
|
|
|
return Response.sendEmptySuccessResponse(req, res);
|
|
} catch (err) {
|
|
return next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
router.post(
|
|
"/login",
|
|
async (
|
|
req: ExpressRequest,
|
|
res: ExpressResponse,
|
|
next: NextFunction,
|
|
): Promise<void> => {
|
|
try {
|
|
const data: JSONObject = req.body["data"];
|
|
|
|
const user: StatusPagePrivateUser = BaseModel.fromJSON(
|
|
data as JSONObject,
|
|
StatusPagePrivateUser,
|
|
) as StatusPagePrivateUser;
|
|
|
|
if (!user.statusPageId) {
|
|
throw new BadDataException("Status Page ID not found");
|
|
}
|
|
|
|
const statusPage: StatusPage | null = await StatusPageService.findOneById(
|
|
{
|
|
id: user.statusPageId,
|
|
props: {
|
|
isRoot: true,
|
|
ignoreHooks: true,
|
|
},
|
|
select: {
|
|
requireSsoForLogin: true,
|
|
},
|
|
},
|
|
);
|
|
|
|
if (!statusPage) {
|
|
throw new BadDataException("Status Page not found");
|
|
}
|
|
|
|
if (statusPage.requireSsoForLogin) {
|
|
throw new BadDataException(
|
|
"Status Page supports authentication by SSO. You cannot use email and password for authentication.",
|
|
);
|
|
}
|
|
|
|
await user.password?.hashValue(EncryptionSecret);
|
|
|
|
const alreadySavedUser: StatusPagePrivateUser | null =
|
|
await StatusPagePrivateUserService.findOneBy({
|
|
query: {
|
|
email: user.email!,
|
|
password: user.password!,
|
|
statusPageId: user.statusPageId!,
|
|
},
|
|
select: {
|
|
_id: true,
|
|
password: true,
|
|
email: true,
|
|
statusPageId: true,
|
|
},
|
|
props: {
|
|
isRoot: true,
|
|
},
|
|
});
|
|
|
|
if (alreadySavedUser) {
|
|
const token: string = JSONWebToken.sign({
|
|
data: alreadySavedUser,
|
|
expiresInSeconds: OneUptimeDate.getSecondsInDays(
|
|
new PositiveNumber(30),
|
|
),
|
|
});
|
|
|
|
CookieUtil.setCookie(
|
|
res,
|
|
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
|
|
token,
|
|
{
|
|
httpOnly: true,
|
|
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
|
|
},
|
|
);
|
|
|
|
return Response.sendEntityResponse(
|
|
req,
|
|
res,
|
|
alreadySavedUser,
|
|
StatusPagePrivateUser,
|
|
{
|
|
miscData: {
|
|
token: token,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
throw new BadDataException(
|
|
"Invalid login: Email or password does not match.",
|
|
);
|
|
} catch (err) {
|
|
return next(err);
|
|
}
|
|
},
|
|
);
|
|
|
|
export default router;
|