oneuptime/Identity/API/StatusPageSSO.ts
2023-10-25 18:06:39 +01:00

309 lines
10 KiB
TypeScript

import Express, {
ExpressRequest,
ExpressResponse,
ExpressRouter,
NextFunction,
} from 'CommonServer/Utils/Express';
import BadRequestException from 'Common/Types/Exception/BadRequestException';
import ServerException from 'Common/Types/Exception/ServerException';
import Response from 'CommonServer/Utils/Response';
import StatusPageSsoService from 'CommonServer/Services/StatusPageSsoService';
import ObjectID from 'Common/Types/ObjectID';
import xml2js from 'xml2js';
import { JSONObject } from 'Common/Types/JSON';
import logger from 'CommonServer/Utils/Logger';
import Email from 'Common/Types/Email';
import OneUptimeDate from 'Common/Types/Date';
import PositiveNumber from 'Common/Types/PositiveNumber';
import JSONWebToken from 'CommonServer/Utils/JsonWebToken';
import URL from 'Common/Types/API/URL';
import SSOUtil from '../Utils/SSO';
import Exception from 'Common/Types/Exception/Exception';
import StatusPageSSO from 'Model/Models/StatusPageSso';
import StatusPagePrivateUser from 'Model/Models/StatusPagePrivateUser';
import StatusPagePrivateUserService from 'CommonServer/Services/StatusPagePrivateUserService';
import HashedString from 'Common/Types/HashedString';
import StatusPageService from 'CommonServer/Services/StatusPageService';
import CookieUtil from 'CommonServer/Utils/Cookie';
const router: ExpressRouter = Express.getRouter();
router.get(
'/status-page-sso/:statusPageId/:statusPageSsoId',
async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
): Promise<void> => {
try {
if (!req.params['statusPageId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page ID not found')
);
}
if (!req.params['statusPageSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page SSO ID not found')
);
}
const statusPageId: ObjectID = new ObjectID(
req.params['statusPageId']
);
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params['statusPageSsoId'],
isEnabled: true,
},
select: {
signOnURL: true,
},
props: {
isRoot: true,
},
});
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
// redirect to Identity Provider.
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign On URL not found')
);
}
return Response.redirect(req, res, statusPageSSO.signOnURL);
} catch (err) {
return next(err);
}
}
);
router.post(
'/status-page-idp-login/:statusPageId/:statusPageSsoId',
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
try {
const samlResponseBase64: string = req.body.SAMLResponse;
const samlResponse: string = Buffer.from(
samlResponseBase64,
'base64'
).toString();
const response: JSONObject = await xml2js.parseStringPromise(
samlResponse
);
let issuerUrl: string = '';
let email: Email | null = null;
if (!req.params['statusPageId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page ID not found')
);
}
if (!req.params['statusPageSsoId']) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Status Page SSO ID not found')
);
}
const statusPageId: ObjectID = new ObjectID(
req.params['statusPageId']
);
const statusPageSSO: StatusPageSSO | null =
await StatusPageSsoService.findOneBy({
query: {
statusPageId: statusPageId,
_id: req.params['statusPageSsoId'],
isEnabled: true,
},
select: {
signOnURL: true,
issuerURL: true,
publicCertificate: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!statusPageSSO) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config not found')
);
}
if (!statusPageSSO.projectId) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('SSO Config Project ID not found')
);
}
const projectId: ObjectID = statusPageSSO.projectId;
// redirect to Identity Provider.
if (!statusPageSSO.issuerURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL not found')
);
}
// redirect to Identity Provider.
if (!statusPageSSO.signOnURL) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Sign on URL not found')
);
}
if (!statusPageSSO.publicCertificate) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Public Certificate not found')
);
}
try {
SSOUtil.isPayloadValid(response);
if (
!SSOUtil.isSignatureValid(
samlResponse,
statusPageSSO.publicCertificate
)
) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Signature is not valid')
);
}
issuerUrl = SSOUtil.getIssuer(response);
email = SSOUtil.getEmail(response);
} catch (err: unknown) {
if (err instanceof Exception) {
return Response.sendErrorResponse(req, res, err);
}
return Response.sendErrorResponse(
req,
res,
new ServerException()
);
}
if (statusPageSSO.issuerURL.toString() !== issuerUrl) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException('Issuer URL does not match')
);
}
// Check if he already belongs to the project, If he does - then log in.
let alreadySavedUser: StatusPagePrivateUser | null =
await StatusPagePrivateUserService.findOneBy({
query: { email: email, statusPageId: statusPageId },
select: {
_id: true,
email: true,
statusPageId: true,
projectId: true,
},
props: {
isRoot: true,
},
});
if (!alreadySavedUser) {
/// Create a user.
alreadySavedUser = new StatusPagePrivateUser();
alreadySavedUser.projectId = projectId;
alreadySavedUser.statusPageId = statusPageId;
alreadySavedUser.email = email;
alreadySavedUser.password = new HashedString(
ObjectID.generate().toString()
);
alreadySavedUser.isSsoUser = true;
alreadySavedUser = await StatusPagePrivateUserService.create({
data: alreadySavedUser,
props: { isRoot: true },
});
}
const token: string = JSONWebToken.sign(
alreadySavedUser,
OneUptimeDate.getSecondsInDays(new PositiveNumber(30))
);
CookieUtil.setCookie(
res,
CookieUtil.getUserTokenKey(alreadySavedUser.statusPageId!),
token,
{
httpOnly: true,
maxAge: OneUptimeDate.getMillisecondsInDays(
new PositiveNumber(30)
),
}
);
// get status page URL.
const statusPageURL: string =
await StatusPageService.getStatusPageFirstURL(statusPageId);
return Response.redirect(
req,
res,
URL.fromString(statusPageURL).addQueryParams({
token: token,
})
);
} catch (err) {
logger.error(err);
Response.sendErrorResponse(req, res, new ServerException());
}
}
);
export default router;