mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 13:27:46 +00:00
Patch unsynchronized username/email for saml/scim
This commit is contained in:
@@ -18,6 +18,7 @@ import { LdapConfigsSchema, LdapGroupMapsSchema } from "@app/db/schemas";
|
|||||||
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
import { TLDAPConfig } from "@app/ee/services/ldap-config/ldap-config-types";
|
||||||
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
import { isValidLdapFilter, searchGroups } from "@app/ee/services/ldap-config/ldap-fns";
|
||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
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";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
@@ -52,6 +53,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req: IncomingMessage, user, cb) => {
|
async (req: IncomingMessage, user, cb) => {
|
||||||
try {
|
try {
|
||||||
|
if (!user.email) throw new BadRequestError({ message: "Invalid request. Missing email." });
|
||||||
const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as TLDAPConfig;
|
const ldapConfig = (req as unknown as FastifyRequest).ldapConfig as TLDAPConfig;
|
||||||
|
|
||||||
let groups: { dn: string; cn: string }[] | undefined;
|
let groups: { dn: string; cn: string }[] | undefined;
|
||||||
@@ -74,7 +76,7 @@ export const registerLdapRouter = async (server: FastifyZodProvider) => {
|
|||||||
username: user.uid,
|
username: user.uid,
|
||||||
firstName: user.givenName ?? user.cn ?? "",
|
firstName: user.givenName ?? user.cn ?? "",
|
||||||
lastName: user.sn ?? "",
|
lastName: user.sn ?? "",
|
||||||
emails: user.mail ? [user.mail] : [],
|
email: user.mail,
|
||||||
groups,
|
groups,
|
||||||
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
relayState: ((req as unknown as FastifyRequest).body as { RelayState?: string }).RelayState,
|
||||||
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
orgId: (req as unknown as FastifyRequest).ldapConfig.organization
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
const email = profile?.email ?? (profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved
|
||||||
|
|
||||||
if (!profile.email || !profile.firstName) {
|
if (!email || !profile.firstName) {
|
||||||
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
throw new BadRequestError({ message: "Invalid request. Missing email or first name" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -249,7 +249,7 @@ export const registerScimRouter = async (server: FastifyZodProvider) => {
|
|||||||
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
|
const primaryEmail = req.body.emails?.find((email) => email.primary)?.value;
|
||||||
|
|
||||||
const user = await req.server.services.scim.createScimUser({
|
const user = await req.server.services.scim.createScimUser({
|
||||||
username: req.body.userName,
|
externalId: req.body.userName,
|
||||||
email: primaryEmail,
|
email: primaryEmail,
|
||||||
firstName: req.body.name.givenName,
|
firstName: req.body.name.givenName,
|
||||||
lastName: req.body.name.familyName,
|
lastName: req.body.name.familyName,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { SecretKeyEncoding, TUsers } from "@app/db/schemas";
|
import { SecretKeyEncoding, TableName, TUsers } from "@app/db/schemas";
|
||||||
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
import { decryptAsymmetric, encryptAsymmetric, infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
|
||||||
import { BadRequestError, ScimRequestError } from "@app/lib/errors";
|
import { BadRequestError, ScimRequestError } from "@app/lib/errors";
|
||||||
|
|
||||||
@@ -188,9 +188,9 @@ export const addUsersToGroupByUserIds = async ({
|
|||||||
// check if all user(s) are part of the organization
|
// check if all user(s) are part of the organization
|
||||||
const existingUserOrgMemberships = await orgDAL.findMembership(
|
const existingUserOrgMemberships = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
orgId: group.orgId,
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: group.orgId,
|
||||||
$in: {
|
$in: {
|
||||||
userId: userIds
|
[`${TableName.OrgMembership}.userId` as "userId"]: userIds
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
OrgMembershipStatus,
|
OrgMembershipStatus,
|
||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
TableName,
|
TableName,
|
||||||
TLdapConfigsUpdate
|
TLdapConfigsUpdate,
|
||||||
|
TUsers
|
||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns";
|
||||||
@@ -25,6 +26,7 @@ import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
|||||||
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
|
||||||
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
|
||||||
@@ -54,6 +56,7 @@ import { TLdapGroupMapDALFactory } from "./ldap-group-map-dal";
|
|||||||
type TLdapConfigServiceFactoryDep = {
|
type TLdapConfigServiceFactoryDep = {
|
||||||
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne">;
|
ldapConfigDAL: Pick<TLdapConfigDALFactory, "create" | "update" | "findOne">;
|
||||||
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
|
ldapGroupMapDAL: Pick<TLdapGroupMapDALFactory, "find" | "create" | "delete" | "findLdapGroupMapsByLdapConfigId">;
|
||||||
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||||
orgDAL: Pick<
|
orgDAL: Pick<
|
||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
@@ -83,6 +86,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
ldapConfigDAL,
|
ldapConfigDAL,
|
||||||
ldapGroupMapDAL,
|
ldapGroupMapDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
orgMembershipDAL,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
@@ -387,7 +391,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
username,
|
username,
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
emails,
|
email,
|
||||||
groups,
|
groups,
|
||||||
orgId,
|
orgId,
|
||||||
relayState
|
relayState
|
||||||
@@ -407,7 +411,7 @@ export const ldapConfigServiceFactory = ({
|
|||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
userId: userAlias.userId,
|
[`${TableName.OrgMembership}.userId` as "userId"]: userAlias.userId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
@@ -434,41 +438,75 @@ export const ldapConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
userAlias = await userDAL.transaction(async (tx) => {
|
userAlias = await userDAL.transaction(async (tx) => {
|
||||||
const uniqueUsername = await normalizeUsername(username, userDAL);
|
let newUser: TUsers | undefined;
|
||||||
const newUser = await userDAL.create(
|
if (serverCfg.trustSamlEmails) {
|
||||||
{
|
newUser = await userDAL.findOne(
|
||||||
username: uniqueUsername,
|
{
|
||||||
email: emails[0],
|
email,
|
||||||
isEmailVerified: serverCfg.trustLdapEmails,
|
isEmailVerified: true
|
||||||
firstName,
|
},
|
||||||
lastName,
|
tx
|
||||||
authMethods: [],
|
);
|
||||||
isGhost: false
|
}
|
||||||
},
|
|
||||||
tx
|
if (!newUser) {
|
||||||
);
|
const uniqueUsername = await normalizeUsername(username, userDAL);
|
||||||
|
newUser = await userDAL.create(
|
||||||
|
{
|
||||||
|
username: serverCfg.trustLdapEmails ? email : uniqueUsername,
|
||||||
|
email,
|
||||||
|
isEmailVerified: serverCfg.trustLdapEmails,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authMethods: [],
|
||||||
|
isGhost: false
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const newUserAlias = await userAliasDAL.create(
|
const newUserAlias = await userAliasDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
username,
|
username,
|
||||||
aliasType: UserAliasType.LDAP,
|
aliasType: UserAliasType.LDAP,
|
||||||
externalId,
|
externalId,
|
||||||
emails,
|
emails: [email],
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await orgDAL.createMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
||||||
orgId,
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
role: OrgMembershipRole.Member,
|
|
||||||
status: OrgMembershipStatus.Invited
|
|
||||||
},
|
},
|
||||||
tx
|
{ tx }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!orgMembership) {
|
||||||
|
await orgMembershipDAL.create(
|
||||||
|
{
|
||||||
|
userId: userAlias.userId,
|
||||||
|
inviteEmail: email,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
|
||||||
|
await orgDAL.updateMembershipById(
|
||||||
|
orgMembership.id,
|
||||||
|
{
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return newUserAlias;
|
return newUserAlias;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export type TLdapLoginDTO = {
|
|||||||
username: string;
|
username: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
emails: string[];
|
email: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
groups?: {
|
groups?: {
|
||||||
dn: string;
|
dn: string;
|
||||||
|
|||||||
@@ -347,7 +347,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
const foundUser = await userDAL.findById(userAlias.userId, tx);
|
||||||
const [orgMembership] = await orgDAL.findMembership(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
userId: foundUser.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: foundUser.id,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
@@ -378,19 +378,33 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
user = await userDAL.transaction(async (tx) => {
|
user = await userDAL.transaction(async (tx) => {
|
||||||
const uniqueUsername = await normalizeUsername(`${firstName ?? ""}-${lastName ?? ""}`, userDAL);
|
let newUser: TUsers | undefined;
|
||||||
const newUser = await userDAL.create(
|
if (serverCfg.trustSamlEmails) {
|
||||||
{
|
newUser = await userDAL.findOne(
|
||||||
username: uniqueUsername,
|
{
|
||||||
email,
|
email,
|
||||||
isEmailVerified: serverCfg.trustSamlEmails,
|
isEmailVerified: true
|
||||||
firstName,
|
},
|
||||||
lastName,
|
tx
|
||||||
authMethods: [],
|
);
|
||||||
isGhost: false
|
}
|
||||||
},
|
|
||||||
tx
|
if (!newUser) {
|
||||||
);
|
const uniqueUsername = await normalizeUsername(`${firstName ?? ""}-${lastName ?? ""}`, userDAL);
|
||||||
|
newUser = await userDAL.create(
|
||||||
|
{
|
||||||
|
username: serverCfg.trustSamlEmails ? email : uniqueUsername,
|
||||||
|
email,
|
||||||
|
isEmailVerified: serverCfg.trustSamlEmails,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authMethods: [],
|
||||||
|
isGhost: false
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await userAliasDAL.create(
|
await userAliasDAL.create(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
userId: newUser.id,
|
||||||
@@ -402,17 +416,36 @@ export const samlConfigServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await orgMembershipDAL.create(
|
const [orgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
userId: newUser.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: newUser.id,
|
||||||
inviteEmail: email,
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
orgId,
|
|
||||||
role: OrgMembershipRole.Member,
|
|
||||||
status: OrgMembershipStatus.Invited
|
|
||||||
},
|
},
|
||||||
tx
|
{ tx }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!orgMembership) {
|
||||||
|
await orgMembershipDAL.create(
|
||||||
|
{
|
||||||
|
userId: newUser.id,
|
||||||
|
inviteEmail: email,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: newUser.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && newUser.isAccepted) {
|
||||||
|
await orgDAL.updateMembershipById(
|
||||||
|
orgMembership.id,
|
||||||
|
{
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return newUser;
|
return newUser;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export type TGetSamlCfgDTO =
|
|||||||
|
|
||||||
export type TSamlLoginDTO = {
|
export type TSamlLoginDTO = {
|
||||||
externalId: string;
|
externalId: string;
|
||||||
email?: string;
|
email: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName?: string;
|
lastName?: string;
|
||||||
authProvider: string;
|
authProvider: string;
|
||||||
|
|||||||
@@ -209,10 +209,10 @@ export const scimServiceFactory = ({
|
|||||||
findOpts
|
findOpts
|
||||||
);
|
);
|
||||||
|
|
||||||
const scimUsers = users.map(({ id, username, firstName, lastName, email }) =>
|
const scimUsers = users.map(({ id, externalId, username, firstName, lastName, email }) =>
|
||||||
buildScimUser({
|
buildScimUser({
|
||||||
orgMembershipId: id ?? "",
|
orgMembershipId: id ?? "",
|
||||||
username,
|
username: externalId ?? username,
|
||||||
firstName: firstName ?? "",
|
firstName: firstName ?? "",
|
||||||
lastName: lastName ?? "",
|
lastName: lastName ?? "",
|
||||||
email,
|
email,
|
||||||
@@ -254,7 +254,7 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
username: membership.username,
|
username: membership.externalId ?? membership.username,
|
||||||
email: membership.email ?? "",
|
email: membership.email ?? "",
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
@@ -262,7 +262,9 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createScimUser = async ({ username, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
|
const createScimUser = async ({ externalId, email, firstName, lastName, orgId }: TCreateScimUserDTO) => {
|
||||||
|
if (!email) throw new ScimRequestError({ detail: "Invalid request. Missing email.", status: 400 });
|
||||||
|
|
||||||
const org = await orgDAL.findById(orgId);
|
const org = await orgDAL.findById(orgId);
|
||||||
|
|
||||||
if (!org)
|
if (!org)
|
||||||
@@ -281,13 +283,13 @@ export const scimServiceFactory = ({
|
|||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
|
|
||||||
const userAlias = await userAliasDAL.findOne({
|
const userAlias = await userAliasDAL.findOne({
|
||||||
externalId: username,
|
externalId,
|
||||||
orgId,
|
orgId,
|
||||||
aliasType: UserAliasType.SAML
|
aliasType: UserAliasType.SAML
|
||||||
});
|
});
|
||||||
|
|
||||||
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
|
const { user: createdUser, orgMembership: createdOrgMembership } = await userDAL.transaction(async (tx) => {
|
||||||
let user: TUsers;
|
let user: TUsers | undefined;
|
||||||
let orgMembership: TOrgMemberships;
|
let orgMembership: TOrgMemberships;
|
||||||
if (userAlias) {
|
if (userAlias) {
|
||||||
user = await userDAL.findById(userAlias.userId, tx);
|
user = await userDAL.findById(userAlias.userId, tx);
|
||||||
@@ -320,39 +322,74 @@ export const scimServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const uniqueUsername = await normalizeUsername(`${firstName}-${lastName}`, userDAL);
|
if (serverCfg.trustSamlEmails) {
|
||||||
user = await userDAL.create(
|
user = await userDAL.findOne(
|
||||||
{
|
{
|
||||||
username: uniqueUsername,
|
email,
|
||||||
email,
|
isEmailVerified: true
|
||||||
isEmailVerified: serverCfg.trustSamlEmails,
|
},
|
||||||
firstName,
|
tx
|
||||||
lastName,
|
);
|
||||||
authMethods: [],
|
}
|
||||||
isGhost: false
|
|
||||||
},
|
if (!user) {
|
||||||
tx
|
const uniqueUsername = await normalizeUsername(`${firstName}-${lastName}`, userDAL);
|
||||||
);
|
user = await userDAL.create(
|
||||||
|
{
|
||||||
|
username: serverCfg.trustSamlEmails ? email : uniqueUsername,
|
||||||
|
email,
|
||||||
|
isEmailVerified: serverCfg.trustSamlEmails,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
authMethods: [],
|
||||||
|
isGhost: false
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await userAliasDAL.create(
|
await userAliasDAL.create(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
aliasType: UserAliasType.SAML,
|
aliasType: UserAliasType.SAML,
|
||||||
externalId: username,
|
externalId,
|
||||||
emails: email ? [email] : [],
|
emails: email ? [email] : [],
|
||||||
orgId
|
orgId
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
orgMembership = await orgMembershipDAL.create(
|
|
||||||
|
const [foundOrgMembership] = await orgDAL.findMembership(
|
||||||
{
|
{
|
||||||
userId: user.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
||||||
inviteEmail: email,
|
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
||||||
orgId,
|
|
||||||
role: OrgMembershipRole.Member,
|
|
||||||
status: OrgMembershipStatus.Invited
|
|
||||||
},
|
},
|
||||||
tx
|
{ tx }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
orgMembership = foundOrgMembership;
|
||||||
|
|
||||||
|
if (!orgMembership) {
|
||||||
|
orgMembership = await orgMembershipDAL.create(
|
||||||
|
{
|
||||||
|
userId: user.id,
|
||||||
|
inviteEmail: email,
|
||||||
|
orgId,
|
||||||
|
role: OrgMembershipRole.Member,
|
||||||
|
status: user.isAccepted ? OrgMembershipStatus.Accepted : OrgMembershipStatus.Invited // if user is fully completed, then set status to accepted, otherwise set it to invited so we can update it later
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
// Only update the membership to Accepted if the user account is already completed.
|
||||||
|
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||||
|
orgMembership = await orgDAL.updateMembershipById(
|
||||||
|
orgMembership.id,
|
||||||
|
{
|
||||||
|
status: OrgMembershipStatus.Accepted
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { user, orgMembership };
|
return { user, orgMembership };
|
||||||
@@ -372,7 +409,7 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
orgMembershipId: createdOrgMembership.id,
|
orgMembershipId: createdOrgMembership.id,
|
||||||
username: createdUser.username,
|
username: externalId,
|
||||||
firstName: createdUser.firstName as string,
|
firstName: createdUser.firstName as string,
|
||||||
lastName: createdUser.lastName as string,
|
lastName: createdUser.lastName as string,
|
||||||
email: createdUser.email ?? "",
|
email: createdUser.email ?? "",
|
||||||
@@ -380,11 +417,11 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateScimUser = async ({ userId, orgId, operations }: TUpdateScimUserDTO) => {
|
const updateScimUser = async ({ orgMembershipId, orgId, operations }: TUpdateScimUserDTO) => {
|
||||||
const [membership] = await orgDAL
|
const [membership] = await orgDAL
|
||||||
.findMembership({
|
.findMembership({
|
||||||
userId,
|
[`${TableName.OrgMembership}.id` as "id"]: orgMembershipId,
|
||||||
[`${TableName.OrgMembership}.orgId` as "id"]: orgId
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
throw new ScimRequestError({
|
throw new ScimRequestError({
|
||||||
@@ -433,7 +470,7 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
username: membership.username,
|
username: membership.externalId ?? membership.username,
|
||||||
email: membership.email,
|
email: membership.email,
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
@@ -467,7 +504,6 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
// tx
|
|
||||||
await deleteOrgMembershipFn({
|
await deleteOrgMembershipFn({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
orgId: membership.orgId,
|
orgId: membership.orgId,
|
||||||
@@ -481,7 +517,7 @@ export const scimServiceFactory = ({
|
|||||||
|
|
||||||
return buildScimUser({
|
return buildScimUser({
|
||||||
orgMembershipId: membership.id,
|
orgMembershipId: membership.id,
|
||||||
username: membership.username,
|
username: membership.externalId ?? membership.username,
|
||||||
email: membership.email,
|
email: membership.email,
|
||||||
firstName: membership.firstName as string,
|
firstName: membership.firstName as string,
|
||||||
lastName: membership.lastName as string,
|
lastName: membership.lastName as string,
|
||||||
@@ -627,9 +663,9 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const orgMemberships = await orgDAL.findMembership({
|
const orgMemberships = await orgDAL.findMembership({
|
||||||
orgId,
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
$in: {
|
$in: {
|
||||||
userId: newGroup.newMembers.map((member) => member.id)
|
[`${TableName.OrgMembership}.userId` as "userId"]: newGroup.newMembers.map((member) => member.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -668,9 +704,11 @@ export const scimServiceFactory = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const orgMemberships = await orgDAL.findMembership({
|
const orgMemberships = await orgDAL.findMembership({
|
||||||
orgId,
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
$in: {
|
$in: {
|
||||||
userId: users.filter((user) => user.isPartOfGroup).map((user) => user.id)
|
[`${TableName.OrgMembership}.userId` as "userId"]: users
|
||||||
|
.filter((user) => user.isPartOfGroup)
|
||||||
|
.map((user) => user.id)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export type TGetScimUserDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TCreateScimUserDTO = {
|
export type TCreateScimUserDTO = {
|
||||||
username: string;
|
externalId: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
lastName: string;
|
lastName: string;
|
||||||
@@ -40,7 +40,7 @@ export type TCreateScimUserDTO = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TUpdateScimUserDTO = {
|
export type TUpdateScimUserDTO = {
|
||||||
userId: string;
|
orgMembershipId: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
operations: {
|
operations: {
|
||||||
op: string;
|
op: string;
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ export const registerRoutes = async (
|
|||||||
ldapConfigDAL,
|
ldapConfigDAL,
|
||||||
ldapGroupMapDAL,
|
ldapGroupMapDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
orgMembershipDAL,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
groupDAL,
|
groupDAL,
|
||||||
groupProjectDAL,
|
groupProjectDAL,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
|
|
||||||
import { OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||||
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
import { convertPendingGroupAdditionsToGroupMemberships } from "@app/ee/services/group/group-fns";
|
||||||
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
@@ -102,6 +102,8 @@ export const authSignupServiceFactory = ({
|
|||||||
code
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await userDAL.updateById(user.id, { isEmailVerified: true });
|
||||||
|
|
||||||
// generate jwt token this is a temporary token
|
// generate jwt token this is a temporary token
|
||||||
const jwtToken = jwt.sign(
|
const jwtToken = jwt.sign(
|
||||||
{
|
{
|
||||||
@@ -171,9 +173,9 @@ export const authSignupServiceFactory = ({
|
|||||||
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
|
// If it's SAML Auth and the organization ID is present, we should check if the user has a pending invite for this org, and accept it
|
||||||
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && organizationId) {
|
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && organizationId) {
|
||||||
const [pendingOrgMembership] = await orgDAL.findMembership({
|
const [pendingOrgMembership] = await orgDAL.findMembership({
|
||||||
userId: user.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
||||||
status: OrgMembershipStatus.Invited,
|
status: OrgMembershipStatus.Invited,
|
||||||
orgId: organizationId
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (pendingOrgMembership) {
|
if (pendingOrgMembership) {
|
||||||
|
|||||||
@@ -262,13 +262,19 @@ export const orgDALFactory = (db: TDbClient) => {
|
|||||||
.where(buildFindFilter(filter))
|
.where(buildFindFilter(filter))
|
||||||
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
|
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.OrgMembership}.userId`)
|
||||||
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`)
|
.join(TableName.Organization, `${TableName.Organization}.id`, `${TableName.OrgMembership}.orgId`)
|
||||||
|
.leftJoin(TableName.UserAliases, function joinUserAlias() {
|
||||||
|
this.on(`${TableName.UserAliases}.userId`, "=", `${TableName.OrgMembership}.userId`)
|
||||||
|
.andOn(`${TableName.UserAliases}.orgId`, "=", `${TableName.OrgMembership}.orgId`)
|
||||||
|
.andOn(`${TableName.UserAliases}.aliasType`, "=", (tx || db).raw("?", ["saml"]));
|
||||||
|
})
|
||||||
.select(
|
.select(
|
||||||
selectAllTableCols(TableName.OrgMembership),
|
selectAllTableCols(TableName.OrgMembership),
|
||||||
db.ref("email").withSchema(TableName.Users),
|
db.ref("email").withSchema(TableName.Users),
|
||||||
db.ref("username").withSchema(TableName.Users),
|
db.ref("username").withSchema(TableName.Users),
|
||||||
db.ref("firstName").withSchema(TableName.Users),
|
db.ref("firstName").withSchema(TableName.Users),
|
||||||
db.ref("lastName").withSchema(TableName.Users),
|
db.ref("lastName").withSchema(TableName.Users),
|
||||||
db.ref("scimEnabled").withSchema(TableName.Organization)
|
db.ref("scimEnabled").withSchema(TableName.Organization),
|
||||||
|
db.ref("externalId").withSchema(TableName.UserAliases)
|
||||||
)
|
)
|
||||||
.where({ isGhost: false });
|
.where({ isGhost: false });
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import crypto from "crypto";
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { Knex } from "knex";
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { OrgMembershipRole, OrgMembershipStatus } from "@app/db/schemas";
|
import { OrgMembershipRole, OrgMembershipStatus, TableName } from "@app/db/schemas";
|
||||||
import { TProjects } from "@app/db/schemas/projects";
|
import { TProjects } from "@app/db/schemas/projects";
|
||||||
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
import { TGroupDALFactory } from "@app/ee/services/group/group-dal";
|
||||||
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
|
||||||
@@ -431,7 +431,13 @@ export const orgServiceFactory = ({
|
|||||||
if (inviteeUser) {
|
if (inviteeUser) {
|
||||||
// if user already exist means its already part of infisical
|
// if user already exist means its already part of infisical
|
||||||
// Thus the signup flow is not needed anymore
|
// Thus the signup flow is not needed anymore
|
||||||
const [inviteeMembership] = await orgDAL.findMembership({ orgId, userId: inviteeUser.id }, { tx });
|
const [inviteeMembership] = await orgDAL.findMembership(
|
||||||
|
{
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId,
|
||||||
|
[`${TableName.OrgMembership}.userId` as "userId"]: inviteeUser.id
|
||||||
|
},
|
||||||
|
{ tx }
|
||||||
|
);
|
||||||
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
|
if (inviteeMembership && inviteeMembership.status === OrgMembershipStatus.Accepted) {
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Failed to invite an existing member of org",
|
message: "Failed to invite an existing member of org",
|
||||||
@@ -523,9 +529,9 @@ export const orgServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Invalid request", name: "Verify user to org" });
|
throw new BadRequestError({ message: "Invalid request", name: "Verify user to org" });
|
||||||
}
|
}
|
||||||
const [orgMembership] = await orgDAL.findMembership({
|
const [orgMembership] = await orgDAL.findMembership({
|
||||||
userId: user.id,
|
[`${TableName.OrgMembership}.userId` as "userId"]: user.id,
|
||||||
status: OrgMembershipStatus.Invited,
|
status: OrgMembershipStatus.Invited,
|
||||||
orgId
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: orgId
|
||||||
});
|
});
|
||||||
if (!orgMembership)
|
if (!orgMembership)
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
);
|
);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Member);
|
||||||
const orgMembers = await orgDAL.findMembership({
|
const orgMembers = await orgDAL.findMembership({
|
||||||
orgId: project.orgId,
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId,
|
||||||
$in: {
|
$in: {
|
||||||
[`${TableName.OrgMembership}.id` as "id"]: members.map(({ orgMembershipId }) => orgMembershipId)
|
[`${TableName.OrgMembership}.id` as "id"]: members.map(({ orgMembershipId }) => orgMembershipId)
|
||||||
}
|
}
|
||||||
@@ -119,7 +119,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
|
|
||||||
const existingMembers = await projectMembershipDAL.find({
|
const existingMembers = await projectMembershipDAL.find({
|
||||||
projectId,
|
projectId,
|
||||||
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) as string[] }
|
$in: { userId: orgMembers.map(({ userId }) => userId).filter(Boolean) }
|
||||||
});
|
});
|
||||||
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
if (existingMembers.length) throw new BadRequestError({ message: "Some users are already part of project" });
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const projectMemberships = await projectMembershipDAL.insertMany(
|
const projectMemberships = await projectMembershipDAL.insertMany(
|
||||||
orgMembers.map(({ userId }) => ({
|
orgMembers.map(({ userId }) => ({
|
||||||
projectId,
|
projectId,
|
||||||
userId: userId as string
|
userId
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -145,12 +145,12 @@ export const projectMembershipServiceFactory = ({
|
|||||||
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
|
const encKeyGroupByOrgMembId = groupBy(members, (i) => i.orgMembershipId);
|
||||||
await projectKeyDAL.insertMany(
|
await projectKeyDAL.insertMany(
|
||||||
orgMembers
|
orgMembers
|
||||||
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId as string))
|
.filter(({ userId }) => !userIdsToExcludeForProjectKeyAddition.has(userId))
|
||||||
.map(({ userId, id }) => ({
|
.map(({ userId, id }) => ({
|
||||||
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
encryptedKey: encKeyGroupByOrgMembId[id][0].workspaceEncryptedKey,
|
||||||
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
nonce: encKeyGroupByOrgMembId[id][0].workspaceEncryptedNonce,
|
||||||
senderId: actorId,
|
senderId: actorId,
|
||||||
receiverId: userId as string,
|
receiverId: userId,
|
||||||
projectId
|
projectId
|
||||||
})),
|
})),
|
||||||
tx
|
tx
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
SecretKeyEncoding,
|
SecretKeyEncoding,
|
||||||
SecretsSchema,
|
SecretsSchema,
|
||||||
SecretVersionsSchema,
|
SecretVersionsSchema,
|
||||||
|
TableName,
|
||||||
TIntegrationAuths,
|
TIntegrationAuths,
|
||||||
TSecretApprovalRequestsSecrets,
|
TSecretApprovalRequestsSecrets,
|
||||||
TSecrets,
|
TSecrets,
|
||||||
@@ -273,7 +274,10 @@ export const projectQueueFactory = ({
|
|||||||
|
|
||||||
for (const key of existingProjectKeys) {
|
for (const key of existingProjectKeys) {
|
||||||
const user = await userDAL.findUserEncKeyByUserId(key.receiverId);
|
const user = await userDAL.findUserEncKeyByUserId(key.receiverId);
|
||||||
const [orgMembership] = await orgDAL.findMembership({ userId: key.receiverId, orgId: project.orgId });
|
const [orgMembership] = await orgDAL.findMembership({
|
||||||
|
[`${TableName.OrgMembership}.userId` as "userId"]: key.receiverId,
|
||||||
|
[`${TableName.OrgMembership}.orgId` as "orgId"]: project.orgId
|
||||||
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`);
|
throw new Error(`User with ID ${key.receiverId} was not found during upgrade.`);
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ export const userServiceFactory = ({
|
|||||||
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
const verifyEmailVerificationCode = async (username: string, code: string) => {
|
||||||
const user = await userDAL.findOne({ username });
|
const user = await userDAL.findOne({ username });
|
||||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||||
|
if (!user.email)
|
||||||
|
throw new BadRequestError({ name: "Failed to verify email verification code due to no email on user" });
|
||||||
if (user.isEmailVerified)
|
if (user.isEmailVerified)
|
||||||
throw new BadRequestError({ name: "Failed to verify email verification code due to email already verified" });
|
throw new BadRequestError({ name: "Failed to verify email verification code due to email already verified" });
|
||||||
|
|
||||||
@@ -72,6 +74,8 @@ export const userServiceFactory = ({
|
|||||||
code
|
code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { email } = user;
|
||||||
|
|
||||||
await userDAL.transaction(async (tx) => {
|
await userDAL.transaction(async (tx) => {
|
||||||
await userDAL.updateById(
|
await userDAL.updateById(
|
||||||
user.id,
|
user.id,
|
||||||
@@ -84,7 +88,7 @@ export const userServiceFactory = ({
|
|||||||
// check if there are users with the same email.
|
// check if there are users with the same email.
|
||||||
const users = await userDAL.find(
|
const users = await userDAL.find(
|
||||||
{
|
{
|
||||||
email: user.email,
|
email,
|
||||||
isEmailVerified: true
|
isEmailVerified: true
|
||||||
},
|
},
|
||||||
{ tx }
|
{ tx }
|
||||||
@@ -129,6 +133,15 @@ export const userServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// update current user's username to [email]
|
||||||
|
await userDAL.updateById(
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
username: email
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,6 +12,10 @@ description: "Learn how to log in to Infisical with LDAP."
|
|||||||
|
|
||||||
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)
|
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)
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- You must have an email address to use LDAP, regardless of whether or not you use that email address to sign in.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Prepare the LDAP configuration in Infisical">
|
<Step title="Prepare the LDAP configuration in Infisical">
|
||||||
In Infisical, head to your Organization Settings > Security > LDAP and select **Manage**.
|
In Infisical, head to your Organization Settings > Security > LDAP and select **Manage**.
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ description: "Learn how to configure JumpCloud LDAP for authenticating into Infi
|
|||||||
it.
|
it.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- You must have an email address to use LDAP, regardless of whether or not you use that email address to sign in.
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
<Step title="Prepare LDAP in JumpCloud">
|
<Step title="Prepare LDAP in JumpCloud">
|
||||||
In JumpCloud, head to USER MANAGEMENT > Users and create a new user via the **Manual user entry** option. This user
|
In JumpCloud, head to USER MANAGEMENT > Users and create a new user via the **Manual user entry** option. This user
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ title: "LDAP Overview"
|
|||||||
sidebarTitle: "Overview"
|
sidebarTitle: "Overview"
|
||||||
description: "Learn how to authenticate into Infisical with LDAP."
|
description: "Learn how to authenticate into Infisical with LDAP."
|
||||||
---
|
---
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
LDAP is a paid feature.
|
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,
|
If you're using Infisical Cloud, then it is available under the **Enterprise Tier**. If you're self-hosting Infisical,
|
||||||
then you should contact [email protected] to purchase an enterprise license to use it.
|
then you should contact [email protected] to purchase an enterprise license to use it.
|
||||||
|
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
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).
|
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).
|
||||||
@@ -25,3 +27,18 @@ Read the general instructions for configuring LDAP [here](/documentation/platfor
|
|||||||
|
|
||||||
If the documentation for your required identity provider is not shown in the list above, please reach out to [[email protected]](mailto:[email protected]) for assistance.
|
If the documentation for your required identity provider is not shown in the list above, please reach out to [[email protected]](mailto:[email protected]) for assistance.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Why does Infisical require additional email verification for users connected via LDAP?">
|
||||||
|
By default, Infisical Cloud is configured to not trust emails from external
|
||||||
|
identity providers to prevent any malicious account takeover attempts via
|
||||||
|
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
|
||||||
|
through an external identity provider and requires an additional email
|
||||||
|
verification step upon their first login.
|
||||||
|
|
||||||
|
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
|
||||||
|
you can configure this behavior in the admin panel.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Okta SAML SSO is a paid feature.
|
Okta SAML SSO is a paid feature. If you're using Infisical Cloud, then it is
|
||||||
|
available under the **Pro Tier**. If you're self-hosting Infisical, then you
|
||||||
If you're using Infisical Cloud, then it is available under the **Pro Tier**. If you're self-hosting Infisical,
|
should contact [email protected] to purchase an enterprise license to use
|
||||||
then you should contact [email protected] to purchase an enterprise license to use it.
|
it.
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
<Steps>
|
<Steps>
|
||||||
@@ -50,6 +50,7 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||

|

|
||||||
|
|
||||||
Once configured, select **Next** to proceed to the Feedback screen and select **Finish**.
|
Once configured, select **Next** to proceed to the Feedback screen and select **Finish**.
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Retrieve Identity Provider (IdP) Information from Okta">
|
<Step title="Retrieve Identity Provider (IdP) Information from Okta">
|
||||||
Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen:
|
Once your application is created, select the **Sign On** tab for the app and select the **View Setup Instructions** button located on the right side of the screen:
|
||||||
@@ -59,12 +60,14 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||
Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring Okta SAML in Infisical.
|
Copy the **Identity Provider Single Sign-On URL**, the **Identity Provider Issuer**, and the **X.509 Certificate** to use when finishing configuring Okta SAML in Infisical.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Finish configuring SAML in Infisical">
|
<Step title="Finish configuring SAML in Infisical">
|
||||||
Back in Infisical, set **Identity Provider Single Sign-On URL**, **Identity Provider Issuer**,
|
Back in Infisical, set **Identity Provider Single Sign-On URL**, **Identity Provider Issuer**,
|
||||||
and **Certificate** to **X.509 Certificate** from step 3. Once you've done that, press **Update** to complete the required configuration.
|
and **Certificate** to **X.509 Certificate** from step 3. Once you've done that, press **Update** to complete the required configuration.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Assign users in Okta to the application">
|
<Step title="Assign users in Okta to the application">
|
||||||
Back in Okta, navigate to the **Assignments** tab and select **Assign**. You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option.
|
Back in Okta, navigate to the **Assignments** tab and select **Assign**. You can assign access to the application on a user-by-user basis using the Assign to People option, or in-bulk using the Assign to Groups option.
|
||||||
@@ -72,11 +75,13 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||

|

|
||||||
|
|
||||||
At this point, you have configured everything you need within the context of the Okta Admin Portal.
|
At this point, you have configured everything you need within the context of the Okta Admin Portal.
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Enable SAML SSO in Infisical">
|
<Step title="Enable SAML SSO in Infisical">
|
||||||
Enabling SAML SSO allows members in your organization to log into Infisical via Okta.
|
Enabling SAML SSO allows members in your organization to log into Infisical via Okta.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
<Step title="Enforce SAML SSO in Infisical">
|
<Step title="Enforce SAML SSO in Infisical">
|
||||||
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
Enforcing SAML SSO ensures that members in your organization can only access Infisical
|
||||||
@@ -89,13 +94,15 @@ description: "Learn how to configure Okta SAML 2.0 for Infisical SSO."
|
|||||||
We recommend ensuring that your account is provisioned the application in Okta
|
We recommend ensuring that your account is provisioned the application in Okta
|
||||||
prior to enforcing SAML SSO to prevent any unintended issues.
|
prior to enforcing SAML SSO to prevent any unintended issues.
|
||||||
</Warning>
|
</Warning>
|
||||||
|
|
||||||
</Step>
|
</Step>
|
||||||
</Steps>
|
</Steps>
|
||||||
|
|
||||||
<Note>
|
<Note>
|
||||||
If you're configuring SAML SSO on a self-hosted instance of Infisical, make sure to
|
If you're configuring SAML SSO on a self-hosted instance of Infisical, make
|
||||||
set the `AUTH_SECRET` and `SITE_URL` environment variable for it to work:
|
sure to set the `AUTH_SECRET` and `SITE_URL` environment variable for it to
|
||||||
|
work: - `AUTH_SECRET`: A secret key used for signing and verifying JWT. This
|
||||||
- `AUTH_SECRET`: A secret key used for signing and verifying JWT. This can be a random 32-byte base64 string generated with `openssl rand -base64 32`.
|
can be a random 32-byte base64 string generated with `openssl rand -base64
|
||||||
- `SITE_URL`: The URL of your self-hosted instance of Infisical - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
32`. - `SITE_URL`: The URL of your self-hosted instance of Infisical - should
|
||||||
|
be an absolute URL including the protocol (e.g. https://app.infisical.com)
|
||||||
</Note>
|
</Note>
|
||||||
@@ -5,11 +5,12 @@ description: "Learn how to log in to Infisical via SSO protocols."
|
|||||||
---
|
---
|
||||||
|
|
||||||
<Info>
|
<Info>
|
||||||
Infisical offers Google SSO and GitHub SSO for free across both Infisical Cloud and Infisical Self-hosted.
|
Infisical offers Google SSO and GitHub SSO for free across both Infisical
|
||||||
|
Cloud and Infisical Self-hosted. Infisical also offers SAML SSO authentication
|
||||||
Infisical also offers SAML SSO authentication but as paid features that can be unlocked on Infisical Cloud's **Pro** tier
|
but as paid features that can be unlocked on Infisical Cloud's **Pro** tier or
|
||||||
or via enterprise license on self-hosted instances of Infisical. On this front, we support industry-leading providers including
|
via enterprise license on self-hosted instances of Infisical. On this front,
|
||||||
Okta, Azure AD, and JumpCloud; with any questions, please reach out to [email protected].
|
we support industry-leading providers including Okta, Azure AD, and JumpCloud;
|
||||||
|
with any questions, please reach out to [email protected].
|
||||||
</Info>
|
</Info>
|
||||||
|
|
||||||
You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
|
You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
|
||||||
@@ -31,3 +32,19 @@ Infisical supports these and many other identity providers:
|
|||||||
- [Google SAML](/documentation/platform/sso/google-saml)
|
- [Google SAML](/documentation/platform/sso/google-saml)
|
||||||
|
|
||||||
If your required identity provider is not shown in the list above, please reach out to [[email protected]](mailto:[email protected]) for assistance.
|
If your required identity provider is not shown in the list above, please reach out to [[email protected]](mailto:[email protected]) for assistance.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
<AccordionGroup>
|
||||||
|
<Accordion title="Why does Infisical require additional email verification for users connected via SAML?">
|
||||||
|
By default, Infisical Cloud is configured to not trust emails from external
|
||||||
|
identity providers to prevent any malicious account takeover attempts via
|
||||||
|
email spoofing. Accordingly, Infisical creates a new user for anyone provisioned
|
||||||
|
through an external identity provider and requires an additional email
|
||||||
|
verification step upon their first login.
|
||||||
|
|
||||||
|
If you're running a self-hosted instance of Infisical and would like it to trust emails from external identity providers,
|
||||||
|
you can configure this behavior in the admin panel.
|
||||||
|
|
||||||
|
</Accordion>
|
||||||
|
</AccordionGroup>
|
||||||
|
|||||||
@@ -54,14 +54,6 @@ export const SignupSSO = ({ providerAuthToken }: Props) => {
|
|||||||
providerAuthToken={providerAuthToken}
|
providerAuthToken={providerAuthToken}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
// case 2:
|
|
||||||
// return (
|
|
||||||
// <MergeUsersStep
|
|
||||||
// username={username}
|
|
||||||
// authType={authType}
|
|
||||||
// organizationSlug={organizationSlug}
|
|
||||||
// />
|
|
||||||
// );
|
|
||||||
case 2:
|
case 2:
|
||||||
return (
|
return (
|
||||||
<BackupPDFStep email={username} password={password} name={`${firstName} ${lastName}`} />
|
<BackupPDFStep email={username} password={password} name={`${firstName} ${lastName}`} />
|
||||||
|
|||||||
Reference in New Issue
Block a user