From 7bda967e2d3266a547c18a0396e6f2b0275526e8 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Tue, 29 Nov 2022 13:29:24 -0300 Subject: [PATCH 01/24] wip: started working on accounts base --- packages/accounts-base/accounts_client.js | 21 +++++----- packages/accounts-base/accounts_server.js | 49 ++++++++++++----------- packages/accounts-base/server_main.js | 5 ++- 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/packages/accounts-base/accounts_client.js b/packages/accounts-base/accounts_client.js index 842e927ad9..6f3314ad04 100644 --- a/packages/accounts-base/accounts_client.js +++ b/packages/accounts-base/accounts_client.js @@ -119,18 +119,21 @@ export class AccountsClient extends AccountsCommon { */ logout(callback) { this._loggingOut.set(true); - this.connection.apply('logout', [], { + + this.connection.applyAsync('logout', [], { + // TODO[FIBERS]: Look this { wait: true } later. wait: true - }, (error, result) => { - this._loggingOut.set(false); - this._loginCallbacksCalled = false; - if (error) { - callback && callback(error); - } else { + }) + .then((result) => { + this._loggingOut.set(false); + this._loginCallbacksCalled = false; this.makeClientLoggedOut(); callback && callback(); - } - }); + }) + .catch((e) => { + this._loggingOut.set(false); + callback && callback(e); + }); } /** diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 2fd0a6d41b..2ad5a2cb83 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -14,6 +14,7 @@ const NonEmptyString = Match.Where(x => { return x.length > 0; }); + /** * @summary Constructor for the `Accounts` namespace on the server. * @locus Server @@ -71,8 +72,6 @@ export class AccountsServer extends AccountsCommon { // list of all registered handlers. this._loginHandlers = []; - - setupUsersCollection(this.users); setupDefaultLoginHandlers(this); setExpireTokensInterval(this); @@ -126,6 +125,10 @@ export class AccountsServer extends AccountsCommon { return currentInvocation.userId; } + async init() { + await setupUsersCollection(this.users); + } + /// /// LOGIN HOOKS /// @@ -259,11 +262,11 @@ export class AccountsServer extends AccountsCommon { }); }; - _successfulLogout(connection, userId) { + async _successfulLogout(connection, userId) { // don't fetch the user object unless there are some callbacks registered let user; - this._onLogoutHook.each(callback => { - if (!user && userId) user = this.users.findOne(userId, {fields: this._options.defaultFieldSelector}); + await this._onLogoutHook.forEachAsync(async callback => { + if (!user && userId) user = await this.users.findOne(userId, { fields: this._options.defaultFieldSelector }); callback({ user, connection }); return true; }); @@ -615,8 +618,8 @@ export class AccountsServer extends AccountsCommon { // Any connections associated with old-style unhashed tokens will be // in the process of becoming associated with hashed tokens and then // they'll get closed. - destroyToken(userId, loginToken) { - this.users.update(userId, { + async destroyToken(userId, loginToken) { + await this.users.update(userId, { $pull: { "services.resume.loginTokens": { $or: [ @@ -653,13 +656,13 @@ export class AccountsServer extends AccountsCommon { return await accounts._attemptLogin(this, "login", arguments, result); }; - methods.logout = function () { + methods.logout = async function () { const token = accounts._getLoginToken(this.connection.id); accounts._setLoginToken(this.userId, this.connection, null); if (token && this.userId) { - accounts.destroyToken(this.userId, token); + await accounts.destroyToken(this.userId, token); } - accounts._successfulLogout(this.connection, this.userId); + await accounts._successfulLogout(this.connection, this.userId); this.setUserId(null); }; @@ -671,8 +674,8 @@ export class AccountsServer extends AccountsCommon { // @returns Object // If successful, returns { token: , id: , // tokenExpires: }. - methods.getNewToken = function () { - const user = accounts.users.findOne(this.userId, { + methods.getNewToken = async function () { + const user = await accounts.users.findOne(this.userId, { fields: { "services.resume.loginTokens": 1 } }); if (! this.userId || ! user) { @@ -972,7 +975,7 @@ export class AccountsServer extends AccountsCommon { // already -- in this case we just clean up the observe that we started). const myObserveNumber = ++this._nextUserObserveNumber; this._userObservesForConnections[connection.id] = myObserveNumber; - Meteor.defer(() => { + Meteor.defer(async () => { // If something else happened on this connection in the meantime (it got // closed, or another call to _setLoginToken happened), just do // nothing. We don't need to start an observe for an old connection or old @@ -985,7 +988,7 @@ export class AccountsServer extends AccountsCommon { // Because we upgrade unhashed login tokens to hashed tokens at // login time, sessions will only be logged in with a hashed // token. Thus we only need to observe hashed tokens here. - const observe = this.users.find({ + const observe = await this.users.find({ _id: userId, 'services.resume.loginTokens.hashedToken': newToken }, { fields: { _id: 1 } }).observeChanges({ @@ -1747,7 +1750,7 @@ function defaultValidateNewUserHook(user) { } } -const setupUsersCollection = users => { +const setupUsersCollection = async users => { /// /// RESTRICTING WRITES TO USER OBJECTS /// @@ -1773,21 +1776,21 @@ const setupUsersCollection = users => { }); /// DEFAULT INDEXES ON USERS - users.createIndex('username', { unique: true, sparse: true }); - users.createIndex('emails.address', { unique: true, sparse: true }); - users.createIndex('services.resume.loginTokens.hashedToken', + await users.createIndex('username', { unique: true, sparse: true }); + await users.createIndex('emails.address', { unique: true, sparse: true }); + await users.createIndex('services.resume.loginTokens.hashedToken', { unique: true, sparse: true }); - users.createIndex('services.resume.loginTokens.token', + await users.createIndex('services.resume.loginTokens.token', { unique: true, sparse: true }); // For taking care of logoutOtherClients calls that crashed before the // tokens were deleted. - users.createIndex('services.resume.haveLoginTokensToDelete', + await users.createIndex('services.resume.haveLoginTokensToDelete', { sparse: true }); // For expiring login tokens - users.createIndex("services.resume.loginTokens.when", { sparse: true }); + await users.createIndex("services.resume.loginTokens.when", { sparse: true }); // For expiring password tokens - users.createIndex('services.password.reset.when', { sparse: true }); - users.createIndex('services.password.enroll.when', { sparse: true }); + await users.createIndex('services.password.reset.when', { sparse: true }); + await users.createIndex('services.password.enroll.when', { sparse: true }); }; diff --git a/packages/accounts-base/server_main.js b/packages/accounts-base/server_main.js index db5020fed5..05ec621117 100644 --- a/packages/accounts-base/server_main.js +++ b/packages/accounts-base/server_main.js @@ -5,7 +5,8 @@ import { AccountsServer } from "./accounts_server.js"; * @summary The namespace for all server-side accounts-related methods. */ Accounts = new AccountsServer(Meteor.server); - +// TODO[FIBERS]: I need TLA +Accounts.init().then() // Users table. Don't use the normal autopublish, since we want to hide // some fields. Code to autopublish this is in accounts_server.js. // XXX Allow users to configure this collection name. @@ -15,7 +16,7 @@ Accounts = new AccountsServer(Meteor.server); * @locus Anywhere * @type {Mongo.Collection} * @importFromPackage meteor -*/ + */ Meteor.users = Accounts.users; export { From e89ecdda323a7532fb0e2d3b46ad2682ed9814d6 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:45:43 -0300 Subject: [PATCH 02/24] tests: removed other tests --- packages/accounts-base/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index b707f589a2..9c89bb5fd5 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -63,5 +63,5 @@ Package.onTest(api => { api.addFiles('accounts_tests_setup.js', 'server'); api.mainModule('server_tests.js', 'server'); - api.mainModule('client_tests.js', 'client'); + // api.mainModule('client_tests.js', 'client'); }); From 2333bc35a3acd208715a59b9fb2390b921db8a4b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:45:58 -0300 Subject: [PATCH 03/24] tests: made setup async --- packages/accounts-base/accounts_tests_setup.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index bd79562fe0..31ed7e2196 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -1,25 +1,25 @@ -const getTokenFromSecret = ({ selector, secret: secretParam }) => { +const getTokenFromSecret = async ({ selector, secret: secretParam }) => { let secret = secretParam; if (!secret) { const { services: { twoFactorAuthentication } = {} } = - Meteor.users.findOne(selector) || {}; + await Meteor.users.findOne(selector) || {}; if (!twoFactorAuthentication) { throw new Meteor.Error(500, 'twoFactorAuthentication not set.'); } secret = twoFactorAuthentication.secret; } - const { token } = Accounts._generate2faToken(secret); + const { token } = Accounts._generate2faToken(secret); return token; }; Meteor.methods({ - removeAccountsTestUser(username) { - Meteor.users.remove({ username }); + async removeAccountsTestUser(username) { + await Meteor.users.remove({ username }); }, - forceEnableUser2fa(selector, secret) { - Meteor.users.update( + async forceEnableUser2fa(selector, secret) { + await Meteor.users.update( selector, { $set: { From ba98f5fe31107652fce5e7eb9bcde5094e41830b Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:46:10 -0300 Subject: [PATCH 04/24] test: made all tests async --- packages/accounts-base/accounts_tests.js | 58 ++++++++++++------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 797bd758f0..266c6a0bd0 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -17,14 +17,14 @@ Tinytest.add( test => test.throws(() => Accounts.config({foo: "bar"})) ); -Tinytest.add('accounts - config - token lifetime', test => { +Tinytest.addAsync('accounts - config - token lifetime', async test => { const { loginExpirationInDays } = Accounts._options; Accounts._options.loginExpirationInDays = 2; test.equal(Accounts._getTokenLifetimeMs(), 2 * 24 * 60 * 60 * 1000); Accounts._options.loginExpirationInDays = loginExpirationInDays; }); -Tinytest.add('accounts - config - unexpiring tokens', test => { +Tinytest.addAsync('accounts - config - unexpiring tokens', async test => { const { loginExpirationInDays } = Accounts._options; // When setting loginExpirationInDays to null in the global Accounts @@ -52,7 +52,7 @@ Tinytest.add('accounts - config - unexpiring tokens', test => { Accounts._options.loginExpirationInDays = loginExpirationInDays; }); -Tinytest.add('accounts - config - default token lifetime', test => { +Tinytest.addAsync('accounts - config - default token lifetime', async test => { const options = Accounts._options; Accounts._options = {}; test.equal( @@ -62,7 +62,7 @@ Tinytest.add('accounts - config - default token lifetime', test => { Accounts._options = options; }); -Tinytest.add('accounts - config - defaultFieldSelector', test => { +Tinytest.addAsync('accounts - config - defaultFieldSelector', async test => { const options = Accounts._options; Accounts._options = {}; const setValue = {bigArray: 0}; @@ -77,12 +77,12 @@ Accounts.validateNewUser(user => { return true; }); -Tinytest.add('accounts - validateNewUser gets passed user with _id', test => { - const newUserId = Accounts.updateOrCreateUserFromExternalService('foobook', {id: Random.id()}).userId; - test.isTrue(newUserId in idsInValidateNewUser); +Tinytest.addAsync('accounts - validateNewUser gets passed user with _id', async test => { + const { userId } = await Accounts.updateOrCreateUserFromExternalService('foobook', { id: Random.id() }); + test.isTrue(userId in idsInValidateNewUser); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { const facebookId = Random.id(); // create an account with facebook @@ -112,7 +112,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Facebook', test Meteor.users.remove(uid1); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Meteor Developer', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { const developerId = Random.id(); const uid1 = Accounts.updateOrCreateUserFromExternalService( 'meteor-developer', @@ -138,7 +138,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Meteor Develope Meteor.users.remove(uid1); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Weibo', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { const weiboId1 = Random.id(); const weiboId2 = Random.id(); @@ -158,7 +158,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Weibo', test => Meteor.users.remove(uid2); }); -Tinytest.add('accounts - updateOrCreateUserFromExternalService - Twitter', test => { +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { const twitterIdOld = parseInt(Random.hexString(4), 16); const twitterIdNew = ''+twitterIdOld; @@ -184,7 +184,7 @@ Tinytest.add('accounts - updateOrCreateUserFromExternalService - Twitter', test }); -Tinytest.add('accounts - insertUserDoc username', test => { +Tinytest.addAsync('accounts - insertUserDoc username', async test => { const userIn = { username: Random.id() }; @@ -210,7 +210,7 @@ Tinytest.add('accounts - insertUserDoc username', test => { Meteor.users.remove(userId); }); -Tinytest.add('accounts - insertUserDoc email', test => { +Tinytest.addAsync('accounts - insertUserDoc email', async test => { const email1 = Random.id(); const email2 = Random.id(); const email3 = Random.id(); @@ -380,7 +380,7 @@ Tinytest.addAsync( } ); -Tinytest.add('accounts - get new token', test => { +Tinytest.addAsync('accounts - get new token', async test => { // Test that the `getNewToken` method returns us a valid token, with // the same expiration as our original token. const userId = Accounts.insertUserDoc({}, { username: Random.id() }); @@ -441,9 +441,9 @@ Tinytest.addAsync('accounts - remove other tokens', (test, onComplete) => { } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - hook callbacks can access Meteor.userId()', - test => { + async test => { const userId = Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); Accounts._insertLoginToken(userId, stampedToken); @@ -491,9 +491,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - hook callbacks obey options.defaultFieldSelector', - test => { + async test => { const ignoreFieldName = "bigArray"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); const stampedToken = Accounts._generateStampedLoginToken(); @@ -548,9 +548,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - Meteor.user() obeys options.defaultFieldSelector', - test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -607,7 +607,7 @@ Tinytest.add( Tinytest.addAsync( 'accounts async - Meteor.userAsync() obeys options.defaultFieldSelector', - async test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -660,9 +660,9 @@ Tinytest.addAsync( Accounts.userId = origAccountsUserId; } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify onExternalLogin hook can update oauth user profiles', - test => { + async test => { // Verify user profile data is saved properly when not using the // onExternalLogin hook. let facebookId = Random.id(); @@ -721,9 +721,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify beforeExternalLogin hook can stop user login', - test => { + async test => { // Verify user data is saved properly when not using the // beforeExternalLogin hook. let facebookId = Random.id(); @@ -762,9 +762,9 @@ Tinytest.add( } ); -Tinytest.add( +Tinytest.addAsync( 'accounts - verify setAdditionalFindUserOnExternalLogin hook can provide user', - test => { + async test => { // create test user, without a google service const testEmail = "test@testdomain.com" const uid0 = Accounts.createUser({email: testEmail}) @@ -795,9 +795,9 @@ Tinytest.add( ); if(Meteor.isServer) { - Tinytest.add( + Tinytest.addAsync( 'accounts - make sure that extra params to accounts urls are added', - test => { + async test => { // No extra params const verifyEmailURL = new URL(Accounts.urls.verifyEmail('test')); test.equal(verifyEmailURL.searchParams.toString(), ""); From ac1dfe897422d0a2da8426e27353d5f03d8e6970 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Wed, 7 Dec 2022 21:46:23 -0300 Subject: [PATCH 05/24] chore: updated test methods --- packages/accounts-base/accounts_server.js | 148 ++++++++++++---------- 1 file changed, 84 insertions(+), 64 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 2ad5a2cb83..f9c4e82bc5 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -918,6 +918,12 @@ export class AccountsServer extends AccountsCommon { ); }; + /** + * + * @param userId + * @private + * @returns {Promise} + */ _clearAllLoginTokens(userId) { this.users.update(userId, { $set: { @@ -1099,7 +1105,14 @@ export class AccountsServer extends AccountsCommon { // tests. oldestValidDate is simulate expiring tokens without waiting // for them to actually expire. userId is used by tests to only expire // tokens for the test user. - _expireTokens(oldestValidDate, userId) { + /** + * + * @param oldestValidDate + * @param userId + * @private + * @return {Promise} + */ + async _expireTokens(oldestValidDate, userId) { const tokenLifetimeMs = this._getTokenLifetimeMs(); // when calling from a test with extra arguments, you must specify both! @@ -1114,7 +1127,7 @@ export class AccountsServer extends AccountsCommon { // Backwards compatible with older versions of meteor that stored login token // timestamps as numbers. - this.users.update({ ...userFilter, + await this.users.update({ ...userFilter, $or: [ { "services.resume.loginTokens.when": { $lt: oldestValidDate } }, { "services.resume.loginTokens.when": { $lt: +oldestValidDate } } @@ -1151,7 +1164,7 @@ export class AccountsServer extends AccountsCommon { }; // Called by accounts-password - insertUserDoc(options, user) { + async insertUserDoc(options, user) { // - clone user document, to protect from modification // - add createdAt timestamp // - prepare an _id, so that you can modify other collections (eg @@ -1196,7 +1209,7 @@ export class AccountsServer extends AccountsCommon { let userId; try { - userId = this.users.insert(fullUser); + userId = await this.users.insert(fullUser); } catch (e) { // XXX string parsing sucks, maybe // https://jira.mongodb.org/browse/SERVER-3069 will get fixed one day @@ -1226,9 +1239,9 @@ export class AccountsServer extends AccountsCommon { /// CLEAN UP FOR `logoutOtherClients` /// - _deleteSavedTokensForUser(userId, tokensToDelete) { + async _deleteSavedTokensForUser(userId, tokensToDelete) { if (tokensToDelete) { - this.users.update(userId, { + await this.users.update(userId, { $unset: { "services.resume.haveLoginTokensToDelete": 1, "services.resume.loginTokensToDelete": 1 @@ -1247,16 +1260,23 @@ export class AccountsServer extends AccountsCommon { // shouldn't happen very often. We shouldn't put a delay here because // that would give a lot of power to an attacker with a stolen login // token and the ability to crash the server. - Meteor.startup(() => { - this.users.find({ + Meteor.startup(async () => { + await this.users.find({ "services.resume.haveLoginTokensToDelete": true - }, {fields: { + }, { + fields: { "services.resume.loginTokensToDelete": 1 - }}).forEach(user => { + } + }).forEach(user => { this._deleteSavedTokensForUser( user._id, user.services.resume.loginTokensToDelete - ); + ) + // We don't need to wait for this to complete. + .then(_ => _) + .catch(err => { + console.log(err); + }); }); }); }; @@ -1276,7 +1296,7 @@ export class AccountsServer extends AccountsCommon { // @returns {Object} Object with token and id keys, like the result // of the "login" method. // - updateOrCreateUserFromExternalService( + async updateOrCreateUserFromExternalService( serviceName, serviceData, options @@ -1311,9 +1331,7 @@ export class AccountsServer extends AccountsCommon { } else { selector[serviceIdKey] = serviceData.id; } - - let user = this.users.findOne(selector, {fields: this._options.defaultFieldSelector}); - + let user = await this.users.findOne(selector, {fields: this._options.defaultFieldSelector}); // Check to see if the developer has a custom way to find the user outside // of the general selectors above. if (!user && this._additionalFindUserOnExternalLogin) { @@ -1337,7 +1355,7 @@ export class AccountsServer extends AccountsCommon { } if (user) { - pinEncryptedFieldsToUser(serviceData, user._id); + await pinEncryptedFieldsToUser(serviceData, user._id); let setAttrs = {}; Object.keys(serviceData).forEach(key => @@ -1347,7 +1365,7 @@ export class AccountsServer extends AccountsCommon { // XXX Maybe we should re-use the selector above and notice if the update // touches nothing? setAttrs = { ...setAttrs, ...opts }; - this.users.update(user._id, { + await this.users.update(user._id, { $set: setAttrs }); @@ -1359,9 +1377,10 @@ export class AccountsServer extends AccountsCommon { // Create a new user with the service data. user = {services: {}}; user.services[serviceName] = serviceData; + const userId = await this.insertUserDoc(opts, user); return { type: serviceName, - userId: this.insertUserDoc(opts, user) + userId }; } }; @@ -1543,7 +1562,7 @@ const setupDefaultLoginHandlers = accounts => { }; // Login handler for resume tokens. -const defaultResumeLoginHandler = (accounts, options) => { +const defaultResumeLoginHandler = async (accounts, options) => { if (!options.resume) return undefined; @@ -1554,7 +1573,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // First look for just the new-style hashed login token, to avoid // sending the unhashed token to the database in a query if we don't // need to. - let user = accounts.users.findOne( + let user = await accounts.users.findOne( {"services.resume.loginTokens.hashedToken": hashedToken}, {fields: {"services.resume.loginTokens.$": 1}}); @@ -1564,7 +1583,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // the old-style token OR the new-style token, because another // client connection logging in simultaneously might have already // converted the token. - user = accounts.users.findOne({ + user = await accounts.users.findOne({ $or: [ {"services.resume.loginTokens.hashedToken": hashedToken}, {"services.resume.loginTokens.token": options.resume} @@ -1583,13 +1602,13 @@ const defaultResumeLoginHandler = (accounts, options) => { // {hashedToken, when} for a hashed token or {token, when} for an // unhashed token. let oldUnhashedStyleToken; - let token = user.services.resume.loginTokens.find(token => + let token = await user.services.resume.loginTokens.find(token => token.hashedToken === hashedToken ); if (token) { oldUnhashedStyleToken = false; } else { - token = user.services.resume.loginTokens.find(token => + token = await user.services.resume.loginTokens.find(token => token.token === options.resume ); oldUnhashedStyleToken = true; @@ -1609,7 +1628,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // after we read it). Using $addToSet avoids getting an index // error if another client logging in simultaneously has already // inserted the new hashed token. - accounts.users.update( + await accounts.users.update( { _id: user._id, "services.resume.loginTokens.token": options.resume @@ -1625,7 +1644,7 @@ const defaultResumeLoginHandler = (accounts, options) => { // Remove the old token *after* adding the new, since otherwise // another client trying to login between our removing the old and // adding the new wouldn't find a token to login with. - accounts.users.update(user._id, { + await accounts.users.update(user._id, { $pull: { "services.resume.loginTokens": { "token": options.resume } } @@ -1641,49 +1660,50 @@ const defaultResumeLoginHandler = (accounts, options) => { }; }; -const expirePasswordToken = ( - accounts, - oldestValidDate, - tokenFilter, - userId -) => { - // boolean value used to determine if this method was called from enroll account workflow - let isEnroll = false; - const userFilter = userId ? {_id: userId} : {}; - // check if this method was called from enroll account workflow - if(tokenFilter['services.password.enroll.reason']) { - isEnroll = true; - } - let resetRangeOr = { - $or: [ - { "services.password.reset.when": { $lt: oldestValidDate } }, - { "services.password.reset.when": { $lt: +oldestValidDate } } - ] - }; - if(isEnroll) { - resetRangeOr = { +const expirePasswordToken = + async ( + accounts, + oldestValidDate, + tokenFilter, + userId + ) => { + // boolean value used to determine if this method was called from enroll account workflow + let isEnroll = false; + const userFilter = userId ? { _id: userId } : {}; + // check if this method was called from enroll account workflow + if (tokenFilter['services.password.enroll.reason']) { + isEnroll = true; + } + let resetRangeOr = { $or: [ - { "services.password.enroll.when": { $lt: oldestValidDate } }, - { "services.password.enroll.when": { $lt: +oldestValidDate } } + { "services.password.reset.when": { $lt: oldestValidDate } }, + { "services.password.reset.when": { $lt: +oldestValidDate } } ] }; - } - const expireFilter = { $and: [tokenFilter, resetRangeOr] }; - if(isEnroll) { - accounts.users.update({...userFilter, ...expireFilter}, { - $unset: { - "services.password.enroll": "" - } - }, { multi: true }); - } else { - accounts.users.update({...userFilter, ...expireFilter}, { - $unset: { - "services.password.reset": "" - } - }, { multi: true }); - } + if (isEnroll) { + resetRangeOr = { + $or: [ + { "services.password.enroll.when": { $lt: oldestValidDate } }, + { "services.password.enroll.when": { $lt: +oldestValidDate } } + ] + }; + } + const expireFilter = { $and: [tokenFilter, resetRangeOr] }; + if (isEnroll) { + await accounts.users.update({ ...userFilter, ...expireFilter }, { + $unset: { + "services.password.enroll": "" + } + }, { multi: true }); + } else { + await accounts.users.update({ ...userFilter, ...expireFilter }, { + $unset: { + "services.password.reset": "" + } + }, { multi: true }); + } -}; + }; const setExpireTokensInterval = accounts => { accounts.expireTokenInterval = Meteor.setInterval(() => { From 919de73cd61c3c1d926c9ab8398746ed1f07f945 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:34:15 -0300 Subject: [PATCH 06/24] tests: solved insertUserDoc username --- packages/accounts-base/accounts_tests.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 266c6a0bd0..94e283e6ef 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -190,19 +190,18 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { }; // user does not already exist. create a user object with fields set. - const userId = Accounts.insertUserDoc( + const userId = await Accounts.insertUserDoc( {profile: {name: 'Foo Bar'}}, userIn ); - const userOut = Meteor.users.findOne(userId); - + const userOut = await Meteor.users.findOne(userId); test.equal(typeof userOut.createdAt, 'object'); test.equal(userOut.profile.name, 'Foo Bar'); test.equal(userOut.username, userIn.username); // run the hook again. now the user exists, so it throws an error. - test.throws( - () => Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + await test.throwsAsync( + async () => await Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), 'Username already exists.' ); From 3492786085d3d20180c8444ccc4c7083221af402 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:37:07 -0300 Subject: [PATCH 07/24] tests: solved insertUserDoc email --- packages/accounts-base/accounts_tests.js | 30 +++++++++++++----------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 94e283e6ef..1bc9fb5c3c 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -180,7 +180,7 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', test.length(users2, 1); // cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(uid1); }); @@ -206,7 +206,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { ); // cleanup - Meteor.users.remove(userId); + await Meteor.users.remove(userId); }); Tinytest.addAsync('accounts - insertUserDoc email', async test => { @@ -219,11 +219,11 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { }; // user does not already exist. create a user object with fields set. - const userId = Accounts.insertUserDoc( + const userId = await Accounts.insertUserDoc( {profile: {name: 'Foo Bar'}}, userIn ); - const userOut = Meteor.users.findOne(userId); + const userOut = await Meteor.users.findOne(userId); test.equal(typeof userOut.createdAt, 'object'); test.equal(userOut.profile.name, 'Foo Bar'); @@ -231,33 +231,35 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // run the hook again with the exact same emails. // run the hook again. now the user exists, so it throws an error. - test.throws( - () => Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + await test.throwsAsync( + async () => await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn), 'Email already exists.' ); // now with only one of them. - test.throws(() => - Accounts.insertUserDoc({}, {emails: [{address: email1}]}), + await test.throwsAsync( + async () => + await Accounts.insertUserDoc({}, { emails: [{ address: email1 }] }), 'Email already exists.' ); - test.throws(() => - Accounts.insertUserDoc({}, {emails: [{address: email2}]}), + await test.throwsAsync( + async () => + await Accounts.insertUserDoc({}, { emails: [{ address: email2 }] }), 'Email already exists.' ); // a third email works. - const userId3 = Accounts.insertUserDoc( + const userId3 = await Accounts.insertUserDoc( {}, {emails: [{address: email3}]} ); - const user3 = Meteor.users.findOne(userId3); + const user3 = await Meteor.users.findOne(userId3); test.equal(typeof user3.createdAt, 'object'); // cleanup - Meteor.users.remove(userId); - Meteor.users.remove(userId3); + await Meteor.users.remove(userId); + await Meteor.users.remove(userId3); }); // More token expiration tests are in accounts-password From 02fc0f0531004f8ff0253530c251a280d1717bb2 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:44:00 -0300 Subject: [PATCH 08/24] tests: solved expire numeric token --- packages/accounts-base/accounts_tests.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 1bc9fb5c3c..e35a79210d 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -263,13 +263,13 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { }); // More token expiration tests are in accounts-password -Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { +Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => { const userIn = { username: Random.id() }; - const userId = Accounts.insertUserDoc({ profile: { + const userId = await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn); const date = new Date(new Date() - 5000); - Meteor.users.update(userId, { + await Meteor.users.update(userId, { $set: { "services.resume.loginTokens": [{ hashedToken: Random.id(), @@ -280,7 +280,7 @@ Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { }] } }); - const observe = Meteor.users.find(userId).observe({ + const observe = await Meteor.users.find(userId).observe({ changed: newUser => { if (newUser.services && newUser.services.resume && (!newUser.services.resume.loginTokens || @@ -290,7 +290,7 @@ Tinytest.addAsync('accounts - expire numeric token', (test, onComplete) => { } } }); - Accounts._expireTokens(new Date(), userId); + await Accounts._expireTokens(new Date(), userId); }); From 61aab831da26d9ced3e831b819253d1c0969b46c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:54:15 -0300 Subject: [PATCH 09/24] chore: asyncfied _insertHashedLoginToken --- packages/accounts-base/accounts_server.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index f9c4e82bc5..006cfe8ce8 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -899,10 +899,10 @@ export class AccountsServer extends AccountsCommon { // Using $addToSet avoids getting an index error if another client // logging in simultaneously has already inserted the new hashed // token. - _insertHashedLoginToken(userId, hashedToken, query) { + async _insertHashedLoginToken(userId, hashedToken, query) { query = query ? { ...query } : {}; query._id = userId; - this.users.update(query, { + await this.users.update(query, { $addToSet: { "services.resume.loginTokens": hashedToken } @@ -910,8 +910,8 @@ export class AccountsServer extends AccountsCommon { }; // Exported for tests. - _insertLoginToken(userId, stampedToken, query) { - this._insertHashedLoginToken( + async _insertLoginToken(userId, stampedToken, query) { + await this._insertHashedLoginToken( userId, this._hashStampedToken(stampedToken), query From b1b7b6b6f951375fd6f05e4aea4a743fb26ad53d Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 14:54:26 -0300 Subject: [PATCH 10/24] tests: resolved login token --- packages/accounts-base/accounts_tests.js | 48 +++++++++++++----------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index e35a79210d..f0a8c6bf5d 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -296,43 +296,48 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => // Login tokens used to be stored unhashed in the database. We want // to make sure users can still login after upgrading. -const insertUnhashedLoginToken = (userId, stampedToken) => { - Meteor.users.update( +const insertUnhashedLoginToken = async (userId, stampedToken) => { + await Meteor.users.update( userId, {$push: {'services.resume.loginTokens': stampedToken}} ); }; -Tinytest.addAsync('accounts - login token', (test, onComplete) => { +Tinytest.addAsync('accounts - login token', async (test) => { // Test that we can login when the database contains a leftover // old style unhashed login token. - const userId1 = Accounts.insertUserDoc({}, {username: Random.id()}); + const userId1 = + await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken1 = Accounts._generateStampedLoginToken(); - insertUnhashedLoginToken(userId1, stampedToken1); + await insertUnhashedLoginToken(userId1, stampedToken1); let connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken1.token}); + await connection.callAsync('login', { resume: stampedToken1.token }); connection.disconnect(); // Steal the unhashed token from the database and use it to login. // This is a sanity check so that when we *can't* login with a // stolen *hashed* token, we know it's not a problem with the test. - const userId2 = Accounts.insertUserDoc({}, {username: Random.id()}); - insertUnhashedLoginToken(userId2, Accounts._generateStampedLoginToken()); - const stolenToken1 = Meteor.users.findOne(userId2).services.resume.loginTokens[0].token; + const userId2 = + await Accounts.insertUserDoc({}, { username: Random.id() }); + await insertUnhashedLoginToken(userId2, Accounts._generateStampedLoginToken()); + const user2 = await Meteor.users.findOne(userId2); + const stolenToken1 = user2.services.resume.loginTokens[0].token; test.isTrue(stolenToken1); connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stolenToken1}); + await connection.callAsync('login', { resume: stolenToken1 }); connection.disconnect(); // Now do the same thing, this time with a stolen hashed token. - const userId3 = Accounts.insertUserDoc({}, {username: Random.id()}); - Accounts._insertLoginToken(userId3, Accounts._generateStampedLoginToken()); - const stolenToken2 = Meteor.users.findOne(userId3).services.resume.loginTokens[0].hashedToken; + const userId3 = + await Accounts.insertUserDoc({}, { username: Random.id() }); + await Accounts._insertLoginToken(userId3, Accounts._generateStampedLoginToken()); + const user3 = await Meteor.users.findOne(userId3); + const stolenToken2 = user3.services.resume.loginTokens[0].hashedToken; test.isTrue(stolenToken2); connection = DDP.connect(Meteor.absoluteUrl()); // evil plan foiled - test.throws( - () => connection.call('login', {resume: stolenToken2}), + await test.throwsAsync( + async () => await connection.callAsync('login', { resume: stolenToken2 }), /You\'ve been logged out by the server/ ); connection.disconnect(); @@ -340,24 +345,25 @@ Tinytest.addAsync('accounts - login token', (test, onComplete) => { // Old style unhashed tokens are replaced by hashed tokens when // encountered. This means that after someone logins once, the // old unhashed token is no longer available to be stolen. - const userId4 = Accounts.insertUserDoc({}, {username: Random.id()}); + const userId4 = + await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken2 = Accounts._generateStampedLoginToken(); - insertUnhashedLoginToken(userId4, stampedToken2); + await insertUnhashedLoginToken(userId4, stampedToken2); connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken2.token}); + await connection.callAsync('login', { resume: stampedToken2.token }); connection.disconnect(); // The token is no longer available to be stolen. - const stolenToken3 = Meteor.users.findOne(userId4).services.resume.loginTokens[0].token; + const user4 = await Meteor.users.findOne(userId4); + const stolenToken3 = user4.services.resume.loginTokens[0].token; test.isFalse(stolenToken3); // After the upgrade, the client can still login with their original // unhashed login token. connection = DDP.connect(Meteor.absoluteUrl()); - connection.call('login', {resume: stampedToken2.token}); + await connection.callAsync('login', { resume: stampedToken2.token }); connection.disconnect(); - onComplete(); }); Tinytest.addAsync( From 4a15dc2ab973cc6d741a9cb94364a9d396c4d5b1 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:14:56 -0300 Subject: [PATCH 11/24] tests: solved remove other tokens --- packages/accounts-base/accounts_tests.js | 46 +++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index f0a8c6bf5d..2665935f21 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -4,8 +4,8 @@ import { Accounts } from 'meteor/accounts-base'; import { Random } from 'meteor/random'; Meteor.methods({ - getCurrentLoginToken: function () { - return Accounts._getLoginToken(this.connection.id); + getCurrentLoginToken: async function () { + return await Accounts._getLoginToken(this.connection.id); } }); @@ -390,56 +390,60 @@ Tinytest.addAsync( Tinytest.addAsync('accounts - get new token', async test => { // Test that the `getNewToken` method returns us a valid token, with // the same expiration as our original token. - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const conn = DDP.connect(Meteor.absoluteUrl()); - conn.call('login', { resume: stampedToken.token }); - test.equal(conn.call('getCurrentLoginToken'), - Accounts._hashLoginToken(stampedToken.token)); + await conn.callAsync('login', { resume: stampedToken.token }); + test.equal(await conn.callAsync('getCurrentLoginToken'), + Accounts._hashLoginToken(stampedToken.token)); - const newTokenResult = conn.call('getNewToken'); + const newTokenResult = await conn.callAsync('getNewToken'); test.equal(newTokenResult.tokenExpires, - Accounts._tokenExpiration(stampedToken.when)); - test.equal(conn.call('getCurrentLoginToken'), - Accounts._hashLoginToken(newTokenResult.token)); + Accounts._tokenExpiration(stampedToken.when)); + const token = await conn.callAsync('getCurrentLoginToken'); + console.log(token); + test.equal(await conn.callAsync('getCurrentLoginToken'), + Accounts._hashLoginToken(newTokenResult.token)); conn.disconnect(); // A second connection should be able to log in with the new token // we got. const secondConn = DDP.connect(Meteor.absoluteUrl()); - secondConn.call('login', { resume: newTokenResult.token }); + await secondConn.callAsync('login', { resume: newTokenResult.token }); secondConn.disconnect(); } ); -Tinytest.addAsync('accounts - remove other tokens', (test, onComplete) => { +Tinytest.addAsync('accounts - remove other tokens', async (test) => { // Test that the `removeOtherTokens` method removes all tokens other // than the caller's token, thereby logging out and closing other // connections. - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedTokens = []; const conns = []; for(let i = 0; i < 2; i++) { stampedTokens.push(Accounts._generateStampedLoginToken()); - Accounts._insertLoginToken(userId, stampedTokens[i]); + await Accounts._insertLoginToken(userId, stampedTokens[i]); const conn = DDP.connect(Meteor.absoluteUrl()); - conn.call('login', { resume: stampedTokens[i].token }); - test.equal(conn.call('getCurrentLoginToken'), + await conn.callAsync('login', { resume: stampedTokens[i].token }); + test.equal(await conn.callAsync('getCurrentLoginToken'), Accounts._hashLoginToken(stampedTokens[i].token)); conns.push(conn); }; - conns[0].call('removeOtherTokens'); - simplePoll(() => { - const tokens = conns.map(conn => conn.call('getCurrentLoginToken')); + await conns[0].callAsync('removeOtherTokens'); + simplePoll(async () => { + let tokens = []; + for (const conn of conns) { + tokens.push(await conn.callAsync('getCurrentLoginToken')); + } return ! tokens[1] && tokens[0] === Accounts._hashLoginToken(stampedTokens[0].token); }, () => { // success conns.forEach(conn => conn.disconnect()); - onComplete(); }, () => { // timed out throw new Error("accounts - remove other tokens timed out"); From 1c60eca86238bae685e8f18e42373712c4966794 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:17:32 -0300 Subject: [PATCH 12/24] tests: solved get new token and removed log --- packages/accounts-base/accounts_tests.js | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 2665935f21..27b8f8d160 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -402,7 +402,6 @@ Tinytest.addAsync('accounts - get new token', async test => { test.equal(newTokenResult.tokenExpires, Accounts._tokenExpiration(stampedToken.when)); const token = await conn.callAsync('getCurrentLoginToken'); - console.log(token); test.equal(await conn.callAsync('getCurrentLoginToken'), Accounts._hashLoginToken(newTokenResult.token)); conn.disconnect(); From 704dcb6d8841e84aeb680c8524d3f612cc42cdfa Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:17:42 -0300 Subject: [PATCH 13/24] chore: added await in get new token --- packages/accounts-base/accounts_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index 006cfe8ce8..d0767854ad 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -694,7 +694,7 @@ export class AccountsServer extends AccountsCommon { } const newStampedToken = accounts._generateStampedLoginToken(); newStampedToken.when = currentStampedToken.when; - accounts._insertLoginToken(this.userId, newStampedToken); + await accounts._insertLoginToken(this.userId, newStampedToken); return accounts._loginUser(this, this.userId, newStampedToken); }; From f1845a7d6be722a91af50410988cae2def9b1811 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:24:03 -0300 Subject: [PATCH 14/24] tests: solved hook callbacks can access Meteor.userId() --- packages/accounts-base/accounts_tests.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 27b8f8d160..78f07476b3 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -454,9 +454,9 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { Tinytest.addAsync( 'accounts - hook callbacks can access Meteor.userId()', async test => { - const userId = Accounts.insertUserDoc({}, { username: Random.id() }); + const userId = await Accounts.insertUserDoc({}, { username: Random.id() }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const validateStopper = Accounts.validateLoginAttempt(attempt => { test.equal(Meteor.userId(), validateAttemptExpectedUserId, "validateLoginAttempt"); @@ -478,20 +478,22 @@ Tinytest.addAsync( // On a new connection, Meteor.userId() should be null until logged in. let validateAttemptExpectedUserId = null; const onLoginExpectedUserId = userId; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Now that the user is logged in on the connection, Meteor.userId() should // return that user. validateAttemptExpectedUserId = userId; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Trigger onLoginFailure callbacks const onLoginFailureExpectedUserId = userId; - test.throws(() => conn.call('login', { resume: "bogus" }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: "bogus" }), '403'); // Trigger onLogout callbacks const onLogoutExpectedUserId = userId; - conn.call('logout'); + await conn.callAsync('logout'); conn.disconnect(); validateStopper.stop(); From bd6d9dab287620e90bbe5b60d17229165688caf6 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:32:50 -0300 Subject: [PATCH 15/24] tests: solved hook callbacks obey options.defaultFieldSelector --- packages/accounts-base/accounts_tests.js | 234 ++++++++++++----------- 1 file changed, 121 insertions(+), 113 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 78f07476b3..efb69dc3b4 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -14,7 +14,7 @@ Meteor.methods({ // (impossible?) Tinytest.add( 'accounts - config validates keys', - test => test.throws(() => Accounts.config({foo: "bar"})) + test => test.throws(() => Accounts.config({ foo: "bar" })) ); Tinytest.addAsync('accounts - config - token lifetime', async test => { @@ -65,8 +65,8 @@ Tinytest.addAsync('accounts - config - default token lifetime', async test => { Tinytest.addAsync('accounts - config - defaultFieldSelector', async test => { const options = Accounts._options; Accounts._options = {}; - const setValue = {bigArray: 0}; - Accounts.config({defaultFieldSelector: setValue}); + const setValue = { bigArray: 0 }; + Accounts.config({ defaultFieldSelector: setValue }); test.equal(Accounts._options.defaultFieldSelector, setValue); Accounts._options = options; }); @@ -87,8 +87,8 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', // create an account with facebook const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId, monkey: 42}, {profile: {foo: 1}}).id; - const users1 = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.facebook.monkey, 42); @@ -96,10 +96,10 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', // create again with the same id, see that we get the same user. // it should update services.facebook but not profile. const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', {id: facebookId, llama: 50}, - {profile: {foo: 1000, bar: 2}}).id; + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }).id; test.equal(uid1, uid2); - const users2 = Meteor.users.find({"services.facebook.id": facebookId}).fetch(); + const users2 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users2, 1); test.equal(users2[0].profile.foo, 1); test.equal(users2[0].profile.bar, undefined); @@ -144,14 +144,14 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', as // users that have different service ids get different users const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', {id: weiboId1}, {profile: {foo: 1}}).id; + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', {id: weiboId2}, {profile: {bar: 2}}).id; - test.equal(Meteor.users.find({"services.weibo.id": {$in: [weiboId1, weiboId2]}}).count(), 2); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId1}).profile.foo, 1); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId1}).emails, undefined); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId2}).profile.bar, 2); - test.equal(Meteor.users.findOne({"services.weibo.id": weiboId2}).emails, undefined); + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; + test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); // cleanup Meteor.users.remove(uid1); @@ -160,12 +160,12 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', as Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { const twitterIdOld = parseInt(Random.hexString(4), 16); - const twitterIdNew = ''+twitterIdOld; + const twitterIdNew = '' + twitterIdOld; // create an account with twitter using the old ID format of integer const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', {id: twitterIdOld, monkey: 42}, {profile: {foo: 1}}).id; - const users1 = Meteor.users.find({"services.twitter.id": twitterIdOld}).fetch(); + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.twitter.monkey, 42); @@ -174,9 +174,9 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', // test that the existing user is found, and that the ID // gets updated to a string value const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', {id: twitterIdNew, monkey: 42}, {profile: {foo: 1}}).id; + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; test.equal(uid1, uid2); - const users2 = Meteor.users.find({"services.twitter.id": twitterIdNew}).fetch(); + const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); test.length(users2, 1); // cleanup @@ -191,7 +191,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { // user does not already exist. create a user object with fields set. const userId = await Accounts.insertUserDoc( - {profile: {name: 'Foo Bar'}}, + { profile: { name: 'Foo Bar' } }, userIn ); const userOut = await Meteor.users.findOne(userId); @@ -201,7 +201,7 @@ Tinytest.addAsync('accounts - insertUserDoc username', async test => { // run the hook again. now the user exists, so it throws an error. await test.throwsAsync( - async () => await Accounts.insertUserDoc({profile: {name: 'Foo Bar'}}, userIn), + async () => await Accounts.insertUserDoc({ profile: { name: 'Foo Bar' } }, userIn), 'Username already exists.' ); @@ -214,13 +214,13 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { const email2 = Random.id(); const email3 = Random.id(); const userIn = { - emails: [{address: email1, verified: false}, - {address: email2, verified: true}] + emails: [{ address: email1, verified: false }, + { address: email2, verified: true }] }; // user does not already exist. create a user object with fields set. const userId = await Accounts.insertUserDoc( - {profile: {name: 'Foo Bar'}}, + { profile: { name: 'Foo Bar' } }, userIn ); const userOut = await Meteor.users.findOne(userId); @@ -252,7 +252,7 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // a third email works. const userId3 = await Accounts.insertUserDoc( - {}, {emails: [{address: email3}]} + {}, { emails: [{ address: email3 }] } ); const user3 = await Meteor.users.findOne(userId3); test.equal(typeof user3.createdAt, 'object'); @@ -265,9 +265,11 @@ Tinytest.addAsync('accounts - insertUserDoc email', async test => { // More token expiration tests are in accounts-password Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => { const userIn = { username: Random.id() }; - const userId = await Accounts.insertUserDoc({ profile: { - name: 'Foo Bar' - } }, userIn); + const userId = await Accounts.insertUserDoc({ + profile: { + name: 'Foo Bar' + } + }, userIn); const date = new Date(new Date() - 5000); await Meteor.users.update(userId, { $set: { @@ -283,7 +285,7 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => const observe = await Meteor.users.find(userId).observe({ changed: newUser => { if (newUser.services && newUser.services.resume && - (!newUser.services.resume.loginTokens || + (!newUser.services.resume.loginTokens || newUser.services.resume.loginTokens.length === 0)) { observe.stop(); onComplete(); @@ -299,7 +301,7 @@ Tinytest.addAsync('accounts - expire numeric token', async (test, onComplete) => const insertUnhashedLoginToken = async (userId, stampedToken) => { await Meteor.users.update( userId, - {$push: {'services.resume.loginTokens': stampedToken}} + { $push: { 'services.resume.loginTokens': stampedToken } } ); }; @@ -422,15 +424,16 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { const stampedTokens = []; const conns = []; - for(let i = 0; i < 2; i++) { + for (let i = 0; i < 2; i++) { stampedTokens.push(Accounts._generateStampedLoginToken()); await Accounts._insertLoginToken(userId, stampedTokens[i]); const conn = DDP.connect(Meteor.absoluteUrl()); await conn.callAsync('login', { resume: stampedTokens[i].token }); test.equal(await conn.callAsync('getCurrentLoginToken'), - Accounts._hashLoginToken(stampedTokens[i].token)); + Accounts._hashLoginToken(stampedTokens[i].token)); conns.push(conn); - }; + } + ; await conns[0].callAsync('removeOtherTokens'); simplePoll(async () => { @@ -438,7 +441,7 @@ Tinytest.addAsync('accounts - remove other tokens', async (test) => { for (const conn of conns) { tokens.push(await conn.callAsync('getCurrentLoginToken')); } - return ! tokens[1] && + return !tokens[1] && tokens[0] === Accounts._hashLoginToken(stampedTokens[0].token); }, () => { // success @@ -507,13 +510,14 @@ Tinytest.addAsync( 'accounts - hook callbacks obey options.defaultFieldSelector', async test => { const ignoreFieldName = "bigArray"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; Accounts._options = {}; - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); - test.equal(Accounts._options.defaultFieldSelector, {[ignoreFieldName]: 0}, 'defaultFieldSelector'); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); + test.equal(Accounts._options.defaultFieldSelector, { [ignoreFieldName]: 0 }, 'defaultFieldSelector'); const validateStopper = Accounts.validateLoginAttempt(attempt => { test.isUndefined(allowLogin != 'bogus' ? attempt.user[ignoreFieldName] : attempt.user, "validateLoginAttempt") @@ -533,23 +537,27 @@ Tinytest.addAsync( // test a new connection let allowLogin = true; - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Now that the user is logged in on the connection, Meteor.userId() should // return that user. - conn.call('login', { resume: stampedToken.token }); + await conn.callAsync('login', { resume: stampedToken.token }); // Trigger onLoginFailure callbacks, this will not include the user object allowLogin = 'bogus'; - test.throws(() => conn.call('login', { resume: "bogus" }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: "bogus" }), '403'); // test a forced login fail which WILL include the user object allowLogin = false; - test.throws(() => conn.call('login', { resume: stampedToken.token }), '403'); + await test.throwsAsync( + async () => + await conn.callAsync('login', { resume: stampedToken.token }), '403'); // Trigger onLogout callbacks const onLogoutExpectedUserId = userId; - conn.call('logout'); + await conn.callAsync('logout'); Accounts._options = options; conn.disconnect(); @@ -581,31 +589,31 @@ Tinytest.addAsync( test.isNotUndefined(user[ignoreFieldName], 'included by default'); // test the field is excluded - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); user = Meteor.user(); test.isUndefined(user[ignoreFieldName], 'excluded'); user = Meteor.user({}); test.isUndefined(user[ignoreFieldName], 'excluded {}'); // test the field can still be retrieved if required - user = Meteor.user({fields: {[ignoreFieldName]: 1}}); + user = Meteor.user({ fields: { [ignoreFieldName]: 1 } }); test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved'); test.isUndefined(user.username, 'field can be retrieved username'); // test a combined negative field specifier - user = Meteor.user({fields: {username: 0}}); + user = Meteor.user({ fields: { username: 0 } }); test.isUndefined(user[ignoreFieldName], 'combined field selector'); test.isUndefined(user.username, 'combined field selector username'); // test an explicit request for the full user object - user = Meteor.user({fields: {}}); + user = Meteor.user({ fields: {} }); test.isNotUndefined(user[ignoreFieldName], 'full selector'); test.isNotUndefined(user.username, 'full selector username'); Accounts._options = {}; // Test that a custom field gets retrieved properly - Accounts.config({defaultFieldSelector: {[customField]: 1}}); + Accounts.config({ defaultFieldSelector: { [customField]: 1 } }); user = Meteor.user() test.isNotUndefined(user[customField]); test.isUndefined(user.username); @@ -619,7 +627,7 @@ Tinytest.addAsync( Tinytest.addAsync( 'accounts async - Meteor.userAsync() obeys options.defaultFieldSelector', - async test => { + async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); @@ -684,7 +692,7 @@ Tinytest.addAsync( { profile: { foo: 1 } }, ).userId; const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}}); + const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); let users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); @@ -696,7 +704,7 @@ Tinytest.addAsync( // Also verify that the user object is filtered by _options.defaultFieldSelector const accountsOptions = Accounts._options; Accounts._options = {}; - Accounts.config({defaultFieldSelector: {[ignoreFieldName]: 0}}); + Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); Accounts.onExternalLogin((options, user) => { options.profile.foo = 2; test.isUndefined(users[ignoreFieldName], 'ignoreField - after limit fields'); @@ -734,79 +742,79 @@ Tinytest.addAsync( ); Tinytest.addAsync( - 'accounts - verify beforeExternalLogin hook can stop user login', - async test => { - // Verify user data is saved properly when not using the - // beforeExternalLogin hook. - let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - ).userId; - const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, {$set: {[ignoreFieldName]: [1]}}); - let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); - test.length(users, 1); - test.equal(users[0].profile.foo, 1); - test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); + 'accounts - verify beforeExternalLogin hook can stop user login', + async test => { + // Verify user data is saved properly when not using the + // beforeExternalLogin hook. + let facebookId = Random.id(); + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + ).userId; + const ignoreFieldName = "bigArray"; + const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + let users = + Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); + test.equal(users[0].profile.foo, 1); + test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); - // Verify that when beforeExternalLogin returns false - // that an error throws and user is not saved - Accounts.beforeExternalLogin((serviceName, serviceData, user) => { - // Check that we get the correct data - test.equal(serviceName, 'facebook'); - test.equal(serviceData, { id: facebookId }); - test.equal(user._id, uid1); - return false - }); + // Verify that when beforeExternalLogin returns false + // that an error throws and user is not saved + Accounts.beforeExternalLogin((serviceName, serviceData, user) => { + // Check that we get the correct data + test.equal(serviceName, 'facebook'); + test.equal(serviceData, { id: facebookId }); + test.equal(user._id, uid1); + return false + }); - test.throws(() => Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - )); + test.throws(() => Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + )); - // Cleanup - Meteor.users.remove(uid1); - Accounts._beforeExternalLoginHook = null; - } + // Cleanup + Meteor.users.remove(uid1); + Accounts._beforeExternalLoginHook = null; + } ); Tinytest.addAsync( 'accounts - verify setAdditionalFindUserOnExternalLogin hook can provide user', async test => { - // create test user, without a google service - const testEmail = "test@testdomain.com" - const uid0 = Accounts.createUser({email: testEmail}) + // create test user, without a google service + const testEmail = "test@testdomain.com" + const uid0 = Accounts.createUser({ email: testEmail }) - // Verify that user is found from email and service merged - Accounts.setAdditionalFindUserOnExternalLogin(({serviceName, serviceData}) => { - if (serviceName === "google") { - return Accounts.findUserByEmail(serviceData.email) - } - }) - - let googleId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'google', - { id: googleId, email: testEmail }, - { profile: { foo: 1 } }, - ).userId; - - test.equal(uid0, uid1) - - // Cleanup - if (uid1 !== uid0) { - Meteor.users.remove(uid0) + // Verify that user is found from email and service merged + Accounts.setAdditionalFindUserOnExternalLogin(({ serviceName, serviceData }) => { + if (serviceName === "google") { + return Accounts.findUserByEmail(serviceData.email) } - Meteor.users.remove(uid1); - Accounts.selectCustomUserOnExternalLogin = null; + }) + + let googleId = Random.id(); + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'google', + { id: googleId, email: testEmail }, + { profile: { foo: 1 } }, + ).userId; + + test.equal(uid0, uid1) + + // Cleanup + if (uid1 !== uid0) { + Meteor.users.remove(uid0) + } + Meteor.users.remove(uid1); + Accounts.selectCustomUserOnExternalLogin = null; } ); -if(Meteor.isServer) { +if (Meteor.isServer) { Tinytest.addAsync( 'accounts - make sure that extra params to accounts urls are added', async test => { @@ -815,7 +823,7 @@ if(Meteor.isServer) { test.equal(verifyEmailURL.searchParams.toString(), ""); // Extra params - const extraParams = { test: 'success'}; + const extraParams = { test: 'success' }; const resetPasswordURL = new URL(Accounts.urls.resetPassword('test', extraParams)); test.equal(resetPasswordURL.searchParams.get('test'), extraParams.test); const enrollAccountURL = new URL(Accounts.urls.enrollAccount('test', extraParams)); From 76ba34a8b0e8e79adb413073c5fbf2017a5351c7 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:38:51 -0300 Subject: [PATCH 16/24] tests: solved Meteor.user() obeys options.defaultFieldSelector --- packages/accounts-base/accounts_tests.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index efb69dc3b4..12b0d36109 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -573,40 +573,42 @@ Tinytest.addAsync( async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; // stub Meteor.userId() so it works outside methods and returns the correct user: const origAccountsUserId = Accounts.userId; - Accounts.userId = () => userId; + Accounts.userId = + () => userId; Accounts._options = {}; // test the field is included by default - let user = Meteor.user(); + let user = await Meteor.user(); test.isNotUndefined(user[ignoreFieldName], 'included by default'); // test the field is excluded Accounts.config({ defaultFieldSelector: { [ignoreFieldName]: 0 } }); - user = Meteor.user(); + user = await Meteor.user(); test.isUndefined(user[ignoreFieldName], 'excluded'); - user = Meteor.user({}); + user = await Meteor.user({}); test.isUndefined(user[ignoreFieldName], 'excluded {}'); // test the field can still be retrieved if required - user = Meteor.user({ fields: { [ignoreFieldName]: 1 } }); + user = await Meteor.user({ fields: { [ignoreFieldName]: 1 } }); test.isNotUndefined(user[ignoreFieldName], 'field can be retrieved'); test.isUndefined(user.username, 'field can be retrieved username'); // test a combined negative field specifier - user = Meteor.user({ fields: { username: 0 } }); + user = await Meteor.user({ fields: { username: 0 } }); test.isUndefined(user[ignoreFieldName], 'combined field selector'); test.isUndefined(user.username, 'combined field selector username'); // test an explicit request for the full user object - user = Meteor.user({ fields: {} }); + user = await Meteor.user({ fields: {} }); test.isNotUndefined(user[ignoreFieldName], 'full selector'); test.isNotUndefined(user.username, 'full selector username'); @@ -614,7 +616,7 @@ Tinytest.addAsync( // Test that a custom field gets retrieved properly Accounts.config({ defaultFieldSelector: { [customField]: 1 } }); - user = Meteor.user() + user = await Meteor.user() test.isNotUndefined(user[customField]); test.isUndefined(user.username); test.isUndefined(user[ignoreFieldName]); From 8e06687c2c3585ad2024fc6bdea2d4f7ea3e0b71 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:41:51 -0300 Subject: [PATCH 17/24] tests: solved verify onExternalLogin hook can update oauth user profiles --- packages/accounts-base/accounts_tests.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 12b0d36109..77b51de146 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -688,15 +688,15 @@ Tinytest.addAsync( // Verify user profile data is saved properly when not using the // onExternalLogin hook. let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( + const u1 = await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 1 } }, - ).userId; + ); const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + const c = await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -712,13 +712,13 @@ Tinytest.addAsync( test.isUndefined(users[ignoreFieldName], 'ignoreField - after limit fields'); return options; }); - Accounts.updateOrCreateUserFromExternalService( + await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 1 } }, ); // test.isUndefined(users[0][ignoreFieldName], 'ignoreField - fields limited'); - users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 2); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - still there'); @@ -726,18 +726,18 @@ Tinytest.addAsync( // Verify user profile data can be modified using the onExternalLogin // hook, for new users. facebookId = Random.id(); - const uid2 = Accounts.updateOrCreateUserFromExternalService( + const u2 = await Accounts.updateOrCreateUserFromExternalService( 'facebook', { id: facebookId }, { profile: { foo: 3 } }, - ).userId; - users = Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + ); + users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); test.length(users, 1); test.equal(users[0].profile.foo, 2); // Cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); + await Meteor.users.remove(u1); + await Meteor.users.remove(u2.userId); Accounts._onExternalLoginHook = null; Accounts._options = accountsOptions; } From bb4c5f2ca58f640bcbf99a40431beb3500bbdf0d Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:46:13 -0300 Subject: [PATCH 18/24] tests: solved verify beforeExternalLogin hook can stop user login --- packages/accounts-base/accounts_tests.js | 43 +++++++++++++++--------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 77b51de146..6130cf30d1 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -694,9 +694,13 @@ Tinytest.addAsync( { profile: { foo: 1 } }, ); const ignoreFieldName = "bigArray"; - const c = await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); + + const c = + await Meteor.users.update(u1.userId, { $set: { [ignoreFieldName]: [1] } }); + let users = await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -749,15 +753,22 @@ Tinytest.addAsync( // Verify user data is saved properly when not using the // beforeExternalLogin hook. let facebookId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - ).userId; + + const u = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + ); + const ignoreFieldName = "bigArray"; - const c = Meteor.users.update(uid1, { $set: { [ignoreFieldName]: [1] } }); + + const c = + await Meteor.users.update(u.userId, { $set: { [ignoreFieldName]: [1] } }); + let users = - Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + await Meteor.users.find({ 'services.facebook.id': facebookId }).fetch(); + test.length(users, 1); test.equal(users[0].profile.foo, 1); test.isNotUndefined(users[0][ignoreFieldName], 'ignoreField - before limit fields'); @@ -768,18 +779,20 @@ Tinytest.addAsync( // Check that we get the correct data test.equal(serviceName, 'facebook'); test.equal(serviceData, { id: facebookId }); - test.equal(user._id, uid1); + test.equal(user._id, u.userId); return false }); - test.throws(() => Accounts.updateOrCreateUserFromExternalService( - 'facebook', - { id: facebookId }, - { profile: { foo: 1 } }, - )); + await test.throwsAsync( + async () => + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', + { id: facebookId }, + { profile: { foo: 1 } }, + )); // Cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(u.userId); Accounts._beforeExternalLoginHook = null; } ); From c5432bd12472221614524236f397c4c4bc39a48a Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:54:00 -0300 Subject: [PATCH 19/24] tests: solved verify setAdditionalFindUserOnExternalLogin hook can provide user --- packages/accounts-base/accounts_tests.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 6130cf30d1..6e1269930f 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -802,29 +802,28 @@ Tinytest.addAsync( async test => { // create test user, without a google service const testEmail = "test@testdomain.com" - const uid0 = Accounts.createUser({ email: testEmail }) + const uid0 = await Accounts.createUser({ email: testEmail }) // Verify that user is found from email and service merged - Accounts.setAdditionalFindUserOnExternalLogin(({ serviceName, serviceData }) => { + Accounts.setAdditionalFindUserOnExternalLogin(async ({ serviceName, serviceData }) => { if (serviceName === "google") { - return Accounts.findUserByEmail(serviceData.email) + return await Accounts.findUserByEmail(serviceData.email) } }) let googleId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( + const u1 = await Accounts.updateOrCreateUserFromExternalService( 'google', { id: googleId, email: testEmail }, { profile: { foo: 1 } }, - ).userId; - - test.equal(uid0, uid1) + ); + test.equal(uid0, u1.userId) // Cleanup - if (uid1 !== uid0) { - Meteor.users.remove(uid0) + if (u1.userId !== uid0) { + await Meteor.users.remove(uid0) } - Meteor.users.remove(uid1); + await Meteor.users.remove(u1.userId); Accounts.selectCustomUserOnExternalLogin = null; } ); From 3d047f8b4d19d66fc27fc9ec33ac2763e89af354 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 15:54:09 -0300 Subject: [PATCH 20/24] chore: added await to _additionalFindUserOnExternalLogin --- packages/accounts-base/accounts_server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index d0767854ad..c6d46fc3cf 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -1335,7 +1335,7 @@ export class AccountsServer extends AccountsCommon { // Check to see if the developer has a custom way to find the user outside // of the general selectors above. if (!user && this._additionalFindUserOnExternalLogin) { - user = this._additionalFindUserOnExternalLogin({serviceName, serviceData, options}) + user = await this._additionalFindUserOnExternalLogin({serviceName, serviceData, options}) } // Before continuing, run user hook to see if we should continue From ece19bad1d7f11d7083750f58fb0daeebc1b6d30 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:03:26 -0300 Subject: [PATCH 21/24] tests: solved facebook --- packages/accounts-base/accounts_tests.js | 32 ++++++++++++++---------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 6e1269930f..9b3029607a 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -13,7 +13,7 @@ Meteor.methods({ // *are* validated, but Accounts._options is global state which makes this hard // (impossible?) Tinytest.add( - 'accounts - config validates keys', + 'accounts - config - validates keys', test => test.throws(() => Accounts.config({ foo: "bar" })) ); @@ -86,20 +86,24 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', const facebookId = Random.id(); // create an account with facebook - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.facebook.monkey, 42); // create again with the same id, see that we get the same user. // it should update services.facebook but not profile. - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, llama: 50 }, - { profile: { foo: 1000, bar: 2 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); test.length(users2, 1); test.equal(users2[0].profile.foo, 1); test.equal(users2[0].profile.bar, undefined); @@ -109,7 +113,7 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', test.equal(users2[0].services.facebook.monkey, 42); // cleanup - Meteor.users.remove(uid1); + await Meteor.users.remove(u1.id); }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { @@ -632,14 +636,16 @@ Tinytest.addAsync( async test => { const ignoreFieldName = "bigArray"; const customField = "customField"; - const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); + const userId = + await Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1], [customField]: 'test' }); const stampedToken = Accounts._generateStampedLoginToken(); - Accounts._insertLoginToken(userId, stampedToken); + await Accounts._insertLoginToken(userId, stampedToken); const options = Accounts._options; // stub Meteor.userId() so it works outside methods and returns the correct user: const origAccountsUserId = Accounts.userId; - Accounts.userId = () => userId; + Accounts.userId = + () => userId; Accounts._options = {}; From d7c2d0868d2283f8b85b20348d770f14d983a22c Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:05:07 -0300 Subject: [PATCH 22/24] tests: solved Meteor Developer --- packages/accounts-base/accounts_tests.js | 217 ++++++++++++----------- 1 file changed, 111 insertions(+), 106 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 9b3029607a..739b6b28f3 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -82,112 +82,6 @@ Tinytest.addAsync('accounts - validateNewUser gets passed user with _id', async test.isTrue(userId in idsInValidateNewUser); }); -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { - const facebookId = Random.id(); - - // create an account with facebook - const u1 = - await Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); - const users1 = - await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.foo, 1); - test.equal(users1[0].services.facebook.monkey, 42); - - // create again with the same id, see that we get the same user. - // it should update services.facebook but not profile. - const u2 = - await Accounts.updateOrCreateUserFromExternalService( - 'facebook', { id: facebookId, llama: 50 }, - { profile: { foo: 1000, bar: 2 } }); - test.equal(u1.id, u2.id); - const users2 = - await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); - test.length(users2, 1); - test.equal(users2[0].profile.foo, 1); - test.equal(users2[0].profile.bar, undefined); - test.equal(users2[0].services.facebook.llama, 50); - // make sure we *don't* lose values not passed this call to - // updateOrCreateUserFromExternalService - test.equal(users2[0].services.facebook.monkey, 42); - - // cleanup - await Meteor.users.remove(u1.id); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { - const developerId = Random.id(); - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'meteor-developer', - { id: developerId, username: 'meteor-developer' }, - { profile: { name: 'meteor-developer' } } - ).id; - const users1 = Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.name, 'meteor-developer'); - - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'meteor-developer', - { id: developerId, username: 'meteor-developer' }, - { profile: { name: 'meteor-developer', username: 'developer' } } - ).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); - test.length(users2, 1); - test.equal(users1[0].profile.name, 'meteor-developer'); - test.equal(users1[0].profile.username, undefined); - - // cleanup - Meteor.users.remove(uid1); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { - const weiboId1 = Random.id(); - const weiboId2 = Random.id(); - - // users that have different service ids get different users - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; - test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); - - // cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); -}); - -Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { - const twitterIdOld = parseInt(Random.hexString(4), 16); - const twitterIdNew = '' + twitterIdOld; - - // create an account with twitter using the old ID format of integer - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); - test.length(users1, 1); - test.equal(users1[0].profile.foo, 1); - test.equal(users1[0].services.twitter.monkey, 42); - - // Update the account with the new ID format of string - // test that the existing user is found, and that the ID - // gets updated to a string value - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); - test.length(users2, 1); - - // cleanup - await Meteor.users.remove(uid1); -}); - - Tinytest.addAsync('accounts - insertUserDoc username', async test => { const userIn = { username: Random.id() @@ -851,3 +745,114 @@ if (Meteor.isServer) { } ); } + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Facebook', async test => { + const facebookId = Random.id(); + + // create an account with facebook + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.foo, 1); + test.equal(users1[0].services.facebook.monkey, 42); + + // create again with the same id, see that we get the same user. + // it should update services.facebook but not profile. + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'facebook', { id: facebookId, llama: 50 }, + { profile: { foo: 1000, bar: 2 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.facebook.id": facebookId }).fetch(); + test.length(users2, 1); + test.equal(users2[0].profile.foo, 1); + test.equal(users2[0].profile.bar, undefined); + test.equal(users2[0].services.facebook.llama, 50); + // make sure we *don't* lose values not passed this call to + // updateOrCreateUserFromExternalService + test.equal(users2[0].services.facebook.monkey, 42); + + // cleanup + await Meteor.users.remove(u1.id); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Developer', async test => { + const developerId = + Random.id(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'meteor-developer', + { id: developerId, username: 'meteor-developer' }, + { profile: { name: 'meteor-developer' } } + ); + const users1 = + await Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.name, 'meteor-developer'); + + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'meteor-developer', + { id: developerId, username: 'meteor-developer' }, + { profile: { name: 'meteor-developer', username: 'developer' } } + ); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ 'services.meteor-developer.id': developerId }).fetch(); + test.length(users2, 1); + test.equal(users1[0].profile.name, 'meteor-developer'); + test.equal(users1[0].profile.username, undefined); + + // cleanup + await Meteor.users.remove(u1); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { + const weiboId1 = Random.id(); + const weiboId2 = Random.id(); + + // users that have different service ids get different users + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; + const uid2 = Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; + test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); + test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); + + // cleanup + Meteor.users.remove(uid1); + Meteor.users.remove(uid2); +}); + +Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { + const twitterIdOld = parseInt(Random.hexString(4), 16); + const twitterIdNew = '' + twitterIdOld; + + // create an account with twitter using the old ID format of integer + const uid1 = Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; + const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); + test.length(users1, 1); + test.equal(users1[0].profile.foo, 1); + test.equal(users1[0].services.twitter.monkey, 42); + + // Update the account with the new ID format of string + // test that the existing user is found, and that the ID + // gets updated to a string value + const uid2 = Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; + test.equal(uid1, uid2); + const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); + test.length(users2, 1); + + // cleanup + await Meteor.users.remove(uid1); +}); + From a5639c11201d629378cf0738e1f752f881e32f1e Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:09:51 -0300 Subject: [PATCH 23/24] tests: solved twtitter --- packages/accounts-base/accounts_tests.js | 55 +++++++++++++++--------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 739b6b28f3..4431c7f6a9 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -812,23 +812,32 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Meteor Dev }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Weibo', async test => { - const weiboId1 = Random.id(); - const weiboId2 = Random.id(); + const weiboId1 = + Random.id(); + const weiboId2 = + Random.id(); // users that have different service ids get different users - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }).id; - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }).id; - test.equal(Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).profile.foo, 1); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId1 }).emails, undefined); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).profile.bar, 2); - test.equal(Meteor.users.findOne({ "services.weibo.id": weiboId2 }).emails, undefined); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId1 }, { profile: { foo: 1 } }); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'weibo', { id: weiboId2 }, { profile: { bar: 2 } }); + test.equal(await Meteor.users.find({ "services.weibo.id": { $in: [weiboId1, weiboId2] } }).count(), 2); + + const user1 = + await Meteor.users.findOne({ "services.weibo.id": weiboId1 }); + const user2 = + await Meteor.users.findOne({ "services.weibo.id": weiboId2 }); + test.equal(user1.profile.foo, 1); + test.equal(user1.emails, undefined); + test.equal(user2.profile.bar, 2); + test.equal(user2.emails, undefined); // cleanup - Meteor.users.remove(uid1); - Meteor.users.remove(uid2); + Meteor.users.remove(u1.id); + Meteor.users.remove(u2.id); }); Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', async test => { @@ -836,9 +845,11 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', const twitterIdNew = '' + twitterIdOld; // create an account with twitter using the old ID format of integer - const uid1 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }).id; - const users1 = Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); + const u1 = + await Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdOld, monkey: 42 }, { profile: { foo: 1 } }); + const users1 = + await Meteor.users.find({ "services.twitter.id": twitterIdOld }).fetch(); test.length(users1, 1); test.equal(users1[0].profile.foo, 1); test.equal(users1[0].services.twitter.monkey, 42); @@ -846,13 +857,15 @@ Tinytest.addAsync('accounts - updateOrCreateUserFromExternalService - Twitter', // Update the account with the new ID format of string // test that the existing user is found, and that the ID // gets updated to a string value - const uid2 = Accounts.updateOrCreateUserFromExternalService( - 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }).id; - test.equal(uid1, uid2); - const users2 = Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); + const u2 = + await Accounts.updateOrCreateUserFromExternalService( + 'twitter', { id: twitterIdNew, monkey: 42 }, { profile: { foo: 1 } }); + test.equal(u1.id, u2.id); + const users2 = + await Meteor.users.find({ "services.twitter.id": twitterIdNew }).fetch(); test.length(users2, 1); // cleanup - await Meteor.users.remove(uid1); + await Meteor.users.remove(u1.id); }); From 6f79cd8d13ee3c080060a20327ee8c4c08866747 Mon Sep 17 00:00:00 2001 From: Gabriel Grubba Date: Thu, 8 Dec 2022 16:39:09 -0300 Subject: [PATCH 24/24] tests: finished --- packages/accounts-base/accounts_client_tests.js | 10 +++++----- packages/accounts-base/accounts_server.js | 17 +++++++++-------- packages/accounts-base/accounts_tests.js | 2 ++ packages/accounts-base/accounts_tests_setup.js | 4 ++-- packages/accounts-base/package.js | 2 +- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/packages/accounts-base/accounts_client_tests.js b/packages/accounts-base/accounts_client_tests.js index 880a71e4fe..5558884273 100644 --- a/packages/accounts-base/accounts_client_tests.js +++ b/packages/accounts-base/accounts_client_tests.js @@ -36,9 +36,9 @@ const createUserAndLogout = (test, done, nextTests) => { }, }, () => { - Meteor.logout(() => { + Meteor.logout(async () => { // Make sure we're logged out - test.isFalse(Meteor.user()); + test.isFalse(await Meteor.userAsync()); // Handle next tests nextTests(test, done); }); @@ -245,13 +245,13 @@ Tinytest.addAsync( ); -Tinytest.addAsync( + Tinytest.addAsync( 'accounts-2fa - Meteor.loginWithPasswordAnd2faCode() fails with invalid code', (test, done) => { createUserAndLogout(test, done, () => { forceEnableUser2fa(() => { - Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', e => { - test.isFalse(Meteor.user()); + Meteor.loginWithPasswordAnd2faCode(username, password, 'ABC', async e => { + test.isFalse(await Meteor.user()); test.equal(e.reason, 'Invalid 2FA code'); removeTestUser(done); }); diff --git a/packages/accounts-base/accounts_server.js b/packages/accounts-base/accounts_server.js index c6d46fc3cf..7b28f3bd22 100644 --- a/packages/accounts-base/accounts_server.js +++ b/packages/accounts-base/accounts_server.js @@ -402,10 +402,10 @@ export class AccountsServer extends AccountsCommon { // indicates that the login token has already been inserted into the // database and doesn't need to be inserted again. (It's used by the // "resume" login handler). - _loginUser(methodInvocation, userId, stampedLoginToken) { + async _loginUser(methodInvocation, userId, stampedLoginToken) { if (! stampedLoginToken) { stampedLoginToken = this._generateStampedLoginToken(); - this._insertLoginToken(userId, stampedLoginToken); + await this._insertLoginToken(userId, stampedLoginToken); } // This order (and the avoidance of yields) is important to make @@ -476,12 +476,13 @@ export class AccountsServer extends AccountsCommon { this._validateLogin(methodInvocation.connection, attempt); if (attempt.allowed) { + const o = await this._loginUser( + methodInvocation, + result.userId, + result.stampedLoginToken + ) const ret = { - ...this._loginUser( - methodInvocation, - result.userId, - result.stampedLoginToken - ), + ...o, ...result.options }; ret.type = attempt.type; @@ -695,7 +696,7 @@ export class AccountsServer extends AccountsCommon { const newStampedToken = accounts._generateStampedLoginToken(); newStampedToken.when = currentStampedToken.when; await accounts._insertLoginToken(this.userId, newStampedToken); - return accounts._loginUser(this, this.userId, newStampedToken); + return await accounts._loginUser(this, this.userId, newStampedToken); }; // Removes all tokens except the token associated with the current diff --git a/packages/accounts-base/accounts_tests.js b/packages/accounts-base/accounts_tests.js index 4431c7f6a9..458c6ce589 100644 --- a/packages/accounts-base/accounts_tests.js +++ b/packages/accounts-base/accounts_tests.js @@ -702,6 +702,8 @@ Tinytest.addAsync( async test => { // create test user, without a google service const testEmail = "test@testdomain.com" + // being sure that the user is not already in the database + await Meteor.users.remove({ "emails.address": testEmail }); const uid0 = await Accounts.createUser({ email: testEmail }) // Verify that user is found from email and service merged diff --git a/packages/accounts-base/accounts_tests_setup.js b/packages/accounts-base/accounts_tests_setup.js index 31ed7e2196..c83ce5677b 100644 --- a/packages/accounts-base/accounts_tests_setup.js +++ b/packages/accounts-base/accounts_tests_setup.js @@ -9,7 +9,7 @@ const getTokenFromSecret = async ({ selector, secret: secretParam }) => { } secret = twoFactorAuthentication.secret; } - const { token } = Accounts._generate2faToken(secret); + const { token } = await Accounts._generate2faToken(secret); return token; }; @@ -30,7 +30,7 @@ Meteor.methods({ }, } ); - return getTokenFromSecret({ selector, secret }); + return await getTokenFromSecret({ selector, secret }); }, getTokenFromSecret, }); diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 9c89bb5fd5..b707f589a2 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -63,5 +63,5 @@ Package.onTest(api => { api.addFiles('accounts_tests_setup.js', 'server'); api.mainModule('server_tests.js', 'server'); - // api.mainModule('client_tests.js', 'client'); + api.mainModule('client_tests.js', 'client'); });