refactor: Update import statements for TimezoneUtil in multiple files

This commit is contained in:
Simon Larsen 2024-06-18 12:44:54 +01:00
parent 786ec6ce7a
commit 48b095f548
No known key found for this signature in database
GPG Key ID: 96C5DCA24769DBCA
17 changed files with 532 additions and 379 deletions

View File

@ -22,7 +22,6 @@ const LoginPage: () => JSX.Element = () => {
Navigation.navigate(DASHBOARD_URL);
}
const [initialValues, setInitialValues] = React.useState<JSONObject>({});
useAsyncEffect(async () => {
@ -105,9 +104,7 @@ const LoginPage: () => JSX.Element = () => {
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/sso")}>
<div
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use single sign-on (SSO) instead
</div>
</Link>

View File

@ -9,7 +9,7 @@ import OneUptimeLogo from "CommonUI/src/Images/logos/OneUptimeSVG/3-transparent.
import Navigation from "CommonUI/src/Utils/Navigation";
import UserUtil from "CommonUI/src/Utils/User";
import User from "Model/Models/User";
import React, { useState } from "react";
import React, { ReactElement, useState } from "react";
import ProjectSSO from "Model/Models/ProjectSSO";
import PageLoader from "CommonUI/src/Components/Loader/PageLoader";
import API from "CommonUI/src/Utils/API/API";
@ -20,188 +20,216 @@ import HTTPResponse from "Common/Types/API/HTTPResponse";
import StaticModelList from "CommonUI/src/Components/ModelList/StaticModelList";
const LoginPage: () => JSX.Element = () => {
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
const apiUrl: URL = SERVICE_PROVIDER_LOGIN_URL;
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
if (UserUtil.isLoggedIn()) {
Navigation.navigate(DASHBOARD_URL);
}
const [error, setError] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [projectSsoConfigList, setProjectSsoConfigList] = useState<Array<ProjectSSO>>([]);
const [error, setError] = useState<string | undefined>(undefined);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [projectSsoConfigList, setProjectSsoConfigList] = useState<
Array<ProjectSSO>
>([]);
const fetchSsoConfigs = async (email: Email) => {
if (email) {
setIsLoading(true);
try {
type FetchSSOConfigsFunction = (email: Email) => Promise<void>;
// get sso config by email.
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> = await API.get(URL.fromString(apiUrl.toString()).addQueryParam("email", email.toString()));
const fetchSsoConfigs: FetchSSOConfigsFunction = async (
email: Email,
): Promise<void> => {
if (email) {
setIsLoading(true);
try {
// get sso config by email.
const listResult: HTTPErrorResponse | HTTPResponse<JSONArray> =
await API.get(
URL.fromString(apiUrl.toString()).addQueryParam(
"email",
email.toString(),
),
);
if (listResult instanceof HTTPErrorResponse) {
throw listResult;
}
if (!listResult.data || (listResult.data as JSONArray).length === 0) {
setError("No SSO configuration found for the email: " + email.toString());
} else {
setProjectSsoConfigList(ProjectSSO.fromJSONArray(listResult['data'], ProjectSSO));
}
} catch (error) {
setError(API.getFriendlyErrorMessage(error as Error));
}
} else {
setError("Email is required to perform this action");
if (listResult instanceof HTTPErrorResponse) {
throw listResult;
}
setIsLoading(false);
};
const getSsoConfigModelList = (configs: Array<ProjectSSO>) => {
return (
<StaticModelList<ProjectSSO>
list={configs}
titleField="name"
selectedItems={[]}
descriptionField="description"
onClick={(item: ProjectSSO) => {
setIsLoading(true);
Navigation.navigate(
URL.fromURL(IDENTITY_URL).addRoute(
new Route(
`/sso/${item.projectId?.toString()}/${item.id?.toString()
}`,
),
),
);
}}
/>);
if (!listResult.data || (listResult.data as JSONArray).length === 0) {
setError(
"No SSO configuration found for the email: " + email.toString(),
);
} else {
setProjectSsoConfigList(
ProjectSSO.fromJSONArray(listResult["data"], ProjectSSO),
);
}
} catch (error) {
setError(API.getFriendlyErrorMessage(error as Error));
}
} else {
setError("Email is required to perform this action");
}
if (isLoading) {
return <PageLoader isVisible={true} />;
}
setIsLoading(false);
};
const getProjectName = (projectId: string): string => {
const projectNames = projectSsoConfigList.filter((config: ProjectSSO) => config.projectId?.toString() === projectId.toString()).map((config: ProjectSSO) => config.project?.name);
return projectNames[0] || 'Project';
}
type GetSsoConfigModelListFunction = (
configs: Array<ProjectSSO>,
) => ReactElement;
if (projectSsoConfigList.length > 0 && !error && !isLoading) {
const getSsoConfigModelList: GetSsoConfigModelListFunction = (
configs: Array<ProjectSSO>,
): ReactElement => {
return (
<StaticModelList<ProjectSSO>
list={configs}
titleField="name"
selectedItems={[]}
descriptionField="description"
onClick={(item: ProjectSSO) => {
setIsLoading(true);
Navigation.navigate(
URL.fromURL(IDENTITY_URL).addRoute(
new Route(
`/sso/${item.projectId?.toString()}/${item.id?.toString()}`,
),
),
);
}}
/>
);
};
const projectIds: Array<string> = projectSsoConfigList.map((config: ProjectSSO) => config.projectId?.toString() as string);
if (isLoading) {
return <PageLoader isVisible={true} />;
}
return (
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
Select Project
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Select the project you want to login to.
</p>
</div>
type GetProjectNameFunction = (projectId: string) => string;
{projectIds.map((projectId: string) => {
return (
<div key={projectId}>
<h3 className="mt-6 font-medium tracking-tight">
{getProjectName(projectId)}
</h3>
{getSsoConfigModelList(projectSsoConfigList.filter((config: ProjectSSO) => config.projectId?.toString() === projectId.toString()))}
</div>
)
})}
</div>
</div>
);
}
const getProjectName: GetProjectNameFunction = (
projectId: string,
): string => {
const projectNames: Array<string | undefined> = projectSsoConfigList
.filter((config: ProjectSSO) => {
return config.projectId?.toString() === projectId.toString();
})
.map((config: ProjectSSO) => {
return config.project?.name;
});
return projectNames[0] || "Project";
};
if (projectSsoConfigList.length > 0 && !error && !isLoading) {
const projectIds: Array<string> = projectSsoConfigList.map(
(config: ProjectSSO) => {
return config.projectId?.toString() as string;
},
);
return (
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Login with SSO
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Login with your SSO provider to access your account.
</p>
</div>
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-10 text-center text-xl tracking-tight text-gray-900">
Select Project
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Select the project you want to login to.
</p>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<BasicForm
modelType={User}
id="login-form"
error={error}
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
title: "Email",
dataTestId: "email",
}
]}
maxPrimaryButtonWidth={true}
submitButtonText="Login with SSO"
onSubmit={async (data: JSONObject) => {
await fetchSsoConfigs(data['email'] as Email);
}}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/login")}>
<div
className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm"
>
Use username and password insead.
</div>
</Link>
</div>
</div>
}
/>
</div>
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>
</div>
{projectIds.map((projectId: string) => {
return (
<div key={projectId}>
<h3 className="mt-6 font-medium tracking-tight">
{getProjectName(projectId)}
</h3>
{getSsoConfigModelList(
projectSsoConfigList.filter((config: ProjectSSO) => {
return (
config.projectId?.toString() === projectId.toString()
);
}),
)}
</div>
);
})}
</div>
</div>
);
}
return (
<div className="flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="">
<img
className="mx-auto h-12 w-auto"
src={OneUptimeLogo}
alt="OneUptime"
/>
<h2 className="mt-6 text-center text-2xl tracking-tight text-gray-900">
Login with SSO
</h2>
<p className="mt-2 text-center text-sm text-gray-600">
Login with your SSO provider to access your account.
</p>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<BasicForm
modelType={User}
id="login-form"
error={error}
name="Login"
fields={[
{
field: {
email: true,
},
fieldType: FormFieldSchemaType.Email,
placeholder: "jeff@example.com",
required: true,
title: "Email",
dataTestId: "email",
},
]}
maxPrimaryButtonWidth={true}
submitButtonText="Login with SSO"
onSubmit={async (data: JSONObject) => {
await fetchSsoConfigs(data["email"] as Email);
}}
footer={
<div className="actions text-center mt-4 hover:underline fw-semibold">
<div>
<Link to={new Route("/accounts/login")}>
<div className="text-indigo-500 hover:text-indigo-900 cursor-pointer text-sm">
Use username and password insead.
</div>
</Link>
</div>
</div>
}
/>
</div>
<div className="mt-10 text-center">
<div className="text-muted mb-0 text-gray-500">
Don&apos;t have an account?{" "}
<Link
to={new Route("/accounts/register")}
className="text-indigo-500 hover:text-indigo-900 cursor-pointer"
>
Register.
</Link>
</div>
</div>
</div>
</div>
);
};
export default LoginPage;

View File

@ -9,9 +9,9 @@ export const LOGIN_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route("/login"),
);
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route("/service-provider-login"),
);
export const SERVICE_PROVIDER_LOGIN_URL: URL = URL.fromURL(
IDENTITY_URL,
).addRoute(new Route("/service-provider-login"));
export const FORGOT_PASSWORD_API_URL: URL = URL.fromURL(IDENTITY_URL).addRoute(
new Route("/forgot-password"),

View File

@ -387,8 +387,6 @@ const BaseAPIFeatureSet: FeatureSet = {
const APP_NAME: string = "api";
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,
new BaseAnalyticsAPI<Log, LogServiceType>(Log, LogService).getRouter(),
@ -1208,7 +1206,6 @@ const BaseAPIFeatureSet: FeatureSet = {
app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI);
//attach api's
app.use(
`/${APP_NAME.toLocaleLowerCase()}`,

View File

@ -30,7 +30,6 @@ import Express, {
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 EmailVerificationToken from "Model/Models/EmailVerificationToken";
@ -181,24 +180,10 @@ router.post(
// Refresh Permissions for this user here.
await AccessTokenService.refreshUserAllPermissions(savedUser.id!);
const token: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: savedUser.id!,
email: savedUser.email!,
name: savedUser.name!,
timezone: savedUser.timezone || null,
isMasterAdmin: savedUser.isMasterAdmin!,
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30),
),
});
// Set a cookie with token.
CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
CookieUtil.setUserCookie({
expressResponse: res,
user: savedUser,
isGlobalLogin: true,
});
logger.info("User signed up: " + savedUser.email?.toString());
@ -577,24 +562,10 @@ router.post(
) {
logger.info("User logged in: " + alreadySavedUser.email?.toString());
const token: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: alreadySavedUser.id!,
email: alreadySavedUser.email!,
name: alreadySavedUser.name!,
timezone: alreadySavedUser.timezone || null,
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
isGlobalLogin: true, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(
new PositiveNumber(30),
),
});
// Set a cookie with token.
CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
CookieUtil.setUserCookie({
expressResponse: res,
user: alreadySavedUser,
isGlobalLogin: true,
});
return Response.sendEntityResponse(req, res, alreadySavedUser, User);

