Complete preliminary SCIM fns, add permissioning to SCIM, add docs for SCIM

This commit is contained in:
Tuan Dang
2024-02-18 18:50:23 -08:00
parent e15ed4cc58
commit 7a65f8c837
61 changed files with 1259 additions and 552 deletions
@@ -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;
};
+2 -2
View File
@@ -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;
+2 -2
View File
@@ -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 = {
@@ -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>
);
}
@@ -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>
);
@@ -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