diff --git a/frontend/src/const.ts b/frontend/src/const.ts index 880d2f021..d2623431d 100644 --- a/frontend/src/const.ts +++ b/frontend/src/const.ts @@ -57,6 +57,10 @@ export const plans = plansProd || plansDev; export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?"; +export enum SessionStorageKeys { + CLI_TERMINAL_TOKEN = "CLI_TERMINAL_TOKEN" +} + export const secretTagsColors = [ { id: 1, diff --git a/frontend/src/hooks/api/users/queries.tsx b/frontend/src/hooks/api/users/queries.tsx index ea3f44676..4629ce4e6 100644 --- a/frontend/src/hooks/api/users/queries.tsx +++ b/frontend/src/hooks/api/users/queries.tsx @@ -1,6 +1,7 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { apiRequest } from "@app/config/request"; +import { SessionStorageKeys } from "@app/const"; import { setAuthToken } from "@app/reactQuery"; import { APIKeyDataV2 } from "../apiKeys/types"; @@ -293,6 +294,7 @@ export const useLogoutUser = (keepQueryClient?: boolean) => { localStorage.removeItem("PRIVATE_KEY"); localStorage.removeItem("orgData.id"); localStorage.removeItem("projectData.id"); + sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN); if (!keepQueryClient) { queryClient.clear(); diff --git a/frontend/src/pages/cli-redirect.tsx b/frontend/src/pages/cli-redirect.tsx index 9838d3ea1..c617e9fb2 100644 --- a/frontend/src/pages/cli-redirect.tsx +++ b/frontend/src/pages/cli-redirect.tsx @@ -1,23 +1,89 @@ import Head from "next/head"; import Image from "next/image"; +import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { IconButton, SecretInput } from "@app/components/v2"; +import { SessionStorageKeys } from "@app/const"; +import { useTimedReset } from "@app/hooks"; + +const getTerminalCliToken = () => { + const cliTerminalTokenInfo = sessionStorage.getItem(SessionStorageKeys.CLI_TERMINAL_TOKEN); + if (!cliTerminalTokenInfo) return; + + const { expiry, data } = JSON.parse(cliTerminalTokenInfo); + if (new Date() > new Date(expiry)) { + sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN); + return; + } + + // eslint-disable-next-line + return data as string; +}; export default function CliRedirect() { + const [isUrlCopied, , setIsUrlCopied] = useTimedReset({ + initialState: false + }); + const cliToken = getTerminalCliToken(); + + const copyUrlToClipboard = () => { + if (cliToken) { + navigator.clipboard.writeText(cliToken); + setIsUrlCopied(true); + sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN); + } + }; + return (
Infisical CLI | Login Successful! -
-
+
+
Infisical Logo
-

- Head back to your terminal -

-

- You've successfully logged in to the Infisical CLI -

