feat: ui option to paste token on sending token to cli fails

This commit is contained in:
=
2024-07-21 19:25:41 +05:30
parent f41272d4df
commit 1ee65205a0
6 changed files with 146 additions and 36 deletions
+4
View File
@@ -57,6 +57,10 @@ export const plans = plansProd || plansDev;
export const leaveConfirmDefaultMessage = export const leaveConfirmDefaultMessage =
"Your changes will be lost if you leave the page. Are you sure you want to continue?"; "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 = [ export const secretTagsColors = [
{ {
id: 1, id: 1,
+2
View File
@@ -1,6 +1,7 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { SessionStorageKeys } from "@app/const";
import { setAuthToken } from "@app/reactQuery"; import { setAuthToken } from "@app/reactQuery";
import { APIKeyDataV2 } from "../apiKeys/types"; import { APIKeyDataV2 } from "../apiKeys/types";
@@ -293,6 +294,7 @@ export const useLogoutUser = (keepQueryClient?: boolean) => {
localStorage.removeItem("PRIVATE_KEY"); localStorage.removeItem("PRIVATE_KEY");
localStorage.removeItem("orgData.id"); localStorage.removeItem("orgData.id");
localStorage.removeItem("projectData.id"); localStorage.removeItem("projectData.id");
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
if (!keepQueryClient) { if (!keepQueryClient) {
queryClient.clear(); queryClient.clear();
+74 -8
View File
@@ -1,23 +1,89 @@
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; 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() { export default function CliRedirect() {
const [isUrlCopied, , setIsUrlCopied] = useTimedReset<boolean>({
initialState: false
});
const cliToken = getTerminalCliToken();
const copyUrlToClipboard = () => {
if (cliToken) {
navigator.clipboard.writeText(cliToken);
setIsUrlCopied(true);
sessionStorage.removeItem(SessionStorageKeys.CLI_TERMINAL_TOKEN);
}
};
return ( return (
<div className="flex flex-col justify-between bg-bunker-800 md:h-screen"> <div className="flex flex-col justify-between bg-bunker-800 md:h-screen">
<Head> <Head>
<title>Infisical CLI | Login Successful!</title> <title>Infisical CLI | Login Successful!</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="flex h-screen w-screen flex-col items-center justify-center text-gray-200"> <div className="flex h-screen w-screen flex-col items-center justify-center space-y-4 text-gray-200">
<div className="mb-8 flex justify-center"> <div className="mb-4 flex justify-center">
<Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" /> <Image src="/images/gradientLogo.svg" height={90} width={120} alt="Infisical Logo" />
</div> </div>
<p className="bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-3xl font-medium text-transparent"> {cliToken ? (
Head back to your terminal <>
</p> <div className="pb-4">
<p className="text-light mb-1 text-lg text-mineshaft-400"> <p className="bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-3xl font-medium text-transparent">
You&apos;ve successfully logged in to the Infisical CLI Login successful!
</p> </p>
<p className="text-light mb-1 text-lg text-mineshaft-400">
However, your token could not be sent to the CLI
</p>
</div>
<div className="dark relative flex max-h-36 max-w-xl flex-col items-center space-y-4 overflow-y-auto rounded-md border border-mineshaft-700 bg-mineshaft-900 p-3">
<SecretInput value={cliToken as string} />
<div className="mx-1 flex">
<IconButton
variant="outline_bg"
colorSchema="primary"
ariaLabel="copy to clipboard"
onClick={copyUrlToClipboard}
className=" flex items-center rounded py-2"
>
<FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy to
clipboard
</IconButton>
</div>
</div>
<p className="text-light mb-1 text-lg text-mineshaft-400">
After copying, return to terminal screen and paste it there
</p>
</>
) : (
<>
<p className="bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-3xl font-medium text-transparent">
Head back to your terminal
</p>
<p className="text-light mb-1 text-lg text-mineshaft-400">
You&apos;ve successfully logged in to the Infisical CLI
</p>
</>
)}
</div> </div>
</div> </div>
); );
@@ -7,11 +7,13 @@ import { useRouter } from "next/router";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { faArrowRight } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import axios from "axios"; import axios from "axios";
import { addSeconds, formatISO } from "date-fns";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin"; import { IsCliLoginSuccessful } from "@app/components/utilities/attemptCliLogin";
import { Button, Spinner } from "@app/components/v2"; import { Button, Spinner } from "@app/components/v2";
import { SessionStorageKeys } from "@app/const";
import { useUser } from "@app/context"; import { useUser } from "@app/context";
import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api"; import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api";
import { Organization } from "@app/hooks/api/types"; import { Organization } from "@app/hooks/api/types";
@@ -94,9 +96,19 @@ export default function LoginPage() {
// send request to server endpoint // send request to server endpoint
const instance = axios.create(); const instance = axios.create();
await instance.post(`http://127.0.0.1:${callbackPort}/`, payload); await instance.post(`http://127.0.0.1:${callbackPort}/`, payload).catch(() => {
// cli page // 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"); router.push("/cli-redirect");
// cli page
} else { } else {
navigateUserToOrg(router, organization.id); navigateUserToOrg(router, organization.id);
} }
@@ -3,6 +3,7 @@ import ReactCodeInput from "react-code-input";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import axios from "axios"; import axios from "axios";
import { addSeconds, formatISO } from "date-fns";
import jwt_decode from "jwt-decode"; import jwt_decode from "jwt-decode";
import Error from "@app/components/basic/Error"; 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 attemptLoginMfa from "@app/components/utilities/attemptLoginMfa";
import SecurityClient from "@app/components/utilities/SecurityClient"; import SecurityClient from "@app/components/utilities/SecurityClient";
import { Button } from "@app/components/v2"; import { Button } from "@app/components/v2";
import { SessionStorageKeys } from "@app/const";
import { useSendMfaToken } from "@app/hooks/api/auth"; import { useSendMfaToken } from "@app/hooks/api/auth";
import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queries"; import { useSelectOrganization, verifyMfaToken } from "@app/hooks/api/auth/queries";
import { fetchOrganizations } from "@app/hooks/api/organization/queries"; import { fetchOrganizations } from "@app/hooks/api/organization/queries";
@@ -79,11 +81,24 @@ export const MFAStep = ({ email, password, providerAuthToken }: Props) => {
if (callbackPort) { if (callbackPort) {
const cliUrl = `http://127.0.0.1:${callbackPort}/`; const cliUrl = `http://127.0.0.1:${callbackPort}/`;
const instance = axios.create(); const instance = axios.create();
await instance.post(cliUrl, { const payload = {
email, email,
privateKey, privateKey,
JTWToken: newJwtToken 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); await navigateUserToOrg(router, organizationId);
} }
@@ -1,4 +1,4 @@
import { useEffect, useMemo } from "react"; import { useMemo } from "react";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
@@ -20,7 +20,7 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
const [hashedHex, key] = urlEncodedPublicKey const [hashedHex, key] = urlEncodedPublicKey
? urlEncodedPublicKey.toString().split("-") ? urlEncodedPublicKey.toString().split("-")
: ["", ""]; : ["", ""];
const publicKey = decodeURIComponent(urlEncodedPublicKey as string); const publicKey = decodeURIComponent(urlEncodedPublicKey as string);
const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex( const { isLoading, data } = useGetActiveSharedSecretByIdAndHashedHex(
id as string, id as string,
@@ -46,12 +46,6 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
initialState: false initialState: false
}); });
useEffect(() => {
if (isUrlCopied) {
setTimeout(() => setIsUrlCopied(false), 2000);
}
}, [isUrlCopied]);
const copyUrlToClipboard = () => { const copyUrlToClipboard = () => {
navigator.clipboard.writeText(decryptedSecret); navigator.clipboard.writeText(decryptedSecret);
setIsUrlCopied(true); setIsUrlCopied(true);
@@ -59,12 +53,12 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const); const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const);
return ( return (
<div className="h-screen dark:[color-scheme:dark] flex flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200"> <div className="flex h-screen flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200 dark:[color-scheme:dark]">
<Head> <Head>
<title>Secret Shared | Infisical</title> <title>Secret Shared | Infisical</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="w-full flex flex-grow items-center justify-center dark:[color-scheme:dark]"> <div className="flex w-full flex-grow items-center justify-center dark:[color-scheme:dark]">
<div className="relative"> <div className="relative">
<div className="mb-4 flex justify-center pt-8"> <div className="mb-4 flex justify-center pt-8">
<Link href="https://infisical.com"> <Link href="https://infisical.com">
@@ -77,9 +71,15 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
/> />
</Link> </Link>
</div> </div>
<div className="w-full flex justify-center"> <div className="flex w-full justify-center">
<h1 className={`${id ? "max-w-sm mb-4": "max-w-md mt-4 mb-6"} bg-gradient-to-b from-white to-bunker-200 bg-clip-text px-4 text-center text-3xl font-medium text-transparent`}> <h1
{id ? "Someone shared a secret via Infisical with you" : "Share a secret via Infisical"} className={`${
id ? "mb-4 max-w-sm" : "mt-4 mb-6 max-w-md"
} bg-gradient-to-b from-white to-bunker-200 bg-clip-text px-4 text-center text-3xl font-medium text-transparent`}
>
{id
? "Someone shared a secret via Infisical with you"
: "Share a secret via Infisical"}
</h1> </h1>
</div> </div>
<div className="m-auto mt-4 flex w-full max-w-2xl justify-center px-6"> <div className="m-auto mt-4 flex w-full max-w-2xl justify-center px-6">
@@ -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" className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
> >
<Button <Button
className="bg-mineshaft-700 text-bunker-200 w-full py-3" className="w-full bg-mineshaft-700 py-3 text-bunker-200"
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
size="sm" size="sm"
@@ -126,34 +126,45 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
</a> </a>
</div> </div>
)} )}
<div className="m-auto my-6 flex w-full max-w-xl justify-center px-8 px-4 sm:my-8"> <div className="m-auto my-6 flex w-full max-w-xl justify-center px-4 sm:my-8">
<div className="w-full border-t border-mineshaft-600" /> <div className="w-full border-t border-mineshaft-600" />
</div> </div>
<div className="flex flex-col justify-center items-center m-auto max-w-2xl px-4 sm:px-6"> <div className="m-auto flex max-w-2xl flex-col items-center justify-center px-4 sm:px-6">
<div className="m-auto mb-12 flex flex max-w-2xl w-full flex-col justify-center rounded-md border border-primary-500/30 bg-primary/5 p-6 pt-5"> <div className="m-auto mb-12 flex w-full max-w-2xl flex-col justify-center rounded-md border border-primary-500/30 bg-primary/5 p-6 pt-5">
<p className="pb-2 font-semibold text-mineshaft-100 md:pb-3 text-lg md:text-xl w-full"> <p className="w-full pb-2 text-lg font-semibold text-mineshaft-100 md:pb-3 md:text-xl">
Open source <span className="bg-clip-text text-transparent bg-gradient-to-tr from-yellow-500 to-primary-500">secret management</span> for developers Open source{" "}
<span className="bg-gradient-to-tr from-yellow-500 to-primary-500 bg-clip-text text-transparent">
secret management
</span>{" "}
for developers
</p> </p>
<div className="flex flex-col sm:flex-row gap-x-4"> <div className="flex flex-col gap-x-4 sm:flex-row">
<p className="md:text-md text-md"> <p className="md:text-md text-md">
<a <a
href="https://github.com/infisical/infisical" href="https://github.com/infisical/infisical"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="bg-clip-text text-transparent bg-gradient-to-tr from-yellow-500 to-primary-500 text-bold" className="text-bold bg-gradient-to-tr from-yellow-500 to-primary-500 bg-clip-text text-transparent"
> >
Infisical Infisical
</a>{" "} is the all-in-one secret management platform to securely manage secrets, configs, and certificates across your team and infrastructure. </a>{" "}
is the all-in-one secret management platform to securely manage secrets, configs,
and certificates across your team and infrastructure.
</p> </p>
<Link href="https://infisical.com"> <Link href="https://infisical.com">
<span className="mt-4 border border-mineshaft-400/40 w-[17.5rem] h-min py-2 px-3 rounded-md bg-mineshaft-600 cursor-pointer duration-200 hover:text-white hover:border-primary/60 hover:bg-primary/20"> <span className="mt-4 h-min w-[17.5rem] cursor-pointer rounded-md border border-mineshaft-400/40 bg-mineshaft-600 py-2 px-3 duration-200 hover:border-primary/60 hover:bg-primary/20 hover:text-white">
Try Infisical <FontAwesomeIcon icon={faArrowRight} className="pl-1"/> Try Infisical <FontAwesomeIcon icon={faArrowRight} className="pl-1" />
</span> </span>
</Link> </Link>
</div> </div>
</div> </div>
</div> </div>
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} isPublic inModal /> <AddShareSecretModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
isPublic
inModal
/>
</div> </div>
</div> </div>
<div className="mt-auto flex w-full items-center justify-center bg-mineshaft-600 p-2"> <div className="mt-auto flex w-full items-center justify-center bg-mineshaft-600 p-2">