feat: enforce oidc sso

This commit is contained in:
Sheen Capadngan
2024-09-30 20:59:48 +08:00
parent efa54e0c46
commit 23abab987f
25 changed files with 219 additions and 35 deletions
@@ -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);
}
});
};
@@ -7,6 +7,7 @@ export type Organization = {
createAt: string;
updatedAt: string;
authEnforced: boolean;
orgAuthMethod: string;
scimEnabled: boolean;
slug: string;
};
+2 -1
View File
@@ -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 = {
+8 -3
View File
@@ -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;
}
@@ -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;
}
@@ -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 => {
</p>
</div>
)}
<div className="py-4">
<div className="mb-2 flex justify-between">
<h3 className="text-md text-mineshaft-100">Enforce OIDC SSO</h3>
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => (
<Switch
id="enforce-org-auth"
isChecked={currentOrg?.authEnforced ?? false}
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
isDisabled={!isAllowed}
/>
)}
</OrgPermissionCan>
</div>
<p className="text-sm text-mineshaft-300">
Enforce members to authenticate via OIDC to access this organization
</p>
</div>
<OIDCModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}