mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 17:26:19 +00:00
Add ldapjs to get user groups upon ldap login
This commit is contained in:
Generated
+108
-9
@@ -45,6 +45,7 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
|
"ldapjs": "^3.0.7",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
@@ -2510,6 +2511,83 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ldapjs/asn1": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-G9+DkEOirNgdPmD0I8nu57ygQJKOOgFEMKknEuQvIHbGLwP3ny1mY+OTUYLCbCaGJP4sox5eYgBJRuSUpnAddA=="
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/attribute": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/attribute/-/attribute-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ptMl2d/5xJ0q+RgmnqOi3Zgwk/TMJYG7dYMC0Keko+yZU6n+oFM59MjQOUht5pxJeS4FWrImhu/LebX24vJNRQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/change": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/change/-/change-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-EOQNFH1RIku3M1s0OAJOzGfAohuFYXFY4s73wOhRm4KFGhmQQ7MChOh2YtYu9Kwgvuq1B0xKciXVzHCGkB5V+Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/attribute": "1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/controls": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/controls/-/controls-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-2pFdD1yRC9V9hXfAWvCCO2RRWK9OdIEcJIos/9cCVP9O4k72BY1bLDQQ4KpUoJnl4y/JoD4iFgM+YWT3IfITWw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^1.2.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/controls/node_modules/@ldapjs/asn1": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/asn1/-/asn1-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-KX/qQJ2xxzvO2/WOvr1UdQ+8P5dVvuOLk/C9b1bIkXxZss8BaR28njXdPgFCpj5aHaf1t8PmuVnea+N9YG9YMw=="
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/dn": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/dn/-/dn-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-R72zH5ZeBj/Fujf/yBu78YzpJjJXG46YHFo5E4W1EqfNpo1UsVPqdLrRMXeKIsJT3x9dJVIfR6OpzgINlKpi0A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/filter": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/filter/-/filter-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-TwPK5eEgNdUO1ABPBUQabcZ+h9heDORE4V9WNZqCtYLKc06+6+UAJ3IAbr0L0bYTnkkWC/JEQD2F+zAFsuikNw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "2.0.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/messages": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/messages/-/messages-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-K7xZpXJ21bj92jS35wtRbdcNrwmxAtPwy4myeh9duy/eR3xQKvikVycbdWVzkYEAVE5Ce520VXNOwCHjomjCZw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^2.0.0",
|
||||||
|
"@ldapjs/attribute": "^1.0.0",
|
||||||
|
"@ldapjs/change": "^1.0.0",
|
||||||
|
"@ldapjs/controls": "^2.1.0",
|
||||||
|
"@ldapjs/dn": "^1.1.0",
|
||||||
|
"@ldapjs/filter": "^2.1.1",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"process-warning": "^2.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ldapjs/protocol": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ldapjs/protocol/-/protocol-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-O89xFDLW2gBoZWNXuXpBSM32/KealKCTb3JGtJdtUQc7RjAk8XzrRgyz02cPAwGKwKPxy0ivuC7UP9bmN87egQ=="
|
||||||
|
},
|
||||||
"node_modules/@lukeed/ms": {
|
"node_modules/@lukeed/ms": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.1.tgz",
|
||||||
@@ -9304,15 +9382,7 @@
|
|||||||
"node": ">=0.8.0"
|
"node": ">=0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ldapauth-fork/node_modules/lru-cache": {
|
"node_modules/ldapauth-fork/node_modules/ldapjs": {
|
||||||
"version": "7.18.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
|
||||||
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ldapjs": {
|
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-2.3.3.tgz",
|
||||||
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
|
"integrity": "sha512-75QiiLJV/PQqtpH+HGls44dXweviFwQ6SiIK27EqzKQ5jU/7UFrl2E5nLdQ3IYRBzJ/AVFJI66u0MZ0uofKYwg==",
|
||||||
@@ -9330,6 +9400,35 @@
|
|||||||
"node": ">=10.13.0"
|
"node": ">=10.13.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ldapauth-fork/node_modules/lru-cache": {
|
||||||
|
"version": "7.18.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||||
|
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ldapjs": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/ldapjs/-/ldapjs-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-1ky+WrN+4CFMuoekUOv7Y1037XWdjKpu0xAPwSP+9KdvmV9PG+qOKlssDV6a+U32apwxdD3is/BZcWOYzN30cg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ldapjs/asn1": "^2.0.0",
|
||||||
|
"@ldapjs/attribute": "^1.0.0",
|
||||||
|
"@ldapjs/change": "^1.0.0",
|
||||||
|
"@ldapjs/controls": "^2.1.0",
|
||||||
|
"@ldapjs/dn": "^1.1.0",
|
||||||
|
"@ldapjs/filter": "^2.1.1",
|
||||||
|
"@ldapjs/messages": "^1.3.0",
|
||||||
|
"@ldapjs/protocol": "^1.2.1",
|
||||||
|
"abstract-logging": "^2.0.1",
|
||||||
|
"assert-plus": "^1.0.0",
|
||||||
|
"backoff": "^2.5.0",
|
||||||
|
"once": "^1.4.0",
|
||||||
|
"vasync": "^2.2.1",
|
||||||
|
"verror": "^1.10.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/leven": {
|
"node_modules/leven": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||||
|
|||||||
@@ -106,6 +106,7 @@
|
|||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"jsrp": "^0.2.4",
|
"jsrp": "^0.2.4",
|
||||||
"knex": "^3.0.1",
|
"knex": "^3.0.1",
|
||||||
|
"ldapjs": "^3.0.7",
|
||||||
"libsodium-wrappers": "^0.7.13",
|
"libsodium-wrappers": "^0.7.13",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.LdapConfig, (t) => {
|
||||||
|
t.string("groupSearchBase").notNullable().defaultTo("");
|
||||||
|
t.string("groupSearchFilter").notNullable().defaultTo("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
await knex.schema.alterTable(TableName.LdapConfig, (t) => {
|
||||||
|
t.dropColumn("groupSearchBase");
|
||||||
|
t.dropColumn("groupSearchFilter");
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -23,7 +23,9 @@ export const LdapConfigsSchema = z.object({
|
|||||||
caCertIV: z.string(),
|
caCertIV: z.string(),
|
||||||
caCertTag: z.string(),
|
caCertTag: z.string(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date(),
|
||||||
|
groupSearchBase: z.string().default(""),
|
||||||
|
groupSearchFilter: z.string().default("")
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
export type TLdapConfigs = z.infer<typeof LdapConfigsSchema>;
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { IncomingMessage } from "node:http";
|
|||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
|
import ldapjs from "ldapjs";
|
||||||
import LdapStrategy from "passport-ldapauth";
|
import LdapStrategy from "passport-ldapauth";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { LdapConfigsSchema } from "@app/db/schemas";
|
import { LdapConfigsSchema } from "@app/db/schemas";
|
||||||
|
import { searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
@@ -44,26 +46,70 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface LDAPConfig {
|
||||||
|
id: string;
|
||||||
|
organization: string;
|
||||||
|
isActive: boolean;
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
groupSearchBase: string;
|
||||||
|
groupSearchFilter: string;
|
||||||
|
caCert: string;
|
||||||
|
}
|
||||||
|
|
||||||
passport.use(
|
passport.use(
|
||||||
new LdapStrategy(
|
new LdapStrategy(
|
||||||
getLdapPassportOpts as any,
|
getLdapPassportOpts as any,
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req: IncomingMessage, user, cb) => {
|
async (req: IncomingMessage, user, cb) => {
|
||||||
try {
|
try {
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.ldap.ldapLogin({
|
const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as LDAPConfig;
|
||||||
externalId: user.uidNumber,
|
|
||||||
username: user.uid,
|
const ldapClient = ldapjs.createClient({
|
||||||
firstName: user.givenName,
|
url: ldapConfig.url,
|
||||||
lastName: user.sn,
|
bindDN: ldapConfig.bindDN,
|
||||||
emails: user.mail ? [user.mail] : [],
|
bindCredentials: ldapConfig.bindPass
|
||||||
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
|
||||||
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
ldapClient.bind(ldapConfig.bindDN, ldapConfig.bindPass, (err) => {
|
||||||
} catch (err) {
|
if (err) {
|
||||||
logger.error(err);
|
ldapClient.unbind();
|
||||||
return cb(err, false);
|
return cb(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupFilter =
|
||||||
|
ldapConfig.groupSearchFilter ||
|
||||||
|
"(|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}}))";
|
||||||
|
const searchFilter = groupFilter.replace("{{.Username}}", user.uid).replace("{{.UserDN}}", user.dn);
|
||||||
|
|
||||||
|
searchGroups(ldapClient, searchFilter, ldapConfig.groupSearchBase)
|
||||||
|
.then(() => {
|
||||||
|
// groups here
|
||||||
|
ldapClient.unbind();
|
||||||
|
return server.services.ldap.ldapLogin({
|
||||||
|
externalId: user.uidNumber,
|
||||||
|
username: user.uid,
|
||||||
|
firstName: user.givenName,
|
||||||
|
lastName: user.sn,
|
||||||
|
emails: user.mail ? [user.mail] : [],
|
||||||
|
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
||||||
|
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.then(({ isUserCompleted, providerAuthToken }) => {
|
||||||
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
|
})
|
||||||
|
.catch((err2) => {
|
||||||
|
ldapClient.unbind();
|
||||||
|
logger.error(err);
|
||||||
|
cb(err2, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
return cb(error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -117,6 +163,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
bindDN: z.string(),
|
bindDN: z.string(),
|
||||||
bindPass: z.string(),
|
bindPass: z.string(),
|
||||||
searchBase: z.string(),
|
searchBase: z.string(),
|
||||||
|
groupSearchBase: z.string(),
|
||||||
|
groupSearchFilter: z.string(),
|
||||||
caCert: z.string()
|
caCert: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -148,6 +196,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim(),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim(),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim(),
|
||||||
|
groupSearchBase: z.string().trim(),
|
||||||
|
groupSearchFilter: z.string().trim(),
|
||||||
caCert: z.string().trim().default("")
|
caCert: z.string().trim().default("")
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
@@ -183,6 +233,8 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
bindDN: z.string().trim(),
|
bindDN: z.string().trim(),
|
||||||
bindPass: z.string().trim(),
|
bindPass: z.string().trim(),
|
||||||
searchBase: z.string().trim(),
|
searchBase: z.string().trim(),
|
||||||
|
groupSearchBase: z.string().trim(),
|
||||||
|
groupSearchFilter: z.string().trim(),
|
||||||
caCert: z.string().trim()
|
caCert: z.string().trim()
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
|
|||||||
@@ -60,6 +60,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
}: TCreateLdapCfgDTO) => {
|
}: TCreateLdapCfgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
@@ -135,6 +137,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindPassIV,
|
bindPassIV,
|
||||||
bindPassTag,
|
bindPassTag,
|
||||||
searchBase,
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
encryptedCACert,
|
encryptedCACert,
|
||||||
caCertIV,
|
caCertIV,
|
||||||
caCertTag
|
caCertTag
|
||||||
@@ -154,6 +158,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
}: TUpdateLdapCfgDTO) => {
|
}: TUpdateLdapCfgDTO) => {
|
||||||
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getOrgPermission(actor, actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
@@ -169,7 +175,9 @@ export const ldapConfigServiceFactory = ({
|
|||||||
const updateQuery: TLdapConfigsUpdate = {
|
const updateQuery: TLdapConfigsUpdate = {
|
||||||
isActive,
|
isActive,
|
||||||
url,
|
url,
|
||||||
searchBase
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter
|
||||||
};
|
};
|
||||||
|
|
||||||
const orgBot = await orgBotDAL.findOne({ orgId });
|
const orgBot = await orgBotDAL.findOne({ orgId });
|
||||||
@@ -271,6 +279,8 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase: ldapConfig.searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
|
groupSearchBase: ldapConfig.groupSearchBase,
|
||||||
|
groupSearchFilter: ldapConfig.groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -305,7 +315,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
bindCredentials: ldapConfig.bindPass,
|
bindCredentials: ldapConfig.bindPass,
|
||||||
searchBase: ldapConfig.searchBase,
|
searchBase: ldapConfig.searchBase,
|
||||||
searchFilter: "(uid={{username}})",
|
searchFilter: "(uid={{username}})",
|
||||||
searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
// searchAttributes: ["uid", "uidNumber", "givenName", "sn", "mail"],
|
||||||
...(ldapConfig.caCert !== ""
|
...(ldapConfig.caCert !== ""
|
||||||
? {
|
? {
|
||||||
tlsOptions: {
|
tlsOptions: {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ export type TCreateLdapCfgDTO = {
|
|||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
|
groupSearchBase: string;
|
||||||
|
groupSearchFilter: string;
|
||||||
caCert: string;
|
caCert: string;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
@@ -18,6 +20,8 @@ export type TUpdateLdapCfgDTO = {
|
|||||||
bindDN: string;
|
bindDN: string;
|
||||||
bindPass: string;
|
bindPass: string;
|
||||||
searchBase: string;
|
searchBase: string;
|
||||||
|
groupSearchBase: string;
|
||||||
|
groupSearchFilter: string;
|
||||||
caCert: string;
|
caCert: string;
|
||||||
}> &
|
}> &
|
||||||
TOrgPermission;
|
TOrgPermission;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import ldap from "ldapjs";
|
||||||
|
|
||||||
|
export const searchGroups = async (
|
||||||
|
ldapClient: ldap.Client,
|
||||||
|
filter: string,
|
||||||
|
base: string
|
||||||
|
): Promise<{ dn: string; cn: string }[]> => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
ldapClient.search(
|
||||||
|
base,
|
||||||
|
{
|
||||||
|
filter,
|
||||||
|
scope: "sub"
|
||||||
|
},
|
||||||
|
(err, res) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const groups: { dn: string; cn: string }[] = [];
|
||||||
|
|
||||||
|
res.on("searchEntry", (entry) => {
|
||||||
|
groups.push({ dn: entry.object.dn, cn: entry.object.cn as string });
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on("error", (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on("end", () => {
|
||||||
|
resolve(groups);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -19,7 +19,7 @@ export const getDefaultOnPremFeatures = () => {
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: true,
|
||||||
groups: false,
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
samlSSO: false,
|
samlSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: true,
|
||||||
groups: false,
|
groups: false,
|
||||||
status: null,
|
status: null,
|
||||||
trial_end: null,
|
trial_end: null,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export type TFeatureSet = {
|
|||||||
auditLogsRetentionDays: 0;
|
auditLogsRetentionDays: 0;
|
||||||
samlSSO: false;
|
samlSSO: false;
|
||||||
scim: false;
|
scim: false;
|
||||||
ldap: false;
|
ldap: true;
|
||||||
groups: false;
|
groups: false;
|
||||||
status: null;
|
status: null;
|
||||||
trial_end: null;
|
trial_end: null;
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
export { useCreateLDAPConfig, useGetLDAPConfig, useUpdateLDAPConfig } from "./queries";
|
export { useCreateLDAPConfig, useUpdateLDAPConfig } from "./mutations";
|
||||||
|
export { useGetLDAPConfig } from "./queries";
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
|
||||||
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
|
import { ldapConfigKeys } from "./queries";
|
||||||
|
|
||||||
|
export const useCreateLDAPConfig = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
organizationId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
|
caCert
|
||||||
|
}: {
|
||||||
|
organizationId: string;
|
||||||
|
isActive: boolean;
|
||||||
|
url: string;
|
||||||
|
bindDN: string;
|
||||||
|
bindPass: string;
|
||||||
|
searchBase: string;
|
||||||
|
groupSearchBase: string;
|
||||||
|
groupSearchFilter: string;
|
||||||
|
caCert?: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.post("/api/v1/ldap/config", {
|
||||||
|
organizationId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
|
caCert
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess(_, dto) {
|
||||||
|
queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUpdateLDAPConfig = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async ({
|
||||||
|
organizationId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
|
caCert
|
||||||
|
}: {
|
||||||
|
organizationId: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
url?: string;
|
||||||
|
bindDN?: string;
|
||||||
|
bindPass?: string;
|
||||||
|
searchBase?: string;
|
||||||
|
groupSearchBase?: string;
|
||||||
|
groupSearchFilter?: string;
|
||||||
|
caCert?: string;
|
||||||
|
}) => {
|
||||||
|
const { data } = await apiRequest.patch("/api/v1/ldap/config", {
|
||||||
|
organizationId,
|
||||||
|
isActive,
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
|
caCert
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
onSuccess(_, dto) {
|
||||||
|
queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
|
||||||
import { apiRequest } from "@app/config/request";
|
import { apiRequest } from "@app/config/request";
|
||||||
|
|
||||||
const ldapConfigKeys = {
|
export const ldapConfigKeys = {
|
||||||
getLDAPConfig: (orgId: string) => [{ orgId }, "organization-ldap"] as const
|
getLDAPConfig: (orgId: string) => [{ orgId }, "organization-ldap"] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -17,79 +17,3 @@ export const useGetLDAPConfig = (organizationId: string) => {
|
|||||||
enabled: true
|
enabled: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useCreateLDAPConfig = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({
|
|
||||||
organizationId,
|
|
||||||
isActive,
|
|
||||||
url,
|
|
||||||
bindDN,
|
|
||||||
bindPass,
|
|
||||||
searchBase,
|
|
||||||
caCert
|
|
||||||
}: {
|
|
||||||
organizationId: string;
|
|
||||||
isActive: boolean;
|
|
||||||
url: string;
|
|
||||||
bindDN: string;
|
|
||||||
bindPass: string;
|
|
||||||
searchBase: string;
|
|
||||||
caCert?: string;
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.post("/api/v1/ldap/config", {
|
|
||||||
organizationId,
|
|
||||||
isActive,
|
|
||||||
url,
|
|
||||||
bindDN,
|
|
||||||
bindPass,
|
|
||||||
searchBase,
|
|
||||||
caCert
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
onSuccess(_, dto) {
|
|
||||||
queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useUpdateLDAPConfig = () => {
|
|
||||||
const queryClient = useQueryClient();
|
|
||||||
return useMutation({
|
|
||||||
mutationFn: async ({
|
|
||||||
organizationId,
|
|
||||||
isActive,
|
|
||||||
url,
|
|
||||||
bindDN,
|
|
||||||
bindPass,
|
|
||||||
searchBase,
|
|
||||||
caCert
|
|
||||||
}: {
|
|
||||||
organizationId: string;
|
|
||||||
isActive?: boolean;
|
|
||||||
url?: string;
|
|
||||||
bindDN?: string;
|
|
||||||
bindPass?: string;
|
|
||||||
searchBase?: string;
|
|
||||||
caCert?: string;
|
|
||||||
}) => {
|
|
||||||
const { data } = await apiRequest.patch("/api/v1/ldap/config", {
|
|
||||||
organizationId,
|
|
||||||
isActive,
|
|
||||||
url,
|
|
||||||
bindDN,
|
|
||||||
bindPass,
|
|
||||||
searchBase,
|
|
||||||
caCert
|
|
||||||
});
|
|
||||||
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
onSuccess(_, dto) {
|
|
||||||
queryClient.invalidateQueries(ldapConfigKeys.getLDAPConfig(dto.organizationId));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ const LDAPFormSchema = z.object({
|
|||||||
bindDN: z.string().default(""),
|
bindDN: z.string().default(""),
|
||||||
bindPass: z.string().default(""),
|
bindPass: z.string().default(""),
|
||||||
searchBase: z.string().default(""),
|
searchBase: z.string().default(""),
|
||||||
|
groupSearchBase: z.string().default(""),
|
||||||
|
groupSearchFilter: z.string().default(""),
|
||||||
caCert: z.string().optional()
|
caCert: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ type Props = {
|
|||||||
|
|
||||||
export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
||||||
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateLDAPConfig();
|
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateLDAPConfig();
|
||||||
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
||||||
@@ -43,12 +45,22 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
bindDN: data?.bindDN ?? "",
|
bindDN: data?.bindDN ?? "",
|
||||||
bindPass: data?.bindPass ?? "",
|
bindPass: data?.bindPass ?? "",
|
||||||
searchBase: data?.searchBase ?? "",
|
searchBase: data?.searchBase ?? "",
|
||||||
|
groupSearchBase: data?.groupSearchBase ?? "",
|
||||||
|
groupSearchFilter: data?.groupSearchFilter ?? "",
|
||||||
caCert: data?.caCert ?? ""
|
caCert: data?.caCert ?? ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
const onSSOModalSubmit = async ({ url, bindDN, bindPass, searchBase, caCert }: TLDAPFormData) => {
|
const onSSOModalSubmit = async ({
|
||||||
|
url,
|
||||||
|
bindDN,
|
||||||
|
bindPass,
|
||||||
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
|
caCert
|
||||||
|
}: TLDAPFormData) => {
|
||||||
try {
|
try {
|
||||||
if (!currentOrg) return;
|
if (!currentOrg) return;
|
||||||
|
|
||||||
@@ -60,6 +72,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -70,6 +84,8 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
bindDN,
|
bindDN,
|
||||||
bindPass,
|
bindPass,
|
||||||
searchBase,
|
searchBase,
|
||||||
|
groupSearchBase,
|
||||||
|
groupSearchFilter,
|
||||||
caCert
|
caCert
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -139,6 +155,32 @@ export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props)
|
|||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="groupSearchBase"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Group Search Base / Group DN (Optional)"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="ou=groups,dc=acme,dc=com" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="groupSearchFilter"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Group Filter (Optional)"
|
||||||
|
errorText={error?.message}
|
||||||
|
isError={Boolean(error)}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="(objectClass=posixGroup)" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="caCert"
|
name="caCert"
|
||||||
|
|||||||
+5
-2
@@ -15,8 +15,9 @@ import { LDAPModal } from "./LDAPModal";
|
|||||||
export const OrgLDAPSection = (): JSX.Element => {
|
export const OrgLDAPSection = (): JSX.Element => {
|
||||||
const { currentOrg } = useOrganization();
|
const { currentOrg } = useOrganization();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
|
|
||||||
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
||||||
|
|
||||||
const { mutateAsync } = useUpdateLDAPConfig();
|
const { mutateAsync } = useUpdateLDAPConfig();
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"addLDAP",
|
"addLDAP",
|
||||||
@@ -63,7 +64,9 @@ export const OrgLDAPSection = (): JSX.Element => {
|
|||||||
url: "",
|
url: "",
|
||||||
bindDN: "",
|
bindDN: "",
|
||||||
bindPass: "",
|
bindPass: "",
|
||||||
searchBase: ""
|
searchBase: "",
|
||||||
|
groupSearchBase: "",
|
||||||
|
groupSearchFilter: ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user