如何将外部服务登录添加到Meteor中现有的帐户?

时间:2013-03-23 22:43:23

标签: meteor

为我的应用创建了个人资料页面后,我想显示用户所在的社交服务列表。让我感到震惊的是,最简单的方法是使用Meteor的内置帐户系统。

是否有一种向现有帐户添加外部服务的好方法?

此外,用户是否可以使用我的应用中的(例如)Facebook 密码登录?

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

4 个答案:

答案 0 :(得分:7)

这是另一种方法。在这个解决方案中,我重写了核心功能并添加了一些自定义行为。我的目标是将服务数据与当前登录的用户相关联,然后允许核心功能像平常那样做。

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核心
  • ,则很容易删除代码

缺点:

  • 要求用户登录。如果用户最初使用Twitter登录,注销,然后使用facebook登录,则会创建两个单独的帐户。
  • 共享计算机的用户可能会无意中合并其帐户。
  • 依赖于updateOrCreateUserFromExternalService如何工作的知识。这并不可怕 - 因为它是Meteor的公共API的一部分,它可能不会彻底改变(不管怎么说)。但它仍然存在风险。

答案 1 :(得分:3)

以下是我向现有用户帐户添加凭据的方式:.../meteor-how-to-login-with-github-account.html

答案 2 :(得分:1)

是的,用户帐户可以与多个服务相关联,并且同时具有基于密码的登录。在Meteor docs中,您可以看到此类用户帐户的结构:

{
  _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。这也可以确保更改经过身份验证和授权。

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

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

  1. 调用一个服务器方法,该方法在当前用户的MongoDB文档中保存一个随机的长令牌并将其返回给客户端。
  2. 使用Accounts.loginWith[OtherService]使用其他服务重新登录用户。这会使用其他服务上的新帐户重新登录用户。
  3. 使用第一个方法中返回的标记作为参数调用第二个服务器方法。第二种方法使用给定令牌搜索用户帐户,并将其数据合并到当前(新)帐户中。

答案 3 :(得分:0)

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

How to use Meteor.loginWithGoogle with mrt:accounts-ui-bootstrap-dropdown

代码:

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
    }
});