我正在开发一个使用Loopback作为框架的项目,并包括用户和身份验证。我添加了一个密码重置路由生成并通过电子邮件发送,一切似乎都正常工作。最近,我发现密码重置似乎不起作用。此处重置密码的过程是:
预期的行为是密码将在密码重置表单上更新。相反,我得到一个授权错误,如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;
}
...
答案 0 :(得分:5)
找出问题所在。在我们的应用程序服务器中,我们没有使用Loopback的令牌中间件。在启动服务器之前添加app.use(loopback.token());
会导致重置链接中提供的访问令牌按预期工作!
答案 1 :(得分:2)
确保您在电子邮件中 用户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);