diff --git a/backend/src/db/migrations/20240930072738_add-oidc-auth-enforced-to-org.ts b/backend/src/db/migrations/20240930072738_add-oidc-auth-enforced-to-org.ts
new file mode 100644
index 000000000..284ac7050
--- /dev/null
+++ b/backend/src/db/migrations/20240930072738_add-oidc-auth-enforced-to-org.ts
@@ -0,0 +1,19 @@
+import { Knex } from "knex";
+
+import { TableName } from "../schemas";
+
+export async function up(knex: Knex): Promise {
+ if (!(await knex.schema.hasColumn(TableName.OidcConfig, "lastUsed"))) {
+ await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
+ tb.datetime("lastUsed");
+ });
+ }
+}
+
+export async function down(knex: Knex): Promise {
+ if (await knex.schema.hasColumn(TableName.OidcConfig, "lastUsed")) {
+ await knex.schema.alterTable(TableName.OidcConfig, (tb) => {
+ tb.dropColumn("lastUsed");
+ });
+ }
+}
diff --git a/backend/src/db/schemas/oidc-configs.ts b/backend/src/db/schemas/oidc-configs.ts
index d78f9e9a7..e8030267d 100644
--- a/backend/src/db/schemas/oidc-configs.ts
+++ b/backend/src/db/schemas/oidc-configs.ts
@@ -26,7 +26,8 @@ export const OidcConfigsSchema = z.object({
isActive: z.boolean(),
createdAt: z.date(),
updatedAt: z.date(),
- orgId: z.string().uuid()
+ orgId: z.string().uuid(),
+ lastUsed: z.date().nullable().optional()
});
export type TOidcConfigs = z.infer;
diff --git a/backend/src/ee/services/oidc/oidc-config-dal.ts b/backend/src/ee/services/oidc/oidc-config-dal.ts
index 470916c61..ffdba2cf7 100644
--- a/backend/src/ee/services/oidc/oidc-config-dal.ts
+++ b/backend/src/ee/services/oidc/oidc-config-dal.ts
@@ -1,5 +1,6 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
+import { DatabaseError } from "@app/lib/errors";
import { ormify } from "@app/lib/knex";
export type TOidcConfigDALFactory = ReturnType;
@@ -7,5 +8,22 @@ export type TOidcConfigDALFactory = ReturnType;
export const oidcConfigDALFactory = (db: TDbClient) => {
const oidcCfgOrm = ormify(db, TableName.OidcConfig);
- return { ...oidcCfgOrm };
+ const findEnforceableOidcCfg = async (orgId: string) => {
+ try {
+ const oidcCfg = await db
+ .replicaNode()(TableName.OidcConfig)
+ .where({
+ orgId,
+ isActive: true
+ })
+ .whereNotNull("lastUsed")
+ .first();
+
+ return oidcCfg;
+ } catch (error) {
+ throw new DatabaseError({ error, name: "Find org by id" });
+ }
+ };
+
+ return { ...oidcCfgOrm, findEnforceableOidcCfg };
};
diff --git a/backend/src/ee/services/oidc/oidc-config-service.ts b/backend/src/ee/services/oidc/oidc-config-service.ts
index f86f29646..983296539 100644
--- a/backend/src/ee/services/oidc/oidc-config-service.ts
+++ b/backend/src/ee/services/oidc/oidc-config-service.ts
@@ -314,6 +314,8 @@ export const oidcConfigServiceFactory = ({
}
);
+ await oidcConfigDAL.update({ orgId }, { lastUsed: new Date() });
+
if (user.email && !user.isEmailVerified) {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
@@ -395,7 +397,8 @@ export const oidcConfigServiceFactory = ({
tokenEndpoint,
userinfoEndpoint,
jwksUri,
- isActive
+ isActive,
+ lastUsed: null
};
if (clientId !== undefined) {
@@ -418,6 +421,7 @@ export const oidcConfigServiceFactory = ({
}
const [ssoConfig] = await oidcConfigDAL.update({ orgId: org.id }, updateQuery);
+ await orgDAL.updateById(org.id, { authEnforced: false, scimEnabled: false });
return ssoConfig;
};
diff --git a/backend/src/ee/services/permission/permission-fns.ts b/backend/src/ee/services/permission/permission-fns.ts
index eda19c215..ce6e3a88f 100644
--- a/backend/src/ee/services/permission/permission-fns.ts
+++ b/backend/src/ee/services/permission/permission-fns.ts
@@ -14,14 +14,19 @@ function isAuthMethodSaml(actorAuthMethod: ActorAuthMethod) {
].includes(actorAuthMethod);
}
-function validateOrgSAML(actorAuthMethod: ActorAuthMethod, isSamlEnforced: TOrganizations["authEnforced"]) {
+function validateOrgSSO(actorAuthMethod: ActorAuthMethod, isOrgSsoEnforced: TOrganizations["authEnforced"]) {
if (actorAuthMethod === undefined) {
throw new UnauthorizedError({ name: "No auth method defined" });
}
- if (isSamlEnforced && actorAuthMethod !== null && !isAuthMethodSaml(actorAuthMethod)) {
+ if (
+ isOrgSsoEnforced &&
+ actorAuthMethod !== null &&
+ !isAuthMethodSaml(actorAuthMethod) &&
+ actorAuthMethod !== AuthMethod.OIDC
+ ) {
throw new UnauthorizedError({ name: "Cannot access org-scoped resource" });
}
}
-export { isAuthMethodSaml, validateOrgSAML };
+export { isAuthMethodSaml, validateOrgSSO };
diff --git a/backend/src/ee/services/permission/permission-service.ts b/backend/src/ee/services/permission/permission-service.ts
index cb1e84677..5b5fe4583 100644
--- a/backend/src/ee/services/permission/permission-service.ts
+++ b/backend/src/ee/services/permission/permission-service.ts
@@ -19,7 +19,7 @@ import { TServiceTokenDALFactory } from "@app/services/service-token/service-tok
import { orgAdminPermissions, orgMemberPermissions, orgNoAccessPermissions, OrgPermissionSet } from "./org-permission";
import { TPermissionDALFactory } from "./permission-dal";
-import { validateOrgSAML } from "./permission-fns";
+import { validateOrgSSO } from "./permission-fns";
import { TBuildOrgPermissionDTO, TBuildProjectPermissionDTO } from "./permission-types";
import {
buildServiceTokenProjectPermission,
@@ -130,7 +130,7 @@ export const permissionServiceFactory = ({
throw new UnauthorizedError({ name: "You are not logged into this organization" });
}
- validateOrgSAML(authMethod, membership.orgAuthEnforced);
+ validateOrgSSO(authMethod, membership.orgAuthEnforced);
const finalPolicyRoles = [{ role: membership.role, permissions: membership.permissions }].concat(
membership?.groups?.map(({ role, customRolePermission }) => ({
@@ -213,7 +213,7 @@ export const permissionServiceFactory = ({
throw new UnauthorizedError({ name: "You are not logged into this organization" });
}
- validateOrgSAML(authMethod, userProjectPermission.orgAuthEnforced);
+ validateOrgSSO(authMethod, userProjectPermission.orgAuthEnforced);
// join two permissions and pass to build the final permission set
const rolePermissions = userProjectPermission.roles?.map(({ role, permissions }) => ({ role, permissions })) || [];
diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts
index 5d9632215..4bdd92402 100644
--- a/backend/src/server/routes/index.ts
+++ b/backend/src/server/routes/index.ts
@@ -506,7 +506,8 @@ export const registerRoutes = async (
smtpService,
userDAL,
groupDAL,
- orgBotDAL
+ orgBotDAL,
+ oidcConfigDAL
});
const signupService = authSignupServiceFactory({
tokenService,
diff --git a/backend/src/server/routes/v1/organization-router.ts b/backend/src/server/routes/v1/organization-router.ts
index 9e2f09821..b113b9f9d 100644
--- a/backend/src/server/routes/v1/organization-router.ts
+++ b/backend/src/server/routes/v1/organization-router.ts
@@ -28,7 +28,9 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
schema: {
response: {
200: z.object({
- organizations: OrganizationsSchema.array()
+ organizations: OrganizationsSchema.extend({
+ orgAuthMethod: z.string()
+ }).array()
})
}
},
diff --git a/backend/src/services/org/org-dal.ts b/backend/src/services/org/org-dal.ts
index d84dd1afd..0093c4f69 100644
--- a/backend/src/services/org/org-dal.ts
+++ b/backend/src/services/org/org-dal.ts
@@ -29,13 +29,37 @@ export const orgDALFactory = (db: TDbClient) => {
};
// special query
- const findAllOrgsByUserId = async (userId: string): Promise => {
+ const findAllOrgsByUserId = async (userId: string): Promise<(TOrganizations & { orgAuthMethod: string })[]> => {
try {
- const org = await db
+ const org = (await db
.replicaNode()(TableName.OrgMembership)
.where({ userId })
.join(TableName.Organization, `${TableName.OrgMembership}.orgId`, `${TableName.Organization}.id`)
- .select(selectAllTableCols(TableName.Organization));
+ .leftJoin(TableName.SamlConfig, (qb) => {
+ qb.on(`${TableName.SamlConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
+ `${TableName.SamlConfig}.isActive`,
+ "=",
+ db.raw("true")
+ );
+ })
+ .leftJoin(TableName.OidcConfig, (qb) => {
+ qb.on(`${TableName.OidcConfig}.orgId`, "=", `${TableName.Organization}.id`).andOn(
+ `${TableName.OidcConfig}.isActive`,
+ "=",
+ db.raw("true")
+ );
+ })
+ .select(selectAllTableCols(TableName.Organization))
+ .select(
+ db.raw(`
+ CASE
+ WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN 'saml'
+ WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN 'oidc'
+ ELSE ''
+ END as "orgAuthMethod"
+ `)
+ )) as (TOrganizations & { orgAuthMethod: string })[];
+
return org;
} catch (error) {
throw new DatabaseError({ error, name: "Find all org by user id" });
diff --git a/backend/src/services/org/org-service.ts b/backend/src/services/org/org-service.ts
index 8417f2444..2c349d744 100644
--- a/backend/src/services/org/org-service.ts
+++ b/backend/src/services/org/org-service.ts
@@ -18,6 +18,7 @@ import {
import { TProjects } from "@app/db/schemas/projects";
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
+import { TOidcConfigDALFactory } from "@app/ee/services/oidc/oidc-config-dal";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
@@ -80,6 +81,7 @@ type TOrgServiceFactoryDep = {
orgMembershipDAL: Pick;
incidentContactDAL: TIncidentContactsDALFactory;
samlConfigDAL: Pick;
+ oidcConfigDAL: Pick;
smtpService: TSmtpService;
tokenService: TAuthTokenServiceFactory;
permissionService: TPermissionServiceFactory;
@@ -114,6 +116,7 @@ export const orgServiceFactory = ({
licenseService,
projectRoleDAL,
samlConfigDAL,
+ oidcConfigDAL,
projectBotDAL,
projectUserMembershipRoleDAL
}: TOrgServiceFactoryDep) => {
@@ -266,10 +269,9 @@ export const orgServiceFactory = ({
const plan = await licenseService.getPlan(orgId);
if (authEnforced !== undefined) {
- if (!plan?.samlSSO)
+ if (!plan?.samlSSO || !plan.oidcSSO)
throw new BadRequestError({
- message:
- "Failed to enforce/un-enforce SAML SSO due to plan restriction. Upgrade plan to enforce/un-enforce SAML SSO."
+ message: "Failed to enforce/un-enforce SSO due to plan restriction. Upgrade plan to enforce/un-enforce SSO."
});
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Sso);
}
@@ -285,10 +287,12 @@ export const orgServiceFactory = ({
if (authEnforced) {
const samlCfg = await samlConfigDAL.findEnforceableSamlCfg(orgId);
- if (!samlCfg)
+ const oidcCfg = await oidcConfigDAL.findEnforceableOidcCfg(orgId);
+
+ if (!samlCfg && !oidcCfg)
throw new BadRequestError({
- name: "No enforceable SAML config found",
- message: "No enforceable SAML config found"
+ name: "No enforceable SSO config found",
+ message: "No enforceable SSO config found"
});
}
diff --git a/docs/documentation/platform/sso/auth0-oidc.mdx b/docs/documentation/platform/sso/auth0-oidc.mdx
index 2b459d5ca..331eecdf1 100644
--- a/docs/documentation/platform/sso/auth0-oidc.mdx
+++ b/docs/documentation/platform/sso/auth0-oidc.mdx
@@ -39,7 +39,7 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."
- 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click **Manage**.
+ 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click **Connect**.

3.2. For configuration type, select **Discovery URL**. Then, set **Discovery Document URL**, **Client ID**, and **Client Secret** from step 2.1 and 2.2.
@@ -54,6 +54,19 @@ description: "Learn how to configure Auth0 OIDC for Infisical SSO."

+
+ Enforcing OIDC SSO ensures that members in your organization can only access Infisical
+ by logging into the organization via Auth0.
+
+ To enforce OIDC SSO, you're required to test out the OpenID connection by successfully authenticating at least one Auth0 user with Infisical.
+ Once you've completed this requirement, you can toggle the **Enforce OIDC SSO** button to enforce OIDC SSO.
+
+
+ We recommend ensuring that your account is provisioned using the application in Auth0
+ prior to enforcing OIDC SSO to prevent any unintended issues.
+
+
+
diff --git a/docs/documentation/platform/sso/general-oidc.mdx b/docs/documentation/platform/sso/general-oidc.mdx
index ae559cc59..b32bea8e5 100644
--- a/docs/documentation/platform/sso/general-oidc.mdx
+++ b/docs/documentation/platform/sso/general-oidc.mdx
@@ -28,7 +28,7 @@ Prerequisites:
1.4. Access the IdP’s OIDC discovery document (usually located at `https:///.well-known/openid-configuration`). This document contains important endpoints such as authorization, token, userinfo, and keys.
- 2.1. Back in Infisical, in the Organization settings > Security > OIDC, click Manage
+ 2.1. Back in Infisical, in the Organization settings > Security > OIDC, click Connect.

2.2. You can configure OIDC either through the Discovery URL (Recommended) or by inputting custom endpoints.
@@ -53,9 +53,20 @@ Prerequisites:
Enabling OIDC SSO allows members in your organization to log into Infisical via the configured Identity Provider
- 
+ 
+
-
+
+ Enforcing OIDC SSO ensures that members in your organization can only access Infisical
+ by logging into the organization via the Identity provider.
+
+ To enforce OIDC SSO, you're required to test out the OpenID connection by successfully authenticating at least one IdP user with Infisical.
+ Once you've completed this requirement, you can toggle the **Enforce OIDC SSO** button to enforce OIDC SSO.
+
+
+ We recommend ensuring that your account is provisioned using the identity provider prior to enforcing OIDC SSO to prevent any unintended issues.
+
+
diff --git a/docs/documentation/platform/sso/keycloak-oidc.mdx b/docs/documentation/platform/sso/keycloak-oidc.mdx
index d3818f61c..d8bb8d306 100644
--- a/docs/documentation/platform/sso/keycloak-oidc.mdx
+++ b/docs/documentation/platform/sso/keycloak-oidc.mdx
@@ -65,7 +65,7 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."
- 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click Manage
+ 3.1. Back in Infisical, in the Organization settings > Security > OIDC, click Connect.

3.2. For configuration type, select Discovery URL. Then, set the appropriate values for **Discovery Document URL**, **Client ID**, and **Client Secret**.
@@ -80,6 +80,19 @@ description: "Learn how to configure Keycloak OIDC for Infisical SSO."

+
+ Enforcing OIDC SSO ensures that members in your organization can only access Infisical
+ by logging into the organization via Keycloak.
+
+ To enforce OIDC SSO, you're required to test out the OpenID connection by successfully authenticating at least one Keycloak user with Infisical.
+ Once you've completed this requirement, you can toggle the **Enforce OIDC SSO** button to enforce OIDC SSO.
+
+
+ We recommend ensuring that your account is provisioned using the application in Keycloak
+ prior to enforcing OIDC SSO to prevent any unintended issues.
+
+
+
diff --git a/docs/images/sso/auth0-oidc/enable-oidc.png b/docs/images/sso/auth0-oidc/enable-oidc.png
index 32682c302..0a43f22ed 100644
Binary files a/docs/images/sso/auth0-oidc/enable-oidc.png and b/docs/images/sso/auth0-oidc/enable-oidc.png differ
diff --git a/docs/images/sso/auth0-oidc/org-oidc-overview.png b/docs/images/sso/auth0-oidc/org-oidc-overview.png
index ce5fb0d60..f5778b97a 100644
Binary files a/docs/images/sso/auth0-oidc/org-oidc-overview.png and b/docs/images/sso/auth0-oidc/org-oidc-overview.png differ
diff --git a/docs/images/sso/general-oidc/org-oidc-enable.png b/docs/images/sso/general-oidc/org-oidc-enable.png
index 69f0062bc..0a43f22ed 100644
Binary files a/docs/images/sso/general-oidc/org-oidc-enable.png and b/docs/images/sso/general-oidc/org-oidc-enable.png differ
diff --git a/docs/images/sso/general-oidc/org-oidc-manage.png b/docs/images/sso/general-oidc/org-oidc-manage.png
index 69a60f0f5..f5778b97a 100644
Binary files a/docs/images/sso/general-oidc/org-oidc-manage.png and b/docs/images/sso/general-oidc/org-oidc-manage.png differ
diff --git a/docs/images/sso/keycloak-oidc/enable-oidc.png b/docs/images/sso/keycloak-oidc/enable-oidc.png
index 6518319c4..0a43f22ed 100644
Binary files a/docs/images/sso/keycloak-oidc/enable-oidc.png and b/docs/images/sso/keycloak-oidc/enable-oidc.png differ
diff --git a/docs/images/sso/keycloak-oidc/manage-org-oidc.png b/docs/images/sso/keycloak-oidc/manage-org-oidc.png
index e6346cd24..f5778b97a 100644
Binary files a/docs/images/sso/keycloak-oidc/manage-org-oidc.png and b/docs/images/sso/keycloak-oidc/manage-org-oidc.png differ
diff --git a/frontend/src/hooks/api/oidcConfig/mutations.tsx b/frontend/src/hooks/api/oidcConfig/mutations.tsx
index 4a0d963cc..9f57c0332 100644
--- a/frontend/src/hooks/api/oidcConfig/mutations.tsx
+++ b/frontend/src/hooks/api/oidcConfig/mutations.tsx
@@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "@app/config/request";
+import { organizationKeys } from "../organization/queries";
import { oidcConfigKeys } from "./queries";
export const useUpdateOIDCConfig = () => {
@@ -53,6 +54,7 @@ export const useUpdateOIDCConfig = () => {
},
onSuccess(_, dto) {
queryClient.invalidateQueries(oidcConfigKeys.getOIDCConfig(dto.orgSlug));
+ queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
}
});
};
diff --git a/frontend/src/hooks/api/organization/types.ts b/frontend/src/hooks/api/organization/types.ts
index 684a53af4..cfda9343c 100644
--- a/frontend/src/hooks/api/organization/types.ts
+++ b/frontend/src/hooks/api/organization/types.ts
@@ -7,6 +7,7 @@ export type Organization = {
createAt: string;
updatedAt: string;
authEnforced: boolean;
+ orgAuthMethod: string;
scimEnabled: boolean;
slug: string;
};
diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts
index c96e3d735..ec0fa5f34 100644
--- a/frontend/src/hooks/api/users/types.ts
+++ b/frontend/src/hooks/api/users/types.ts
@@ -9,7 +9,8 @@ export enum AuthMethod {
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml",
KEYCLOAK_SAML = "keycloak-saml",
- LDAP = "ldap"
+ LDAP = "ldap",
+ OIDC = "oidc"
}
export type User = {
diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx
index 382404c91..ccdc7c101 100644
--- a/frontend/src/layouts/AppLayout/AppLayout.tsx
+++ b/frontend/src/layouts/AppLayout/AppLayout.tsx
@@ -80,6 +80,7 @@ import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types";
import { Workspace } from "@app/hooks/api/types";
import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation";
import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries";
+import { AuthMethod } from "@app/hooks/api/users/types";
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
import { CreateOrgModal } from "@app/views/Org/components";
@@ -385,9 +386,13 @@ export const AppLayout = ({ children }: LayoutProps) => {
// -> logout + redirect to SAML SSO
await logout.mutateAsync();
- window.open(
- `/api/v1/sso/redirect/saml2/organizations/${org.slug}`
- );
+ if (org.orgAuthMethod === AuthMethod.OIDC) {
+ window.open(`/api/v1/sso/oidc/login?orgSlug=${org.slug}`);
+ } else {
+ window.open(
+ `/api/v1/sso/redirect/saml2/organizations/${org.slug}`
+ );
+ }
window.close();
return;
}
diff --git a/frontend/src/pages/login/select-organization.tsx b/frontend/src/pages/login/select-organization.tsx
index 13ec3e414..5b9c0f72c 100644
--- a/frontend/src/pages/login/select-organization.tsx
+++ b/frontend/src/pages/login/select-organization.tsx
@@ -18,6 +18,7 @@ import { useUser } from "@app/context";
import { useGetOrganizations, useLogoutUser, useSelectOrganization } from "@app/hooks/api";
import { UserAgentType } from "@app/hooks/api/auth/types";
import { Organization } from "@app/hooks/api/types";
+import { AuthMethod } from "@app/hooks/api/users/types";
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
import { navigateUserToOrg } from "@app/views/Login/Login.utils";
@@ -57,14 +58,21 @@ export default function LoginPage() {
if (organization.authEnforced) {
// org has an org-level auth method enabled (e.g. SAML)
// -> logout + redirect to SAML SSO
- let samlUrl = `/api/v1/sso/redirect/saml2/organizations/${organization.slug}`;
+ await logout.mutateAsync();
+ let url = "";
+ if (organization.orgAuthMethod === AuthMethod.OIDC) {
+ url = `/api/v1/sso/oidc/login?orgSlug=${organization.slug}${
+ callbackPort ? `&callbackPort=${callbackPort}` : ""
+ }`;
+ } else {
+ url = `/api/v1/sso/redirect/saml2/organizations/${organization.slug}`;
- if (callbackPort) {
- samlUrl += `?callback_port=${callbackPort}`;
+ if (callbackPort) {
+ url += `?callback_port=${callbackPort}`;
+ }
}
- await logout.mutateAsync();
- window.open(samlUrl);
+ window.open(url);
window.close();
return;
}
diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx
index 6ec56f7b3..d3b6be055 100644
--- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx
+++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/OrgOIDCSection.tsx
@@ -7,7 +7,7 @@ import {
useOrganization,
useSubscription
} from "@app/context";
-import { useGetOIDCConfig } from "@app/hooks/api";
+import { useGetOIDCConfig, useLogoutUser, useUpdateOrg } from "@app/hooks/api";
import { useUpdateOIDCConfig } from "@app/hooks/api/oidcConfig/mutations";
import { usePopUp } from "@app/hooks/usePopUp";
@@ -19,6 +19,9 @@ export const OrgOIDCSection = (): JSX.Element => {
const { data, isLoading } = useGetOIDCConfig(currentOrg?.slug ?? "");
const { mutateAsync } = useUpdateOIDCConfig();
+ const { mutateAsync: updateOrg } = useUpdateOrg();
+
+ const logout = useLogoutUser();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addOIDC",
"upgradePlan"
@@ -51,6 +54,37 @@ export const OrgOIDCSection = (): JSX.Element => {
}
};
+ const handleEnforceOrgAuthToggle = async (value: boolean) => {
+ try {
+ if (!currentOrg?.id) return;
+ if (!subscription?.samlSSO) {
+ handlePopUpOpen("upgradePlan");
+ return;
+ }
+
+ await updateOrg({
+ orgId: currentOrg?.id,
+ authEnforced: value
+ });
+
+ createNotification({
+ text: `Successfully ${value ? "enforced" : "un-enforced"} org-level auth`,
+ type: "success"
+ });
+
+ if (value) {
+ await logout.mutateAsync();
+ window.open(`/api/v1/sso/oidc/login?orgSlug=${currentOrg.slug}`);
+ window.close();
+ }
+ } catch (err) {
+ createNotification({
+ text: (err as { response: { data: { message: string } } }).response.data.message,
+ type: "error"
+ });
+ }
+ };
+
const addOidcButtonClick = async () => {
if (subscription?.oidcSSO && currentOrg) {
handlePopUpOpen("addOIDC");
@@ -102,6 +136,24 @@ export const OrgOIDCSection = (): JSX.Element => {
)}
+
+
+
Enforce OIDC SSO
+
+ {(isAllowed) => (
+ handleEnforceOrgAuthToggle(value)}
+ isDisabled={!isAllowed}
+ />
+ )}
+
+
+
+ Enforce members to authenticate via OIDC to access this organization
+
+