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

Reset Loopback Password with Access Token

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

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

我在第一次添加时确实花了时间测试它,但我无法弄清楚会发生什么变化来破坏它。我一直在关注 loopback-faq-user-management 中的示例,但我们有 Angular front-end 而不是该示例中的服务器端视图。

编辑:

我尝试完全打开 ACL,看看是否可以更新我的用户 object(继承自 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;
}
...

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

@OverlappingElvis 让我走上了正确的轨道。这是针对其他 运行 的更完整答案。环回文档在这方面非常有限。

确保您在电子邮件中收到用户 ID 和令牌,并在表单中填充这些内容。

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

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

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

}

虽然上述所有答案都将被证明是有帮助的,但请注意 Loopback destroys a token during validation when it prove it to be invalid 。令牌将消失。因此,当您尝试解决 401 问题时,请确保每次尝试新的代码迭代时都创建了一个新的密码重置令牌。

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

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

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

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