From 3d2b2cbbab3c091fe2fd090214775e7c5b5da67d Mon Sep 17 00:00:00 2001 From: = Date: Thu, 8 May 2025 16:03:43 +0530 Subject: [PATCH] feat: updated logic to have login sso --- backend/src/services/user/user-service.ts | 2 +- frontend/src/hooks/api/users/queries.tsx | 15 +- .../components/PasswordStep/PasswordStep.tsx | 23 ++- .../EmailDuplicationConfirmation.tsx | 149 +++++++++++++++++ .../auth/SelectOrgPage/SelectOrgPage.tsx | 150 ++---------------- 5 files changed, 190 insertions(+), 149 deletions(-) create mode 100644 frontend/src/pages/auth/SelectOrgPage/EmailDuplicationConfirmation.tsx diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index baf34887f..ffc390290 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -151,7 +151,7 @@ export const userServiceFactory = ({ if (duplicatedAccounts.length && myAccount) { await userDAL.transaction(async (tx) => { await userDAL.delete({ $in: { id: duplicatedAccounts?.map((el) => el.id) } }, tx); - await userDAL.updateById(userId, { username: myAccount.username.toLowerCase() }, tx); + await userDAL.updateById(userId, { username: (myAccount.email || myAccount.username).toLowerCase() }, tx); }); } }; diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index 8bd891b4b..9a65b12fa 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -37,15 +37,18 @@ export const useGetUser = () => queryFn: fetchUserDetails }); +export const fetchUserDuplicateAccounts = async () => { + const { data } = await apiRequest.get<{ + users: Array; + }>("/api/v1/user/duplicate-accounts"); + return data.users; +}; + export const useGetMyDuplicateAccount = () => useQuery({ queryKey: userKeys.getMyDuplicateAccount, - queryFn: async () => { - const { data } = await apiRequest.get<{ - users: Array; - }>("/api/v1/user/duplicate-accounts"); - return data.users; - }, + staleTime: 60 * 1000, // 1 min in ms + queryFn: fetchUserDuplicateAccounts, select: (users) => ({ duplicateAccounts: users.filter((el) => !el.isMyAccount), myAccount: users?.find((el) => el.isMyAccount) diff --git a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx index 529509b49..91432f9be 100644 --- a/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx +++ b/frontend/src/pages/auth/LoginPage/components/PasswordStep/PasswordStep.tsx @@ -18,7 +18,8 @@ import { useToggle } from "@app/hooks"; import { useOauthTokenExchange, useSelectOrganization } from "@app/hooks/api"; import { MfaMethod } from "@app/hooks/api/auth/types"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; -import { fetchMyPrivateKey } from "@app/hooks/api/users/queries"; +import { fetchMyPrivateKey, fetchUserDuplicateAccounts } from "@app/hooks/api/users/queries"; +import { EmailDuplicationConfirmation } from "@app/pages/auth/SelectOrgPage/EmailDuplicationConfirmation"; import { navigateUserToOrg, useNavigateToSelectOrganization } from "../../Login.utils"; @@ -40,6 +41,7 @@ export const PasswordStep = ({ const [isLoading, setIsLoading] = useState(false); const { t } = useTranslation(); const navigate = useNavigate(); + const [removeDuplicateLater, setRemoveDuplicateLater] = useState(true); const { mutateAsync: selectOrganization } = useSelectOrganization(); const { mutateAsync: oauthTokenExchange } = useOauthTokenExchange(); const [shouldShowMfa, toggleShowMfa] = useToggle(false); @@ -109,6 +111,13 @@ export const PasswordStep = ({ return; } + const userDuplicateAccount = await fetchUserDuplicateAccounts(); + const hasDuplicate = userDuplicateAccount?.length > 1; + if (hasDuplicate) { + setRemoveDuplicateLater(false); + return; + } + await navigateUserToOrg(navigate, organizationId); }; @@ -306,6 +315,18 @@ export const PasswordStep = ({ ); } + if (!removeDuplicateLater) { + return ( + + navigateUserToOrg(navigate, organizationId).catch(() => + createNotification({ text: "Failed to navigate user", type: "error" }) + ) + } + /> + ); + } + if (hasExchangedPrivateKey) { return (
diff --git a/frontend/src/pages/auth/SelectOrgPage/EmailDuplicationConfirmation.tsx b/frontend/src/pages/auth/SelectOrgPage/EmailDuplicationConfirmation.tsx new file mode 100644 index 000000000..bd98b51b1 --- /dev/null +++ b/frontend/src/pages/auth/SelectOrgPage/EmailDuplicationConfirmation.tsx @@ -0,0 +1,149 @@ +import { useCallback } from "react"; +import { Helmet } from "react-helmet"; +import { useTranslation } from "react-i18next"; +import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { Link, useNavigate } from "@tanstack/react-router"; +import { format } from "date-fns"; + +import { createNotification } from "@app/components/notifications"; +import { Alert, Button, Tooltip } from "@app/components/v2"; +import { + useGetMyDuplicateAccount, + useLogoutUser, + useRemoveMyDuplicateAccounts +} from "@app/hooks/api"; + +type Props = { + onRemoveDuplicateLater: () => void; +}; + +export const EmailDuplicationConfirmation = ({ onRemoveDuplicateLater }: Props) => { + const duplicateAccounts = useGetMyDuplicateAccount(); + const removeDuplicateEmails = useRemoveMyDuplicateAccounts(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const logout = useLogoutUser(true); + const handleLogout = useCallback(async () => { + try { + console.log("Logging out..."); + await logout.mutateAsync(); + navigate({ to: "/login" }); + } catch (error) { + console.error(error); + } + }, [logout, navigate]); + + return ( +
+ + {t("common.head-title", { title: t("login.title") })} + + + + + +
+ +
+ Infisical logo +
+ +
+
+

+ Multiple Accounts Detected +

+

+ Your current account is: {" "} + {duplicateAccounts?.data?.myAccount?.username}. +

+ +
+ We've detected multiple accounts using variations of the same email address. + Confirm to retain this account {duplicateAccounts?.data?.myAccount?.username} + . Upon confirmation other accounts will be removed. +
+
+
+
+ {duplicateAccounts?.data?.duplicateAccounts?.map((el) => { + const lastSession = el.devices?.at(-1); + return ( +
+
+
{el.username}
+
+ Last login: {format(new Date(el.updatedAt), "Pp")} +
+
+
+ +
IP: {lastSession?.ip || "-"}
+
User Agent: {lastSession?.userAgent || "-"}
+
+ } + > + + +
+
+ ); + })} +
+
+
+ + +
+ +
+ +
+
+
+ ); +}; diff --git a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx index df3c1497c..f66be2d4b 100644 --- a/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx +++ b/frontend/src/pages/auth/SelectOrgPage/SelectOrgPage.tsx @@ -1,19 +1,9 @@ -import { useCallback, useState } from "react"; -import { Helmet } from "react-helmet"; -import { useTranslation } from "react-i18next"; -import { faInfoCircle } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Link, useNavigate } from "@tanstack/react-router"; -import { format } from "date-fns"; +import { useState } from "react"; -import { createNotification } from "@app/components/notifications"; -import { Alert, Button, Spinner, Tooltip } from "@app/components/v2"; -import { - useGetMyDuplicateAccount, - useLogoutUser, - useRemoveMyDuplicateAccounts -} from "@app/hooks/api"; +import { Spinner } from "@app/components/v2"; +import { useGetMyDuplicateAccount } from "@app/hooks/api"; +import { EmailDuplicationConfirmation } from "./EmailDuplicationConfirmation"; import { SelectOrganizationSection } from "./SelectOrgSection"; const LoadingScreen = () => { @@ -27,139 +17,17 @@ const LoadingScreen = () => { export const SelectOrganizationPage = () => { const duplicateAccounts = useGetMyDuplicateAccount(); - const removeDuplicateEmails = useRemoveMyDuplicateAccounts(); const [removeDuplicateLater, setRemoveDuplicateLater] = useState(false); - const { t } = useTranslation(); - const navigate = useNavigate(); - const logout = useLogoutUser(true); - const handleLogout = useCallback(async () => { - try { - console.log("Logging out..."); - await logout.mutateAsync(); - navigate({ to: "/login" }); - } catch (error) { - console.error(error); - } - }, [logout, navigate]); if (duplicateAccounts.isPending) { return ; } - if (!duplicateAccounts.data?.duplicateAccounts?.length || removeDuplicateLater) { - return ; + if (duplicateAccounts.data?.duplicateAccounts?.length && !removeDuplicateLater) { + return ( + setRemoveDuplicateLater(true)} /> + ); } - return ( -
- - {t("common.head-title", { title: t("login.title") })} - - - - - -
- -
- Infisical logo -
- -
-
-

- Multiple Accounts Detected -

-

- Your current account is: {" "} - {duplicateAccounts?.data?.myAccount?.username}. -

- -
- We've detected multiple accounts using variations of the same email address. - Confirm to retain this account {duplicateAccounts?.data?.myAccount?.username} - . Upon confirmation other accounts will be removed. -
-
-
-
- {duplicateAccounts?.data?.duplicateAccounts?.map((el) => { - const lastSession = el.devices?.at(-1); - return ( -
-
-
{el.username}
-
- Last login: {format(new Date(el.updatedAt), "Pp")} -
-
-
- -
IP: {lastSession?.ip || "-"}
-
User Agent: {lastSession?.userAgent || "-"}
-
- } - > - - -
-
- ); - })} -
-
-
- - -
- -
- -
-
-
- ); + return ; };