+ {cliToken ? ( + <> +
+

+ Login successful! +

+

+ However, your token could not be sent to the CLI +

+
+
+ +
+ + Copy to + clipboard + +
+
+

+ After copying, return to terminal screen and paste it there +

+ + ) : ( + <> +

+ Head back to your terminal +

+

+ You've successfully logged in to the Infisical CLI +

+ + )}
); diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx index 22006408e..349952ba5 100644 --- a/frontend/src/pages/login/select-organization.tsx +++ b/frontend/src/pages/login/select-organization.tsx @@ -7,11 +7,13 @@ import { useRouter } from "next/router"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import axios from "axios"; +import { addSeconds, formatISO } from "date-fns"; import jwt_decode from "jwt-decode"; import { createNotification } from "@app/components/notifications"; import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin"; import { Button, Spinner } from "@app/components/v2"; +import { SessionStorageKeys } from "@app/const"; import { useUser } from "@app/context"; import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; import { Organization } from "@app/hooks/api/types"; @@ -94,9 +96,19 @@ export default function LoginPage() { // send request to server endpoint const instance = axios.create(); - await instance.post(`http://127.0.0.1:${callbackPort}/`, payload); - // cli page + await instance.post(`http://127.0.0.1:${callbackPort}/`, payload).catch(() => { + // if error happens to communicate we set the token with an expiry in sessino storage + // the cli-redirect page has logic to show this to user and ask them to paste it in terminal + sessionStorage.setItem( + SessionStorageKeys.CLI_TERMINAL_TOKEN, + JSON.stringify({ + expiry: formatISO(addSeconds(new Date(), 30)), + data: window.btoa(JSON.stringify(payload)) + }) + ); + }); router.push("/cli-redirect"); + // cli page } else { navigateUserToOrg(router, organization.id); } diff --git a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx index 5f04454bc..70f3eee25 100644 --- a/frontend/src/views/Login/components/MFAStep/MFAStep.tsx +++ b/frontend/src/views/Login/components/MFAStep/MFAStep.tsx @@ -3,6 +3,7 @@ import ReactCodeInput from "react-code-input"; import { useTranslation } from "react-i18next"; import { useRouter } from "next/router"; import axios from "axios"; +import { addSeconds, formatISO } from "date-fns"; import jwt_decode from "jwt-decode"; import Error from "@app/components/basic/Error"; @@ -11,6 +12,7 @@ import attemptCliLoginMfa from "@app/components/utilities/attemptCliLoginMfa"; import attemptLoginMfa from "@app/components/utilities/attemptLoginMfa"; import SecurityClient from "@app/components/utilities/SecurityClient"; import { Button } from "@app/components/v2"; +import { SessionStorageKeys } from "@app/const"; import { useSendMfaToken } from "@app/hooks/api/auth"; import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries"; @@ -79,11 +81,24 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => { if (callbackPort) { const cliUrl = `http://127.0.0.1:${callbackPort}/`; const instance = axios.create(); - await instance.post(cliUrl, { + const payload = { email, privateKey, JTWToken: newJwtToken + }; + await instance.post(cliUrl, payload).catch(() => { + // if error happens to communicate we set the token with an expiry in sessino storage + // the cli-redirect page has logic to show this to user and ask them to paste it in terminal + sessionStorage.setItem( + SessionStorageKeys.CLI_TERMINAL_TOKEN, + JSON.stringify({ + expiry: formatISO(addSeconds(new Date(), 30)), + data: window.btoa(JSON.stringify(payload)) + }) + ); }); + router.push("/cli-redirect"); + return; } await navigateUserToOrg(router, organizationId); } diff --git a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx index 297c20bf8..661c8505e 100644 --- a/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx +++ b/frontend/src/views/ShareSecretPublicPage/ShareSecretPublicPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo } from "react"; +import { useMemo } from "react"; import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; @@ -20,7 +20,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean const [hashedHex, key] = urlEncodedPublicKey ? urlEncodedPublicKey.toString().split("-") : ["", ""]; - + const publicKey = decodeURIComponent(urlEncodedPublicKey as string); const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex( id as string, @@ -46,12 +46,6 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean initialState: false }); - useEffect(() => { - if (isUrlCopied) { - setTimeout(() => setIsUrlCopied(false), 2000); - } - }, [isUrlCopied]); - const copyUrlToClipboard = () => { navigator.clipboard.writeText(decryptedSecret); setIsUrlCopied(true); @@ -59,12 +53,12 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const); return ( -
+
Secret Shared | Infisical -
+
@@ -77,9 +71,15 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean />
-
-

- {id ? "Someone shared a secret via Infisical with you" : "Share a secret via Infisical"} +
+

+ {id + ? "Someone shared a secret via Infisical with you" + : "Share a secret via Infisical"}

@@ -114,7 +114,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100" >
)} -
+
-
-
-

- Open source secret management for developers +

+
+

+ Open source{" "} + + secret management + {" "} + for developers

-
+

Infisical - {" "} is the all-in-one secret management platform to securely manage secrets, configs, and certificates across your team and infrastructure. + {" "} + is the all-in-one secret management platform to securely manage secrets, configs, + and certificates across your team and infrastructure.

- - Try Infisical + + Try Infisical
- +