mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Added defaultFieldSelector and used it in Accounts callbacks. #10469
This commit is contained in:
@@ -132,6 +132,7 @@ export class AccountsCommon {
|
||||
* @param {Number} options.passwordResetTokenExpirationInDays The number of days from when a link to reset password is sent until token expires and user can't reset password with the link anymore. Defaults to 3.
|
||||
* @param {Number} options.passwordEnrollTokenExpirationInDays The number of days from when a link to set inital password is sent until token expires and user can't set password with the link anymore. Defaults to 30.
|
||||
* @param {Boolean} options.ambiguousErrorMessages Return ambiguous error messages from login failures to prevent user enumeration. Defaults to false.
|
||||
* @param {Object} options.defaultFieldSelector Default Mongo field selector, to exclude large custom fields from `Meteor.user()` & `Meteor.findUserBy...()` functions when called without a field selector and `onLogin`, `onLoginFailure` & `onLogout` callbacks. Example: `Accounts.config({ defaultFieldSelector: { myBigArray: 0 }})`.
|
||||
*/
|
||||
config(options) {
|
||||
// We don't want users to accidentally only call Accounts.config on the
|
||||
@@ -166,7 +167,8 @@ export class AccountsCommon {
|
||||
// validate option keys
|
||||
const VALID_KEYS = ["sendVerificationEmail", "forbidClientAccountCreation", "passwordEnrollTokenExpirationInDays",
|
||||
"restrictCreationByEmailDomain", "loginExpirationInDays", "passwordResetTokenExpirationInDays",
|
||||
"ambiguousErrorMessages", "bcryptRounds"];
|
||||
"ambiguousErrorMessages", "bcryptRounds", "defaultFieldSelector"];
|
||||
|
||||
Object.keys(options).forEach(key => {
|
||||
if (!VALID_KEYS.includes(key)) {
|
||||
throw new Error(`Accounts.config: Invalid key: ${key}`);
|
||||
@@ -321,4 +323,4 @@ export const EXPIRE_TOKENS_INTERVAL_MS = 600 * 1000; // 10 minutes
|
||||
export const CONNECTION_CLOSE_DELAY_MS = 10 * 1000;
|
||||
// A large number of expiration days (approximately 100 years worth) that is
|
||||
// used when creating unexpiring tokens.
|
||||
const LOGIN_UNEXPIRING_TOKEN_DAYS = 365 * 100;
|
||||
const LOGIN_UNEXPIRING_TOKEN_DAYS = 365 * 100;
|
||||
@@ -188,7 +188,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
};
|
||||
|
||||
_successfulLogout(connection, userId) {
|
||||
const user = userId && this.users.findOne(userId);
|
||||
const user = userId && this.users.findOne(userId, {fields: this._options.defaultFieldSelector});
|
||||
this._onLogoutHook.each(callback => {
|
||||
callback({ user, connection });
|
||||
return true;
|
||||
@@ -317,7 +317,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
|
||||
let user;
|
||||
if (result.userId)
|
||||
user = this.users.findOne(result.userId);
|
||||
user = this.users.findOne(result.userId, {fields: this._options.defaultFieldSelector});
|
||||
|
||||
const attempt = {
|
||||
type: result.type || "unknown",
|
||||
@@ -398,7 +398,7 @@ export class AccountsServer extends AccountsCommon {
|
||||
};
|
||||
|
||||
if (result.userId) {
|
||||
attempt.user = this.users.findOne(result.userId);
|
||||
attempt.user = this.users.findOne(result.userId, {fields: this._options.defaultFieldSelector});
|
||||
}
|
||||
|
||||
this._validateLogin(methodInvocation.connection, attempt);
|
||||
@@ -1576,4 +1576,4 @@ const setupUsersCollection = users => {
|
||||
users._ensureIndex("services.resume.loginTokens.when", { sparse: 1 });
|
||||
// For expiring password tokens
|
||||
users._ensureIndex('services.password.reset.when', { sparse: 1 });
|
||||
};
|
||||
};
|
||||
@@ -57,6 +57,15 @@ Tinytest.add('accounts - config - default token lifetime', test => {
|
||||
Accounts._options = options;
|
||||
});
|
||||
|
||||
Tinytest.add('accounts - config - defaultFieldSelector', test => {
|
||||
const options = Accounts._options;
|
||||
Accounts._options = {};
|
||||
const setValue = {bigArray: 0};
|
||||
Accounts.config({defaultFieldSelector: setValue});
|
||||
test.equal(Accounts._options.defaultFieldSelector, setValue);
|
||||
Accounts._options = options;
|
||||
});
|
||||
|
||||
const idsInValidateNewUser = {};
|
||||
Accounts.validateNewUser(user => {
|
||||
idsInValidateNewUser[user._id] = true;
|
||||
@@ -451,6 +460,63 @@ Tinytest.add(
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.add(
|
||||
'accounts - hook callbacks obey options.defaultFieldSelector',
|
||||
test => {
|
||||
const ignoreFieldName = "bigArray";
|
||||
const userId = Accounts.insertUserDoc({}, { username: Random.id(), [ignoreFieldName]: [1] });
|
||||
const stampedToken = Accounts._generateStampedLoginToken();
|
||||
Accounts._insertLoginToken(userId, stampedToken);
|
||||
const options = Accounts._options;
|
||||
Accounts._options = {};
|
||||
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")
|
||||
return allowLogin;
|
||||
});
|
||||
const onLoginStopper = Accounts.onLogin(attempt =>
|
||||
test.isUndefined(attempt.user[ignoreFieldName], "onLogin")
|
||||
);
|
||||
const onLogoutStopper = Accounts.onLogout(logoutContext =>
|
||||
test.isUndefined(logoutContext.user[ignoreFieldName], "onLogout")
|
||||
);
|
||||
const onLoginFailureStopper = Accounts.onLoginFailure(attempt =>
|
||||
test.isUndefined(allowLogin != 'bogus' ? attempt.user[ignoreFieldName] : attempt.user, "onLoginFailure")
|
||||
);
|
||||
|
||||
const conn = DDP.connect(Meteor.absoluteUrl());
|
||||
|
||||
// test a new connection
|
||||
let allowLogin = true;
|
||||
conn.call('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 });
|
||||
|
||||
// Trigger onLoginFailure callbacks, this will not include the user object
|
||||
allowLogin = 'bogus';
|
||||
test.throws(() => conn.call('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');
|
||||
|
||||
// Trigger onLogout callbacks
|
||||
const onLogoutExpectedUserId = userId;
|
||||
conn.call('logout');
|
||||
|
||||
Accounts._options = options;
|
||||
conn.disconnect();
|
||||
validateStopper.stop();
|
||||
onLoginStopper.stop();
|
||||
onLogoutStopper.stop();
|
||||
onLoginFailureStopper.stop();
|
||||
}
|
||||
);
|
||||
|
||||
Tinytest.add(
|
||||
'accounts - verify onExternalLogin hook can update oauth user profiles',
|
||||
test => {
|
||||
@@ -499,4 +565,4 @@ Tinytest.add(
|
||||
Meteor.users.remove(uid2);
|
||||
Accounts._onExternalLoginHook = null;
|
||||
}
|
||||
);
|
||||
);
|
||||
Reference in New Issue
Block a user