mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: redesigned org security settings page
This commit is contained in:
@@ -4,8 +4,17 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
import {
|
||||||
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
|
FormControl,
|
||||||
|
Input,
|
||||||
|
Modal,
|
||||||
|
ModalContent,
|
||||||
|
TextArea
|
||||||
|
} from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
import {
|
import {
|
||||||
useCreateLDAPConfig,
|
useCreateLDAPConfig,
|
||||||
useGetLDAPConfig,
|
useGetLDAPConfig,
|
||||||
@@ -32,9 +41,10 @@ type Props = {
|
|||||||
popUp: UsePopUpState<["addLDAP"]>;
|
popUp: UsePopUpState<["addLDAP"]>;
|
||||||
handlePopUpClose: (popUpName: keyof UsePopUpState<["addLDAP"]>) => void;
|
handlePopUpClose: (popUpName: keyof UsePopUpState<["addLDAP"]>) => void;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addLDAP"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addLDAP"]>, state?: boolean) => void;
|
||||||
|
hideDelete?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelete }: Props) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
||||||
@@ -46,6 +56,39 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
resolver: zodResolver(LDAPFormSchema)
|
resolver: zodResolver(LDAPFormSchema)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle();
|
||||||
|
|
||||||
|
const handleLdapSoftDelete = async () => {
|
||||||
|
if (!currentOrg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateMutateAsync({
|
||||||
|
organizationId: currentOrg.id,
|
||||||
|
isActive: false,
|
||||||
|
url: "",
|
||||||
|
bindDN: "",
|
||||||
|
bindPass: "",
|
||||||
|
searchBase: "",
|
||||||
|
searchFilter: "",
|
||||||
|
uniqueUserAttribute: "",
|
||||||
|
groupSearchBase: "",
|
||||||
|
groupSearchFilter: "",
|
||||||
|
caCert: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted OIDC configuration.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed deleting OIDC configuration.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const watchUrl = watch("url");
|
const watchUrl = watch("url");
|
||||||
const watchBindDN = watch("bindDN");
|
const watchBindDN = watch("bindDN");
|
||||||
const watchBindPass = watch("bindPass");
|
const watchBindPass = watch("bindPass");
|
||||||
@@ -175,138 +218,154 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
isOpen={popUp?.addLDAP?.isOpen}
|
<Modal
|
||||||
onOpenChange={(isOpen) => {
|
isOpen={popUp?.addLDAP?.isOpen}
|
||||||
handlePopUpToggle("addLDAP", isOpen);
|
onOpenChange={(isOpen) => {
|
||||||
reset();
|
handlePopUpToggle("addLDAP", isOpen);
|
||||||
}}
|
reset();
|
||||||
>
|
}}
|
||||||
<ModalContent title="Manage LDAP configuration">
|
>
|
||||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
<ModalContent title="Manage LDAP configuration">
|
||||||
<Controller
|
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||||
control={control}
|
<Controller
|
||||||
name="url"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="url"
|
||||||
<FormControl label="URL" errorText={error?.message} isError={Boolean(error)}>
|
render={({ field, fieldState: { error } }) => (
|
||||||
<Input {...field} placeholder="ldaps://ldap.myorg.com:636" />
|
<FormControl label="URL" errorText={error?.message} isError={Boolean(error)}>
|
||||||
</FormControl>
|
<Input {...field} placeholder="ldaps://ldap.myorg.com:636" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="bindDN"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="bindDN"
|
||||||
<FormControl label="Bind DN" errorText={error?.message} isError={Boolean(error)}>
|
render={({ field, fieldState: { error } }) => (
|
||||||
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
|
<FormControl label="Bind DN" errorText={error?.message} isError={Boolean(error)}>
|
||||||
</FormControl>
|
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="bindPass"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="bindPass"
|
||||||
<FormControl label="Bind Pass" errorText={error?.message} isError={Boolean(error)}>
|
render={({ field, fieldState: { error } }) => (
|
||||||
<Input {...field} type="password" placeholder="********" />
|
<FormControl label="Bind Pass" errorText={error?.message} isError={Boolean(error)}>
|
||||||
</FormControl>
|
<Input {...field} type="password" placeholder="********" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="searchBase"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="searchBase"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="User Search Base / User DN"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="User Search Base / User DN"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<Input {...field} placeholder="ou=people,dc=acme,dc=com" />
|
>
|
||||||
</FormControl>
|
<Input {...field} placeholder="ou=people,dc=acme,dc=com" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="uniqueUserAttribute"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="uniqueUserAttribute"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="Unique User Attribute (Optional)"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Unique User Attribute (Optional)"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<Input {...field} placeholder="uidNumber" />
|
>
|
||||||
</FormControl>
|
<Input {...field} placeholder="uidNumber" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="searchFilter"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="searchFilter"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="User Search Filter (Optional)"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="User Search Filter (Optional)"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<Input {...field} placeholder="(uid={{username}})" />
|
>
|
||||||
</FormControl>
|
<Input {...field} placeholder="(uid={{username}})" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="groupSearchBase"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="groupSearchBase"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="Group Search Base / Group DN (Optional)"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Group Search Base / Group DN (Optional)"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<Input {...field} placeholder="ou=groups,dc=acme,dc=com" />
|
>
|
||||||
</FormControl>
|
<Input {...field} placeholder="ou=groups,dc=acme,dc=com" />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="groupSearchFilter"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="groupSearchFilter"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="Group Filter (Optional)"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Group Filter (Optional)"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<Input
|
>
|
||||||
{...field}
|
<Input
|
||||||
placeholder="(&(objectClass=posixGroup)(memberUid={{.Username}}))"
|
{...field}
|
||||||
/>
|
placeholder="(&(objectClass=posixGroup)(memberUid={{.Username}}))"
|
||||||
</FormControl>
|
/>
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
<Controller
|
||||||
name="caCert"
|
control={control}
|
||||||
render={({ field, fieldState: { error } }) => (
|
name="caCert"
|
||||||
<FormControl
|
render={({ field, fieldState: { error } }) => (
|
||||||
label="CA Certificate"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="CA Certificate"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
>
|
isError={Boolean(error)}
|
||||||
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
>
|
||||||
</FormControl>
|
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
||||||
)}
|
</FormControl>
|
||||||
/>
|
)}
|
||||||
<div className="mt-8 flex items-center">
|
/>
|
||||||
<Button
|
<div className="mt-8 flex justify-between">
|
||||||
className="mr-4"
|
<div className="flex items-center">
|
||||||
size="sm"
|
<Button
|
||||||
type="submit"
|
className="mr-4"
|
||||||
isLoading={createIsLoading || updateIsLoading}
|
size="sm"
|
||||||
>
|
type="submit"
|
||||||
{!data ? "Add" : "Update"}
|
isLoading={createIsLoading || updateIsLoading}
|
||||||
</Button>
|
>
|
||||||
<Button colorSchema="secondary" onClick={handleTestLDAPConnection}>
|
{!data ? "Add" : "Update"}
|
||||||
Test Connection
|
</Button>
|
||||||
</Button>
|
<Button colorSchema="secondary" onClick={handleTestLDAPConnection}>
|
||||||
</div>
|
Test Connection
|
||||||
</form>
|
</Button>
|
||||||
</ModalContent>
|
</div>
|
||||||
</Modal>
|
{!hideDelete && (
|
||||||
|
<Button colorSchema="danger" onClick={() => setIsDeletePopupOpen.on()}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={isDeletePopupOpen}
|
||||||
|
title="Are you sure want to delete LDAP?"
|
||||||
|
onChange={() => setIsDeletePopupOpen.toggle()}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleLdapSoftDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -28,6 +29,7 @@ type Props = {
|
|||||||
popUp: UsePopUpState<["addOIDC"]>;
|
popUp: UsePopUpState<["addOIDC"]>;
|
||||||
handlePopUpClose: (popUpName: keyof UsePopUpState<["addOIDC"]>) => void;
|
handlePopUpClose: (popUpName: keyof UsePopUpState<["addOIDC"]>) => void;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addOIDC"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addOIDC"]>, state?: boolean) => void;
|
||||||
|
hideDelete?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const schema = z
|
const schema = z
|
||||||
@@ -94,11 +96,13 @@ const schema = z
|
|||||||
|
|
||||||
export type OIDCFormData = z.infer<typeof schema>;
|
export type OIDCFormData = z.infer<typeof schema>;
|
||||||
|
|
||||||
export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelete }: Props) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateOIDCConfig();
|
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateOIDCConfig();
|
||||||
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateOIDCConfig();
|
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateOIDCConfig();
|
||||||
|
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle(false);
|
||||||
|
|
||||||
const { data } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
const { data } = useGetOIDCConfig(currentOrg?.slug ?? "");
|
||||||
|
|
||||||
const { control, handleSubmit, reset, setValue, watch } = useForm<OIDCFormData>({
|
const { control, handleSubmit, reset, setValue, watch } = useForm<OIDCFormData>({
|
||||||
@@ -112,6 +116,36 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
const [isClientSecretFocused, setIsClientSecretFocused] = useToggle();
|
const [isClientSecretFocused, setIsClientSecretFocused] = useToggle();
|
||||||
|
|
||||||
const configurationTypeValue = watch("configurationType");
|
const configurationTypeValue = watch("configurationType");
|
||||||
|
const handleOidcSoftDelete = async () => {
|
||||||
|
if (!currentOrg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateMutateAsync({
|
||||||
|
issuer: "",
|
||||||
|
discoveryURL: "",
|
||||||
|
authorizationEndpoint: "",
|
||||||
|
allowedEmailDomains: "",
|
||||||
|
jwksUri: "",
|
||||||
|
tokenEndpoint: "",
|
||||||
|
userinfoEndpoint: "",
|
||||||
|
clientId: "",
|
||||||
|
clientSecret: "",
|
||||||
|
isActive: false,
|
||||||
|
orgSlug: currentOrg.slug
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted OIDC configuration.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed deleting OIDC configuration.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data) {
|
||||||
@@ -193,212 +227,232 @@ export const OIDCModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
isOpen={popUp?.addOIDC?.isOpen}
|
<Modal
|
||||||
onOpenChange={(isOpen) => {
|
isOpen={popUp?.addOIDC?.isOpen}
|
||||||
handlePopUpToggle("addOIDC", isOpen);
|
onOpenChange={(isOpen) => {
|
||||||
reset();
|
handlePopUpToggle("addOIDC", isOpen);
|
||||||
}}
|
reset();
|
||||||
>
|
}}
|
||||||
<ModalContent title="Manage OIDC configuration">
|
>
|
||||||
<form onSubmit={handleSubmit(onOIDCModalSubmit)}>
|
<ModalContent title="Manage OIDC configuration">
|
||||||
<Controller
|
<form onSubmit={handleSubmit(onOIDCModalSubmit)}>
|
||||||
control={control}
|
|
||||||
name="configurationType"
|
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Configuration Type"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Select
|
|
||||||
className="w-full"
|
|
||||||
defaultValue="discoveryURL"
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
>
|
|
||||||
<SelectItem value={ConfigurationType.DISCOVERY_URL}>Discovery URL</SelectItem>
|
|
||||||
<SelectItem value={ConfigurationType.CUSTOM}>Custom</SelectItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{configurationTypeValue === ConfigurationType.DISCOVERY_URL && (
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="discoveryURL"
|
name="configurationType"
|
||||||
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Configuration Type"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
className="w-full"
|
||||||
|
defaultValue="discoveryURL"
|
||||||
|
{...field}
|
||||||
|
onValueChange={(e) => onChange(e)}
|
||||||
|
>
|
||||||
|
<SelectItem value={ConfigurationType.DISCOVERY_URL}>Discovery URL</SelectItem>
|
||||||
|
<SelectItem value={ConfigurationType.CUSTOM}>Custom</SelectItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{configurationTypeValue === ConfigurationType.DISCOVERY_URL && (
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="discoveryURL"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Discovery Document URL"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://accounts.google.com/.well-known/openid-configuration"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{configurationTypeValue === ConfigurationType.CUSTOM && (
|
||||||
|
<>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="issuer"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Issuer" errorText={error?.message} isError={Boolean(error)}>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://accounts.google.com"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="authorizationEndpoint"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Authorization Endpoint"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://accounts.google.com/o/oauth2/v2/auth"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="tokenEndpoint"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Token Endpoint"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://oauth2.googleapis.com/token"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="userinfoEndpoint"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="User info endpoint"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://openidconnect.googleapis.com/v1/userinfo"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="jwksUri"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="JWKS URI"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder="https://www.googleapis.com/oauth2/v3/certs"
|
||||||
|
autoComplete="off"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="allowedEmailDomains"
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
label="Discovery Document URL"
|
label="Allowed Email Domains (defaults to any)"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="infisical.com, google.com" autoComplete="off" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="clientId"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Client ID" errorText={error?.message} isError={Boolean(error)}>
|
||||||
|
<Input
|
||||||
|
placeholder="Client ID"
|
||||||
|
type={isClientIdFocused ? "text" : "password"}
|
||||||
|
onFocus={() => setIsClientIdFocused.on()}
|
||||||
|
{...field}
|
||||||
|
onBlur={() => {
|
||||||
|
field.onBlur();
|
||||||
|
setIsClientIdFocused.off();
|
||||||
|
}}
|
||||||
|
autoComplete="off"
|
||||||
|
className="bg-mineshaft-800"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="clientSecret"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Client Secret"
|
||||||
errorText={error?.message}
|
errorText={error?.message}
|
||||||
isError={Boolean(error)}
|
isError={Boolean(error)}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
placeholder="https://accounts.google.com/.well-known/openid-configuration"
|
placeholder="Client Secret"
|
||||||
|
type={isClientSecretFocused ? "text" : "password"}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
onFocus={() => setIsClientSecretFocused.on()}
|
||||||
|
onBlur={() => {
|
||||||
|
field.onBlur();
|
||||||
|
setIsClientSecretFocused.off();
|
||||||
|
}}
|
||||||
|
className="bg-mineshaft-800"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
<div className="mt-8 flex justify-between">
|
||||||
{configurationTypeValue === ConfigurationType.CUSTOM && (
|
<div className="flex items-center">
|
||||||
<>
|
<Button
|
||||||
<Controller
|
className="mr-4"
|
||||||
control={control}
|
size="sm"
|
||||||
name="issuer"
|
type="submit"
|
||||||
render={({ field, fieldState: { error } }) => (
|
isLoading={createIsLoading || updateIsLoading}
|
||||||
<FormControl label="Issuer" errorText={error?.message} isError={Boolean(error)}>
|
>
|
||||||
<Input
|
{!data ? "Add" : "Update"}
|
||||||
{...field}
|
</Button>
|
||||||
placeholder="https://accounts.google.com"
|
<Button
|
||||||
autoComplete="off"
|
colorSchema="secondary"
|
||||||
/>
|
variant="plain"
|
||||||
</FormControl>
|
onClick={() => handlePopUpClose("addOIDC")}
|
||||||
)}
|
>
|
||||||
/>
|
Cancel
|
||||||
<Controller
|
</Button>
|
||||||
control={control}
|
</div>
|
||||||
name="authorizationEndpoint"
|
{!hideDelete && (
|
||||||
render={({ field, fieldState: { error } }) => (
|
<Button colorSchema="danger" onClick={() => setIsDeletePopupOpen.on()}>
|
||||||
<FormControl
|
Delete
|
||||||
label="Authorization Endpoint"
|
</Button>
|
||||||
errorText={error?.message}
|
)}
|
||||||
isError={Boolean(error)}
|
</div>
|
||||||
>
|
</form>
|
||||||
<Input
|
</ModalContent>
|
||||||
{...field}
|
</Modal>
|
||||||
placeholder="https://accounts.google.com/o/oauth2/v2/auth"
|
<DeleteActionModal
|
||||||
autoComplete="off"
|
isOpen={isDeletePopupOpen}
|
||||||
/>
|
title="Are you sure want to delete OIDC?"
|
||||||
</FormControl>
|
onChange={() => setIsDeletePopupOpen.toggle()}
|
||||||
)}
|
deleteKey="confirm"
|
||||||
/>
|
onDeleteApproved={handleOidcSoftDelete}
|
||||||
<Controller
|
/>
|
||||||
control={control}
|
</>
|
||||||
name="tokenEndpoint"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Token Endpoint"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="https://oauth2.googleapis.com/token"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="userinfoEndpoint"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="User info endpoint"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="https://openidconnect.googleapis.com/v1/userinfo"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="jwksUri"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl label="JWKS URI" errorText={error?.message} isError={Boolean(error)}>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="https://www.googleapis.com/oauth2/v3/certs"
|
|
||||||
autoComplete="off"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="allowedEmailDomains"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Allowed Email Domains (defaults to any)"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder="infisical.com, google.com" autoComplete="off" />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="clientId"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl label="Client ID" errorText={error?.message} isError={Boolean(error)}>
|
|
||||||
<Input
|
|
||||||
placeholder="Client ID"
|
|
||||||
type={isClientIdFocused ? "text" : "password"}
|
|
||||||
onFocus={() => setIsClientIdFocused.on()}
|
|
||||||
{...field}
|
|
||||||
onBlur={() => {
|
|
||||||
field.onBlur();
|
|
||||||
setIsClientIdFocused.off();
|
|
||||||
}}
|
|
||||||
autoComplete="off"
|
|
||||||
className="bg-mineshaft-800"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="clientSecret"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Client Secret"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="Client Secret"
|
|
||||||
type={isClientSecretFocused ? "text" : "password"}
|
|
||||||
autoComplete="off"
|
|
||||||
onFocus={() => setIsClientSecretFocused.on()}
|
|
||||||
onBlur={() => {
|
|
||||||
field.onBlur();
|
|
||||||
setIsClientSecretFocused.off();
|
|
||||||
}}
|
|
||||||
className="bg-mineshaft-800"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<div className="mt-8 flex items-center">
|
|
||||||
<Button
|
|
||||||
className="mr-4"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={createIsLoading || updateIsLoading}
|
|
||||||
>
|
|
||||||
{!data ? "Add" : "Update"}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
colorSchema="secondary"
|
|
||||||
variant="plain"
|
|
||||||
onClick={() => handlePopUpClose("addOIDC")}
|
|
||||||
>
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,34 +1,189 @@
|
|||||||
import { OrgPermissionActions, OrgPermissionSubjects, useServerConfig } from "@app/context";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { Button, ContentLoader, UpgradePlanModal } from "@app/components/v2";
|
||||||
|
import {
|
||||||
|
OrgPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
|
useServerConfig,
|
||||||
|
useSubscription
|
||||||
|
} from "@app/context";
|
||||||
import { withPermission } from "@app/hoc";
|
import { withPermission } from "@app/hoc";
|
||||||
|
import { usePopUp } from "@app/hooks";
|
||||||
|
import { useGetLDAPConfig, useGetOIDCConfig, useGetSSOConfig } from "@app/hooks/api";
|
||||||
import { LoginMethod } from "@app/hooks/api/admin/types";
|
import { LoginMethod } from "@app/hooks/api/admin/types";
|
||||||
|
|
||||||
|
import { LDAPModal } from "./LDAPModal";
|
||||||
|
import { OIDCModal } from "./OIDCModal";
|
||||||
import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection";
|
import { OrgGeneralAuthSection } from "./OrgGeneralAuthSection";
|
||||||
import { OrgLDAPSection } from "./OrgLDAPSection";
|
import { OrgLDAPSection } from "./OrgLDAPSection";
|
||||||
import { OrgOIDCSection } from "./OrgOIDCSection";
|
import { OrgOIDCSection } from "./OrgOIDCSection";
|
||||||
import { OrgScimSection } from "./OrgSCIMSection";
|
import { OrgScimSection } from "./OrgSCIMSection";
|
||||||
import { OrgSSOSection } from "./OrgSSOSection";
|
import { OrgSSOSection } from "./OrgSSOSection";
|
||||||
|
import { SSOModal } from "./SSOModal";
|
||||||
|
|
||||||
export const OrgAuthTab = withPermission(
|
export const OrgAuthTab = withPermission(
|
||||||
() => {
|
() => {
|
||||||
const {
|
const {
|
||||||
config: { enabledLoginMethods }
|
config: { enabledLoginMethods }
|
||||||
} = useServerConfig();
|
} = useServerConfig();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
|
"addLDAP",
|
||||||
|
"addSSO",
|
||||||
|
"addOIDC",
|
||||||
|
"upgradePlan"
|
||||||
|
] as const);
|
||||||
|
|
||||||
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
|
const { data: oidcConfig, isLoading: isLoadingOidcConfig } = useGetOIDCConfig(
|
||||||
|
currentOrg?.slug ?? ""
|
||||||
|
);
|
||||||
|
const { data: samlConfig, isLoading: isLoadingSamlConfig } = useGetSSOConfig(
|
||||||
|
currentOrg?.id ?? ""
|
||||||
|
);
|
||||||
|
const { data: ldapConfig, isLoading: isLdapConfig } = useGetLDAPConfig(currentOrg?.id ?? "");
|
||||||
|
const areConfigsLoading = isLoadingOidcConfig || isLoadingSamlConfig || isLdapConfig;
|
||||||
|
|
||||||
const shouldDisplaySection = (method: LoginMethod) =>
|
const shouldDisplaySection = (method: LoginMethod) =>
|
||||||
!enabledLoginMethods || enabledLoginMethods.includes(method);
|
!enabledLoginMethods || enabledLoginMethods.includes(method);
|
||||||
|
|
||||||
|
const isOidcConfigured = oidcConfig && (oidcConfig.discoveryURL || oidcConfig.issuer);
|
||||||
|
const isSamlConfigured = samlConfig && samlConfig.entryPoint;
|
||||||
|
const isLdapConfigured = ldapConfig && ldapConfig.url;
|
||||||
|
|
||||||
|
const shouldShowCreateIdentityProviderView =
|
||||||
|
!isOidcConfigured && !isSamlConfigured && !isLdapConfigured;
|
||||||
|
|
||||||
|
const createIdentityProviderView = (shouldDisplaySection(LoginMethod.SAML) ||
|
||||||
|
shouldDisplaySection(LoginMethod.OIDC) ||
|
||||||
|
shouldDisplaySection(LoginMethod.LDAP)) && (
|
||||||
|
<>
|
||||||
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
|
<p className="text-xl font-semibold text-gray-200">Connect an Identity Provider</p>
|
||||||
|
<p className="mb-2 mt-1 text-gray-400">
|
||||||
|
Connect your identity provider to simplify user management
|
||||||
|
</p>
|
||||||
|
{shouldDisplaySection(LoginMethod.SAML) && (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"mt-4 flex items-center justify-between",
|
||||||
|
(shouldDisplaySection(LoginMethod.OIDC) ||
|
||||||
|
shouldDisplaySection(LoginMethod.LDAP)) &&
|
||||||
|
"border-b border-mineshaft-500 pb-4"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className="text-lg text-gray-200">SAML</p>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
if (!subscription?.samlSSO) {
|
||||||
|
handlePopUpOpen("upgradePlan", { feature: "SAML SSO", plan: "Pro" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("addSSO");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{shouldDisplaySection(LoginMethod.OIDC) && (
|
||||||
|
<div
|
||||||
|
className={twMerge(
|
||||||
|
"mt-4 flex items-center justify-between",
|
||||||
|
shouldDisplaySection(LoginMethod.LDAP) && "border-b border-mineshaft-500 pb-4"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<p className="text-lg text-gray-200">OIDC</p>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
if (!subscription?.oidcSSO) {
|
||||||
|
handlePopUpOpen("upgradePlan", { feature: "OIDC SSO", plan: "Pro" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("addOIDC");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{shouldDisplaySection(LoginMethod.LDAP) && (
|
||||||
|
<div className="mt-4 flex items-center justify-between">
|
||||||
|
<p className="text-lg text-gray-200">LDAP</p>
|
||||||
|
<Button
|
||||||
|
colorSchema="secondary"
|
||||||
|
onClick={() => {
|
||||||
|
if (!subscription?.ldap) {
|
||||||
|
handlePopUpOpen("upgradePlan", { feature: "LDAP", plan: "Enterprise" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePopUpOpen("addLDAP");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Connect
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<SSOModal
|
||||||
|
hideDelete
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpClose={handlePopUpClose}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
<OIDCModal
|
||||||
|
hideDelete
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpClose={handlePopUpClose}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
<LDAPModal
|
||||||
|
hideDelete
|
||||||
|
popUp={popUp}
|
||||||
|
handlePopUpClose={handlePopUpClose}
|
||||||
|
handlePopUpToggle={handlePopUpToggle}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (areConfigsLoading) {
|
||||||
|
return <ContentLoader />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
<>
|
||||||
{shouldDisplaySection(LoginMethod.SAML) && (
|
{shouldShowCreateIdentityProviderView ? (
|
||||||
|
createIdentityProviderView
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
<OrgGeneralAuthSection />
|
{isSamlConfigured && shouldDisplaySection(LoginMethod.SAML) && (
|
||||||
<OrgSSOSection />
|
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
|
<OrgGeneralAuthSection />
|
||||||
|
<OrgSSOSection />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{isOidcConfigured && shouldDisplaySection(LoginMethod.OIDC) && <OrgOIDCSection />}
|
||||||
|
{isLdapConfigured && shouldDisplaySection(LoginMethod.LDAP) && <OrgLDAPSection />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{shouldDisplaySection(LoginMethod.OIDC) && <OrgOIDCSection />}
|
|
||||||
{shouldDisplaySection(LoginMethod.LDAP) && <OrgLDAPSection />}
|
|
||||||
<OrgScimSection />
|
<OrgScimSection />
|
||||||
</div>
|
<UpgradePlanModal
|
||||||
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
text={`You can use ${
|
||||||
|
(popUp.upgradePlan.data as { feature: string })?.feature
|
||||||
|
} if you switch to Infisical's ${
|
||||||
|
(popUp.upgradePlan.data as { plan: string })?.plan
|
||||||
|
} plan.`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
|
{ action: OrgPermissionActions.Read, subject: OrgPermissionSubjects.Sso }
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ export const OrgGeneralAuthSection = () => {
|
|||||||
Enforce members to authenticate via SAML to access this organization
|
Enforce members to authenticate via SAML to access this organization
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-mineshaft-600" />
|
|
||||||
<UpgradePlanModal
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h2 className="text-md text-mineshaft-100">LDAP</h2>
|
<h2 className="text-md text-mineshaft-100">LDAP</h2>
|
||||||
@@ -151,7 +151,6 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<hr className="border-mineshaft-600" />
|
|
||||||
<LDAPModal
|
<LDAPModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
@@ -167,6 +166,6 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h2 className="text-md text-mineshaft-100">OIDC</h2>
|
<h2 className="text-md text-mineshaft-100">OIDC</h2>
|
||||||
@@ -102,7 +102,6 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<hr className="border-mineshaft-600" />
|
|
||||||
<OIDCModal
|
<OIDCModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
@@ -113,6 +112,6 @@ export const OrgOIDCSection = (): JSX.Element => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can use OIDC SSO if you switch to Infisical's Pro plan."
|
text="You can use OIDC SSO if you switch to Infisical's Pro plan."
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -57,7 +57,8 @@ export const OrgScimSection = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-6">
|
||||||
|
<p className="text-xl font-semibold text-gray-200">Provision users via SCIM</p>
|
||||||
<div className="py-4">
|
<div className="py-4">
|
||||||
<div className="mb-2 flex items-center justify-between">
|
<div className="mb-2 flex items-center justify-between">
|
||||||
<h2 className="text-md text-mineshaft-100">SCIM</h2>
|
<h2 className="text-md text-mineshaft-100">SCIM</h2>
|
||||||
@@ -68,7 +69,7 @@ export const OrgScimSection = () => {
|
|||||||
colorSchema="secondary"
|
colorSchema="secondary"
|
||||||
isDisabled={!isAllowed}
|
isDisabled={!isAllowed}
|
||||||
>
|
>
|
||||||
Manage
|
Configure
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</OrgPermissionCan>
|
</OrgPermissionCan>
|
||||||
@@ -109,6 +110,6 @@ export const OrgScimSection = () => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
||||||
/>
|
/>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ export const OrgSSOSection = (): JSX.Element => {
|
|||||||
Allow members to authenticate into Infisical with SAML
|
Allow members to authenticate into Infisical with SAML
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<hr className="border-mineshaft-600" />
|
|
||||||
<SSOModal
|
<SSOModal
|
||||||
popUp={popUp}
|
popUp={popUp}
|
||||||
handlePopUpClose={handlePopUpClose}
|
handlePopUpClose={handlePopUpClose}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|||||||
import { createNotification } from "@app/components/notifications";
|
import { createNotification } from "@app/components/notifications";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
DeleteActionModal,
|
||||||
FormControl,
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
Modal,
|
Modal,
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
TextArea
|
TextArea
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useOrganization } from "@app/context";
|
import { useOrganization } from "@app/context";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
import { useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api";
|
import { useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
@@ -49,13 +51,15 @@ type Props = {
|
|||||||
popUp: UsePopUpState<["addSSO"]>;
|
popUp: UsePopUpState<["addSSO"]>;
|
||||||
handlePopUpClose: (popUpName: keyof UsePopUpState<["addSSO"]>) => void;
|
handlePopUpClose: (popUpName: keyof UsePopUpState<["addSSO"]>) => void;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addSSO"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addSSO"]>, state?: boolean) => void;
|
||||||
|
hideDelete?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle, hideDelete }: Props) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateSSOConfig();
|
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateSSOConfig();
|
||||||
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateSSOConfig();
|
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateSSOConfig();
|
||||||
|
const [isDeletePopupOpen, setIsDeletePopupOpen] = useToggle();
|
||||||
const { data } = useGetSSOConfig(currentOrg?.id ?? "");
|
const { data } = useGetSSOConfig(currentOrg?.id ?? "");
|
||||||
|
|
||||||
const { control, handleSubmit, reset, watch } = useForm<AddSSOFormData>({
|
const { control, handleSubmit, reset, watch } = useForm<AddSSOFormData>({
|
||||||
@@ -76,6 +80,31 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
const handleSamlSoftDelete = async () => {
|
||||||
|
if (!currentOrg) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await updateMutateAsync({
|
||||||
|
organizationId: currentOrg.id,
|
||||||
|
isActive: false,
|
||||||
|
entryPoint: "",
|
||||||
|
issuer: "",
|
||||||
|
cert: ""
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully deleted SAML SSO configuration.",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
createNotification({
|
||||||
|
text: "Failed deleting SAML SSO configuration.",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const onSSOModalSubmit = async ({ authProvider, entryPoint, issuer, cert }: AddSSOFormData) => {
|
const onSSOModalSubmit = async ({ authProvider, entryPoint, issuer, cert }: AddSSOFormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentOrg) return;
|
if (!currentOrg) return;
|
||||||
@@ -177,112 +206,133 @@ export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
const authProvider = watch("authProvider");
|
const authProvider = watch("authProvider");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<>
|
||||||
isOpen={popUp?.addSSO?.isOpen}
|
<Modal
|
||||||
onOpenChange={(isOpen) => {
|
isOpen={popUp?.addSSO?.isOpen}
|
||||||
handlePopUpToggle("addSSO", isOpen);
|
onOpenChange={(isOpen) => {
|
||||||
reset();
|
handlePopUpToggle("addSSO", isOpen);
|
||||||
}}
|
reset();
|
||||||
>
|
}}
|
||||||
<ModalContent title="Manage SAML configuration">
|
>
|
||||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
<ModalContent title="Manage SAML configuration">
|
||||||
<Controller
|
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||||
control={control}
|
<Controller
|
||||||
name="authProvider"
|
control={control}
|
||||||
defaultValue="okta-saml"
|
name="authProvider"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
defaultValue="okta-saml"
|
||||||
<FormControl label="Type" errorText={error?.message} isError={Boolean(error)}>
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
<Select
|
<FormControl label="Type" errorText={error?.message} isError={Boolean(error)}>
|
||||||
defaultValue={field.value}
|
<Select
|
||||||
{...field}
|
defaultValue={field.value}
|
||||||
onValueChange={(e) => onChange(e)}
|
{...field}
|
||||||
className="w-full"
|
onValueChange={(e) => onChange(e)}
|
||||||
>
|
className="w-full"
|
||||||
{ssoAuthProviders.map(({ label, value }) => (
|
>
|
||||||
<SelectItem value={String(value || "")} key={label}>
|
{ssoAuthProviders.map(({ label, value }) => (
|
||||||
{label}
|
<SelectItem value={String(value || "")} key={label}>
|
||||||
</SelectItem>
|
{label}
|
||||||
))}
|
</SelectItem>
|
||||||
</Select>
|
))}
|
||||||
</FormControl>
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{authProvider && data && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-sm text-mineshaft-400">
|
||||||
|
{renderLabels(authProvider).acsUrl}
|
||||||
|
</h3>
|
||||||
|
<p className="text-md break-all text-gray-400">{`${window.origin}/api/v1/sso/saml2/${data.id}`}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4">
|
||||||
|
<h3 className="text-sm text-mineshaft-400">
|
||||||
|
{renderLabels(authProvider).entityId}
|
||||||
|
</h3>
|
||||||
|
<p className="text-md text-gray-400">{window.origin}</p>
|
||||||
|
</div>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="entryPoint"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={renderLabels(authProvider).entryPoint}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={renderLabels(authProvider).entryPointPlaceholder}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="issuer"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label={renderLabels(authProvider).issuer}
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={renderLabels(authProvider).issuerPlaceholder}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="cert"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Certificate"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
|
||||||
{authProvider && data && (
|
|
||||||
<>
|
|
||||||
<div className="mb-4">
|
|
||||||
<h3 className="text-sm text-mineshaft-400">{renderLabels(authProvider).acsUrl}</h3>
|
|
||||||
<p className="text-md break-all text-gray-400">{`${window.origin}/api/v1/sso/saml2/${data.id}`}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4">
|
|
||||||
<h3 className="text-sm text-mineshaft-400">
|
|
||||||
{renderLabels(authProvider).entityId}
|
|
||||||
</h3>
|
|
||||||
<p className="text-md text-gray-400">{window.origin}</p>
|
|
||||||
</div>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="entryPoint"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label={renderLabels(authProvider).entryPoint}
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder={renderLabels(authProvider).entryPointPlaceholder}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="issuer"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label={renderLabels(authProvider).issuer}
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder={renderLabels(authProvider).issuerPlaceholder} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="cert"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Certificate"
|
|
||||||
errorText={error?.message}
|
|
||||||
isError={Boolean(error)}
|
|
||||||
>
|
|
||||||
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-8 flex items-center">
|
<div className="mt-8 flex justify-between">
|
||||||
<Button
|
<div className="flex items-center">
|
||||||
className="mr-4"
|
<Button
|
||||||
size="sm"
|
className="mr-4"
|
||||||
type="submit"
|
size="sm"
|
||||||
isLoading={createIsLoading || updateIsLoading}
|
type="submit"
|
||||||
>
|
isLoading={createIsLoading || updateIsLoading}
|
||||||
{!data ? "Add" : "Update"}
|
>
|
||||||
</Button>
|
{!data ? "Add" : "Update"}
|
||||||
<Button
|
</Button>
|
||||||
colorSchema="secondary"
|
<Button
|
||||||
variant="plain"
|
colorSchema="secondary"
|
||||||
onClick={() => handlePopUpClose("addSSO")}
|
variant="plain"
|
||||||
>
|
onClick={() => handlePopUpClose("addSSO")}
|
||||||
Cancel
|
>
|
||||||
</Button>
|
Cancel
|
||||||
</div>
|
</Button>
|
||||||
</form>
|
</div>
|
||||||
</ModalContent>
|
{!hideDelete && (
|
||||||
</Modal>
|
<Button colorSchema="danger" onClick={() => setIsDeletePopupOpen.on()}>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
<DeleteActionModal
|
||||||
|
isOpen={isDeletePopupOpen}
|
||||||
|
title="Are you sure want to delete SAML SSO?"
|
||||||
|
onChange={() => setIsDeletePopupOpen.toggle()}
|
||||||
|
deleteKey="confirm"
|
||||||
|
onDeleteApproved={handleSamlSoftDelete}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user