oneuptime/Identity/API/StatusPageAuthentication.ts

306 lines
10 KiB
TypeScript
Raw Normal View History

2022-12-19 08:53:45 +00:00
import {
HttpProtocol,
EncryptionSecret,
Domain,
FileRoute,
} from 'CommonServer/Config';
import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import { JSONObject } from 'Common/Types/JSON';
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
import ObjectID from 'Common/Types/ObjectID';
import BadDataException from 'Common/Types/Exception/BadDataException';
import MailService from 'CommonServer/Services/MailService';
import EmailTemplateType from 'Common/Types/Email/EmailTemplateType';
import URL from 'Common/Types/API/URL';
import Response from 'CommonServer/Utils/Response';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import OneUptimeDate from 'Common/Types/Date';
import PositiveNumber from 'Common/Types/PositiveNumber';
import logger from 'CommonServer/Utils/Logger';
import JSONFunctions from 'Common/Types/JSONFunctions';
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
import StatusPage from 'Model/Models/StatusPage';
2022-12-19 09:39:55 +00:00
import StatusPageService from 'CommonServer/Services/StatusPageService';
2022-12-19 08:53:45 +00:00
const router: ExpressRouter = Express.getRouter();
router.post(
'/forgot-password',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
const data: JSONObject = req.body['data'];
const user: StatusPagePrivateUser = JSONFunctions.fromJSON(
data as JSONObject,
StatusPagePrivateUser
) as StatusPagePrivateUser;
2022-12-19 14:36:50 +00:00
if (!user.statusPageId) {
2022-12-19 09:39:55 +00:00
throw new BadDataException('Status Page ID is required.');
2022-12-19 08:53:45 +00:00
}
2022-12-19 09:39:55 +00:00
const statusPage: StatusPage | null =
await StatusPageService.findOneById({
2022-12-19 14:36:50 +00:00
id: user.statusPageId!,
2022-12-19 09:39:55 +00:00
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
},
});
2022-12-19 08:53:45 +00:00
if (!statusPage) {
2022-12-19 09:39:55 +00:00
throw new BadDataException('Status Page not found');
2022-12-19 08:53:45 +00:00
}
2022-12-19 13:05:47 +00:00
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
2022-12-19 08:53:45 +00:00
2022-12-19 09:39:55 +00:00
const statusPageURL: string =
await StatusPageService.getStatusPageURL(statusPage.id!);
2022-12-19 08:53:45 +00:00
2022-12-19 09:39:55 +00:00
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
email: user.email!,
2022-12-19 14:36:50 +00:00
statusPageId: user.statusPageId!,
2022-12-19 09:39:55 +00:00
},
select: {
_id: true,
password: true,
email: true,
},
props: {
isRoot: true,
},
});
2022-12-19 08:53:45 +00:00
if (alreadySavedUser) {
const token: string = ObjectID.generate().toString();
await StatusPagePrivateUserService.updateOneBy({
query: {
2023-01-31 15:12:02 +00:00
_id: alreadySavedUser._id!,
2022-12-19 08:53:45 +00:00
},
data: {
resetPasswordToken: token,
resetPasswordExpires: OneUptimeDate.getOneDayAfter(),
},
props: {
isRoot: true,
},
});
MailService.sendMail({
toEmail: user.email!,
2022-12-19 09:39:55 +00:00
subject: 'Password Reset Request for ' + statusPageName,
2022-12-19 08:53:45 +00:00
templateType: EmailTemplateType.StatusPageForgotPassword,
vars: {
statusPageName: statusPageName!,
logoUrl: statusPage.logoFileId
2022-12-19 09:39:55 +00:00
? new URL(HttpProtocol, Domain)
.addRoute(FileRoute)
.addRoute('/image/' + statusPage.logoFileId)
.toString()
: '',
2022-12-19 08:53:45 +00:00
homeURL: statusPageURL,
2022-12-19 09:39:55 +00:00
tokenVerifyUrl: URL.fromString(statusPageURL)
.addRoute('/reset-password/' + token)
.toString(),
2022-12-19 08:53:45 +00:00
},
}).catch((err: Error) => {
logger.error(err);
});
return Response.sendEmptyResponse(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 = req.body['data'];
const user: StatusPagePrivateUser = JSONFunctions.fromJSON(
data as JSONObject,
StatusPagePrivateUser
) as StatusPagePrivateUser;
await user.password?.hashValue(EncryptionSecret);
2022-12-19 09:39:55 +00:00
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: {
resetPasswordToken:
(user.resetPasswordToken as string) || '',
},
select: {
_id: true,
password: true,
email: true,
resetPasswordExpires: true,
},
props: {
isRoot: true,
},
});
2022-12-19 08:53:45 +00:00
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.'
);
}
2022-12-19 09:39:55 +00:00
const statusPage: StatusPage | null =
await StatusPageService.findOneById({
id: new ObjectID(data['statusPageId'] as string),
props: {
isRoot: true,
ignoreHooks: true,
},
select: {
_id: true,
name: true,
pageTitle: true,
logoFileId: true,
},
});
2022-12-19 08:53:45 +00:00
if (!statusPage) {
2022-12-19 09:39:55 +00:00
throw new BadDataException('Status Page not found');
2022-12-19 08:53:45 +00:00
}
2022-12-19 13:05:47 +00:00
const statusPageName: string | undefined =
statusPage.pageTitle || statusPage.name;
2022-12-19 08:53:45 +00:00
2022-12-19 09:39:55 +00:00
const statusPageURL: string =
await StatusPageService.getStatusPageURL(statusPage.id!);
2022-12-19 08:53:45 +00:00
await StatusPagePrivateUserService.updateOneById({
id: alreadySavedUser.id!,
data: {
password: user.password!,
resetPasswordToken: null!,
resetPasswordExpires: null!,
},
props: {
isRoot: true,
},
});
MailService.sendMail({
toEmail: alreadySavedUser.email!,
subject: 'Password Changed.',
templateType: EmailTemplateType.StatusPagePasswordChanged,
vars: {
homeURL: statusPageURL,
statusPageName: statusPageName || '',
logoUrl: statusPage.logoFileId
? new URL(HttpProtocol, Domain)
.addRoute(FileRoute)
2022-12-19 09:39:55 +00:00
.addRoute('/image/' + statusPage.logoFileId)
2022-12-19 08:53:45 +00:00
.toString()
: '',
},
}).catch((err: Error) => {
logger.error(err);
});
return Response.sendEmptyResponse(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 = JSONFunctions.fromJSON(
data as JSONObject,
2022-12-19 11:54:03 +00:00
StatusPagePrivateUser
) as StatusPagePrivateUser;
2022-12-19 08:53:45 +00:00
await user.password?.hashValue(EncryptionSecret);
2022-12-19 09:39:55 +00:00
const alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: { email: user.email!, password: user.password! },
select: {
_id: true,
password: true,
email: true,
2022-12-19 13:05:47 +00:00
statusPageId: true,
2022-12-19 09:39:55 +00:00
},
props: {
isRoot: true,
},
});
2022-12-19 08:53:45 +00:00
if (alreadySavedUser) {
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
return Response.sendJsonObjectResponse(req, res, {
token: token,
2022-12-19 13:05:47 +00:00
user: JSONFunctions.toJSON(
alreadySavedUser,
StatusPagePrivateUser
),
2022-12-19 08:53:45 +00:00
});
}
throw new BadDataException(
'Invalid login: Email or password does not match.'
);
} catch (err) {
return next(err);
}
}
);
export default router;