Finish preliminary email validation, merge user flow w saml/ldap

This commit is contained in:
Tuan Dang
2024-04-26 20:19:43 -07:00
parent d0cb06d875
commit 858a35812a
28 changed files with 225 additions and 104 deletions
@@ -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> {}
+1 -1
View File
@@ -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(),
-2
View File
@@ -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;
+1 -1
View File
@@ -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"
}
+16 -17
View File
@@ -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)