diff --git a/backend/src/db/migrations/20240223184912_ldap-config.ts b/backend/src/db/migrations/20240223184912_ldap-config.ts index ab66a04dd..a77dda8a7 100644 --- a/backend/src/db/migrations/20240223184912_ldap-config.ts +++ b/backend/src/db/migrations/20240223184912_ldap-config.ts @@ -17,8 +17,8 @@ export async function up(knex: Knex): Promise { t.string("encryptedBindPass"); t.string("bindPassIV"); t.string("bindPassTag"); - t.text("searchBase").notNullable(); - t.string("encryptedCACert"); + t.string("searchBase").notNullable(); + t.text("encryptedCACert"); t.string("caCertIV"); t.string("caCertTag"); t.timestamps(true, true, true); @@ -43,7 +43,7 @@ export async function down(knex: Knex): Promise { await knex.schema.alterTable(TableName.Users, (t) => { t.dropColumn("username"); t.dropColumn("orgId"); - t.string("email").notNullable().alter(); + // t.string("email").notNullable().alter(); }); await dropOnUpdateTrigger(knex, TableName.LdapConfig); } diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index f46f6fd84..cec6f56a2 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -42,7 +42,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { return cb(null, { isUserCompleted, providerAuthToken }); } catch (err) { logger.error(err); - return cb(null, false); + return cb(err, false); } } ) @@ -57,9 +57,9 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { }) }, preValidation: passport.authenticate("ldapauth", { - session: false, - failureFlash: true, - failureRedirect: "/login/provider/error" + session: false + // failureFlash: true, + // failureRedirect: "/login/provider/error" // this is due to zod type difference }) as any, handler: (req, res) => { diff --git a/backend/src/services/user/user-service.ts b/backend/src/services/user/user-service.ts index e309cdb17..c85e40eb3 100644 --- a/backend/src/services/user/user-service.ts +++ b/backend/src/services/user/user-service.ts @@ -34,6 +34,12 @@ export const userServiceFactory = ({ userDAL }: TUserServiceFactoryDep) => { const user = await userDAL.findById(userId); if (!user) throw new BadRequestError({ name: "Update auth methods" }); + if (user.authMethods?.includes(AuthMethod.LDAP)) + throw new BadRequestError({ message: "LDAP auth method cannot be updated", name: "Update auth methods" }); + + if (authMethods.includes(AuthMethod.LDAP)) + throw new BadRequestError({ message: "LDAP auth method cannot be updated", name: "Update auth methods" }); + const updatedUser = await userDAL.updateById(userId, { authMethods }); return updatedUser; }; diff --git a/docs/documentation/platform/ldap/jumpcloud.mdx b/docs/documentation/platform/ldap/jumpcloud.mdx new file mode 100644 index 000000000..b80cb49a1 --- /dev/null +++ b/docs/documentation/platform/ldap/jumpcloud.mdx @@ -0,0 +1,56 @@ +--- +title: "JumpCloud LDAP" +description: "Configure JumpCloud LDAP for Logging into Infisical" +--- + + + LDAP is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + + + + In JumpCloud, head to USER MANAGEMENT > Users and create a new user via the **Manual user entry** option. This user + will be used as a privileged service account to facilitate Infisical's ability to bind/search the LDAP directory. + + When creating the user, input their **First Name**, **Last Name**, **Username** (required), **Company Email** (required), and **Description**. + Also, create a password for the user. + + Next, under User Security Settings and Permissions > Permission Settings, check the box next to **Enable as LDAP Bind DN**. + + ![LDAP JumpCloud](/images/platform/ldap/jumpcloud/ldap-jumpcloud-enable-bind-dn.png) + + + + In Infisical, head to your Organization Settings > Authentication > LDAP Configuration and select **Set up LDAP**. + + Next, input your JumpCloud LDAP server settings. + + ![LDAP configuration](/images/platform/ldap/ldap-config.png) + + Here's some guidance for each field: + + - URL: The LDAP server to connect to (`ldaps://ldap.jumpcloud.com:636`). + - Bind DN: The distinguished name of object to bind when performing the user search (`uid=,ou=Users,o=,dc=jumpcloud,dc=com`). + - Bind Pass: The password to use along with `Bind DN` when performing the user search. + - Search Base / User DN: Base DN under which to perform user search (`ou=Users,o=,dc=jumpcloud,dc=com`). + - CA Certificate: The CA certificate to use when verifying the LDAP server certificate (instructions to obtain the certificate for JumpCloud [here](https://jumpcloud.com/support/connect-to-ldap-with-tls-ssl)). + + + When filling out the **Bind DN** and **Bind Pass** fields, refer to the username and password of the user created in Step 1. + + Also, for the **Bind DN** and **Search Base / User DN** fields, you'll want to use the organization ID that appears + in your LDAP instance **ORG DN**. + + + + Enabling LDAP allows members in your organization to log into Infisical via LDAP. + + ![LDAP toggle](/images/platform/ldap/ldap-toggle.png) + + + +Resources: +- [JumpCloud Cloud LDAP Guide](https://jumpcloud.com/support/use-cloud-ldap) \ No newline at end of file diff --git a/docs/documentation/platform/ldap/overview.mdx b/docs/documentation/platform/ldap/overview.mdx new file mode 100644 index 000000000..9d16ed879 --- /dev/null +++ b/docs/documentation/platform/ldap/overview.mdx @@ -0,0 +1,18 @@ +--- +title: "LDAP Overview" +description: "Log in to Infisical with LDAP" +--- + + LDAP is a paid feature. + + If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical, + then you should contact team@infisical.com to purchase an enterprise license to use it. + + +You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol) + +To note, configuring LDAP retains the end-to-end encrypted architecture of Infisical because we decouple the authentication and decryption steps; the LDAP server cannot and will not have access to the decryption key needed to decrypt your secrets. + +LDAP providers: + +- [JumpCloud LDAP](/documentation/platform/ldap/jumpcloud) \ No newline at end of file diff --git a/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-enable-bind-dn.png b/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-enable-bind-dn.png new file mode 100644 index 000000000..b50c1e0be Binary files /dev/null and b/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-enable-bind-dn.png differ diff --git a/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-org-dn.png b/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-org-dn.png new file mode 100644 index 000000000..cd6166b1f Binary files /dev/null and b/docs/images/platform/ldap/jumpcloud/ldap-jumpcloud-org-dn.png differ diff --git a/docs/mint.json b/docs/mint.json index 8248ca3e4..3c2c210eb 100644 --- a/docs/mint.json +++ b/docs/mint.json @@ -149,7 +149,13 @@ "documentation/platform/sso/jumpcloud" ] }, - "documentation/platform/ldap", + { + "group": "LDAP", + "pages": [ + "documentation/platform/ldap/overview", + "documentation/platform/ldap/jumpcloud" + ] + }, { "group": "SCIM", "pages": [ diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index 7328e13d3..266e8aed2 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -7,13 +7,15 @@ export enum AuthMethod { GITLAB = "gitlab", OKTA_SAML = "okta-saml", AZURE_SAML = "azure-saml", - JUMPCLOUD_SAML = "jumpcloud-saml" + JUMPCLOUD_SAML = "jumpcloud-saml", + LDAP = "ldap" } export type User = { createdAt: Date; updatedAt: Date; - email: string; + username: string; + email?: string; superAdmin: boolean; firstName?: string; lastName?: string; diff --git a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx index 0c5012cdb..d74acef0f 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/ChangePasswordSection/ChangePasswordSection.tsx @@ -48,8 +48,6 @@ export const ChangePasswordSection = () => { const onFormSubmit = async ({ oldPassword, newPassword }: FormData) => { try { - if (!user?.email) return; - const errorCheck = await checkPassword({ password: newPassword, setErrors @@ -59,7 +57,7 @@ export const ChangePasswordSection = () => { setIsLoading(true); await attemptChangePassword({ - email: user.email, + email: user.username, currentPassword: oldPassword, newPassword }); diff --git a/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx index 73f04113e..78de8d5cb 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/PersonalAuthTab/PersonalAuthTab.tsx @@ -1,12 +1,22 @@ +import { + useGetUser +} from "@app/hooks/api"; +import { AuthMethod } from "@app/hooks/api/users/types"; + import { AuthMethodSection } from "../AuthMethodSection"; import { ChangePasswordSection } from "../ChangePasswordSection"; import { MFASection } from "../SecuritySection"; export const PersonalAuthTab = () => { + const { data: user } = useGetUser(); return (
- - + {user && !user.authMethods.includes(AuthMethod.LDAP) && ( + <> + + + + )}
); diff --git a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx index 6f52b7427..109247bd1 100644 --- a/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx +++ b/frontend/src/views/Settings/PersonalSettingsPage/SecuritySection/MFASection.tsx @@ -4,6 +4,7 @@ import { useGetUser, useUpdateMfaEnabled} from "@app/hooks/api"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; +import { AuthMethod } from "@app/hooks/api/users/types"; import { usePopUp } from "@app/hooks/usePopUp"; export const MFASection = () => { @@ -18,6 +19,15 @@ export const MFASection = () => { const toggleMfa = async (state: boolean) => { try { + if (!user) return; + if (user.authMethods.includes(AuthMethod.LDAP)) { + createNotification({ + text: "Two-factor authentication is not available for LDAP users.", + type: "error" + }); + return; + } + const newUser = await mutateAsync({ isMfaEnabled: state });