diff --git a/backend/src/ee/routes/v1/saml-router.ts b/backend/src/ee/routes/v1/saml-router.ts index 6cae30f7a..4f5bebec8 100644 --- a/backend/src/ee/routes/v1/saml-router.ts +++ b/backend/src/ee/routes/v1/saml-router.ts @@ -99,6 +99,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { // eslint-disable-next-line async (req, profile, cb) => { try { + console.log("saml login profile: ", 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 @@ -107,6 +108,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => { } const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({ + externalId: profile.nameID, username: profile.nameID ?? email, email, firstName: profile.firstName as string, 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 85c537684..c144a42cd 100644 --- a/backend/src/ee/services/ldap-config/ldap-config-service.ts +++ b/backend/src/ee/services/ldap-config/ldap-config-service.ts @@ -1,7 +1,13 @@ import { ForbiddenError } from "@casl/ability"; import jwt from "jsonwebtoken"; -import { OrgMembershipRole, OrgMembershipStatus, SecretKeyEncoding, TLdapConfigsUpdate } from "@app/db/schemas"; +import { + OrgMembershipRole, + OrgMembershipStatus, + SecretKeyEncoding, + TableName, + TLdapConfigsUpdate +} from "@app/db/schemas"; import { TGroupDALFactory } from "@app/ee/services/group/group-dal"; import { addUsersToGroupByUserIds, removeUsersFromGroupByUserIds } from "@app/ee/services/group/group-fns"; import { TUserGroupMembershipDALFactory } from "@app/ee/services/group/user-group-membership-dal"; @@ -25,6 +31,7 @@ import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal import { TUserDALFactory } from "@app/services/user/user-dal"; import { normalizeUsername } from "@app/services/user/user-fns"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; +import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -388,7 +395,7 @@ export const ldapConfigServiceFactory = ({ let userAlias = await userAliasDAL.findOne({ externalId, orgId, - aliasType: AuthMethod.LDAP + aliasType: UserAliasType.LDAP }); const organization = await orgDAL.findOrgById(orgId); @@ -396,7 +403,13 @@ export const ldapConfigServiceFactory = ({ if (userAlias) { await userDAL.transaction(async (tx) => { - const [orgMembership] = await orgDAL.findMembership({ userId: userAlias.userId }, { tx }); + const [orgMembership] = await orgDAL.findMembership( + { + userId: userAlias.userId, + [`${TableName.OrgMembership}.orgId` as "id"]: orgId + }, + { tx } + ); if (!orgMembership) { await orgDAL.createMembership( { @@ -426,7 +439,7 @@ export const ldapConfigServiceFactory = ({ email: emails[0], firstName, lastName, - authMethods: [AuthMethod.LDAP], + authMethods: [AuthMethod.LDAP], // should this be empty? isGhost: false }, tx @@ -435,7 +448,7 @@ export const ldapConfigServiceFactory = ({ { userId: newUser.id, username, - aliasType: AuthMethod.LDAP, + aliasType: UserAliasType.LDAP, externalId, emails, orgId diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index 6f9c3ce6f..9a2de2f00 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -7,7 +7,8 @@ import { SecretKeyEncoding, TableName, TSamlConfigs, - TSamlConfigsUpdate + TSamlConfigsUpdate, + TUsers } from "@app/db/schemas"; import { getConfig } from "@app/lib/config/env"; import { @@ -19,11 +20,13 @@ import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; -import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; +import { AuthTokenType } from "@app/services/auth/auth-type"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TUserDALFactory } from "@app/services/user/user-dal"; +import { normalizeUsername } from "@app/services/user/user-fns"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; +import { UserAliasType } from "@app/services/user-alias/user-alias-types"; import { TLicenseServiceFactory } from "../license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission"; @@ -33,7 +36,7 @@ import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } f type TSamlConfigServiceFactoryDep = { samlConfigDAL: TSamlConfigDALFactory; - userDAL: Pick; + userDAL: Pick; userAliasDAL: Pick; orgDAL: Pick< TOrgDALFactory, @@ -51,6 +54,7 @@ export const samlConfigServiceFactory = ({ orgBotDAL, orgDAL, userDAL, + userAliasDAL, permissionService, licenseService }: TSamlConfigServiceFactoryDep) => { @@ -307,7 +311,8 @@ export const samlConfigServiceFactory = ({ }; const samlLogin = async ({ - username, + externalId, + username, // what to do about this? email, firstName, lastName, @@ -315,22 +320,36 @@ export const samlConfigServiceFactory = ({ orgId, relayState }: TSamlLoginDTO) => { + console.log("samlLogin args: ", { + externalId, + username, + email, + firstName, + lastName, + authProvider, + orgId, + relayState + }); const appCfg = getConfig(); - let user = await userDAL.findOne({ username }); + const userAlias = await userAliasDAL.findOne({ + externalId, + orgId, + aliasType: UserAliasType.SAML + }); + + console.log("found userAlias: ", userAlias); const organization = await orgDAL.findOrgById(orgId); if (!organization) throw new BadRequestError({ message: "Org not found" }); - // TODO(dangtony98): remove this after aliases update - if (authProvider === AuthMethod.KEYCLOAK_SAML && appCfg.LICENSE_SERVER_KEY) { - throw new BadRequestError({ message: "Keycloak SAML is not yet available on Infisical Cloud" }); - } - - if (user) { - await userDAL.transaction(async (tx) => { + let user: TUsers; + if (userAlias) { + console.log("samlLogin A"); + user = await userDAL.transaction(async (tx) => { + const foundUser = await userDAL.findById(userAlias.userId, tx); const [orgMembership] = await orgDAL.findMembership( { - userId: user.id, + userId: foundUser.id, [`${TableName.OrgMembership}.orgId` as "id"]: orgId }, { tx } @@ -338,11 +357,10 @@ export const samlConfigServiceFactory = ({ if (!orgMembership) { await orgDAL.createMembership( { - userId: user.id, + userId: userAlias.userId, orgId, - inviteEmail: email, 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 + status: foundUser.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 ); @@ -356,30 +374,53 @@ export const samlConfigServiceFactory = ({ tx ); } + + return foundUser; }); } else { + console.log("samlLogin B"); user = await userDAL.transaction(async (tx) => { + const uniqueUsername = await normalizeUsername(username, userDAL); const newUser = await userDAL.create( { - username, + username: uniqueUsername, email, isEmailVerified: false, firstName, lastName, - authMethods: [AuthMethod.EMAIL], + authMethods: [], isGhost: false }, tx ); - await orgDAL.createMembership({ - inviteEmail: email, - orgId, - role: OrgMembershipRole.Member, - status: OrgMembershipStatus.Invited - }); + await userAliasDAL.create( + { + userId: newUser.id, + username, + aliasType: UserAliasType.SAML, + externalId, + emails: email ? [email] : [], + orgId + }, + tx + ); + + await orgDAL.createMembership( + // note: this creates a duplicate membership atm + { + userId: newUser.id, + orgId, + role: OrgMembershipRole.Member, + status: OrgMembershipStatus.Invited + }, + tx + ); + return newUser; }); } + console.log("samlLogin C"); + const isUserCompleted = Boolean(user.isAccepted); const providerAuthToken = jwt.sign( { diff --git a/backend/src/ee/services/saml-config/saml-config-types.ts b/backend/src/ee/services/saml-config/saml-config-types.ts index df7694920..e7c1a5674 100644 --- a/backend/src/ee/services/saml-config/saml-config-types.ts +++ b/backend/src/ee/services/saml-config/saml-config-types.ts @@ -45,6 +45,7 @@ export type TGetSamlCfgDTO = }; export type TSamlLoginDTO = { + externalId: string; username: string; email?: string; firstName: string; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 70ba31ac8..9f63d416a 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -260,6 +260,7 @@ export const registerRoutes = async ( orgBotDAL, orgDAL, userDAL, + userAliasDAL, samlConfigDAL, licenseService }); diff --git a/backend/src/services/user-alias/user-alias-types.ts b/backend/src/services/user-alias/user-alias-types.ts index e69de29bb..09204644f 100644 --- a/backend/src/services/user-alias/user-alias-types.ts +++ b/backend/src/services/user-alias/user-alias-types.ts @@ -0,0 +1,4 @@ +export enum UserAliasType { + LDAP = "ldap", + SAML = "saml" +}