mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #774 from Infisical/saml-sso-edge-cases
Block inviting members to organization if SAML SSO is configured
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { MembershipOrg, Organization, User } from "../../models";
|
import { MembershipOrg, Organization, User } from "../../models";
|
||||||
|
import { SSOConfig } from "../../ee/models";
|
||||||
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
|
import { deleteMembershipOrg as deleteMemberFromOrg } from "../../helpers/membershipOrg";
|
||||||
import { createToken } from "../../helpers/auth";
|
import { createToken } from "../../helpers/auth";
|
||||||
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||||
@@ -110,6 +111,18 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(organizationId);
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
const ssoConfig = await SSOConfig.findOne({
|
||||||
|
organization: new Types.ObjectId(organizationId)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ssoConfig && ssoConfig.isActive) {
|
||||||
|
// case: SAML SSO is enabled for the organization
|
||||||
|
return res.status(400).send({
|
||||||
|
message:
|
||||||
|
"Failed to invite member due to SAML SSO configured for organization"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (plan.memberLimit !== null) {
|
if (plan.memberLimit !== null) {
|
||||||
// case: limit imposed on number of members allowed
|
// case: limit imposed on number of members allowed
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { getSSOConfigHelper } from "../../helpers/organizations";
|
|||||||
import { client } from "../../../config";
|
import { client } from "../../../config";
|
||||||
import { ResourceNotFoundError } from "../../../utils/errors";
|
import { ResourceNotFoundError } from "../../../utils/errors";
|
||||||
import { getSiteURL } from "../../../config";
|
import { getSiteURL } from "../../../config";
|
||||||
|
import { EELicenseService } from "../../services";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redirect user to appropriate SSO endpoint after successful authentication
|
* Redirect user to appropriate SSO endpoint after successful authentication
|
||||||
@@ -58,6 +59,12 @@ export const updateSSOConfig = async (req: Request, res: Response) => {
|
|||||||
cert,
|
cert,
|
||||||
audience
|
audience
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
if (!plan.samlSSO) return res.status(400).send({
|
||||||
|
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
||||||
|
});
|
||||||
|
|
||||||
interface PatchUpdate {
|
interface PatchUpdate {
|
||||||
authProvider?: string;
|
authProvider?: string;
|
||||||
@@ -203,6 +210,12 @@ export const createSSOConfig = async (req: Request, res: Response) => {
|
|||||||
cert,
|
cert,
|
||||||
audience
|
audience
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
|
const plan = await EELicenseService.getPlan(organizationId);
|
||||||
|
|
||||||
|
if (!plan.samlSSO) return res.status(400).send({
|
||||||
|
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
|
||||||
|
});
|
||||||
|
|
||||||
const key = await BotOrgService.getSymmetricKey(
|
const key = await BotOrgService.getSymmetricKey(
|
||||||
new Types.ObjectId(organizationId)
|
new Types.ObjectId(organizationId)
|
||||||
|
|||||||
@@ -183,7 +183,9 @@ export default function Users() {
|
|||||||
<div className="ml-2 flex min-w-max flex-row items-start justify-start">
|
<div className="ml-2 flex min-w-max flex-row items-start justify-start">
|
||||||
<Button
|
<Button
|
||||||
text={String(t("section.members.add-member"))}
|
text={String(t("section.members.add-member"))}
|
||||||
onButtonPressed={openAddModal}
|
onButtonPressed={() => {
|
||||||
|
openAddModal();
|
||||||
|
}}
|
||||||
color="mineshaft"
|
color="mineshaft"
|
||||||
size="md"
|
size="md"
|
||||||
icon={faPlus}
|
icon={faPlus}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
@@ -26,9 +27,11 @@ import {
|
|||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tr,
|
Tr,
|
||||||
UpgradePlanModal} from "@app/components/v2";
|
UpgradePlanModal
|
||||||
import { useWorkspace } from "@app/context";
|
} from "@app/components/v2";
|
||||||
|
import { useOrganization , useWorkspace } from "@app/context";
|
||||||
import { usePopUp, useToggle } from "@app/hooks";
|
import { usePopUp, useToggle } from "@app/hooks";
|
||||||
|
import { useGetSSOConfig } from "@app/hooks/api";
|
||||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||||
import { OrgUser, Workspace } from "@app/hooks/api/types";
|
import { OrgUser, Workspace } from "@app/hooks/api/types";
|
||||||
|
|
||||||
@@ -69,6 +72,9 @@ export const OrgMembersTable = ({
|
|||||||
setCompleteInviteLink
|
setCompleteInviteLink
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
const { data: ssoConfig, isLoading: isLoadingSSOConfig } = useGetSSOConfig(currentOrg?._id ?? "");
|
||||||
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
||||||
const {data: serverDetails } = useFetchServerStatus()
|
const {data: serverDetails } = useFetchServerStatus()
|
||||||
const { workspaces } = useWorkspace();
|
const { workspaces } = useWorkspace();
|
||||||
@@ -79,7 +85,7 @@ export const OrgMembersTable = ({
|
|||||||
"upgradePlan",
|
"upgradePlan",
|
||||||
"setUpEmail"
|
"setUpEmail"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (router.query.action === "invite") {
|
if (router.query.action === "invite") {
|
||||||
handlePopUpOpen("addMember");
|
handlePopUpOpen("addMember");
|
||||||
@@ -152,6 +158,15 @@ export const OrgMembersTable = ({
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||||
onClick={() => {
|
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) {
|
if (isMoreUserNotAllowed) {
|
||||||
handlePopUpOpen("upgradePlan");
|
handlePopUpOpen("upgradePlan");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -69,13 +69,13 @@ export const UserInfoSSOStep = ({
|
|||||||
const [nameError, setNameError] = useState(false);
|
const [nameError, setNameError] = useState(false);
|
||||||
const [organizationName, setOrganizationName] = useState("");
|
const [organizationName, setOrganizationName] = useState("");
|
||||||
const [organizationNameError, setOrganizationNameError] = useState(false);
|
const [organizationNameError, setOrganizationNameError] = useState(false);
|
||||||
|
const [attributionSource, setAttributionSource] = useState("");
|
||||||
const [errors, setErrors] = useState<Errors>({});
|
const [errors, setErrors] = useState<Errors>({});
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("providerOrganizationName: ", providerOrganizationName);
|
if (providerOrganizationName !== undefined) {
|
||||||
if (providerOrganizationName) {
|
|
||||||
setOrganizationName(providerOrganizationName);
|
setOrganizationName(providerOrganizationName);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@@ -171,7 +171,8 @@ export const UserInfoSSOStep = ({
|
|||||||
providerAuthToken,
|
providerAuthToken,
|
||||||
salt: result.salt,
|
salt: result.salt,
|
||||||
verifier: result.verifier,
|
verifier: result.verifier,
|
||||||
organizationName
|
organizationName,
|
||||||
|
attributionSource
|
||||||
});
|
});
|
||||||
|
|
||||||
// unset signup JWT token and set JWT token
|
// unset signup JWT token and set JWT token
|
||||||
@@ -209,7 +210,7 @@ export const UserInfoSSOStep = ({
|
|||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full mx-auto mb-36 w-max rounded-xl md:px-8 md:mb-16">
|
<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">
|
<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">
|
||||||
@@ -228,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>}
|
{nameError && <p className='text-left w-full text-xs text-red-600 mt-1 ml-1'>Please, specify your name</p>}
|
||||||
</div>
|
</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">
|
{providerOrganizationName === undefined && (
|
||||||
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p>
|
<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">
|
||||||
<Input
|
<p className='text-left w-full text-sm text-bunker-300 mb-1 ml-1 font-medium'>Organization Name</p>
|
||||||
placeholder="Infisical"
|
<Input
|
||||||
value={organizationName}
|
placeholder="Infisical"
|
||||||
onChange={(e) => setOrganizationName(e.target.value)}
|
value={organizationName}
|
||||||
isRequired
|
onChange={(e) => setOrganizationName(e.target.value)}
|
||||||
className="h-12"
|
isRequired
|
||||||
disabled
|
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>
|
{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">
|
<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
|
<InputField
|
||||||
label={t("section.password.password")}
|
label={t("section.password.password")}
|
||||||
|
|||||||
Reference in New Issue
Block a user