<commit_before>// Attempt to log in with a password.
//
// @param selector {String|Object} One of the following:
//   - {username: (username)}
//   - {email: (email)}
//   - a string which may be a username or email, depending on whether
//     it contains "@".
// @param password {String}
// @param callback {Function(error|undefined)}

/**
 * @summary Log the user in with a password.
 * @locus Client
 * @param {Object | String} user Either a string interpreted as a username or an email; or an object with a single key: `email`, `username` or `id`.
 * @param {String} password <PASSWORD>.
 * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 */
Meteor.loginWithPassword = function (selector, password, callback) {
  if (typeof selector === 'string')
    if (selector.indexOf('@') === -1)
      selector = {username: selector};
    else
      selector = {email: selector};

  Accounts.callLoginMethod({
    methodArguments: [{
      user: selector,
      password: Accounts._hashPassword(password)
    }],
    userCallback: function (error, result) {
      if (error && error.error === 400 &&
          error.reason === 'old password format') {
        // The "reason" string should match the error thrown in the
        // password login handler in password_server.js.

        // XXX COMPAT WITH 0.8.1.3
        // If this user's last login was with a previous version of
        // Meteor that used SRP, then the server throws this error to
        // indicate that we should try again. The error includes the
        // user's SRP identity. We provide a value derived from the
        // identity and the password to prove to the server that we know
        // the password without requiring a full SRP flow, as well as
        // SHA256(password), which the server bcrypts and stores in
        // place of the old SRP information for this user.
        srpUpgradePath({
          upgradeError: error,
          userSelector: selector,
          plaintextPassword: password
        }, callback);
      }
      else if (error) {
        callback && callback(error);
      } else {
        callback && callback();
      }
    }
  });
};

Accounts._hashPassword = function (password) {
  return {
    digest: SHA256(password),
    algorithm: "sha-256"
  };
};

// XXX COMPAT WITH 0.8.1.3
// The server requested an upgrade from the old SRP password format,
// so supply the needed SRP identity to login. Options:
//   - upgradeError: the error object that the server returned to tell
//     us to upgrade from SRP to bcrypt.
//   - userSelector: selector to retrieve the user object
//   - plaintextPassword: the password as a string
var srpUpgradePath = function (options, callback) {
  var details;
  try {
    details = EJSON.parse(options.upgradeError.details);
  } catch (e) {}
  if (!(details && details.format === 'srp')) {
    callback && callback(
      new Meteor.Error(400, "Password is old. Please reset your " +
                       "password."));
  } else {
    Accounts.callLoginMethod({
      methodArguments: [{
        user: options.userSelector,
        srp: SHA256(details.identity + ":" + options.plaintextPassword),
        password: <PASSWORD>(options.plaintextPassword)
      }],
      userCallback: callback
    });
  }
};


// Attempt to log in as a new user.

/**
 * @summary Create a new user.
 * @locus Anywhere
 * @param {Object} options
 * @param {String} options.username A unique name for this user.
 * @param {String} options.email The user's email address.
 * @param {String} options.password <PASSWORD>. This is __not__ sent in plain text over the wire.
 * @param {Object} options.profile The user's profile, typically including the `name` field.
 * @param {Function} [callback] Client only, optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 */
Accounts.createUser = function (options, callback) {
  options = _.clone(options); // we'll be modifying options

  if (typeof options.password !== 'string')
    throw new Error("Must set options.password");
  if (!options.password) {
    callback(new Meteor.Error(400, "Password may not be empty"));
    return;
  }

  // Replace password with the hashed password.
  options.password = Accounts._hashPassword(options.password);

  Accounts.callLoginMethod({
    methodName: 'createUser',
    methodArguments: [options],
    userCallback: callback
  });
};



// Change password. Must be logged in.
//<commit_msg>Improve Accounts.createUser error message<commit_after>// Attempt to log in with a password.
//
// @param selector {String|Object} One of the following:
//   - {username: (username)}
//   - {email: (email)}
//   - a string which may be a username or email, depending on whether
//     it contains "@".
// @param password {String}
// @param callback {Function(error|undefined)}

