diff --git a/src/core/server/errors/index.ts b/src/core/server/errors/index.ts index 3f931a2f6..3646cae67 100644 --- a/src/core/server/errors/index.ts +++ b/src/core/server/errors/index.ts @@ -252,8 +252,8 @@ export class StoryURLInvalidError extends CoralError { } export class DuplicateUserError extends CoralError { - constructor() { - super({ code: ERROR_CODES.DUPLICATE_USER }); + constructor(cause?: Error) { + super({ cause, code: ERROR_CODES.DUPLICATE_USER }); } } diff --git a/src/core/server/models/user/user.ts b/src/core/server/models/user/user.ts index 069737864..2113c5ac6 100644 --- a/src/core/server/models/user/user.ts +++ b/src/core/server/models/user/user.ts @@ -637,7 +637,9 @@ export async function findOrCreateUser( if (err.errmsg && err.errmsg.includes("tenantID_1_email_1")) { throw new DuplicateEmailError(input.email!); } - throw new DuplicateUserError(); + + // Some other error occured. + throw new DuplicateUserError(err); } throw err; @@ -666,7 +668,8 @@ export async function createUser( throw new DuplicateEmailError(input.email!); } - throw new DuplicateUserError(); + // Some other error occured. + throw new DuplicateUserError(err); } throw err; diff --git a/src/core/server/services/users/users.ts b/src/core/server/services/users/users.ts index 871d051e1..1fc181af5 100644 --- a/src/core/server/services/users/users.ts +++ b/src/core/server/services/users/users.ts @@ -138,44 +138,48 @@ export async function findOrCreate( // Validate the input. validateFindOrCreateUserInput(input, options); - let user: Readonly; - let wasUpserted: boolean; - try { - const result = await findOrCreateUser(mongo, tenant.id, input, now); - user = result.user; - wasUpserted = result.wasUpserted; + // Try to find or create the user. + const { user } = await findOrCreateUser(mongo, tenant.id, input, now); + + return user; } catch (err) { + // If this error is related to a duplicate user error, we might have + // encountered a race related to the unique index. We should try once more + // to perform the operation. + if (err instanceof DuplicateUserError) { + // Retry the operation once more, if this operation fails, the error will + // exit this function. + const { user } = await findOrCreateUser(mongo, tenant.id, input, now); + + return user; + } + // If this is an error related to a duplicate email, we might be in a // position where the user can link their accounts. This can only occur if // the tenant has both local and another social profile enabled. if (err instanceof DuplicateEmailError && linkUsersAvailable(tenant)) { // Pull the email address out of the input, and re-try creating the user - // given that. + // given that. We need to pull the verified property out because we don't + // want to have that embedded in the `...rest` object. const { email, emailVerified, ...rest } = input; // Create the user again this time, but associate the duplicate email to // the user account. - const result = await findOrCreateUser( + const { user } = await findOrCreateUser( mongo, tenant.id, { ...rest, duplicateEmail: email }, now ); - user = result.user; - wasUpserted = result.wasUpserted; - } else { - throw err; + return user; } - } - if (wasUpserted) { - // TODO: (wyattjoh) emit that a user was created + // The error wasn't related to a duplicate user or duplicate email error, + // so just re-throw that error again. + throw err; } - - // TODO: (wyattjoh) evaluate the tenant to determine if we should send the verification email. - return user; } export type CreateUser = FindOrCreateUserInput;