Meteor великолепен, он позволяет вам работать очень продуктивно за очень короткое время. Это включает в себя предоставление вам полнофункциональной, простой в использовании системы учетных записей. Среди множества функций одной из наиболее полезных является выход из системы других клиентов с сохранением текущего сеанса.

Но, как вы могли заметить, при использовании этой функции, если у пользователя открыто более одной вкладки в одном и том же браузере, он также закроет их все, оставив только одну.

Почему

Каждый раз, когда пользователь открывает новую вкладку, Meteor генерирует новый resumeToken, чтобы отслеживать это соединение, поэтому, когда мы вызываем Meteor.logoutOtherClients(), он удаляет все resumeToken, потому что на практике это новый логин.

Проблема

Если я вам нравлюсь, вы хотите запретить пользователям делиться своими учетными данными, позволяя нескольким людям использовать один и тот же логин и пароль, использование только Meteor.logoutOtherClients() не создаст наилучшего взаимодействия с пользователем.

Ваш пользователь может захотеть работать с несколькими вкладками (а кто не хочет?), Но если вы прикрепите его к Accounts.onLogin, вы всегда будете убивать предыдущую вкладку, что действительно расстраивает.

Нижеприведенное решение мы используем в течение многих лет в производстве (на EducationLink), оно проверено на практике.

Использование отпечатков пальцев

Прежде чем продолжить, вы можете проверить этот другой метод выполнения тех же действий, но с использованием только серверного кода и без зависимостей: https://forums.meteor.com/t/how-to-how-to-prevent- несколько-логинов-с-метеором-разрешением-несколькими-вкладками / 32356

Отпечатки браузера - это:

Снятие отпечатков пальцев - это метод анонимной идентификации веб-браузера с точностью до 94%, описанный в исследовании Electronic Frontier Foundation.

У браузера запрашивается строка агента, глубина цвета экрана, язык, установленные плагины с поддерживаемыми типами MIME, смещение часового пояса и другие возможности, такие как локальное хранилище и хранилище сеансов. Затем эти значения передаются через функцию хеширования для получения отпечатка пальца, который дает слабые гарантии уникальности.

Файлы cookie для идентификации браузера не сохраняются.

Источник: https://github.com/Valve/fingerprintjs (устаревшая библиотека)

Хотя он не идеален и вызывает большие споры при использовании в маркетинговых / рекламных целях, он довольно точно определяет уникальность браузера.

Решение

Установите новый пакет NPM Fingerprintjs2:

meteor npm install fingerprintjs2

На стороне сервера обновите SimpleSchema (если вы ее используете), чтобы разрешить поле отпечатка пальца:

// User schemas
import { Meteor } from 'meteor/meteor';
import SimpleSchema from 'simpl-schema';
const userSchema = new SimpleSchema({
  ...mySchema, // Here just of illustrative purposes, this is your schema
  fingerprint: {
    type: String,
    optional: true,
  },
});
Meteor.users.attachSchema(userSchema);

Тем не менее на стороне сервера создайте новый метод для проверки отпечатка пальца и его обновления:

// User methods: update fingerprint
import { Meteor } from 'meteor/meteor';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import SimpleSchema from 'simpl-schema';
import { loggedIn } from '/imports/api/mixins';
export const updateFingerprint = new ValidatedMethod({
  name: 'user.updateFingerprint',
  mixins: [loggedIn],

  validate: new SimpleSchema({
    fingerprint: {
      type: String,
    },
  }).validator(),

  run({ fingerprint }) {
    this.unblock();
    // Retrieve current fingerprint
    const userId = this.userId;
    const user = Meteor.users.findOne({ _id: userId }, { fields: { fingerprint: true } });

    // If no fingerprint, store it
    if (user && !user.fingerprint) {
      Meteor.users.update({ _id: userId }, { $set: { fingerprint } });
      return;
    }

    // If fingerprint is different, throw error and update the      fingerprint
    // On the client side, it will logout other users
    if (user && user.fingerprint !== fingerprint) {
      Meteor.users.update({ _id: userId }, { $set: { fingerprint } });
      throw new Meteor.Error('multiple-logins');
    }
  },
});

Обратите внимание, что я использую mixin, чтобы проверить, вошел ли пользователь в систему, если у вас его нет, избавьтесь от него.

На стороне клиента:

// On login hook
import { Accounts } from 'meteor/accounts-base';
import { Meteor } from 'meteor/meteor';
// Logout all other clients on login to prevent users using same user
Accounts.onLogin(() => {
  if (Meteor.isClient) {
    import('fingerprintjs2')
      .then((mod) => {
        let Fingerprint2 = null;
        if (process.env.NODE_ENV === 'test') {
          Fingerprint2 = mod;
        }
        Fingerprint2 = mod.default;
        const options = {
          excludes: {
            plugins: true,
            adBlock: true,
            screenResolution: true,
            availableScreenResolution: true,
          },
        };
       if (window.requestIdleCallback) {
         requestIdleCallback(() => {
           Fingerprint2.get(options, (components) => {
             const values = components.map((component) => component.value);
             const fingerprint = Fingerprint2.x64hash128(values.join(''), 31);
             Meteor.call('users.updateFingerprint', { fingerprint }, (error) => {
               if (error) {
                 Meteor.logoutOtherClients();
               }
             });
          });
        });
      } else {
        setTimeout(() => {
          Fingerprint2.get(options, (components) => {
            const values = components.map((component) => component.value);
            const fingerprint = Fingerprint2.x64hash128(values.join(''), 31);
            Meteor.call('users.updateFingerprint', { fingerprint }, (error) => {
              if (error) {
                Meteor.logoutOtherClients();
              }
            });
          });
        }, 500);
      }
    }
});

Что он делает?

Каждый раз, когда пользователь входит в систему, Meteor вызывает Accounts.onLogin. На этой ловушке мы вызываем асинхронную функцию Fingerprint2().get, чтобы получить отпечаток пальца и отправить его методу Meteor.

Метод проверит его, и если он выдаст ошибку, мы завершим другие сеансы с помощью Meteor.logoutOtherClients, сохраним новый отпечаток пальца и предупредим пользователя (необязательно).

Вывод

Вот и все, теперь пользователи смогут использовать ваш SaaS с несколькими вкладками, и вы будете достаточно уверены, что они не поделятся своими учетными данными в обход лицензии / места / подписки на пользователя.

Этот скрипт прошел боевые испытания уже 3 года. Работает как шарм. Мы используем его в нашем SaaS.

Обновление от 19 мая 2017 года: добавьте this.unblock, избавьтесь от .js при импорте модуля (ESLint) и сохраните идентификатор пользователя в константе, чтобы предотвратить три раза вызов Meteor.userId ().

Обновление от 17 июня 2017 года: добавлено «{excludePlugins: true}», чтобы предотвратить срабатывание ошибки Fingerprintjs2, даже если это тот же браузер.

Обновление от 4 октября 2018 г.: Улучшение стиля кода. Обновите SimpleSchema до пакета npm. Кроме того, мы добавляем setTimeout, чтобы предотвратить ошибку отпечатка пальца.

Обновление от 17 сентября 2019 года: обновленная версия Fingerprintjs2, удалите параметр, который теперь используется по умолчанию, и используйте requestIdleCallback.