Merge pull request #2667 from scott-ray-wilson/oidc-default-org-slug

Feature: OIDC Default Org
This commit is contained in:
Maidul Islam
2024-10-31 21:56:53 -04:00
committed by GitHub
8 changed files with 108 additions and 17 deletions
@@ -29,6 +29,8 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}).extend({ }).extend({
isMigrationModeOn: z.boolean(), isMigrationModeOn: z.boolean(),
defaultAuthOrgSlug: z.string().nullable(), defaultAuthOrgSlug: z.string().nullable(),
defaultAuthOrgAuthEnforced: z.boolean().nullish(),
defaultAuthOrgAuthMethod: z.string().nullish(),
isSecretScanningDisabled: z.boolean() isSecretScanningDisabled: z.boolean()
}) })
}) })
+1 -1
View File
@@ -291,7 +291,7 @@ export const orgServiceFactory = ({
} }
if (authEnforced !== undefined) { if (authEnforced !== undefined) {
if (!plan?.samlSSO || !plan.oidcSSO) if (!plan?.samlSSO && !plan.oidcSSO)
throw new BadRequestError({ throw new BadRequestError({
message: "Failed to enforce/un-enforce SSO due to plan restriction. Upgrade plan to enforce/un-enforce SSO." message: "Failed to enforce/un-enforce SSO due to plan restriction. Upgrade plan to enforce/un-enforce SSO."
}); });
@@ -14,9 +14,31 @@ export const superAdminDALFactory = (db: TDbClient) => {
const config = await (tx || db)(TableName.SuperAdmin) const config = await (tx || db)(TableName.SuperAdmin)
.where(`${TableName.SuperAdmin}.id`, id) .where(`${TableName.SuperAdmin}.id`, id)
.leftJoin(TableName.Organization, `${TableName.SuperAdmin}.defaultAuthOrgId`, `${TableName.Organization}.id`) .leftJoin(TableName.Organization, `${TableName.SuperAdmin}.defaultAuthOrgId`, `${TableName.Organization}.id`)
.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( .select(
db.ref("*").withSchema(TableName.SuperAdmin) as unknown as keyof TSuperAdmin, db.ref("*").withSchema(TableName.SuperAdmin) as unknown as keyof TSuperAdmin,
db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug") db.ref("slug").withSchema(TableName.Organization).as("defaultAuthOrgSlug"),
db.ref("authEnforced").withSchema(TableName.Organization).as("defaultAuthOrgAuthEnforced"),
db.raw(`
CASE
WHEN ${TableName.SamlConfig}."orgId" IS NOT NULL THEN 'saml'
WHEN ${TableName.OidcConfig}."orgId" IS NOT NULL THEN 'oidc'
ELSE NULL
END as "defaultAuthOrgAuthMethod"
`)
) )
.first(); .first();
@@ -27,7 +49,11 @@ export const superAdminDALFactory = (db: TDbClient) => {
return { return {
...config, ...config,
defaultAuthOrgSlug: config?.defaultAuthOrgSlug || null defaultAuthOrgSlug: config?.defaultAuthOrgSlug || null
} as TSuperAdmin & { defaultAuthOrgSlug: string | null }; } as TSuperAdmin & {
defaultAuthOrgSlug: string | null;
defaultAuthOrgAuthEnforced?: boolean | null;
defaultAuthOrgAuthMethod?: string | null;
};
}; };
const updateById = async (id: string, data: TSuperAdminUpdate, tx?: Knex) => { const updateById = async (id: string, data: TSuperAdminUpdate, tx?: Knex) => {
@@ -29,7 +29,13 @@ type TSuperAdminServiceFactoryDep = {
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>; export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
// eslint-disable-next-line // eslint-disable-next-line
export let getServerCfg: () => Promise<TSuperAdmin & { defaultAuthOrgSlug: string | null }>; export let getServerCfg: () => Promise<
TSuperAdmin & {
defaultAuthOrgSlug: string | null;
defaultAuthOrgAuthEnforced?: boolean | null;
defaultAuthOrgAuthMethod?: string | null;
}
>;
const ADMIN_CONFIG_KEY = "infisical-admin-cfg"; const ADMIN_CONFIG_KEY = "infisical-admin-cfg";
const ADMIN_CONFIG_KEY_EXP = 60; // 60s const ADMIN_CONFIG_KEY_EXP = 60; // 60s
+2
View File
@@ -19,6 +19,8 @@ export type TServerConfig = {
isSecretScanningDisabled: boolean; isSecretScanningDisabled: boolean;
defaultAuthOrgSlug: string | null; defaultAuthOrgSlug: string | null;
defaultAuthOrgId: string | null; defaultAuthOrgId: string | null;
defaultAuthOrgAuthMethod?: string | null;
defaultAuthOrgAuthEnforced?: boolean | null;
enabledLoginMethods: LoginMethod[]; enabledLoginMethods: LoginMethod[];
}; };
+2 -1
View File
@@ -11,7 +11,8 @@ export enum AuthMethod {
JUMPCLOUD_SAML = "jumpcloud-saml", JUMPCLOUD_SAML = "jumpcloud-saml",
KEYCLOAK_SAML = "keycloak-saml", KEYCLOAK_SAML = "keycloak-saml",
LDAP = "ldap", LDAP = "ldap",
OIDC = "oidc" OIDC = "oidc",
SAML = "saml"
} }
export type User = { export type User = {
@@ -1,4 +1,4 @@
import { FormEvent, useCallback, useEffect, useRef, useState } from "react"; import { FormEvent, useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@@ -17,6 +17,7 @@ import { Button, IconButton, Input, Tooltip } from "@app/components/v2";
import { useServerConfig } from "@app/context"; import { useServerConfig } from "@app/context";
import { useFetchServerStatus } from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api";
import { LoginMethod } from "@app/hooks/api/admin/types"; import { LoginMethod } from "@app/hooks/api/admin/types";
import { AuthMethod } from "@app/hooks/api/users/types";
import { useNavigateToSelectOrganization } from "../../Login.utils"; import { useNavigateToSelectOrganization } from "../../Login.utils";
@@ -51,17 +52,33 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
router.push(redirectUrl); router.push(redirectUrl);
}; };
const redirectToOidc = (orgSlug: string) => {
const callbackPort = queryParams.get("callback_port");
const redirectUrl = `/api/v1/sso/oidc/login?orgSlug=${orgSlug}${
callbackPort ? `&callbackPort=${callbackPort}` : ""
}`;
router.push(redirectUrl);
};
useEffect(() => { useEffect(() => {
if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug); if (serverDetails?.samlDefaultOrgSlug) redirectToSaml(serverDetails.samlDefaultOrgSlug);
}, [serverDetails?.samlDefaultOrgSlug]); }, [serverDetails?.samlDefaultOrgSlug]);
const handleSaml = useCallback((step: number) => { const handleSaml = () => {
if (config.defaultAuthOrgSlug) { if (config.defaultAuthOrgSlug) {
redirectToSaml(config.defaultAuthOrgSlug); redirectToSaml(config.defaultAuthOrgSlug);
} else { } else {
setStep(step); setStep(2);
} }
}, []); };
const handleOidc = () => {
if (config.defaultAuthOrgSlug) {
redirectToOidc(config.defaultAuthOrgSlug);
} else {
setStep(3);
}
};
const shouldDisplayLoginMethod = (method: LoginMethod) => const shouldDisplayLoginMethod = (method: LoginMethod) =>
!config.enabledLoginMethods || config.enabledLoginMethods.includes(method); !config.enabledLoginMethods || config.enabledLoginMethods.includes(method);
@@ -142,6 +159,46 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
setIsLoading(false); setIsLoading(false);
}; };
if (config.defaultAuthOrgAuthEnforced && config.defaultAuthOrgAuthMethod) {
return (
<form
onSubmit={handleLogin}
className="mx-auto flex w-full flex-col items-center justify-center"
>
<h1 className="mb-8 bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
Login to Infisical
</h1>
<RegionSelect />
{config.defaultAuthOrgAuthMethod === AuthMethod.SAML && (
<div className="w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
<Button
colorSchema="primary"
variant="outline_bg"
onClick={handleSaml}
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
className="mx-0 h-10 w-full"
>
Continue with SAML
</Button>
</div>
)}
{config.defaultAuthOrgAuthMethod === AuthMethod.OIDC && (
<div className="mt-2 w-1/4 min-w-[21.2rem] rounded-md text-center md:min-w-[20.1rem] lg:w-1/6">
<Button
colorSchema="primary"
variant="outline_bg"
onClick={handleOidc}
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
className="mx-0 h-10 w-full"
>
Continue with OIDC
</Button>
</div>
)}
</form>
);
}
return ( return (
<form <form
onSubmit={handleLogin} onSubmit={handleLogin}
@@ -156,9 +213,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
<Button <Button
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
onClick={() => { onClick={handleSaml}
handleSaml(2);
}}
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />} leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
className="mx-0 h-10 w-full" className="mx-0 h-10 w-full"
> >
@@ -171,9 +226,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:
<Button <Button
colorSchema="primary" colorSchema="primary"
variant="outline_bg" variant="outline_bg"
onClick={() => { onClick={handleOidc}
setStep(3);
}}
leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />} leftIcon={<FontAwesomeIcon icon={faLock} className="mr-2" />}
className="mx-0 h-10 w-full" className="mx-0 h-10 w-full"
> >
@@ -212,8 +212,9 @@ export const AdminDashboardPage = () => {
Default organization Default organization
</div> </div>
<div className="mb-4 max-w-sm text-sm text-mineshaft-400"> <div className="mb-4 max-w-sm text-sm text-mineshaft-400">
Select the default organization you want to set for SAML/LDAP based logins. When Select the default organization you want to set for SAML/LDAP/OIDC based logins.
selected, user logins will be automatically scoped to the selected organization. When selected, user logins will be automatically scoped to the selected
organization.
</div> </div>
<Controller <Controller
control={control} control={control}