mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 01:29:38 +00:00
Complete preliminary SCIM fns, add permissioning to SCIM, add docs for SCIM
This commit is contained in:
@@ -13,6 +13,7 @@ export enum OrgPermissionSubjects {
|
||||
Member = "member",
|
||||
Settings = "settings",
|
||||
IncidentAccount = "incident-contact",
|
||||
Scim = "scim",
|
||||
Sso = "sso",
|
||||
Billing = "billing",
|
||||
SecretScanning = "secret-scanning",
|
||||
@@ -26,6 +27,7 @@ export type OrgPermissionSet =
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Member]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Settings]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Scim]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Sso]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||
| [OrgPermissionActions, OrgPermissionSubjects.Billing]
|
||||
|
||||
@@ -71,12 +71,14 @@ export const useUpdateOrg = () => {
|
||||
mutationFn: ({
|
||||
name,
|
||||
authEnforced,
|
||||
scimEnabled,
|
||||
slug,
|
||||
orgId
|
||||
}) => {
|
||||
return apiRequest.patch(`/api/v1/organization/${orgId}`, {
|
||||
name,
|
||||
authEnforced,
|
||||
scimEnabled,
|
||||
slug
|
||||
});
|
||||
},
|
||||
|
||||
@@ -4,6 +4,7 @@ export type Organization = {
|
||||
createAt: string;
|
||||
updatedAt: string;
|
||||
authEnforced: boolean;
|
||||
scimEnabled: boolean;
|
||||
slug: string;
|
||||
};
|
||||
|
||||
@@ -11,6 +12,7 @@ export type UpdateOrgDTO = {
|
||||
orgId: string;
|
||||
name?: string;
|
||||
authEnforced?: boolean;
|
||||
scimEnabled?: boolean;
|
||||
slug?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,12 +13,12 @@ export const useCreateScimToken = () => {
|
||||
mutationFn: async ({
|
||||
organizationId,
|
||||
description,
|
||||
ttl
|
||||
ttlDays
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v1/scim/scim-tokens", {
|
||||
organizationId,
|
||||
description,
|
||||
ttl
|
||||
ttlDays
|
||||
});
|
||||
|
||||
return data;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export type ScimTokenData = {
|
||||
id: string;
|
||||
ttl: number;
|
||||
ttlDays: number;
|
||||
description: string;
|
||||
tokenSuffix: string;
|
||||
orgId: string;
|
||||
@@ -11,7 +11,7 @@ export type ScimTokenData = {
|
||||
export type CreateScimTokenDTO = {
|
||||
organizationId: string;
|
||||
description?: string;
|
||||
ttl?: number;
|
||||
ttlDays?: number;
|
||||
}
|
||||
|
||||
export type DeleteScimTokenDTO = {
|
||||
|
||||
+21
-3
@@ -1,16 +1,25 @@
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Switch } from "@app/components/v2";
|
||||
import {
|
||||
Switch,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { useLogoutUser,useUpdateOrg } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { useLogoutUser, useUpdateOrg } from "@app/hooks/api";
|
||||
|
||||
export const OrgGeneralAuthSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
@@ -19,6 +28,10 @@ export const OrgGeneralAuthSection = () => {
|
||||
const handleEnforceOrgAuthToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.samlSSO) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
@@ -60,6 +73,11 @@ export const OrgGeneralAuthSection = () => {
|
||||
</Switch>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can enforce SAML SSO if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+61
-34
@@ -1,37 +1,34 @@
|
||||
import { useState } from "react";
|
||||
import { faPlus, faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
// import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
// import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Switch,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
// OrgPermissionActions,
|
||||
// OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useSubscription,
|
||||
useOrganization
|
||||
} from "@app/context";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
import { ScimTokenModal } from "./ScimTokenModal";
|
||||
|
||||
// TODO: add permissioning for enteprise SCIM
|
||||
import { useUpdateOrg } from "@app/hooks/api";
|
||||
|
||||
export const OrgScimSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
// const { createNotification } = useNotificationContext();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"scimToken",
|
||||
"deleteScimToken",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const [scimEnabled, setScimEnabled] = useState(false); // sync this with backend
|
||||
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
const addScimTokenBtnClick = () => {
|
||||
if (subscription?.scim) {
|
||||
@@ -41,11 +38,29 @@ export const OrgScimSection = () => {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSCIMToggle = (value: boolean) => {
|
||||
const handleEnableSCIMToggle = async (value: boolean) => {
|
||||
try {
|
||||
setScimEnabled(value);
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.scim) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
scimEnabled: value
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to ${value ? "enable" : "disable"} SCIM provisioning`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,23 +68,35 @@ export const OrgScimSection = () => {
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-8 flex items-center">
|
||||
<h2 className="flex-1 text-xl font-semibold text-white">SCIM Configuration</h2>
|
||||
<Button
|
||||
onClick={addScimTokenBtnClick}
|
||||
colorSchema="secondary"
|
||||
// isDisabled={!isAllowed}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Manage SCIM Tokens
|
||||
</Button>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Scim}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={addScimTokenBtnClick}
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
Manage SCIM Tokens
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<Switch
|
||||
id="enable-scim"
|
||||
onCheckedChange={(value) => handleSCIMToggle(value)}
|
||||
isChecked={scimEnabled}
|
||||
isDisabled={false}
|
||||
>
|
||||
Enable SCIM Provisioning
|
||||
</Switch>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Scim}>
|
||||
<Switch
|
||||
id="enable-scim"
|
||||
onCheckedChange={(value) => {
|
||||
if (subscription?.scim) {
|
||||
handleEnableSCIMToggle(value)
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
isChecked={currentOrg?.scimEnabled ?? false}
|
||||
isDisabled={false}
|
||||
>
|
||||
Enable SCIM Provisioning
|
||||
</Switch>
|
||||
</OrgPermissionCan>
|
||||
<ScimTokenModal
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
@@ -78,7 +105,7 @@ export const OrgScimSection = () => {
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use SCIM Provisioning if you switch to Infisical's Pro plan."
|
||||
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
+19
-28
@@ -35,11 +35,9 @@ import {
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { useOrganization } from "@app/context";
|
||||
|
||||
// TODO: turn TTL into a select component
|
||||
|
||||
const schema = yup.object({
|
||||
description: yup.string(),
|
||||
ttl: yup.string()
|
||||
ttlDays: yup.string().required("TTL is required")
|
||||
});
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
@@ -86,7 +84,7 @@ export const ScimTokenModal = ({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
ttl: ""
|
||||
ttlDays: "365"
|
||||
}
|
||||
});
|
||||
|
||||
@@ -103,14 +101,14 @@ export const ScimTokenModal = ({
|
||||
return () => clearTimeout(timer);
|
||||
}, [isScimTokenCopied, isScimUrlCopied]);
|
||||
|
||||
const onFormSubmit = async ({ description, ttl }: FormData) => {
|
||||
const onFormSubmit = async ({ description, ttlDays }: FormData) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
const { scimToken } = await createScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
description,
|
||||
ttl: Number(ttl)
|
||||
organizationId: currentOrg.id,
|
||||
description,
|
||||
ttlDays: Number(ttlDays)
|
||||
});
|
||||
|
||||
setToken(scimToken);
|
||||
@@ -130,19 +128,12 @@ export const ScimTokenModal = ({
|
||||
|
||||
const onDeleteScimTokenSubmit = async (scimTokenId: string) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
await deleteScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
scimTokenId
|
||||
});
|
||||
|
||||
// TODO: find alt way
|
||||
|
||||
// if (token.startsWith(clientSecretPrefix)) {
|
||||
// reset();
|
||||
// setToken("");
|
||||
// }
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
await deleteScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
scimTokenId
|
||||
});
|
||||
|
||||
handlePopUpToggle("deleteScimToken", false);
|
||||
|
||||
@@ -242,11 +233,11 @@ export const ScimTokenModal = ({
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="ttl"
|
||||
defaultValue="365"
|
||||
name="ttlDays"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="TTL (seconds - optional)"
|
||||
label="TTL (days)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
@@ -287,19 +278,19 @@ export const ScimTokenModal = ({
|
||||
({
|
||||
id,
|
||||
description,
|
||||
ttl,
|
||||
ttlDays,
|
||||
createdAt
|
||||
}) => {
|
||||
|
||||
let expiresAt;
|
||||
if (ttl > 0) {
|
||||
expiresAt = new Date(new Date(createdAt).getTime() + ttl * 1000);
|
||||
if (ttlDays > 0) {
|
||||
expiresAt = new Date(new Date(createdAt).getTime() + ttlDays * 86400);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr className="h-10 items-center" key={`mi-client-secret-${id}`}>
|
||||
<Td>{description === "" ? "-" : description}</Td>
|
||||
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd") : "-"}</Td>
|
||||
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd HH:mm:ss") : "-"}</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd HH:mm:ss")}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
|
||||
Reference in New Issue
Block a user