mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 06:29:04 +00:00
Finish preliminary email validation, merge user flow w saml/ldap
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.UserAliases, (t) => {
|
||||
t.string("username").nullable().alter();
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(): Promise<void> {}
|
||||
@@ -10,7 +10,7 @@ import { TImmutableDBKeys } from "./models";
|
||||
export const UserAliasesSchema = z.object({
|
||||
id: z.string().uuid(),
|
||||
userId: z.string().uuid(),
|
||||
username: z.string(),
|
||||
username: z.string().nullable().optional(),
|
||||
aliasType: z.string(),
|
||||
externalId: z.string(),
|
||||
emails: z.string().array().nullable().optional(),
|
||||
|
||||
@@ -99,7 +99,6 @@ 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
|
||||
|
||||
@@ -109,7 +108,6 @@ 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,
|
||||
lastName: profile.lastName as string,
|
||||
|
||||
@@ -31,7 +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 { TUserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@@ -395,7 +395,7 @@ export const ldapConfigServiceFactory = ({
|
||||
let userAlias = await userAliasDAL.findOne({
|
||||
externalId,
|
||||
orgId,
|
||||
aliasType: UserAliasType.LDAP
|
||||
aliasType: TUserAliasType.LDAP
|
||||
});
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
@@ -437,9 +437,10 @@ export const ldapConfigServiceFactory = ({
|
||||
{
|
||||
username: uniqueUsername,
|
||||
email: emails[0],
|
||||
isEmailVerified: false,
|
||||
firstName,
|
||||
lastName,
|
||||
authMethods: [AuthMethod.LDAP], // should this be empty?
|
||||
authMethods: [],
|
||||
isGhost: false
|
||||
},
|
||||
tx
|
||||
@@ -448,7 +449,7 @@ export const ldapConfigServiceFactory = ({
|
||||
{
|
||||
userId: newUser.id,
|
||||
username,
|
||||
aliasType: UserAliasType.LDAP,
|
||||
aliasType: TUserAliasType.LDAP,
|
||||
externalId,
|
||||
emails,
|
||||
orgId
|
||||
@@ -556,11 +557,14 @@ export const ldapConfigServiceFactory = ({
|
||||
authTokenType: AuthTokenType.PROVIDER_TOKEN,
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
...(user.email && { email: user.email }),
|
||||
firstName,
|
||||
lastName,
|
||||
organizationName: organization.name,
|
||||
organizationId: organization.id,
|
||||
organizationSlug: organization.slug,
|
||||
authMethod: AuthMethod.LDAP,
|
||||
authType: TUserAliasType.LDAP,
|
||||
isUserCompleted,
|
||||
...(relayState
|
||||
? {
|
||||
|
||||
@@ -23,10 +23,11 @@ import { BadRequestError } from "@app/lib/errors";
|
||||
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 { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-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 { TUserAliasType } from "@app/services/user-alias/user-alias-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@@ -35,13 +36,14 @@ import { TSamlConfigDALFactory } from "./saml-config-dal";
|
||||
import { TCreateSamlCfgDTO, TGetSamlCfgDTO, TSamlLoginDTO, TUpdateSamlCfgDTO } from "./saml-config-types";
|
||||
|
||||
type TSamlConfigServiceFactoryDep = {
|
||||
samlConfigDAL: TSamlConfigDALFactory;
|
||||
samlConfigDAL: TSamlConfigDALFactory; // TODO: Pick
|
||||
userDAL: Pick<TUserDALFactory, "create" | "findOne" | "transaction" | "updateById" | "findById">;
|
||||
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
|
||||
orgDAL: Pick<
|
||||
TOrgDALFactory,
|
||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||
>;
|
||||
orgMembershipDAL: TOrgMembershipDALFactory; // TODO: Pick
|
||||
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
@@ -53,6 +55,7 @@ export const samlConfigServiceFactory = ({
|
||||
samlConfigDAL,
|
||||
orgBotDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
permissionService,
|
||||
@@ -312,7 +315,6 @@ export const samlConfigServiceFactory = ({
|
||||
|
||||
const samlLogin = async ({
|
||||
externalId,
|
||||
username, // what to do about this?
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
@@ -320,31 +322,18 @@ export const samlConfigServiceFactory = ({
|
||||
orgId,
|
||||
relayState
|
||||
}: TSamlLoginDTO) => {
|
||||
console.log("samlLogin args: ", {
|
||||
externalId,
|
||||
username,
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
authProvider,
|
||||
orgId,
|
||||
relayState
|
||||
});
|
||||
const appCfg = getConfig();
|
||||
const userAlias = await userAliasDAL.findOne({
|
||||
externalId,
|
||||
orgId,
|
||||
aliasType: UserAliasType.SAML
|
||||
aliasType: TUserAliasType.SAML
|
||||
});
|
||||
|
||||
console.log("found userAlias: ", userAlias);
|
||||
|
||||
const organization = await orgDAL.findOrgById(orgId);
|
||||
if (!organization) throw new BadRequestError({ message: "Org not found" });
|
||||
|
||||
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(
|
||||
@@ -355,9 +344,10 @@ export const samlConfigServiceFactory = ({
|
||||
{ tx }
|
||||
);
|
||||
if (!orgMembership) {
|
||||
await orgDAL.createMembership(
|
||||
await orgMembershipDAL.create(
|
||||
{
|
||||
userId: userAlias.userId,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role: OrgMembershipRole.Member,
|
||||
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
|
||||
@@ -365,7 +355,7 @@ export const samlConfigServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
// Only update the membership to Accepted if the user account is already completed.
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && user.isAccepted) {
|
||||
} else if (orgMembership.status === OrgMembershipStatus.Invited && foundUser.isAccepted) {
|
||||
await orgDAL.updateMembershipById(
|
||||
orgMembership.id,
|
||||
{
|
||||
@@ -378,9 +368,8 @@ export const samlConfigServiceFactory = ({
|
||||
return foundUser;
|
||||
});
|
||||
} else {
|
||||
console.log("samlLogin B");
|
||||
user = await userDAL.transaction(async (tx) => {
|
||||
const uniqueUsername = await normalizeUsername(username, userDAL);
|
||||
const uniqueUsername = await normalizeUsername(externalId, userDAL);
|
||||
const newUser = await userDAL.create(
|
||||
{
|
||||
username: uniqueUsername,
|
||||
@@ -396,8 +385,7 @@ export const samlConfigServiceFactory = ({
|
||||
await userAliasDAL.create(
|
||||
{
|
||||
userId: newUser.id,
|
||||
username,
|
||||
aliasType: UserAliasType.SAML,
|
||||
aliasType: TUserAliasType.SAML,
|
||||
externalId,
|
||||
emails: email ? [email] : [],
|
||||
orgId
|
||||
@@ -405,10 +393,10 @@ export const samlConfigServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
|
||||
await orgDAL.createMembership(
|
||||
// note: this creates a duplicate membership atm
|
||||
await orgMembershipDAL.create(
|
||||
{
|
||||
userId: newUser.id,
|
||||
inviteEmail: email,
|
||||
orgId,
|
||||
role: OrgMembershipRole.Member,
|
||||
status: OrgMembershipStatus.Invited
|
||||
@@ -419,7 +407,6 @@ export const samlConfigServiceFactory = ({
|
||||
return newUser;
|
||||
});
|
||||
}
|
||||
console.log("samlLogin C");
|
||||
|
||||
const isUserCompleted = Boolean(user.isAccepted);
|
||||
const providerAuthToken = jwt.sign(
|
||||
@@ -432,7 +419,9 @@ export const samlConfigServiceFactory = ({
|
||||
lastName,
|
||||
organizationName: organization.name,
|
||||
organizationId: organization.id,
|
||||
organizationSlug: organization.slug,
|
||||
authMethod: authProvider,
|
||||
authType: TUserAliasType.SAML,
|
||||
isUserCompleted,
|
||||
...(relayState
|
||||
? {
|
||||
|
||||
@@ -46,7 +46,6 @@ export type TGetSamlCfgDTO =
|
||||
|
||||
export type TSamlLoginDTO = {
|
||||
externalId: string;
|
||||
username: string;
|
||||
email?: string;
|
||||
firstName: string;
|
||||
lastName?: string;
|
||||
|
||||
@@ -259,6 +259,7 @@ export const registerRoutes = async (
|
||||
permissionService,
|
||||
orgBotDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
samlConfigDAL,
|
||||
@@ -334,7 +335,6 @@ export const registerRoutes = async (
|
||||
const userService = userServiceFactory({
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
orgDAL,
|
||||
orgMembershipDAL,
|
||||
tokenService,
|
||||
smtpService
|
||||
|
||||
@@ -135,6 +135,11 @@ export const authSignupServiceFactory = ({
|
||||
userAgent,
|
||||
authorization
|
||||
}: TCompleteAccountSignupDTO) => {
|
||||
console.log("completeEmailAccountSignup args: ", {
|
||||
email,
|
||||
firstName,
|
||||
lastName
|
||||
});
|
||||
const user = await userDAL.findOne({ username: email });
|
||||
if (!user || (user && user.isAccepted)) {
|
||||
throw new Error("Failed to complete account for complete user");
|
||||
@@ -169,9 +174,8 @@ export const authSignupServiceFactory = ({
|
||||
tx
|
||||
);
|
||||
// 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) && organizationId) {
|
||||
if ((isAuthMethodSaml(authMethod) || authMethod === AuthMethod.LDAP) && organizationId) {
|
||||
const [pendingOrgMembership] = await orgDAL.findMembership({
|
||||
inviteEmail: email,
|
||||
userId: user.id,
|
||||
status: OrgMembershipStatus.Invited,
|
||||
orgId: organizationId
|
||||
|
||||
@@ -102,7 +102,8 @@ export const superAdminServiceFactory = ({
|
||||
superAdmin: true,
|
||||
isGhost: false,
|
||||
isAccepted: true,
|
||||
authMethods: [AuthMethod.EMAIL]
|
||||
authMethods: [AuthMethod.EMAIL],
|
||||
isEmailVerified: true
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export enum UserAliasType {
|
||||
export enum TUserAliasType {
|
||||
LDAP = "ldap",
|
||||
SAML = "saml"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
|
||||
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||
@@ -9,14 +8,23 @@ import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
|
||||
import { AuthMethod } from "../auth/auth-type";
|
||||
import { TUserDALFactory } from "./user-dal";
|
||||
|
||||
// TODO: Pick all of these
|
||||
type TUserServiceFactoryDep = {
|
||||
userDAL: TUserDALFactory;
|
||||
userAliasDAL: TUserAliasDALFactory;
|
||||
orgDAL: TOrgDALFactory;
|
||||
orgMembershipDAL: TOrgMembershipDALFactory;
|
||||
tokenService: TAuthTokenServiceFactory;
|
||||
smtpService: TSmtpService;
|
||||
userDAL: Pick<
|
||||
TUserDALFactory,
|
||||
| "find"
|
||||
| "findOne"
|
||||
| "findById"
|
||||
| "transaction"
|
||||
| "updateById"
|
||||
| "deleteById"
|
||||
| "findOneUserAction"
|
||||
| "createUserAction"
|
||||
| "findUserEncKeyByUserId"
|
||||
>;
|
||||
userAliasDAL: Pick<TUserAliasDALFactory, "find" | "insertMany">;
|
||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "find" | "insertMany">;
|
||||
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">;
|
||||
smtpService: Pick<TSmtpService, "sendMail">;
|
||||
};
|
||||
|
||||
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
|
||||
@@ -24,13 +32,11 @@ export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
|
||||
export const userServiceFactory = ({
|
||||
userDAL,
|
||||
userAliasDAL,
|
||||
// orgDAL,
|
||||
orgMembershipDAL,
|
||||
tokenService,
|
||||
smtpService
|
||||
}: TUserServiceFactoryDep) => {
|
||||
const sendEmailVerificationCode = async (userId: string) => {
|
||||
console.log("sendEmailVerificationCode userId: ", userId);
|
||||
const user = await userDAL.findById(userId);
|
||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||
if (!user.email)
|
||||
@@ -38,13 +44,11 @@ export const userServiceFactory = ({
|
||||
if (user.isEmailVerified)
|
||||
throw new BadRequestError({ name: "Failed to send email verification code due to email already verified" });
|
||||
|
||||
console.log("sendEmailVerificationCode user: ", user);
|
||||
const token = await tokenService.createTokenForUser({
|
||||
type: TokenType.TOKEN_EMAIL_VERIFICATION,
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
console.log("sendEmailVerificationCode 2");
|
||||
await smtpService.sendMail({
|
||||
template: SmtpTemplates.EmailVerification,
|
||||
subjectLine: "Infisical confirmation code",
|
||||
@@ -56,11 +60,6 @@ export const userServiceFactory = ({
|
||||
};
|
||||
|
||||
const verifyEmailVerificationCode = async (userId: string, code: string) => {
|
||||
console.log("verifyEmailVerificationCode args: ", {
|
||||
userId,
|
||||
code
|
||||
});
|
||||
|
||||
const user = await userDAL.findById(userId);
|
||||
if (!user) throw new BadRequestError({ name: "Failed to find user" });
|
||||
if (user.isEmailVerified)
|
||||
|
||||
Reference in New Issue
Block a user