diff --git a/docs/_config.yml b/docs/_config.yml index 6834fe8ced..395aaca44c 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -213,6 +213,7 @@ redirects: /#/full/accounts-setusername: 'api/passwords.html#accounts-setusername' /#/full/accounts-addemail: 'api/passwords.html#accounts-addemail' /#/full/accounts-removeemail: 'api/passwords.html#accounts-removeemail' + /#/full/accounts_replaceemail: 'api/passwords.html#Accounts-replaceEmail' /#/full/accounts_verifyemail: 'api/passwords.html#Accounts-verifyEmail' /#/full/accounts-finduserbyusername: 'api/passwords.html#accounts-finduserbyusername' /#/full/accounts-finduserbyemail: 'api/passwords.html#accounts-finduserbyemail' diff --git a/docs/generators/changelog/versions/3.0.0.md b/docs/generators/changelog/versions/3.0.0.md index c5fa1f1a1e..4c863db6ad 100644 --- a/docs/generators/changelog/versions/3.0.0.md +++ b/docs/generators/changelog/versions/3.0.0.md @@ -55,6 +55,7 @@ - `Accounts.sendVerificationEmail` - `Accounts.addEmail` - `Accounts.removeEmail` + - `Accounts.replaceEmailAsync` - `Accounts.verifyEmail` - `Accounts.createUserVerifyingEmail` - `Accounts.createUser` diff --git a/docs/source/api/passwords.md b/docs/source/api/passwords.md index bb0ddf6b17..908617aa3e 100644 --- a/docs/source/api/passwords.md +++ b/docs/source/api/passwords.md @@ -59,6 +59,8 @@ By default, an email address is added with `{ verified: false }`. Use [`Accounts.sendVerificationEmail`](#Accounts-sendVerificationEmail) to send an email with a link the user can use to verify their email address. +{% apibox "Accounts.replaceEmailAsync" %} + {% apibox "Accounts.removeEmail" %} {% apibox "Accounts.verifyEmail" %} diff --git a/packages/accounts-base/accounts-base.d.ts b/packages/accounts-base/accounts-base.d.ts index 7fbbfde0a6..b11344ce08 100644 --- a/packages/accounts-base/accounts-base.d.ts +++ b/packages/accounts-base/accounts-base.d.ts @@ -188,6 +188,8 @@ export namespace Accounts { function removeEmail(userId: string, email: string): Promise; + function replaceEmailAsync(userId: string, oldEmail: string, newEmail: string, verified?: boolean): Promise; + function onCreateUser( func: (options: { profile?: {} | undefined }, user: Meteor.User) => void ): void; diff --git a/packages/accounts-password/password_server.js b/packages/accounts-password/password_server.js index 455c071cc3..6477bdcc3d 100644 --- a/packages/accounts-password/password_server.js +++ b/packages/accounts-password/password_server.js @@ -1022,6 +1022,52 @@ Meteor.methods( } }); + +/** + * @summary Asynchronously replace an email address for a user. Use this instead of directly + * updating the database. The operation will fail if there is a different user + * with an email only differing in case. If the specified user has an existing + * email only differing in case however, we replace it. + * @locus Server + * @param {String} userId The ID of the user to update. + * @param {String} oldEmail The email address to replace. + * @param {String} newEmail The new email address to use. + * @param {Boolean} [verified] Optional - whether the new email address should + * be marked as verified. Defaults to false. + * @importFromPackage accounts-base + */ +Accounts.replaceEmailAsync = async (userId, oldEmail, newEmail, verified) => { + check(userId, NonEmptyString); + check(oldEmail, NonEmptyString); + check(newEmail, NonEmptyString); + check(verified, Match.Optional(Boolean)); + + if (verified === void 0) { + verified = false; + } + + const user = await getUserById(userId, { fields: { _id: 1 } }); + if (!user) + throw new Meteor.Error(403, "User not found"); + + // Ensure no user already has this new email + await Accounts._checkForCaseInsensitiveDuplicates( + "emails.address", + "Email", + newEmail, + user._id + ); + + const result = await Meteor.users.updateAsync( + { _id: user._id, 'emails.address': oldEmail }, + { $set: { 'emails.$.address': newEmail, 'emails.$.verified': verified } } + ); + + if (result.modifiedCount === 0) { + throw new Meteor.Error(404, "No user could be found with old email"); + } +}; + /** * @summary Asynchronously add an email address for a user. Use this instead of directly * updating the database. The operation will fail if there is a different user diff --git a/packages/accounts-password/password_tests.js b/packages/accounts-password/password_tests.js index e488c9a89e..f34e172dc7 100644 --- a/packages/accounts-password/password_tests.js +++ b/packages/accounts-password/password_tests.js @@ -1789,7 +1789,30 @@ if (Meteor.isServer) (() => { ]); }); - Tinytest.addAsync("passwords - remove email", + + +Tinytest.addAsync("accounts emails - replace email", async test => { + const origEmail = `originalemail@test.com`; + const userId = await Accounts.createUserAsync({ + email: origEmail, + password: 'password' + }); + + const newEmail = `newemail@test.com`; + + const u1 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u1.emails, [ + { address: origEmail, verified: false } + ]); + + await Accounts.replaceEmailAsync(userId, origEmail, newEmail); + const u2 = await Accounts._findUserByQuery({ id: userId }) + test.equal(u2.emails, [ + { address: newEmail, verified: false } + ]); +}) + + Tinytest.addAsync("passwords - remove email", async test => { const origEmail = `${ Random.id() }@turing.com`; const userId = await Accounts.createUser({ diff --git a/v3-docs/docs/api/accounts.md b/v3-docs/docs/api/accounts.md index 2ac9317275..a40c6ef496 100644 --- a/v3-docs/docs/api/accounts.md +++ b/v3-docs/docs/api/accounts.md @@ -888,6 +888,8 @@ email with a link the user can use to verify their email address. + + If the user trying to verify the email has 2FA enabled, this error will be thrown: diff --git a/v3-docs/docs/generators/changelog/versions/3.0.0.md b/v3-docs/docs/generators/changelog/versions/3.0.0.md index 15008c98fe..202729af5d 100644 --- a/v3-docs/docs/generators/changelog/versions/3.0.0.md +++ b/v3-docs/docs/generators/changelog/versions/3.0.0.md @@ -58,6 +58,7 @@ - `Accounts.sendVerificationEmail` - `Accounts.addEmail` - `Accounts.removeEmail` + - `Accounts.replaceEmailAsync` - `Accounts.verifyEmail` - `Accounts.createUserVerifyingEmail` - `Accounts.createUser`