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 jwt_decode from "jwt-decode"; import Error from "@app/components/basic/Error"; import { createNotification } from "@app/components/notifications"; import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; import { Button } from "@app/components/v2"; import { useUpdateUserAuthMethods } from "@app/hooks/api"; import { useSendMfaToken } from "@app/hooks/api/auth"; import { useSelectOrganization } 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, navigateUserToSelectOrg } from "../../Login.utils"; // The style for the verification code input const props = { inputStyle: { fontFamily: "monospace", margin: "4px", MozAppearance: "textfield", width: "48px", borderRadius: "5px", fontSize: "24px", height: "48px", paddingLeft: "7", backgroundColor: "#0d1117", color: "white", border: "1px solid #2d2f33", textAlign: "center", outlineColor: "#8ca542", borderColor: "#2d2f33" } } as const; type Props = { email: string; password: string; providerAuthToken?: string; callbackPort?: string | null; }; export const MFAStep = ({ email, password, providerAuthToken }: Props) => { const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const [isLoadingResend, setIsLoadingResend] = useState(false); const [mfaCode, setMfaCode] = useState(""); const [triesLeft, setTriesLeft] = useState(undefined); const { t } = useTranslation(); const sendMfaToken = useSendMfaToken(); const { mutateAsync: updateUserAuthMethodsMutateAsync } = useUpdateUserAuthMethods(); const { mutateAsync: selectOrganization } = useSelectOrganization(); const handleLoginMfa = async () => { try { let isLinkingRequired: undefined | boolean; 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", type: "error" }); return; } setIsLoading(true); if (callbackPort) { // attemptCliLogin const isCliLoginSuccessful = await attemptCliLoginMfa({ email, password, providerAuthToken, mfaToken: mfaCode }); if (isCliLoginSuccessful && isCliLoginSuccessful.success) { const cliUrl = `http://127.0.0.1:${callbackPort}/`; // 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 }); 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({ email, password, providerAuthToken, mfaToken: mfaCode }); if (isLoginSuccessful) { setIsLoading(false); // case: login does not require MFA step createNotification({ text: "Successfully logged in", type: "success" }); if (isLinkingRequired && authMethod) { const user = await fetchUserDetails(); const newAuthMethods = [...user.authMethods, authMethod]; await updateUserAuthMethodsMutateAsync({ authMethods: newAuthMethods }); } if (organizationId) { await navigateUserToOrg(router, organizationId); } else { navigateUserToSelectOrg(router); } } else { createNotification({ text: "Failed to log in", type: "error" }); } } } catch (err: any) { if (err.response.data.error === "User Locked") { createNotification({ title: err.response.data.error, text: err.response.data.message, type: "error" }); setIsLoading(false); return; } createNotification({ text: "Failed to log in", type: "error" }); if (triesLeft) { setTriesLeft((left) => { if (triesLeft === 1) { router.push("/"); } return (left as number) - 1; }); } else { setTriesLeft(2); } setIsLoading(false); } }; const handleResendMfaCode = async () => { try { setIsLoadingResend(true); await sendMfaToken.mutateAsync({ email }); setIsLoadingResend(false); } catch (err) { console.error(err); setIsLoadingResend(false); } }; return (

{t("mfa.step2-message")}

{email}

{typeof triesLeft === "number" && ( )}
{t("signup.step2-resend-alert")}

{t("signup.step2-spam-alert")}

); };