如何向Meteor中已有的帐户添加外部服务登录?

如何向Meteor中已有的帐户添加外部服务登录?,meteor,Meteor,为我的应用程序创建了一个个人资料页面后,我想显示用户所使用的社交服务列表。我突然想到,最简单的方法就是使用Meteor的内置帐户系统来实现这一点 有没有一种好方法可以将外部服务添加到现有帐户 此外,用户是否可以从我的应用程序中使用(例如)Facebook和他的密码登录 另一个自然会出现的问题是:有没有一种好方法可以将特定于应用程序的密码添加到使用外部服务创建的帐户中?是的,用户帐户可以与多个服务关联,并同时具有基于密码的登录。在中,您可以看到这样一个用户帐户的结构: { _id: "bbca

为我的应用程序创建了一个个人资料页面后,我想显示用户所使用的社交服务列表。我突然想到,最简单的方法就是使用Meteor的内置帐户系统来实现这一点

有没有一种好方法可以将外部服务添加到现有帐户

此外,用户是否可以从我的应用程序中使用(例如)Facebook和他的密码登录


另一个自然会出现的问题是:有没有一种好方法可以将特定于应用程序的密码添加到使用外部服务创建的帐户中?

是的,用户帐户可以与多个服务关联,并同时具有基于密码的登录。在中,您可以看到这样一个用户帐户的结构:

{
  _id: "bbca5d6a-2156-41c4-89da-0329e8c99a4f",  // Meteor.userId()
  username: "cool_kid_13", // unique name
  emails: [
    // each email address can only belong to one user.
    { address: "cool@example.com", verified: true },
    { address: "another@different.com", verified: false }
  ],
  createdAt: 1349761684042,
  profile: {
    // The profile is writable by the user by default.
    name: "Joe Schmoe"
  },
  services: {
    facebook: {
      id: "709050", // facebook id
      accessToken: "AAACCgdX7G2...AbV9AZDZD"
    },
    resume: {
      loginTokens: [
        { token: "97e8c205-c7e4-47c9-9bea-8e2ccc0694cd",
          when: 1349761684048 }
      ]
    }
  }
}
要将用户名/密码登录添加到现有帐户,可以使用服务器端的Accounts.sendResetPasswordEmail。这还可以确保更改经过身份验证和授权

当然,您也可以自己用新密码更新服务器端的用户记录,但这可能会在应用程序中造成安全漏洞。如果可能的话,我还建议您不要为此实施自己的加密协议

例如,如果您想添加电子邮件以外的其他服务,您可以

  • 调用一个服务器方法,该方法在当前用户的MongoDB文档中保存一个随机的长令牌并将其返回给客户端
  • 使用
    Accounts.login和[OtherService]
    使用另一项服务重新登录用户。这将使用另一个服务上的新帐户注销用户并再次登录
  • 使用第一个方法返回的令牌作为参数调用第二个服务器方法。第二种方法使用给定的令牌搜索用户帐户,并将其数据合并到当前(新)帐户中

  • 下面是我如何向现有用户帐户添加凭据的方法:

    这里有一种替代方法。在这个解决方案中,我重写了一个核心函数并添加了一些自定义行为。我的目标是将服务数据与当前登录的用户相关联,然后允许核心功能正常工作

    orig_updateOrCreateUserFromExternalService = Accounts.updateOrCreateUserFromExternalService;
    Accounts.updateOrCreateUserFromExternalService = function(serviceName, serviceData, options) {
      var loggedInUser = Meteor.user();
      if(loggedInUser && typeof(loggedInUser.services[serviceName]) === "undefined") {
        var setAttr = {};
        setAttr["services." + serviceName] = serviceData;
        Meteor.users.update(loggedInUser._id, {$set: setAttr});
      }
      return orig_updateOrCreateUserFromExternalService.apply(this, arguments);
    }
    
    优点:

    • 避免创建不必要的帐户
    • 代码简短易懂
    • 如果将此功能添加到Meteor core中,代码很容易删除
    缺点:

    • 要求用户登录。如果用户最初使用twitter登录,然后注销,然后使用facebook登录,那么将创建两个独立的帐户
    • 共享计算机的用户可能会无意中合并其帐户
    • 依赖于updateOrCreateUserFromExternalService如何工作的知识。这并不可怕——因为它是Meteor公共api的一部分,所以它可能不会有太大的变化(至少不会经常发生)。但这仍然有风险

    查看此帖子中的示例并回答。它几乎为您提供了集成多个外部和内部帐户的代码。通过一些小的调整,您可以根据需要为每个帐户添加密码字段

    代码:


    是的,但是如何添加多个服务?开始。显然,这似乎是一个可怕的黑客,但它应该内置到流星。明天我会试一试,如果我得到任何快乐,我会接受你的回答。我已经给了你奖金,但这只是因为你是唯一的回答者。我认为这不是一个令人满意的答案,所以我没有接受。谢谢!我还认为这应该包括在Meteor本身中,即使Meteor自己的实现最终看起来很相似。您是否能够以这种方式实现它,或者甚至找到更好的实现?是否有人能够更优雅地实现它?如果原始帐户中附加了重要数据,因此无法删除该数据,该怎么办。任何人都有一个使用新的独立oauth包的工作示例吗?我正在学习,您是按照答案中的解释还是按照您自己的方式来做的?这可能是我能找到的最佳答案,但我无法让它工作。我将其添加到服务器上运行,包括帐户密码和facebook帐户。然后我用密码登录,调用Meteor.loginWithFacebook(),它将一个空的服务对象添加到Meteor().user()。它还在服务器控制台中记录了一个错误,上面写着:调用方法“login”时异常错误:Meteor.userId只能在方法调用中调用。发布函数中的User this.userId。虽然这在理论上可以回答问题,但在此处包含答案的基本部分,并提供链接以供参考。
    isProdEnv = function () {
        if (process.env.ROOT_URL == "http://localhost:3000") {
            return false;
        } else {
            return true;
        }
    }
    
    Accounts.loginServiceConfiguration.remove({
        service: 'google'
    });
    
    Accounts.loginServiceConfiguration.remove({
        service: 'facebook'
    });
    
    Accounts.loginServiceConfiguration.remove({
        service: 'twitter'
    });
    
    Accounts.loginServiceConfiguration.remove({
        service: 'github'
    });
    
    if (isProdEnv()) {
        Accounts.loginServiceConfiguration.insert({
            service: 'github',
            clientId: '00000',
            secret: '00000'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'twitter',
            consumerKey: '00000',
            secret: '00000'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'google',
            appId: '00000',
            secret: '00000'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'facebook',
            appId: '00000',
            secret: '00000'
        });
    } else {
        // dev environment
        Accounts.loginServiceConfiguration.insert({
            service: 'github',
            clientId: '11111',
            secret: '11111'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'twitter',
            consumerKey: '11111',
            secret: '11111'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'google',
            clientId: '11111',
            secret: '11111'
        });
        Accounts.loginServiceConfiguration.insert({
            service: 'facebook',
            appId: '11111',
            secret: '11111'
        });
    }
    
    Accounts.onCreateUser(function (options, user) {
        if (user.services) {
            if (options.profile) {
                user.profile = options.profile
            }
            var service = _.keys(user.services)[0];
            var email = user.services[service].email;
            if (!email) {
                if (user.emails) {
                    email = user.emails.address;
                }
            }
            if (!email) {
                email = options.email;
            }
            if (!email) {
                // if email is not set, there is no way to link it with other accounts
                return user;
            }
    
            // see if any existing user has this email address, otherwise create new
            var existingUser = Meteor.users.findOne({'emails.address': email});
            if (!existingUser) {
                // check for email also in other services
                var existingGitHubUser = Meteor.users.findOne({'services.github.email': email});
                var existingGoogleUser = Meteor.users.findOne({'services.google.email': email});
                var existingTwitterUser = Meteor.users.findOne({'services.twitter.email': email});
                var existingFacebookUser = Meteor.users.findOne({'services.facebook.email': email});
                var doesntExist = !existingGitHubUser && !existingGoogleUser && !existingTwitterUser && !existingFacebookUser;
                if (doesntExist) {
                    // return the user as it came, because there he doesn't exist in the DB yet
                    return user;
                } else {
                    existingUser = existingGitHubUser || existingGoogleUser || existingTwitterUser || existingFacebookUser;
                    if (existingUser) {
                        if (user.emails) {
                            // user is signing in by email, we need to set it to the existing user
                            existingUser.emails = user.emails;
                        }
                    }
                }
            }
    
            // precaution, these will exist from accounts-password if used
            if (!existingUser.services) {
                existingUser.services = { resume: { loginTokens: [] }};
            }
    
            // copy accross new service info
            existingUser.services[service] = user.services[service];
            existingUser.services.resume.loginTokens.push(
                user.services.resume.loginTokens[0]
            );
    
            // even worse hackery
            Meteor.users.remove({_id: existingUser._id}); // remove existing record
            return existingUser;                  // record is re-inserted
        }
    });