View File

@ -28,7 +28,6 @@ import Express, {
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 ProjectSSO from "Model/Models/ProjectSso";
@ -41,79 +40,111 @@ const router: ExpressRouter = Express.getRouter();
// This route is used to get the SSO config for the user.
// when the user logs in from OneUptime and not from the IDP.
router.get("/service-provider-login", async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
router.get(
"/service-provider-login",
async (req: ExpressRequest, res: ExpressResponse): Promise<void> => {
if (!req.query["email"]) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Email is required"),
);
}
if (!req.query['email']) {
return Response.sendErrorResponse(req, res, new BadRequestException("Email is required"));
}
const email: Email = new Email(req.query["email"] as string);
const email: Email = new Email(req.query['email'] as string);
if (!email) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("Email is required"),
);
}
if (!email) {
return Response.sendErrorResponse(req, res, new BadRequestException("Email is required"));
}
// get sso config for this user.
// get sso config for this user.
const user: User | null = await UserService.findOneBy({
query: { email: email },
select: {
_id: true,
},
props: {
isRoot: true,
},
});
if (!user) {
return Response.sendErrorResponse(req, res, new BadRequestException("No SSO config found for this user"));
}
const userId: ObjectID = user.id!;
if (!userId) {
return Response.sendErrorResponse(req, res, new BadRequestException("No SSO config found for this user"));
}
const projectUserBelongsTo: Array<ObjectID> = (await TeamMemberService.findBy({
query: { userId: userId },
select: {
projectId: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
})).map((teamMember: TeamMember) => teamMember.projectId!);
if (projectUserBelongsTo.length === 0) {
return Response.sendErrorResponse(req, res, new BadRequestException("No SSO config found for this user"));
}
const projectSSOList: Array<ProjectSSO> = await ProjectSSOService.findBy({
query: { projectId: QueryHelper.any(projectUserBelongsTo), isEnabled: true },
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
description: true,
_id: true,
projectId: true,
project: {
name: true,
const user: User | null = await UserService.findOneBy({
query: { email: email },
select: {
_id: true,
},
},
props: {
isRoot: true,
},
});
props: {
isRoot: true,
},
});
return Response.sendEntityArrayResponse(req, res, projectSSOList, projectSSOList.length, ProjectSSO);
if (!user) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
});
const userId: ObjectID = user.id!;
if (!userId) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
const projectUserBelongsTo: Array<ObjectID> = (
await TeamMemberService.findBy({
query: { userId: userId },
select: {
projectId: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
props: {
isRoot: true,
},
})
).map((teamMember: TeamMember) => {
return teamMember.projectId!;
});
if (projectUserBelongsTo.length === 0) {
return Response.sendErrorResponse(
req,
res,
new BadRequestException("No SSO config found for this user"),
);
}
const projectSSOList: Array<ProjectSSO> = await ProjectSSOService.findBy({
query: {
projectId: QueryHelper.any(projectUserBelongsTo),
isEnabled: true,
},
limit: LIMIT_PER_PROJECT,
skip: 0,
select: {
name: true,
description: true,
_id: true,
projectId: true,
project: {
name: true,
},
},
props: {
isRoot: true,
},
});
return Response.sendEntityArrayResponse(
req,
res,
projectSSOList,
projectSSOList.length,
ProjectSSO,
);
},
);
router.get(
"/sso/:projectId/:projectSsoId",
@ -341,9 +372,9 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
if (projectSSO.issuerURL.toString() !== issuerUrl) {
logger.error(
"Issuer URL does not match. It should be " +
projectSSO.issuerURL.toString() +
" but it is " +
issuerUrl.toString(),
projectSSO.issuerURL.toString() +
" but it is " +
issuerUrl.toString(),
);
return Response.sendErrorResponse(
req,
@ -454,39 +485,18 @@ const loginUserWithSso: LoginUserWithSsoFunction = async (
const projectId: ObjectID = new ObjectID(req.params["projectId"] as string);
const ssoToken: string = JSONWebToken.sign({
data: {
userId: alreadySavedUser.id!,
projectId: projectId,
name: alreadySavedUser.name!,
email: email,
isMasterAdmin: false,
isGeneralLogin: false,
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
alreadySavedUser.email = email;
CookieUtil.setSSOCookie({
user: alreadySavedUser,
projectId: projectId,
expressResponse: res,
});
const oneUptimeToken: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: alreadySavedUser.id!,
email: alreadySavedUser.email!,
name: alreadySavedUser.name!,
isMasterAdmin: alreadySavedUser.isMasterAdmin!,
timezone: alreadySavedUser.timezone || null,
isGlobalLogin: false, // This is a general login without SSO. So, we will set this to false. This will give access to all the projects that dont require SSO.
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
});
// Set a cookie with token.
CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), oneUptimeToken, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
});
CookieUtil.setCookie(res, CookieUtil.getUserSSOKey(projectId), ssoToken, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
CookieUtil.setUserCookie({
expressResponse: res,
user: alreadySavedUser,
isGlobalLogin: false,
});
// Refresh Permissions for this user here.

View File

@ -0,0 +1,11 @@
enum CookieName {
UserID = "user-id",
Email = "user-email",
Token = "user-token",
Name = "user-name",
Timezone = "user-timezone",
IsMasterAdmin = "user-is-master-admin",
ProfilePicID = "user-profile-pic-id",
}
export default CookieName;

View File

@ -2,13 +2,137 @@ import { ExpressRequest, ExpressResponse } from "./Express";
import Dictionary from "Common/Types/Dictionary";
import ObjectID from "Common/Types/ObjectID";
import { CookieOptions } from "express";
import JSONWebToken from "./JsonWebToken";
import User from "Model/Models/User";
import OneUptimeDate from "Common/Types/Date";
import PositiveNumber from "Common/Types/PositiveNumber";
import CookieName from "Common/Types/CookieName";
export default class CookieUtil {
// set cookie with express response
public static setSSOCookie(data: {
user: User;
projectId: ObjectID;
expressResponse: ExpressResponse;
}): void {
const { user, projectId, expressResponse: res } = data;
const ssoToken: string = JSONWebToken.sign({
data: {
userId: user.id!,
projectId: projectId,
name: user.name!,
email: user.email,
isMasterAdmin: false,
isGeneralLogin: false,
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
});
CookieUtil.setCookie(res, CookieUtil.getUserSSOKey(projectId), ssoToken, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
});
}
public static setUserCookie(data: {
expressResponse: ExpressResponse;
user: User;
isGlobalLogin: boolean;
}): void {
const { expressResponse: res, user, isGlobalLogin } = data;
const token: string = JSONWebToken.signUserLoginToken({
tokenData: {
userId: user.id!,
email: user.email!,
name: user.name!,
timezone: user.timezone || null,
isMasterAdmin: user.isMasterAdmin!,
isGlobalLogin: isGlobalLogin, // This is a general login without SSO. So, we will set this to true. This will give access to all the projects that dont require SSO.
},
expiresInSeconds: OneUptimeDate.getSecondsInDays(new PositiveNumber(30)),
});
// Set a cookie with token.
CookieUtil.setCookie(res, CookieUtil.getUserTokenKey(), token, {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: true,
});
if (user.id) {
// set user id cookie
CookieUtil.setCookie(res, CookieName.UserID, user.id!.toString(), {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
});
}
if (user.email) {
// set user email cookie
CookieUtil.setCookie(
res,
CookieName.Email,
user.email?.toString() || "",
{
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
},
);
}
if (user.name) {
// set user name cookie
CookieUtil.setCookie(res, CookieName.Name, user.name?.toString() || "", {
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
});
}
if (user.timezone) {
// set user timezone cookie
CookieUtil.setCookie(
res,
CookieName.Timezone,
user.timezone?.toString() || "",
{
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
},
);
}
if (user.isMasterAdmin) {
// set user isMasterAdmin cookie
CookieUtil.setCookie(
res,
CookieName.IsMasterAdmin,
user.isMasterAdmin?.toString() || "",
{
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
},
);
}
if (user.profilePictureId) {
// set user profile picture id cookie
CookieUtil.setCookie(
res,
CookieName.ProfilePicID,
user.profilePictureId?.toString() || "",
{
maxAge: OneUptimeDate.getMillisecondsInDays(new PositiveNumber(30)),
httpOnly: false,
},
);
}
}
public static setCookie(
res: ExpressResponse,
name: string,
name: string | CookieName,
value: string,
options: CookieOptions,
): void {
@ -37,10 +161,10 @@ export default class CookieUtil {
public static getUserTokenKey(id?: ObjectID): string {
if (!id) {
return `user-token`;
return CookieName.Token;
}
return `user-token-${id.toString()}`;
return `${CookieName.Token}-${id.toString()}`;
}
public static getUserSSOKey(id: ObjectID): string {

View File

@ -121,7 +121,6 @@ const BasicForm: ForwardRefExoticComponent<any> = forwardRef(
null,
);
useEffect(() => {
if (formSteps && formSteps.length > 0 && formSteps[0]) {
setCurrentFormStepId(formSteps[0].id);

View File

@ -97,5 +97,4 @@ export default interface Field<TEntity> {
// set this to true if you want to show this field in the form even when the form is in edit mode.
doNotShowWhenEditing?: boolean | undefined;
doNotShowWhenCreating?: boolean | undefined;
}

View File

@ -6,10 +6,11 @@ import { JSONObject, JSONValue } from "Common/Types/JSON";
import JSONFunctions from "Common/Types/JSONFunctions";
import Typeof from "Common/Types/Typeof";
import UniversalCookies, { CookieSetOptions } from "universal-cookie";
import CookieName from "Common/Types/CookieName";
export default class Cookie {
public static setItem(
key: string,
key: CookieName,
value: JSONValue | Email | URL,
options?:
| {
@ -42,9 +43,9 @@ export default class Cookie {
cookies.set(key, value as string, cookieOptions);
}
public static getItem(key: string): JSONValue {
public static getItem(cookieName: CookieName): JSONValue {
const cookies: UniversalCookies = new UniversalCookies();
const value: JSONValue = cookies.get(key) as JSONValue;
const value: JSONValue = cookies.get(cookieName) as JSONValue;
try {
if (value) {
@ -58,13 +59,13 @@ export default class Cookie {
}
}
public static removeItem(key: string): void {
public static removeItem(key: CookieName): void {
const cookies: UniversalCookies = new UniversalCookies();
cookies.remove(key);
}
// check if cookie exists
public static exists(key: string): boolean {
public static exists(key: CookieName): boolean {
const cookies: UniversalCookies = new UniversalCookies();
return Boolean(cookies.get(key));
}

View File

@ -1,9 +1,8 @@
import { JSONObject } from "Common/Types/JSON";
import { jwtDecode } from "jwt-decode";
export default class JWTToken {
public static decodeToken(token: string): JSONObject {
return jwtDecode(token);
}
}
export default class JWTToken {
public static decodeToken(token: string): JSONObject {
return jwtDecode(token);
}
}

View File

@ -4,12 +4,13 @@ import URL from "Common/Types/API/URL";
import Dictionary from "Common/Types/Dictionary";
import Email from "Common/Types/Email";
import BadDataException from "Common/Types/Exception/BadDataException";
import { JSONObject } from "Common/Types/JSON";
import { JSONObject, JSONValue } from "Common/Types/JSON";
import Name from "Common/Types/Name";
import ObjectID from "Common/Types/ObjectID";
import Timezone from "Common/Types/Timezone";
import API from "Common/Utils/API";
import JWTToken from "./JWT";
import Cookie from "./Cookie";
import CookieName from "Common/Types/CookieName";
export default class User {
public static setProfilePicId(id: ObjectID | null): void {
@ -22,6 +23,16 @@ export default class User {
}
public static getProfilePicId(): ObjectID | null {
// check cookie first.
const profilePicIdCookie: JSONValue | string = Cookie.getItem(
CookieName.ProfilePicID,
);
if (profilePicIdCookie) {
return new ObjectID(profilePicIdCookie as string);
}
if (!LocalStorage.getItem("profile_pic_id")) {
return null;
}
@ -36,6 +47,16 @@ export default class User {
}
public static getSavedUserTimezone(): Timezone {
// check cookie first.
const userTimezoneCookie: JSONValue | string = Cookie.getItem(
CookieName.Timezone,
);
if (userTimezoneCookie) {
return userTimezoneCookie as Timezone;
}
return LocalStorage.getItem("user_timezone") as Timezone;
}
@ -52,10 +73,24 @@ export default class User {
}
public static getUserId(): ObjectID {
// check cookie first.
const userIdCookie: JSONValue | string = Cookie.getItem(CookieName.UserID);
if (userIdCookie) {
return new ObjectID(userIdCookie as string);
}
return new ObjectID((LocalStorage.getItem("user_id") as string) || "");
}
public static getName(): Name {
// check cookie first.
const userNameCookie: JSONValue | string = Cookie.getItem(CookieName.Name);
if (userNameCookie) {
return new Name(userNameCookie as string);
}
return new Name((LocalStorage.getItem("user_name") as string) || "");
}
@ -63,32 +98,17 @@ export default class User {
LocalStorage.setItem("user_name", name.toString());
}
public static refreshUserDataFromToken(token: string): void {
const decodedToken: JSONObject = JWTToken.decodeToken(token);
if(decodedToken["userId"]) {
this.setUserId(new ObjectID(decodedToken["userId"] as string));
}
if(decodedToken["email"]) {
this.setEmail(new Email(decodedToken["email"] as string));
}
if(decodedToken["name"]) {
this.setName(new Name(decodedToken["name"] as string));
}
if(decodedToken["isMasterAdmin"]) {
this.setIsMasterAdmin(decodedToken["isMasterAdmin"] as boolean);
}
if(decodedToken["timezone"]) {
this.setSavedUserTimezone(decodedToken["timezone"] as Timezone);
}
}
public static getEmail(): Email | null {
// check cookie first.
const userEmailCookie: JSONValue | string = Cookie.getItem(
CookieName.Email,
);
if (userEmailCookie) {
return new Email(userEmailCookie as string);
}
if (!LocalStorage.getItem("user_email")) {
return null;
}
@ -138,6 +158,16 @@ export default class User {
}
public static isMasterAdmin(): boolean {
// check cookie first.
const isMasterAdminCookie: JSONValue | string = Cookie.getItem(
CookieName.IsMasterAdmin,
);
if (isMasterAdminCookie) {
return isMasterAdminCookie === "true" || isMasterAdminCookie === true;
}
return LocalStorage.getItem("is_master_admin") as boolean;
}

View File

@ -54,9 +54,6 @@ import {
useParams,
} from "react-router-dom";
import useAsyncEffect from "use-async-effect";
import User from "CommonUI/src/Utils/User";
import Cookie from "CommonUI/src/Utils/Cookie";
import { JSONValue } from "Common/Types/JSON";
const App: () => JSX.Element = () => {
Navigation.setNavigateHook(useNavigate());
@ -78,18 +75,6 @@ const App: () => JSX.Element = () => {
const [hasPaymentMethod, setHasPaymentMethod] = useState<boolean>(false);
useEffect(() => {
const token: JSONValue = Cookie.getItem("user_token");
if(!token) {
return;
}
User.refreshUserDataFromToken(token.toString());
}, []);
useAsyncEffect(async () => {
try {
if (selectedProject && selectedProject._id) {

View File

@ -18,9 +18,8 @@ export interface ComponentProps {
const MonitorCriteriaIncidentForm: FunctionComponent<ComponentProps> = (
props: ComponentProps,
): ReactElement => {
const [showAdvancedFields, setShowAdvancedFields] = React.useState<boolean>(false);
const [showAdvancedFields, setShowAdvancedFields] =
React.useState<boolean>(false);
useEffect(() => {
if (props.initialValue && props.initialValue.remediationNotes) {
@ -111,12 +110,20 @@ const MonitorCriteriaIncidentForm: FunctionComponent<ComponentProps> = (
required: false,
showIf: () => {
return showAdvancedFields;
}
},
},
]}
/>
{!showAdvancedFields && <Button title="Show Advanced Fields" onClick={() => setShowAdvancedFields(true)} buttonStyle={ButtonStyleType.SECONDARY_LINK} />}
{!showAdvancedFields && (
<Button
title="Show Advanced Fields"
onClick={() => {
return setShowAdvancedFields(true);
}}
buttonStyle={ButtonStyleType.SECONDARY_LINK}
/>
)}
{/* <div className='mt-4'>
<Button

View File

@ -590,7 +590,7 @@ const IncidentView: FunctionComponent<
}}
/>
<CardModelDetail
<CardModelDetail
name="Remediation Notes"
cardProps={{
title: "Remediation Notes",
@ -628,7 +628,6 @@ const IncidentView: FunctionComponent<
modelId: modelId,
}}
/>
</Fragment>
);
};

View File

@ -1016,8 +1016,6 @@ export default class Incident extends BaseModel {
})
public isCreatedAutomatically?: boolean = undefined;
@ColumnAccessControl({
create: [
Permission.ProjectOwner,
@ -1050,6 +1048,4 @@ export default class Incident extends BaseModel {
type: ColumnType.Markdown,
})
public remediationNotes?: string = undefined;
}