【问题标题】:Reset Loopback Password with Access Token使用访问令牌重置环回密码
【发布时间】:2015-12-18 18:36:49
【问题描述】:

我正在开发一个使用 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;
}
...

【问题讨论】:

  • 我完全被这个迷惑了。为什么我无法更新我自己的任何属性?
  • 你能附上你的一些代码吗? 401 似乎表明您无权在这里做某事
  • 编辑帖子以包含相关的小片段。

标签: javascript angularjs node.js loopbackjs strongloop


【解决方案1】:

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

【讨论】:

  • 你能不能更具体一点,我需要在哪里改变什么?我有同样的问题,但仍然不知道你做了什么来完成这项工作
  • @EdmundoDelGusto 将其添加到 boot/server.js 文件中,在 app.start 函数之前
  • github.com/strongloop/loopback-example-user-management 环回中的示例用户管理示例,您需要在电子邮件连接器中设置电子邮件和密码。整齐地给予每一件事
【解决方案2】:

虽然以上所有答案都将被证明是有帮助的,但请注意 Loopback destroys a token 在验证期间被证明是无效的。令牌将消失。因此,当您在解决 401 问题时,请确保每次尝试新的代码迭代时都创建一个新的密码重置令牌。

否则,您可能会发现自己正在查看非常健康的代码来更改密码,但使用的令牌已在之前的代码迭代中被删除,从而导致您得出错误的结论,即当您看到代码时需要对代码进行处理另一个 401。

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

【讨论】:

    【解决方案3】:

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

    确保您同时在您的电子邮件中获得用户 ID 和令牌,并将它们填充到表单中。

    从表单中,以下代码完成了这项工作:

     function resetPassword(id, token, password) {
      $http.defaults.headers.common.authorization = token;
    
      return User
        .prototype$updateAttributes({id:id}, {
         password: password
       })
       .$promise;
    
    }
    

    【讨论】:

    • 你在哪里添加这个功能?在模型定义中?
    【解决方案4】:

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

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

    【讨论】:

    • 我对远程方法声明做了一些小改动,但总体上是相同的代码。我没有使用 {arg:'accessToken', type:'object'...},而是将整个请求发送到我的方法,然后从那里获取 tokenId
    猜你喜欢
    • 2017-05-09
    • 2014-08-24
    • 2017-04-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    • 2017-10-25
    相关资源
    最近更新 更多