mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 17:30:06 +00:00
chore: resolve merge conflicts
This commit is contained in:
@@ -35,7 +35,7 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
// generate CSRF token for OAuth2 code-token exchange integrations
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
|
||||
|
||||
let link = "";
|
||||
switch (integrationOption.slug) {
|
||||
case "azure-key-vault":
|
||||
@@ -86,6 +86,9 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
case "railway":
|
||||
link = `${window.location.origin}/integrations/railway/authorize`;
|
||||
break;
|
||||
case "terraform-cloud":
|
||||
link = `${window.location.origin}/integrations/terraform-cloud/authorize`;
|
||||
break;
|
||||
case "hashicorp-vault":
|
||||
link = `${window.location.origin}/integrations/hashicorp-vault/authorize`;
|
||||
break;
|
||||
@@ -98,6 +101,12 @@ export const redirectForProviderAuth = (integrationOption: TCloudIntegration) =>
|
||||
case "codefresh":
|
||||
link = `${window.location.origin}/integrations/codefresh/authorize`;
|
||||
break;
|
||||
case "digital-ocean-app-platform":
|
||||
link = `${window.location.origin}/integrations/digital-ocean-app-platform/authorize`;
|
||||
break;
|
||||
case "cloud-66":
|
||||
link = `${window.location.origin}/integrations/cloud-66/authorize`;
|
||||
break;
|
||||
case "northflank":
|
||||
link = `${window.location.origin}/integrations/northflank/authorize`;
|
||||
break;
|
||||
|
||||
+4
-13
@@ -32,7 +32,7 @@ export const CloudIntegrationSection = ({
|
||||
const isEmpty = !isLoading && !cloudIntegrations?.length;
|
||||
|
||||
const sortedCloudIntegrations = cloudIntegrations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="m-4 mt-7 flex max-w-5xl flex-col items-start justify-between px-2 text-xl">
|
||||
@@ -67,18 +67,9 @@ export const CloudIntegrationSection = ({
|
||||
width={70}
|
||||
alt="integration logo"
|
||||
/>
|
||||
{cloudIntegration.name.split(" ").length > 2 ? (
|
||||
<div className="ml-4 max-w-xs text-3xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
<div>{cloudIntegration.name.split(" ")[0]}</div>
|
||||
<div className="text-base">
|
||||
{cloudIntegration.name.split(" ")[1]} {cloudIntegration.name.split(" ")[2]}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
{cloudIntegration.name}
|
||||
</div>
|
||||
)}
|
||||
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
{cloudIntegration.name}
|
||||
</div>
|
||||
{cloudIntegration.isAvailable &&
|
||||
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
|
||||
<div className="absolute top-0 right-0 z-40 h-full">
|
||||
|
||||
@@ -18,6 +18,8 @@ export const Login = () => {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
|
||||
useEffect(() => {
|
||||
// TODO(akhilmhdh): workspace will be controlled by a workspace context
|
||||
const redirectToDashboard = async () => {
|
||||
@@ -30,7 +32,6 @@ export const Login = () => {
|
||||
const userDetails = await fetchUserDetails()
|
||||
// send details back to client
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
if (queryParams && queryParams.get("callback_port")) {
|
||||
const callbackPort = queryParams.get("callback_port")
|
||||
|
||||
@@ -67,6 +68,7 @@ export const Login = () => {
|
||||
email={email}
|
||||
password={password}
|
||||
providerAuthToken={undefined}
|
||||
callbackPort={queryParams.get("callback_port")}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useRouter } from "next/router"
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
import {
|
||||
@@ -14,25 +13,20 @@ type Props = {
|
||||
export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [password, setPassword] = useState("");
|
||||
const router = useRouter();
|
||||
|
||||
const {
|
||||
email,
|
||||
isUserCompleted,
|
||||
callbackPort
|
||||
} = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!isUserCompleted) {
|
||||
router.push(`/signup/sso?token=${encodeURIComponent(providerAuthToken)}`);
|
||||
}
|
||||
|
||||
if (isUserCompleted) {
|
||||
setStep(1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderView = () => {
|
||||
// TODO: consider adding a complete account step here that's uniquely for SSO
|
||||
switch (step) {
|
||||
case 0:
|
||||
return (
|
||||
@@ -42,6 +36,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
return (
|
||||
<PasswordStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
callbackPort={callbackPort}
|
||||
email={email}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
@@ -52,6 +47,7 @@ export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
return (
|
||||
<MFAStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
callbackPort={callbackPort}
|
||||
email={email}
|
||||
password={password}
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,9 @@ export const InitialStep = ({
|
||||
const { t } = useTranslation();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [loginError, setLoginError] = useState(false);
|
||||
const [loginEmailChosen, setLoginEmailChosen] = useState(false);
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const handleLogin = async (e: FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault()
|
||||
@@ -42,7 +45,6 @@ export const InitialStep = ({
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
if (queryParams && queryParams.get("callback_port")) {
|
||||
const callbackPort = queryParams.get("callback_port")
|
||||
|
||||
@@ -115,34 +117,49 @@ export const InitialStep = ({
|
||||
return (
|
||||
<form onSubmit={handleLogin} className='flex flex-col mx-auto w-full justify-center items-center'>
|
||||
<h1 className='text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8' >Login to Infisical</h1>
|
||||
<div className="relative md:px-1.5 flex items-center justify-center lg:w-1/6 w-1/4 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full md:px-2 md:py-1 rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant={loginEmailChosen ? "outline_bg" : "solid"}
|
||||
onClick={() => {
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
|
||||
window.open(`/api/v1/sso/redirect/google${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
||||
window.close();
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||
className="h-12 w-full mx-0"
|
||||
>
|
||||
{t("login.continue-with-google")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative pt-2 md:pt-0 md:px-1.5 flex items-center justify-center w-1/4 lg:w-1/6 min-w-[21.3rem] md:min-w-[22rem] mx-auto rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full md:p-2 rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="h-12 select:-webkit-autofill:focus"
|
||||
/>
|
||||
</div>
|
||||
{loginEmailChosen && <>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
||||
<div className='w-full border-t border-mineshaft-500' />
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md'>
|
||||
<Input
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
type="email"
|
||||
placeholder="Enter your email..."
|
||||
isRequired
|
||||
autoComplete="username"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="h-12 select:-webkit-autofill:focus"
|
||||
/>
|
||||
</div>
|
||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -152,28 +169,25 @@ export const InitialStep = ({
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
isLoading={isLoading}
|
||||
> Login </Button>
|
||||
> Continue with Email </Button>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center my-4 py-2'>
|
||||
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||
<span className='px-4 text-sm text-bunker-400'>or</span>
|
||||
<div className='w-1/2 border-t border-mineshaft-500' />
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] rounded-md'>
|
||||
{!isLoading && loginError && <Error text={t("login.error-login") ?? ""} />}
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] flex flex-row items-center mt-4 py-2'>
|
||||
<div className='w-full border-t border-mineshaft-500' />
|
||||
</div></>}
|
||||
{!loginEmailChosen && <div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="solid"
|
||||
onClick={() => {
|
||||
window.open("/api/v1/sso/redirect/google");
|
||||
window.close();
|
||||
}}
|
||||
leftIcon={<FontAwesomeIcon icon={faGoogle} className="mr-1" />}
|
||||
className="h-14 w-full mx-0"
|
||||
>
|
||||
{t("login.continue-with-google")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[20rem] text-center rounded-md mt-4'>
|
||||
setLoginEmailChosen(true);
|
||||
}}
|
||||
size="sm"
|
||||
isFullWidth
|
||||
className='h-12'
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
> Continue with Email </Button>
|
||||
</div>}
|
||||
<div className='lg:w-1/6 w-1/4 min-w-[21.2rem] md:min-w-[20.1rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
@@ -181,7 +195,7 @@ export const InitialStep = ({
|
||||
setStep(2);
|
||||
}}
|
||||
isFullWidth
|
||||
className="h-14 w-full mx-0"
|
||||
className="h-12 w-full mx-0"
|
||||
>
|
||||
Continue with SAML SSO
|
||||
</Button>
|
||||
|
||||
@@ -36,6 +36,7 @@ type Props = {
|
||||
email: string;
|
||||
password: string;
|
||||
providerAuthToken?: string;
|
||||
callbackPort?: string | null;
|
||||
}
|
||||
|
||||
interface VerifyMfaTokenError {
|
||||
@@ -53,7 +54,8 @@ interface VerifyMfaTokenError {
|
||||
export const MFAStep = ({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken
|
||||
providerAuthToken,
|
||||
callbackPort
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const router = useRouter();
|
||||
@@ -77,9 +79,7 @@ export const MFAStep = ({
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
if (queryParams && queryParams.get("callback_port")){
|
||||
const callbackPort = queryParams.get("callback_port")
|
||||
if (callbackPort){
|
||||
|
||||
// attemptCliLogin
|
||||
const isCliLoginSuccessful = await attemptCliLoginMfa({
|
||||
|
||||
@@ -2,14 +2,17 @@ import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router"
|
||||
import axios from "axios"
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import attemptCliLogin from "@app/components/utilities/attemptCliLogin";
|
||||
import attemptLogin from "@app/components/utilities/attemptLogin";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
import getOrganizations from "@app/pages/api/organization/getOrgs";
|
||||
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
callbackPort?: string;
|
||||
email: string;
|
||||
password: string;
|
||||
setPassword: (password: string) => void;
|
||||
@@ -18,6 +21,7 @@ type Props = {
|
||||
|
||||
export const PasswordStep = ({
|
||||
providerAuthToken,
|
||||
callbackPort,
|
||||
email,
|
||||
password,
|
||||
setPassword,
|
||||
@@ -31,34 +35,64 @@ export const PasswordStep = ({
|
||||
const handleLogin = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const loginAttempt = await attemptLogin({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
});
|
||||
|
||||
if (callbackPort) {
|
||||
// attemptCliLogin
|
||||
const isCliLoginSuccessful = await attemptCliLogin({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken
|
||||
})
|
||||
|
||||
if (loginAttempt && loginAttempt.success) {
|
||||
// case: login was successful
|
||||
if (isCliLoginSuccessful && isCliLoginSuccessful.success) {
|
||||
|
||||
if (loginAttempt.mfaEnabled) {
|
||||
// TODO: deal with MFA
|
||||
// case: login requires MFA step
|
||||
setIsLoading(false);
|
||||
setStep(2);
|
||||
return;
|
||||
if (isCliLoginSuccessful.mfaEnabled) {
|
||||
// case: login requires MFA step
|
||||
setStep(2);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
// case: login was successful
|
||||
const cliUrl = `http://localhost:${callbackPort}`
|
||||
|
||||
// send request to server endpoint
|
||||
const instance = axios.create()
|
||||
await instance.post(cliUrl, { ...isCliLoginSuccessful.loginResponse })
|
||||
|
||||
// cli page
|
||||
router.push("/cli-redirect");
|
||||
|
||||
// on success, router.push to cli Login Successful page
|
||||
}
|
||||
|
||||
// case: login does not require MFA step
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrg = userOrgs[0]._id;
|
||||
setIsLoading(false);
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
} else {
|
||||
const loginAttempt = await attemptLogin({
|
||||
email,
|
||||
password,
|
||||
providerAuthToken,
|
||||
});
|
||||
router.push(`/org/${userOrg?._id}/overview`);
|
||||
}
|
||||
|
||||
if (loginAttempt && loginAttempt.success) {
|
||||
// case: login was successful
|
||||
|
||||
if (loginAttempt.mfaEnabled) {
|
||||
// TODO: deal with MFA
|
||||
// case: login requires MFA step
|
||||
setIsLoading(false);
|
||||
setStep(2);
|
||||
return;
|
||||
}
|
||||
|
||||
// case: login does not require MFA step
|
||||
const userOrgs = await getOrganizations();
|
||||
const userOrg = userOrgs[0]._id;
|
||||
setIsLoading(false);
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
router.push(`/org/${userOrg?._id}/overview`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setIsLoading(false);
|
||||
createNotification({
|
||||
|
||||
@@ -13,6 +13,8 @@ export const SAMLSSOStep = ({
|
||||
const [ssoIdentifier, setSSOIdentifier] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
@@ -37,7 +39,8 @@ export const SAMLSSOStep = ({
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
onClick={() => {
|
||||
window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}`);
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
window.open(`/api/v1/sso/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
||||
window.close();
|
||||
}}
|
||||
isFullWidth
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
@@ -26,9 +27,11 @@ import {
|
||||
Th,
|
||||
THead,
|
||||
Tr,
|
||||
UpgradePlanModal} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization , useWorkspace } from "@app/context";
|
||||
import { usePopUp, useToggle } from "@app/hooks";
|
||||
import { useGetSSOConfig } from "@app/hooks/api";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { OrgUser, Workspace } from "@app/hooks/api/types";
|
||||
|
||||
@@ -69,6 +72,9 @@ export const OrgMembersTable = ({
|
||||
setCompleteInviteLink
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(currentOrg?._id ?? "");
|
||||
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
const { workspaces } = useWorkspace();
|
||||
@@ -79,7 +85,7 @@ export const OrgMembersTable = ({
|
||||
"upgradePlan",
|
||||
"setUpEmail"
|
||||
] as const);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (router.query.action === "invite") {
|
||||
handlePopUpOpen("addMember");
|
||||
@@ -152,6 +158,15 @@ export const OrgMembersTable = ({
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => {
|
||||
if (!isLoadingSSOConfig && ssoConfig && ssoConfig.isActive) {
|
||||
createNotification({
|
||||
text: "You cannot invite users when SAML SSO is configured for your organization",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMoreUserNotAllowed) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { IPAllowlistSection } from "./components";
|
||||
|
||||
export const IPAllowlistPage = () => {
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<div className="max-w-7xl px-6 w-full">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">IP Allowlist</p>
|
||||
<div />
|
||||
</div>
|
||||
<IPAllowlistSection />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import {
|
||||
useAddTrustedIp,
|
||||
useGetMyIp,
|
||||
useUpdateTrustedIp
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup.object({
|
||||
ipAddress: yup.string().required("IP address is required"),
|
||||
comment: yup.string()
|
||||
}).required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["trustedIp"]>;
|
||||
handlePopUpClose: (popUpName: keyof UsePopUpState<["trustedIp"]>) => void;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["trustedIp"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const IPAllowlistModal = ({
|
||||
popUp,
|
||||
handlePopUpClose,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { data, isLoading } = useGetMyIp();
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const addTrustedIp = useAddTrustedIp();
|
||||
const updateTrustedIp = useUpdateTrustedIp();
|
||||
|
||||
const {
|
||||
control,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const trustedIpData = popUp?.trustedIp?.data as {
|
||||
ipAddress: string;
|
||||
comment: string;
|
||||
prefix: number;
|
||||
};
|
||||
|
||||
if (popUp?.trustedIp?.data) {
|
||||
reset({
|
||||
ipAddress: `${trustedIpData.ipAddress}${trustedIpData.prefix !== undefined ? `/${trustedIpData.prefix}` : ""}`,
|
||||
comment: trustedIpData.comment
|
||||
});
|
||||
} else {
|
||||
reset({
|
||||
ipAddress: "",
|
||||
comment: ""
|
||||
});
|
||||
}
|
||||
|
||||
}, [popUp?.trustedIp?.data]);
|
||||
|
||||
const onIPAllowlistModalSubmit = async ({
|
||||
ipAddress,
|
||||
comment
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!currentWorkspace?._id) return;
|
||||
|
||||
if (popUp?.trustedIp?.data) {
|
||||
await updateTrustedIp.mutateAsync({
|
||||
workspaceId: currentWorkspace._id,
|
||||
trustedIpId: (popUp?.trustedIp?.data as { trustedIpId: string })?.trustedIpId,
|
||||
ipAddress,
|
||||
comment,
|
||||
isActive: true
|
||||
});
|
||||
} else {
|
||||
await addTrustedIp.mutateAsync({
|
||||
workspaceId: currentWorkspace._id,
|
||||
ipAddress,
|
||||
comment,
|
||||
isActive: true
|
||||
});
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${popUp?.trustedIp?.data ? "updated" : "added"} trusted IP`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
handlePopUpClose("trustedIp");
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: `Failed to ${popUp?.trustedIp?.data ? "update" : "add"} trusted IP`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.trustedIp?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("trustedIp", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title={popUp?.trustedIp?.data ? "Update IP" : "Add IP"}>
|
||||
<form onSubmit={handleSubmit(onIPAllowlistModalSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="ipAddress"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="IPv4/IPv6 Address / CIDR Notation"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="123.456.789.0"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{!isLoading && data && (
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
type="button"
|
||||
onClick={() => setValue("ipAddress", data)}
|
||||
className="mb-8"
|
||||
>
|
||||
Add current IP address
|
||||
</Button>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="comment"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Comment"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="My IP address"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{popUp?.trustedIp?.data ? "Update" : "Add"}
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpClose("trustedIp")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { useSubscription,useWorkspace } from "@app/context";
|
||||
import {
|
||||
useDeleteTrustedIp
|
||||
} from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { IPAllowlistModal } from "./IPAllowlistModal";
|
||||
import { IPAllowlistTable } from "./IPAllowlistTable";
|
||||
|
||||
export const IPAllowlistSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync } = useDeleteTrustedIp();
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"trustedIp",
|
||||
"deleteTrustedIp",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const onDeleteTrustedIpSubmit = async (trustedIpId: string) => {
|
||||
try {
|
||||
|
||||
if (!currentWorkspace?._id) return;
|
||||
|
||||
await mutateAsync({
|
||||
workspaceId: currentWorkspace._id,
|
||||
trustedIpId
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted IP access range",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
handlePopUpClose("deleteTrustedIp");
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({
|
||||
text: "Failed to delete IP access range",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<div className="flex items-center mb-8">
|
||||
<h2 className="text-xl font-semibold flex-1 text-white">
|
||||
IP Allowlist
|
||||
</h2>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
handlePopUpOpen("trustedIp")
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
colorSchema="secondary"
|
||||
isLoading={false}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Add IP
|
||||
</Button>
|
||||
</div>
|
||||
<IPAllowlistTable
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<IPAllowlistModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteTrustedIp.isOpen}
|
||||
title={`Are you sure want to delete ${
|
||||
(popUp?.deleteTrustedIp?.data as { name: string })?.name || " "
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteTrustedIp", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() =>
|
||||
onDeleteTrustedIpSubmit((popUp?.deleteTrustedIp?.data as { trustedIpId: string })?.trustedIpId)
|
||||
}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { faGlobe, faPencil, faXmark } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { useSubscription, useWorkspace } from "@app/context";
|
||||
import {
|
||||
useGetTrustedIps
|
||||
} from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["upgradePlan"]>;
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["trustedIp", "deleteTrustedIp", "upgradePlan"]>,
|
||||
data?: {
|
||||
trustedIpId: string;
|
||||
ipAddress?: string;
|
||||
comment?: string;
|
||||
isActive?: boolean;
|
||||
prefix?: number;
|
||||
},
|
||||
) => void;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["upgradePlan"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const IPAllowlistTable = ({
|
||||
popUp,
|
||||
handlePopUpOpen,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const { subscription } = useSubscription();
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data, isLoading } = useGetTrustedIps(currentWorkspace?._id ?? "");
|
||||
|
||||
const formatType = (type: string, prefix?: number) => {
|
||||
return `${type.slice(0, 2).toUpperCase() + type.slice(2)} ${(prefix !== undefined) ? "CIDR" : ""}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<TableContainer className="mt-4">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th className="flex-1">IP Address / Range</Th>
|
||||
<Th className="flex-1">Format</Th>
|
||||
<Th className="flex-1">Comment</Th>
|
||||
{/* <Th className="flex-1">Status</Th> */}
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{!isLoading && data && data?.length > 0 && data
|
||||
.sort((a, b) => a.ipAddress.localeCompare(b.ipAddress))
|
||||
.map(({
|
||||
_id,
|
||||
ipAddress,
|
||||
comment,
|
||||
type,
|
||||
prefix,
|
||||
isActive
|
||||
}) => {
|
||||
return (
|
||||
<Tr
|
||||
key={`ip-access-range-${_id}`}
|
||||
className="h-10"
|
||||
>
|
||||
<Td>
|
||||
{`${ipAddress}${(prefix !== undefined) ? `/${prefix}` : ""}`}
|
||||
</Td>
|
||||
<Td>
|
||||
{formatType(type, prefix)}
|
||||
</Td>
|
||||
<Td>
|
||||
{comment}
|
||||
</Td>
|
||||
{/* <Td>
|
||||
<div className="flex items-center">
|
||||
<FontAwesomeIcon
|
||||
icon={faCircle}
|
||||
color="#2ecc71"
|
||||
/>
|
||||
<p className="ml-4">Active</p>
|
||||
</div>
|
||||
</Td> */}
|
||||
<Td className="flex items-center">
|
||||
<IconButton
|
||||
className="mr-3 py-2"
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
handlePopUpOpen("trustedIp", {
|
||||
trustedIpId: _id,
|
||||
ipAddress,
|
||||
comment,
|
||||
prefix,
|
||||
isActive
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
>
|
||||
<FontAwesomeIcon icon={faPencil} />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (subscription?.ipAllowlisting) {
|
||||
handlePopUpOpen("deleteTrustedIp", {
|
||||
trustedIpId: _id
|
||||
});
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="danger"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
{isLoading && <TableSkeleton columns={4} key="ip-access-ranges" />}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={5}>
|
||||
<EmptyState
|
||||
title="No IP addresses added"
|
||||
icon={faGlobe}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { IPAllowlistSection } from "./IPAllowlistSection";
|
||||
@@ -0,0 +1 @@
|
||||
export { IPAllowlistPage } from "./IPAllowlistPage";
|
||||
+2
-1
@@ -11,7 +11,8 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr} from "@app/components/v2";
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import {
|
||||
useDeleteOrgPmtMethod,
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ import {
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr} from "@app/components/v2";
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import {
|
||||
useGetOrgInvoices
|
||||
|
||||
+1
-1
@@ -102,7 +102,7 @@ export const AuthMethodSection = () => {
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
className="w-full bg-mineshaft-800 border border-mineshaft-600"
|
||||
>
|
||||
{authMethods.map((authMethod) => {
|
||||
return (
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["updateEnv", "deleteEnv", "deleteEnv", "upgradePlan"]>,
|
||||
popUpName: keyof UsePopUpState<["updateEnv", "deleteEnv", "upgradePlan"]>,
|
||||
{
|
||||
name,
|
||||
slug
|
||||
|
||||
@@ -69,13 +69,13 @@ export const UserInfoSSOStep = ({
|
||||
const [nameError, setNameError] = useState(false);
|
||||
const [organizationName, setOrganizationName] = useState("");
|
||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||
const [attributionSource, setAttributionSource] = useState("");
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
console.log("providerOrganizationName: ", providerOrganizationName);
|
||||
if (providerOrganizationName) {
|
||||
if (providerOrganizationName !== undefined) {
|
||||
setOrganizationName(providerOrganizationName);
|
||||
}
|
||||
}, []);
|
||||
@@ -112,10 +112,6 @@ export const UserInfoSSOStep = ({
|
||||
const privateKey = encodeBase64(secretKeyUint8Array);
|
||||
const publicKey = encodeBase64(publicKeyUint8Array);
|
||||
localStorage.setItem("PRIVATE_KEY", privateKey);
|
||||
|
||||
console.log("make");
|
||||
console.log("email: ", email);
|
||||
console.log("password: ", password);
|
||||
|
||||
client.init(
|
||||
{
|
||||
@@ -175,7 +171,8 @@ export const UserInfoSSOStep = ({
|
||||
providerAuthToken,
|
||||
salt: result.salt,
|
||||
verifier: result.verifier,
|
||||
organizationName
|
||||
organizationName,
|
||||
attributionSource
|
||||
});
|
||||
|
||||
// unset signup JWT token and set JWT token
|
||||
@@ -213,7 +210,7 @@ export const UserInfoSSOStep = ({
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="h-full mx-auto mb-36 w-max rounded-xl md:px-8 md:mb-16">
|
||||
<p className="mx-8 mb-6 flex justify-center text-xl font-bold text-medium md:mx-16 text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200">
|
||||
@@ -232,18 +229,31 @@ export const UserInfoSSOStep = ({
|
||||
/>
|
||||
{nameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your name</p>}
|
||||
</div>
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p>
|
||||
<Input
|
||||
placeholder="Infisical"
|
||||
value={organizationName}
|
||||
onChange={(e) => setOrganizationName(e.target.value)}
|
||||
isRequired
|
||||
className="h-12"
|
||||
disabled
|
||||
/>
|
||||
{organizationNameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your organization name</p>}
|
||||
</div>
|
||||
{providerOrganizationName === undefined && (
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p>
|
||||
<Input
|
||||
placeholder="Infisical"
|
||||
value={organizationName}
|
||||
onChange={(e) => setOrganizationName(e.target.value)}
|
||||
isRequired
|
||||
className="h-12"
|
||||
disabled
|
||||
/>
|
||||
{organizationNameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your organization name</p>}
|
||||
</div>
|
||||
)}
|
||||
{providerOrganizationName === undefined && (
|
||||
<div className="relative z-0 lg:w-1/6 w-1/4 min-w-[20rem] flex flex-col items-center justify-end w-full py-2 rounded-lg">
|
||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Where did you hear about us? <span className="font-light">(optional)</span></p>
|
||||
<Input
|
||||
placeholder=""
|
||||
onChange={(e) => setAttributionSource(e.target.value)}
|
||||
value={attributionSource}
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 flex lg:w-1/6 w-1/4 min-w-[20rem] max-h-60 w-full flex-col items-center justify-center rounded-lg py-2">
|
||||
<InputField
|
||||
label={t("section.password.password")}
|
||||
|
||||
Reference in New Issue
Block a user