/**
 * @summary Log the user in with a password.
 * @locus Client
 * @param {Object | String} user Either a string interpreted as a username or an email; or an object with a single key: `email`, `username` or `id`.
 * @param {String} password The <PASSWORD>.
 * @param {Function} [callback] Optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 */
Meteor.loginWithPassword = function (selector, password, callback) {
  if (typeof selector === 'string')
    if (selector.indexOf('@') === -1)
      selector = {username: selector};
    else
      selector = {email: selector};

  Accounts.callLoginMethod({
    methodArguments: [{
      user: selector,
      password: Accounts._hashPassword(password)
    }],
    userCallback: function (error, result) {
      if (error && error.error === 400 &&
          error.reason === 'old password format') {
        // The "reason" string should match the error thrown in the
        // password login handler in password_server.js.

        // XXX COMPAT WITH 0.8.1.3
        // If this user's last login was with a previous version of
        // Meteor that used SRP, then the server throws this error to
        // indicate that we should try again. The error includes the
        // user's SRP identity. We provide a value derived from the
        // identity and the password to prove to the server that we know
        // the password without requiring a full SRP flow, as well as
        // SHA256(password), which the server bcrypts and stores in
        // place of the old SRP information for this user.
        srpUpgradePath({
          upgradeError: error,
          userSelector: selector,
          plaintextPassword: password
        }, callback);
      }
      else if (error) {
        callback && callback(error);
      } else {
        callback && callback();
      }
    }
  });
};

Accounts._hashPassword = function (password) {
  return {
    digest: SHA256(password),
    algorithm: "sha-256"
  };
};

// XXX COMPAT WITH 0.8.1.3
// The server requested an upgrade from the old SRP password format,
// so supply the needed SRP identity to login. Options:
//   - upgradeError: the error object that the server returned to tell
//     us to upgrade from SRP to bcrypt.
//   - userSelector: selector to retrieve the user object
//   - plaintextPassword: the password as a string
var srpUpgradePath = function (options, callback) {
  var details;
  try {
    details = EJSON.parse(options.upgradeError.details);
  } catch (e) {}
  if (!(details && details.format === 'srp')) {
    callback && callback(
      new Meteor.Error(400, "Password is old. Please reset your " +
                       "password."));
  } else {
    Accounts.callLoginMethod({
      methodArguments: [{
        user: options.userSelector,
        srp: SHA256(details.identity + ":" + options.plaintextPassword),
        password: Accounts._hashPassword(options.plaintextPassword)
      }],
      userCallback: callback
    });
  }
};


// Attempt to log in as a new user.

/**
 * @summary Create a new user.
 * @locus Anywhere
 * @param {Object} options
 * @param {String} options.username A unique name for this user.
 * @param {String} options.email The user's email address.
 * @param {String} options.password The <PASSWORD>'s password. This is __not__ sent in plain text over the wire.
 * @param {Object} options.profile The user's profile, typically including the `name` field.
 * @param {Function} [callback] Client only, optional callback. Called with no arguments on success, or with a single `Error` argument on failure.
 */
Accounts.createUser = function (options, callback) {
  options = _.clone(options); // we'll be modifying options

  if (typeof options.password !== 'string')
    throw new Error("options.password must be a string");
  if (!options.password) {
    callback(new Meteor.Error(400, "Password may not be empty"));
    return;
  }

  // Replace password with the hashed password.
  options.password = Accounts._hashPassword(options.password);

  Accounts.callLoginMethod({
    methodName: 'createUser',
    methodArguments: [options],
    userCallback: callback
  });
};



// Change password. Must be logged in.
//
{'fraction_non_alphanumeric': 0.112375533428165, 'fraction_numerical': 0.00656526972316446, 'mean_word_length': 2.845183003786285, 'pattern_counts': {'":': 2, '<': 8, '<?xml version=': 0, '>': 8, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 4, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3511607', 'n_tokens_mistral': 2537, 'n_tokens_neox': 2451, 'n_words': 972}