19

所以 Meteor 有这个很棒的 Accounts 包,它允许通过密码或其他服务轻松登录。但是,我目前正在创建一个需要多种服务的 Web 服务(facebook / twitter / 等)。此处的链接:如何将外部服务登录添加到 Meteor 中的现有帐户?通过创建重复帐户并仅合并数据来建议“破解”,但这对我来说似乎很不满意。

所以我的问题是:

1) 有没有一种更优雅的方式来使用 Accounts-xxx 包来创建一个用户但附加了多个服务?

2)如果没有,我可以使用现在分离的 oauth 包将令牌添加到一个用户。例如,如果稍后手动“附加”了 github 令牌,Accounts.loginWithGithub 以后还会发现手动合并的帐户吗?

谢谢你的帮助。

4

2 回答 2

27

解决了!

我已经通过对accounts-oauth-X包进行逆向工程解决了这个问题!处理所有边缘情况的最佳解决方案是让一个故事用于登录,另一个用于显式关联。以下是用 literate coffeescript 编写的解决方案。

前:

确保您拥有所需的软件包:

meteor add google
meteor add facebook
meteor add oauth

多服务登录方法:

如果帐户具有相同的电子邮件,则会自动加入帐户。最适合用于登录,因为它很健壮。但是,这对于显式关联并不好,因为如果电子邮件地址不同,那么它将不起作用。(手动合并将注销用户并强制您重新登录)。对于显式关联帐户,即使它有不同的电子邮件,请参阅下面的解决方案。

服务器端代码:

Accounts.onCreateUser (options, user) ->
  user.city = null
  user.networks = []
  user.attending =[]
  user.profile ?= {}

如果用户通过 oauth 服务注册,我们需要执行额外的逻辑,以便我们可以标准化姓名和电子邮件的位置。

  if user.services?
    service = _.keys(user.services)[0]

检查任何现有帐户是否已有与该服务关联的电子邮件。如果是这样,只需将用户信息纳入其中。

    email = user.services[service].email
    if email?
      oldUser = Meteor.users.findOne({"emails.address": email})

确保旧帐户具有所需的服务对象。

      if oldUser?
        oldUser.services ?= {}
        if service == "google" or service == "facebook"

将新的服务密钥合并到我们的旧用户中。我们还会检查是否有新的电子邮件要添加给我们的用户。然后从数据库中删除旧用户,再次返回即可直接再次创建。要创建的假定新用户被丢弃。

          oldUser.services[service] = user.services[service]
          Meteor.users.remove(oldUser._id)
          user = oldUser

否则,只需照常创建用户,标准化电子邮件和姓名字段。

      else
        if service == "google" or service == "facebook"
          if user.services[service].email?
            user.emails = [{address: user.services[service].email, verified: true}]
          else
            throw new Meteor.Error(500, "#{service} account has no email attached")
          user.profile.name = user.services[service].name

  return user

显式关联的方法:

这会将服务添加到用户记录上的服务散列中,就像它是由内置散列系统创建的一样。代码需要在客户端(获取服务 oauth 令牌)和部分服务器端(进行进一步的 API 调用以获取用户数据并将其与用户记录相关联)

客户端代码:

此功能是向我们的帐户添加功能的入口点。

addUserService = (service) ->

如果他们选择电子邮件,我们需要使用 Meteor 内置的电子邮件验证系统。

  if service == "email"

  else
    switch service

对于标准的 oauth 服务,我们在客户端请求凭据,然后将它们传递给服务器以进行进一步的 API 调用以收集必要的信息并将该信息添加到我们的帐户中。

      when "facebook"
        Facebook.requestCredential(
          requestPermissions: ["email", "user_friends", "manage_notifications"],
        , (token) ->
          Meteor.call "userAddOauthCredentials", token, Meteor.userId(), service, (err, resp) ->
            if err?
              Meteor.userError.throwError(err.reason)
        )
      when "google"
        Google.requestCredential
          requestPermissions: ["email", "https://www.googleapis.com/auth/calendar"]
          requestOfflineToken: true,
        , (token) ->
          Meteor.call "userAddOauthCredentials", token, Meteor.userId(), service, (err, resp) ->
            if err?
              Meteor.userError.throwError(err.reason)

我们只需要设置一个简单的绑定将它们粘合在一起。

Template.userAddServices.events
  "click button": (e) ->
    e.preventDefault()
    service = $(event.target).data("service")
    addUserService(service)

服务器端代码:

这里我们定义了与用户模型相关的服务器端方法。

Meteor.methods

这从客户端传递了数据,客户端已经获取了访问令牌。使用令牌来发现有关该服务用户的其余信息。然后它检查该帐户是否已经注册,如果没有 - 将其添加到当前帐户。

  userAddOauthCredentials: (token, userId, service) ->
    switch service
      when "facebook"
        data = Facebook.retrieveCredential(token).serviceData
      when "google"
        data = Google.retrieveCredential(token).serviceData

    selector = "services.#{service}.id"
    oldUser = Meteor.users.findOne({selector: data.id})
    if oldUser?
      throw new Meteor.Error(500, "This #{service} account has already" +
      "been assigned to another user.")

    updateSelector = "services.#{service}"
    Meteor.users.update(userId, {$set: {updateSelector: data }})

我们也可以通过此帐户发送电子邮件。如果当前用户还没有它,我们可以抓取它并添加它。

    if not _.contains(Meteor.user().emails, data.email)
      Meteor.users.update(userId, {$push: {"emails": {address: data.email, verified: true} }})
于 2013-08-22T13:38:37.240 回答
2

删除现有用户可能会在某些情况下产生问题。在我的用例中,我需要在使用另一个 oauth 帐户登录系统时合并 google 帐户。这可以通过以下修改来实现。

修改 Accounts.onCreateUser

Accounts.onCreateUser(function(options, user) {

if ( user.services )
    service = _.keys( user.services )[0]; // get service type

var email = user.services[service].email ;
var oldUser = Meteor.users.findOne({ 'emails.address': email });

if ( oldUser ){
    oldUser.services = oldUser.services ? oldUser.services : {};

    oldUser.services[service] = user.services[ service ];     
    return oldUser;     

}

return user;

});

修改accounts-base包

userId = Meteor.users.insert(fullUser); 

packages/accounts-base/accounts_server.js 的 Accounts.insertUserDoc 函数中的行应替换为以下代码。希望 Meteor 能解决这个问题。直到必须使用自定义包。

if(Meteor.users.findOne(fullUser._id)){
  userId = fullUser._id;
  delete fullUser._id;
  Meteor.users.update(userId, {$set: fullUser});
} else
  userId = Meteor.users.insert(fullUser);
于 2015-01-18T22:50:21.953 回答