使用访问令牌重置环回密码

时间:2015-09-21 22:53:13

标签: javascript angularjs node.js loopbackjs strongloop

我正在开发一个使用Loopback作为框架的项目,并包括用户和身份验证。我添加了一个密码重置路由生成并通过电子邮件发送,一切似乎都正常工作。最近,我发现密码重置似乎不起作用。此处重置密码的过程是:

  • 为用户调用密码重置方法
  • 从重置事件发送电子邮件,包括用户ID和访问令牌
  • 从重置链接,将$ http.defaults.headers.common.authorization设置为传递的令牌
  • 调用user.prototype $ updateAttributes(由lb-ng生成)以根据表单更新密码属性

预期的行为是密码将在密码重置表单上更新。相反,我得到一个授权错误,如401或500(似乎来回)。我注意到在发送给API的实际标头中,授权令牌与我从路由传递的内容不匹配。尝试使用LoopBackAUth.setUser设置它不起作用,也没有在实际发送请求之前不更新授权属性。

我在第一次添加它时肯定花时间测试它,我无法弄清楚会有什么改变来打破这个。我一直在关注loopback-faq-user-management中的示例,但在该示例中我们有一个Angular前端而不是服务器端视图。

修改

我尝试完全打开ACL,看看我是否可以更新用户对象的密码(或任何属性)(继承自User,但它是自己的类型)。在尝试这样做的时候,我仍然得到401。

编辑#2:

以下是我的ACL以及我如何调用它的示例代码。

来自模型定义的ACL

...
{
    "accessType": "*",
    "principalType": "ROLE",
    "principalId": "$owner",
    "permission": "ALLOW"
},
{
    "accessType": "EXECUTE",
    "principalType": "ROLE",
    "principalId": "$owner",
    "permission": "ALLOW",
    "property": "updateAttributes"
}
...

auth.js

...
resetPassword: function(user) {
    return MyUser.prototype$updateAttributes(user, user).$promise;
}
...

4 个答案:

答案 0 :(得分:5)

找出问题所在。在我们的应用程序服务器中,我们没有使用Loopback的令牌中间件。在启动服务器之前添加app.use(loopback.token());会导致重置链接中提供的访问令牌按预期工作!

答案 1 :(得分:2)

@OverlappingElvis让我走上正轨。对于遇到此问题的其他人来说,这是一个更完整的答案。环回文档在这方面非常有限。

确保您在电子邮件中 用户ID和令牌,这些将填写在表单中。

从表单中可以看到以下代码:

 function resetPassword(id, token, password) {
  $http.defaults.headers.common.authorization = token;

  return User
    .prototype$updateAttributes({id:id}, {
     password: password
   })
   .$promise;

}

答案 2 :(得分:2)

虽然上述所有答案都会有所帮助,但在验证时,请注意Loopback destroys a token,证明它无效。令牌将消失。因此,当您正在使用401的解决方案时,请确保每次尝试新的代码迭代时都要创建一个新的密码重置令牌。

否则,您可能会发现自己正在寻找完全健康的代码来更改密码,但是在代码的上一次迭代中已经删除了一个令牌,导致您得出错误的结论,当您看到时需要处理代码另一个401。

在我的特定情况下,访问令牌存储在SQL Server数据库中,由于引入了时区问题,令牌将始终立即过期,因为我将options.useUTC设置为false。这导致所有新令牌在过去7200秒,超过密码重置令牌有效的900秒。我没注意到那些令牌被立即销毁并得出结论我的代码仍然存在问题,因为我看到了401的回报。事实上,401是由使用已经在服务器上消失的令牌引起的。

答案 3 :(得分:1)

这比它应该的更复杂。这是我的完整解决方案:

1)我在服务器端公开新方法,从令牌更新密码。

Member.updatePasswordFromToken = (accessToken, __, newPassword, cb) => {
  const buildError = (code, error) => {
    const err = new Error(error);
    err.statusCode = 400;
    err.code = code;
    return err;
  };

  if (!accessToken) {
    cb(buildError('INVALID_TOKEN', 'token is null'));
    return;
  }

  Member.findById(accessToken.userId, function (err, user) {
    if (err) {
      cb(buildError('INVALID_USER', err));
      return;
    };
    user.updateAttribute('password', newPassword, function (err, user) {
      if (err) {
        cb(buildError('INVALID_OPERATION', err));
        return;
      }

      // successful,
      // notify that everything is OK!
      cb(null, null);
    });
  });
}

我还定义了辅助功能:

Member.remoteMethod('updatePasswordFromToken', {
  isStatic: true,
  accepts: [
    {
      arg: 'accessToken',
      type: 'object',
      http: function(ctx) {
        return ctx.req.accessToken;
      }
    },
    {arg: 'access_token', type: 'string', required: true, 'http': { source: 'query' }},
    {arg: 'newPassword', type: 'string', required: true},      
  ],
  http: {path: '/update-password-from-token', verb: 'post'},
  returns: {type: 'boolean', arg: 'passwordChanged'}
});

从客户端,我只是这样称呼它:

this.memberApi.updatePasswordFromToken(token, newPassword);