From de715c03ad8e7208a2569f900f3e833bf8b62a41 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard <62331820+DanielHougaard@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:40:11 +0100 Subject: [PATCH] Fix: Org scoped JWT's, MFA support --- .../Login/components/MFAStep/MFAStep.tsx | 126 +++++++++++------- 1 file changed, 79 insertions(+), 47 deletions(-) diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 89e6c2f70..8fdd831bd 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -2,20 +2,22 @@ import React, { useState } from "react"; import ReactCodeInput from "react-code-input"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/router"; -import axios from "axios" +import axios from "axios"; import jwt_decode from "jwt-decode"; import Error from "@app/components/basic/Error"; // which to notification import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; -import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa" +import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; -import { Button } from "@app/components/v2"; +import { Button } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; import { useSendMfaToken } from "@app/hooks/api/auth"; +import { selectOrganization } from "@app/hooks/api/auth/queries"; +import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchUserDetails } from "@app/hooks/api/users/queries"; import { AuthMethod } from "@app/hooks/api/users/types"; -import { navigateUserToOrg } from "../../Login.utils"; +import { navigateUserToOrg, navigateUserToSelectOrg } from "../../Login.utils"; // The style for the verification code input const props = { @@ -42,7 +44,7 @@ type Props = { password: string; providerAuthToken?: string; callbackPort?: string | null; -} +}; interface VerifyMfaTokenError { response: { @@ -56,11 +58,7 @@ interface VerifyMfaTokenError { }; } -export const MFAStep = ({ - email, - password, - providerAuthToken -}: Props) => { +export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const { createNotification } = useNotificationContext(); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); @@ -71,7 +69,7 @@ export const MFAStep = ({ const { t } = useTranslation(); const sendMfaToken = useSendMfaToken(); - const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); + const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); const handleLoginMfa = async () => { try { @@ -79,16 +77,20 @@ export const MFAStep = ({ let callbackPort: undefined | string; let authMethod: undefined | AuthMethod; let organizationId: undefined | string; - + + const queryParams = new URLSearchParams(window.location.search); + + callbackPort = queryParams.get("callback_port") || undefined; + if (providerAuthToken) { const decodedToken = jwt_decode(providerAuthToken) as any; - + isLinkingRequired = decodedToken.isLinkingRequired; callbackPort = decodedToken.callbackPort; authMethod = decodedToken.authMethod; organizationId = decodedToken?.organizationId; } - + if (mfaCode.length !== 6) { createNotification({ text: "Please enter a 6-digit MFA code and try again", @@ -99,25 +101,49 @@ export const MFAStep = ({ setIsLoading(true); if (callbackPort) { - // attemptCliLogin const isCliLoginSuccessful = await attemptCliLoginMfa({ email, password, providerAuthToken, mfaToken: mfaCode - }) + }); - if (isCliLoginSuccessful && isCliLoginSuccessful.success){ - // case: login was successful - const cliUrl = `http://127.0.0.1:${callbackPort}/` + if (isCliLoginSuccessful && isCliLoginSuccessful.success) { + const cliUrl = `http://127.0.0.1:${callbackPort}/`; - // send request to server endpoint - const instance = axios.create() - await instance.post(cliUrl,{...isCliLoginSuccessful.loginResponse,email}) + // case: organization ID is present from the provider auth token -- select the org and use the new jwt token in the CLI, then navigate to the org + if (organizationId) { + const { token: newJwtToken } = await selectOrganization({ organizationId }); - // cli page - router.push("/cli-redirect"); + console.log( + "organization id was present. new JWT token to be used in CLI:", + newJwtToken + ); + + const instance = axios.create(); + await instance.post(cliUrl, { + ...isCliLoginSuccessful.loginResponse, + JTWToken: newJwtToken + }); + + await navigateUserToOrg(router, organizationId); + } + // case: no organization ID is present -- navigate to the select org page IF the user has any orgs + // if the user has no orgs, navigate to the create org page + else { + const userOrgs = await fetchOrganizations(); + + // case: user has orgs, so we navigate the user to select an org + if (userOrgs.length > 0) { + navigateUserToSelectOrg(router, callbackPort); + } + // case: no orgs found, so we navigate the user to create an org + // cli login will fail in this case + else { + await navigateUserToOrg(router); + } + } } } else { const isLoginSuccessful = await attemptLoginMfa({ @@ -126,25 +152,29 @@ export const MFAStep = ({ providerAuthToken, mfaToken: mfaCode }); - + if (isLoginSuccessful) { setIsLoading(false); // case: login does not require MFA step createNotification({ - text: "Successfully logged in", - type: "success" + text: "Successfully logged in", + type: "success" }); if (isLinkingRequired && authMethod) { const user = await fetchUserDetails(); - const newAuthMethods = [...user.authMethods, authMethod] + const newAuthMethods = [...user.authMethods, authMethod]; await updateUserAuthMethodsMutateAsync({ - authMethods: newAuthMethods + authMethods: newAuthMethods }); } - - await navigateUserToOrg(router, organizationId); + + if (organizationId) { + await navigateUserToOrg(router, organizationId); + } else { + navigateUserToSelectOrg(router); + } } else { createNotification({ text: "Failed to log in", @@ -152,7 +182,6 @@ export const MFAStep = ({ }); } } - } catch (err) { const error = err as VerifyMfaTokenError; createNotification({ @@ -184,11 +213,11 @@ export const MFAStep = ({ } }; - return ( -