From 48b095f5487c861e103c3c955ab386642094d66f Mon Sep 17 00:00:00 2001 From: Simon Larsen Date: Tue, 18 Jun 2024 12:44:54 +0100 Subject: [PATCH] refactor: Update import statements for TimezoneUtil in multiple files --- Accounts/src/Pages/Login.tsx | 5 +- Accounts/src/Pages/LoginWithSSO.tsx | 352 ++++++++++-------- Accounts/src/Utils/ApiPaths.ts | 6 +- App/FeatureSet/BaseAPI/Index.ts | 3 - App/FeatureSet/Identity/API/Authentication.ts | 45 +-- App/FeatureSet/Identity/API/SSO.ts | 212 ++++++----- Common/Types/CookieName.ts | 11 + CommonServer/Utils/Cookie.ts | 130 ++++++- CommonUI/src/Components/Forms/BasicForm.tsx | 1 - CommonUI/src/Components/Forms/Types/Field.ts | 1 - CommonUI/src/Utils/Cookie.ts | 11 +- CommonUI/src/Utils/JWT.ts | 11 +- CommonUI/src/Utils/User.ts | 84 +++-- Dashboard/src/App.tsx | 15 - .../Monitor/MonitorCriteriaIncidentForm.tsx | 17 +- Dashboard/src/Pages/Incidents/View/Index.tsx | 3 +- Model/Models/Incident.ts | 4 - 17 files changed, 532 insertions(+), 379 deletions(-) create mode 100644 Common/Types/CookieName.ts diff --git a/Accounts/src/Pages/Login.tsx b/Accounts/src/Pages/Login.tsx index 3bf35f0268..cf8add7f73 100644 --- a/Accounts/src/Pages/Login.tsx +++ b/Accounts/src/Pages/Login.tsx @@ -22,7 +22,6 @@ const LoginPage: () => JSX.Element = () => { Navigation.navigate(DASHBOARD_URL); } - const [initialValues, setInitialValues] = React.useState({}); useAsyncEffect(async () => { @@ -105,9 +104,7 @@ const LoginPage: () => JSX.Element = () => {
-
+
Use single sign-on (SSO) instead
diff --git a/Accounts/src/Pages/LoginWithSSO.tsx b/Accounts/src/Pages/LoginWithSSO.tsx index 0017dc4bfd..94141a3823 100644 --- a/Accounts/src/Pages/LoginWithSSO.tsx +++ b/Accounts/src/Pages/LoginWithSSO.tsx @@ -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(undefined); - const [isLoading, setIsLoading] = useState(false); - const [projectSsoConfigList, setProjectSsoConfigList] = useState>([]); + const [error, setError] = useState(undefined); + const [isLoading, setIsLoading] = useState(false); + const [projectSsoConfigList, setProjectSsoConfigList] = useState< + Array + >([]); - const fetchSsoConfigs = async (email: Email) => { - if (email) { - setIsLoading(true); - try { + type FetchSSOConfigsFunction = (email: Email) => Promise; - // get sso config by email. - const listResult: HTTPErrorResponse | HTTPResponse = await API.get(URL.fromString(apiUrl.toString()).addQueryParam("email", email.toString())); + const fetchSsoConfigs: FetchSSOConfigsFunction = async ( + email: Email, + ): Promise => { + if (email) { + setIsLoading(true); + try { + // get sso config by email. + const listResult: HTTPErrorResponse | HTTPResponse = + 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) => { - return ( - - - 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 ; - } + 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, + ) => ReactElement; - if (projectSsoConfigList.length > 0 && !error && !isLoading) { + const getSsoConfigModelList: GetSsoConfigModelListFunction = ( + configs: Array, + ): ReactElement => { + return ( + + 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 = projectSsoConfigList.map((config: ProjectSSO) => config.projectId?.toString() as string); + if (isLoading) { + return ; + } - return ( -
-
-
- OneUptime -

- Select Project -

-

- Select the project you want to login to. -

-
+ type GetProjectNameFunction = (projectId: string) => string; - {projectIds.map((projectId: string) => { - return ( -
-

- {getProjectName(projectId)} -

- {getSsoConfigModelList(projectSsoConfigList.filter((config: ProjectSSO) => config.projectId?.toString() === projectId.toString()))} -
- ) - })} -
-
- ); - - } + const getProjectName: GetProjectNameFunction = ( + projectId: string, + ): string => { + const projectNames: Array = 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 = projectSsoConfigList.map( + (config: ProjectSSO) => { + return config.projectId?.toString() as string; + }, + ); return ( +
-
- OneUptime -

- Login with SSO -

-

- Login with your SSO provider to access your account. -

-
+
+ OneUptime +

+ Select Project +

+

+ Select the project you want to login to. +

+
- - -
-
- { - await fetchSsoConfigs(data['email'] as Email); - }} - footer={ -
-
- -
- Use username and password insead. -
- -
-
- } - /> -
-
-
- Don't have an account?{" "} - - Register. - -
-
-
+ {projectIds.map((projectId: string) => { + return ( +
+

+ {getProjectName(projectId)} +

+ {getSsoConfigModelList( + projectSsoConfigList.filter((config: ProjectSSO) => { + return ( + config.projectId?.toString() === projectId.toString() + ); + }), + )} +
+ ); + })}
+
); + } + + return ( +
+
+ OneUptime +

+ Login with SSO +

+

+ Login with your SSO provider to access your account. +

+
+ +
+
+ { + await fetchSsoConfigs(data["email"] as Email); + }} + footer={ +
+
+ +
+ Use username and password insead. +
+ +
+
+ } + /> +
+
+
+ Don't have an account?{" "} + + Register. + +
+
+
+
+ ); }; export default LoginPage; diff --git a/Accounts/src/Utils/ApiPaths.ts b/Accounts/src/Utils/ApiPaths.ts index 4c7a7ae860..b2bf715630 100644 --- a/Accounts/src/Utils/ApiPaths.ts +++ b/Accounts/src/Utils/ApiPaths.ts @@ -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"), diff --git a/App/FeatureSet/BaseAPI/Index.ts b/App/FeatureSet/BaseAPI/Index.ts index 69be7e4ecf..3307fcd1c7 100644 --- a/App/FeatureSet/BaseAPI/Index.ts +++ b/App/FeatureSet/BaseAPI/Index.ts @@ -387,8 +387,6 @@ const BaseAPIFeatureSet: FeatureSet = { const APP_NAME: string = "api"; - - app.use( `/${APP_NAME.toLocaleLowerCase()}`, new BaseAnalyticsAPI(Log, LogService).getRouter(), @@ -1208,7 +1206,6 @@ const BaseAPIFeatureSet: FeatureSet = { app.use(`/${APP_NAME.toLocaleLowerCase()}`, NotificationAPI); - //attach api's app.use( `/${APP_NAME.toLocaleLowerCase()}`, diff --git a/App/FeatureSet/Identity/API/Authentication.ts b/App/FeatureSet/Identity/API/Authentication.ts index 4f5c8a5e05..665235709a 100644 --- a/App/FeatureSet/Identity/API/Authentication.ts +++ b/App/FeatureSet/Identity/API/Authentication.ts @@ -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); diff --git a/App/FeatureSet/Identity/API/SSO.ts b/App/FeatureSet/Identity/API/SSO.ts index e498b982c2..10efa71f5b 100644 --- a/App/FeatureSet/Identity/API/SSO.ts +++ b/App/FeatureSet/Identity/API/SSO.ts @@ -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 => { +router.get( + "/service-provider-login", + async (req: ExpressRequest, res: ExpressResponse): Promise => { + 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 = (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 = 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 = ( + 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 = 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. diff --git a/Common/Types/CookieName.ts b/Common/Types/CookieName.ts new file mode 100644 index 0000000000..1d87f8a3f1 --- /dev/null +++ b/Common/Types/CookieName.ts @@ -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; diff --git a/CommonServer/Utils/Cookie.ts b/CommonServer/Utils/Cookie.ts index 86f4ad5817..42f2b228bc 100644 --- a/CommonServer/Utils/Cookie.ts +++ b/CommonServer/Utils/Cookie.ts @@ -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 { diff --git a/CommonUI/src/Components/Forms/BasicForm.tsx b/CommonUI/src/Components/Forms/BasicForm.tsx index aef2621435..366e3f5595 100644 --- a/CommonUI/src/Components/Forms/BasicForm.tsx +++ b/CommonUI/src/Components/Forms/BasicForm.tsx @@ -121,7 +121,6 @@ const BasicForm: ForwardRefExoticComponent = forwardRef( null, ); - useEffect(() => { if (formSteps && formSteps.length > 0 && formSteps[0]) { setCurrentFormStepId(formSteps[0].id); diff --git a/CommonUI/src/Components/Forms/Types/Field.ts b/CommonUI/src/Components/Forms/Types/Field.ts index 52d89618e8..0b41c71002 100644 --- a/CommonUI/src/Components/Forms/Types/Field.ts +++ b/CommonUI/src/Components/Forms/Types/Field.ts @@ -97,5 +97,4 @@ export default interface Field { // 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; - } diff --git a/CommonUI/src/Utils/Cookie.ts b/CommonUI/src/Utils/Cookie.ts index 3a501b2dd8..50214ccdd1 100644 --- a/CommonUI/src/Utils/Cookie.ts +++ b/CommonUI/src/Utils/Cookie.ts @@ -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)); } diff --git a/CommonUI/src/Utils/JWT.ts b/CommonUI/src/Utils/JWT.ts index 987bc99817..cecee63418 100644 --- a/CommonUI/src/Utils/JWT.ts +++ b/CommonUI/src/Utils/JWT.ts @@ -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); - } -} \ No newline at end of file +export default class JWTToken { + public static decodeToken(token: string): JSONObject { + return jwtDecode(token); + } +} diff --git a/CommonUI/src/Utils/User.ts b/CommonUI/src/Utils/User.ts index 1a2589610c..58ac9f3776 100644 --- a/CommonUI/src/Utils/User.ts +++ b/CommonUI/src/Utils/User.ts @@ -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; } diff --git a/Dashboard/src/App.tsx b/Dashboard/src/App.tsx index 6d117f4e78..08d36ca59b 100644 --- a/Dashboard/src/App.tsx +++ b/Dashboard/src/App.tsx @@ -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(false); - - useEffect(() => { - const token: JSONValue = Cookie.getItem("user_token"); - - if(!token) { - return; - } - - User.refreshUserDataFromToken(token.toString()); - }, []); - - useAsyncEffect(async () => { try { if (selectedProject && selectedProject._id) { diff --git a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx index eafa8f8390..fba8b771f9 100644 --- a/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx +++ b/Dashboard/src/Components/Form/Monitor/MonitorCriteriaIncidentForm.tsx @@ -18,9 +18,8 @@ export interface ComponentProps { const MonitorCriteriaIncidentForm: FunctionComponent = ( props: ComponentProps, ): ReactElement => { - - - const [showAdvancedFields, setShowAdvancedFields] = React.useState(false); + const [showAdvancedFields, setShowAdvancedFields] = + React.useState(false); useEffect(() => { if (props.initialValue && props.initialValue.remediationNotes) { @@ -111,12 +110,20 @@ const MonitorCriteriaIncidentForm: FunctionComponent = ( required: false, showIf: () => { return showAdvancedFields; - } + }, }, ]} /> - {!showAdvancedFields &&