From 75949290426cbbb50885c819e8b5f4d9dc1612a8 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 10 Mar 2024 12:28:50 -0700 Subject: [PATCH] Separate ldap boot/parent wrapper logic, move ldap services into docker compose profile, update ldap form logic to use zod --- Makefile | 3 + .../migrations/20240305165532_ldap-config.ts | 2 +- backend/src/db/seed-data.ts | 2 +- backend/src/ee/routes/v1/ldap-router.ts | 20 ++++- .../ldap-config/ldap-config-service.ts | 80 +++++++------------ docker-compose.dev.yml | 2 + .../src/layouts/AdminLayout/AdminLayout.tsx | 4 +- .../components/OrgAuthTab/LDAPModal.tsx | 28 +++---- 8 files changed, 71 insertions(+), 70 deletions(-) diff --git a/Makefile b/Makefile index 2bddeec0e..11143162e 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ push: up-dev: docker compose -f docker-compose.dev.yml up --build +up-dev-ldap: + docker compose -f docker-compose.dev.yml --profile ldap up --build + up-prod: docker-compose -f docker-compose.prod.yml up --build diff --git a/backend/src/db/migrations/20240305165532_ldap-config.ts b/backend/src/db/migrations/20240305165532_ldap-config.ts index 9d1415e79..0b4e36250 100644 --- a/backend/src/db/migrations/20240305165532_ldap-config.ts +++ b/backend/src/db/migrations/20240305165532_ldap-config.ts @@ -8,7 +8,7 @@ export async function up(knex: Knex): Promise { await knex.schema.createTable(TableName.LdapConfig, (t) => { t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("orgId").notNullable().unique(); - t.foreign("orgId").references("id").inTable(TableName.Organization); + t.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE"); t.boolean("isActive").notNullable(); t.string("url").notNullable(); t.string("encryptedBindDN"); diff --git a/backend/src/db/seed-data.ts b/backend/src/db/seed-data.ts index 5f4ea1b4f..13bb77f3c 100644 --- a/backend/src/db/seed-data.ts +++ b/backend/src/db/seed-data.ts @@ -21,7 +21,7 @@ export let userPublicKey: string | undefined; export const seedData1 = { id: "3dafd81d-4388-432b-a4c5-f735616868c1", - username: process.env.TEST_USER_USERNAME || "test@localhost.local", + username: process.env.TEST_USER_USERNAME || "test", email: process.env.TEST_USER_EMAIL || "test@localhost.local", password: process.env.TEST_USER_PASSWORD || "testInfisical@1", organization: { diff --git a/backend/src/ee/routes/v1/ldap-router.ts b/backend/src/ee/routes/v1/ldap-router.ts index ea33ab2ca..97527e783 100644 --- a/backend/src/ee/routes/v1/ldap-router.ts +++ b/backend/src/ee/routes/v1/ldap-router.ts @@ -27,11 +27,27 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => { await server.register(passport.initialize()); await server.register(passport.secureSession()); + const getLdapPassportOpts = (req: FastifyRequest, done: any) => { + const { organizationSlug } = req.body as { + organizationSlug: string; + }; + + process.nextTick(async () => { + try { + const { opts, ldapConfig } = await server.services.ldap.bootLdap(organizationSlug); + req.ldapConfig = ldapConfig; + done(null, opts); + } catch (err) { + done(err); + } + }); + }; + passport.use( new LdapStrategy( - server.services.ldap.getLdapPassportOpts as any, + getLdapPassportOpts as any, // eslint-disable-next-line - async (req: IncomingMessage, user, cb) => { + async (req: IncomingMessage, user, cb) => { try { const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({ externalId: user.uidNumber, diff --git a/backend/src/ee/services/ldap-config/ldap-config-service.ts b/backend/src/ee/services/ldap-config/ldap-config-service.ts index 7c1f475b1..37e1fb6f8 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,5 +1,4 @@ import { ForbiddenError } from "@casl/ability"; -import { FastifyRequest } from "fastify"; import jwt from "jsonwebtoken"; import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas"; @@ -13,7 +12,6 @@ import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; -import { logger } from "@app/lib/logger"; import { TOrgPermission } from "@app/lib/types"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; @@ -284,54 +282,35 @@ export const ldapConfigServiceFactory = ({ }); }; - // eslint-disable-next-line - const getLdapPassportOpts = (req: FastifyRequest, done: any) => { - const { organizationSlug } = req.body as { - organizationSlug: string; - }; + const bootLdap = async (organizationSlug: string) => { + const organization = await orgDAL.findOne({ slug: organizationSlug }); + if (!organization) throw new BadRequestError({ message: "Org not found" }); - const boot = async () => { - try { - const organization = await orgDAL.findOne({ slug: organizationSlug }); - if (!organization) throw new BadRequestError({ message: "Org not found" }); - - const ldapConfig = await getLdapCfg({ - orgId: organization.id, - isActive: true - }); - req.ldapConfig = ldapConfig; - - const opts = { - server: { - url: ldapConfig.url, - bindDN: ldapConfig.bindDN, - bindCredentials: ldapConfig.bindPass, - searchBase: ldapConfig.searchBase, - searchFilter: "(uid={{username}})", - searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], - ...(ldapConfig.caCert !== "" - ? { - tlsOptions: { - ca: [ldapConfig.caCert] - } - } - : {}) - }, - passReqToCallback: true - }; - - // eslint-disable-next-line - done(null, opts); - } catch (err) { - logger.error(err); - // eslint-disable-next-line - done(err); - } - }; - - process.nextTick(async () => { - await boot(); + const ldapConfig = await getLdapCfg({ + orgId: organization.id, + isActive: true }); + + const opts = { + server: { + url: ldapConfig.url, + bindDN: ldapConfig.bindDN, + bindCredentials: ldapConfig.bindPass, + searchBase: ldapConfig.searchBase, + searchFilter: "(uid={{username}})", + searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"], + ...(ldapConfig.caCert !== "" + ? { + tlsOptions: { + ca: [ldapConfig.caCert] + } + } + : {}) + }, + passReqToCallback: true + }; + + return { opts, ldapConfig }; }; const ldapLogin = async ({ externalId, username, firstName, lastName, emails, orgId, relayState }: TLdapLoginDTO) => { @@ -443,7 +422,8 @@ export const ldapConfigServiceFactory = ({ updateLdapCfg, getLdapCfgWithPermissionCheck, getLdapCfg, - getLdapPassportOpts, - ldapLogin + // getLdapPassportOpts, + ldapLogin, + bootLdap }; }; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 656448705..f07aeb190 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -140,6 +140,7 @@ services: volumes: - ldap_data:/var/lib/ldap - ldap_config:/etc/ldap/slapd.d + profiles: [ldap] phpldapadmin: # username: cn=admin,dc=acme,dc=com, pass is admin image: osixia/phpldapadmin:latest @@ -151,6 +152,7 @@ services: - 6433:80 depends_on: - openldap + profiles: [ldap] volumes: postgres-data: diff --git a/frontend/src/layouts/AdminLayout/AdminLayout.tsx b/frontend/src/layouts/AdminLayout/AdminLayout.tsx index ad728dbae..539aedfd9 100644 --- a/frontend/src/layouts/AdminLayout/AdminLayout.tsx +++ b/frontend/src/layouts/AdminLayout/AdminLayout.tsx @@ -91,7 +91,7 @@ export const AdminLayout = ({ children }: LayoutProps) => { console.error(error); } }; - + return ( <>
@@ -121,7 +121,7 @@ export const AdminLayout = ({ children }: LayoutProps) => {
-
{user.username}
+
{user?.username}
Personal Settings diff --git a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx index e2bfb836a..37bc87e2c 100644 --- a/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx +++ b/frontend/src/views/Settings/OrgSettingsPage/components/OrgAuthTab/LDAPModal.tsx @@ -1,7 +1,7 @@ import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; -import { yupResolver } from "@hookform/resolvers/yup"; -import * as yup from "yup"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; import { @@ -20,15 +20,15 @@ import { } from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; -const schema = yup.object({ - url: yup.string().required("URL is required"), - bindDN: yup.string().required("Bind DN is required"), - bindPass: yup.string().required("Bind Pass is required"), - searchBase: yup.string().required("Search Base is required"), - caCert: yup.string() -}).required(); +const LDAPFormSchema = z.object({ + url: z.string().min(1, "URL is requiredx"), + bindDN: z.string().min(1, "Bind DN is requiredx"), + bindPass: z.string().min(1, "Bind Pass is required"), + searchBase: z.string().min(1, "Search Base is required"), + caCert: z.string().optional() +}); -export type AddLDAPFormData = yup.InferType; +export type TLDAPFormData = z.infer; type Props = { popUp: UsePopUpState<["addLDAP"]>; @@ -51,9 +51,9 @@ export const LDAPModal = ({ control, handleSubmit, reset, - } = useForm({ - resolver: yupResolver(schema) - }); + } = useForm({ + resolver: zodResolver(LDAPFormSchema) + }) useEffect(() => { if (data) { @@ -73,7 +73,7 @@ export const LDAPModal = ({ bindPass, searchBase, caCert - }: AddLDAPFormData) => { + }: TLDAPFormData) => { try { if (!currentOrg) return;