我想补充一下 YeeHaw1234 的回答。我计划按照他描述的方式使用 Mixins,但我需要更多的字段来过滤数据,而不仅仅是用户 ID。我还有 3 个其他字段要添加到访问令牌中,这样我就可以在尽可能低的级别强制执行数据规则。
我想在会话中添加一些字段,但不知道如何在 Loopback 中添加。我查看了 express-session 和 cookie-express,但问题是我不想重写 Loopback 登录,而 Login 似乎是应该设置会话字段的地方。
我的解决方案是创建自定义用户和自定义访问令牌并添加我需要的字段。然后,我使用操作挂钩(保存之前)在写入新访问令牌之前插入我的新字段。
现在每次有人登录时,我都会获得额外的字段。如果有更简单的方法可以将字段添加到会话中,请随时告诉我。我计划添加一个更新访问令牌,以便如果用户在登录时更改权限,他们将在会话中看到这些更改。
这里是一些代码。
/common/models/mr-access-token.js
var app = require('../../server/server');
module.exports = function(MrAccessToken) {
MrAccessToken.observe('before save', function addUserData(ctx, next) {
const MrUser = app.models.MrUser;
if (ctx.instance) {
MrUser.findById(ctx.instance.userId)
.then(result => {
ctx.instance.setAttribute("role");
ctx.instance.setAttribute("teamId");
ctx.instance.setAttribute("leagueId");
ctx.instance.setAttribute("schoolId");
ctx.instance.role = result.role;
ctx.instance.teamId = result.teamId;
ctx.instance.leagueId = result.leagueId;
ctx.instance.schoolId = result.schoolId;
next();
})
.catch(err => {
console.log('Yikes!');
})
} else {
MrUser.findById(ctx.instance.userId)
.then(result => {
ctx.data.setAttribute("role");
ctx.data.setAttribute("teamId");
ctx.data.setAttribute("leagueId");
ctx.data.setAttribute("schoolId");
ctx.data.role = result.role;
ctx.data.teamId = result.teamId;
ctx.data.leagueId = result.leagueId;
ctx.data.schoolId = result.schoolId;
next();
})
.catch(err => {
console.log('Yikes!');
})
}
})
};
这花了我很长时间来调试。这是我遇到的一些障碍。我最初认为它需要在 /server/boot 中,但我没有看到保存时触发的代码。当我将它移到 /common/models 时,它开始触发。试图弄清楚如何从观察者中引用第二个模型不在文档中。 var app = ... 在另一个 SO 答案中。最后一个大问题是我在异步 findById 之外有next(),因此实例被原样返回,然后异步代码将修改该值。
/common/models/mr-user.js
{
"name": "MrUser",
"base": "User",
"options": {
"idInjection": false,
"mysql": {
"schema": "matrally",
"table": "MrUser"
}
},
"properties": {
"role": {
"type": "String",
"enum": ["TEAM-OWNER",
"TEAM-ADMIN",
"TEAM-MEMBER",
"SCHOOL-OWNER",
"SCHOOL-ADMIN",
"SCHOOL-MEMBER",
"LEAGUE-OWNER",
"LEAGUE-ADMIN",
"LEAGUE-MEMBER",
"NONE"],
"default": "NONE"
}
},
"relations": {
"accessTokens": {
"type": "hasMany",
"model": "MrAccessToken",
"foreignKey": "userId",
"options": {
"disableInclude": true
}
},
"league": {
"model": "League",
"type": "belongsTo"
},
"school": {
"model": "School",
"type": "belongsTo"
},
"team": {
"model": "Team",
"type": "belongsTo"
}
}
}
/common/models/mr-user.js
{
"name": "MrAccessToken",
"base": "AccessToken",
"options": {
"idInjection": false,
"mysql": {
"schema": "matrally",
"table": "MrAccessToken"
}
},
"properties": {
"role": {
"type": "String"
}
},
"relations": {
"mrUser": {
"model": "MrUser",
"type": "belongsTo"
},
"league": {
"model": "League",
"type": "belongsTo"
},
"school": {
"model": "School",
"type": "belongsTo"
},
"team": {
"model": "Team",
"type": "belongsTo"
}
}
}
/server/boot/mrUserRemoteMethods.js
var senderAddress = "curtis@abcxyz.com"; //Replace this address with your actual address
var config = require('../../server/config.json');
var path = require('path');
module.exports = function(app) {
const MrUser = app.models.MrUser;
//send verification email after registration
MrUser.afterRemote('create', function(context, user, next) {
var options = {
type: 'email',
to: user.email,
from: senderAddress,
subject: 'Thanks for registering.',
template: path.resolve(__dirname, '../../server/views/verify.ejs'),
redirect: '/verified',
user: user
};
user.verify(options, function(err, response) {
if (err) {
MrUser.deleteById(user.id);
return next(err);
}
context.res.render('response', {
title: 'Signed up successfully',
content: 'Please check your email and click on the verification link ' +
'before logging in.',
redirectTo: '/',
redirectToLinkText: 'Log in'
});
});
});
// Method to render
MrUser.afterRemote('prototype.verify', function(context, user, next) {
context.res.render('response', {
title: 'A Link to reverify your identity has been sent '+
'to your email successfully',
content: 'Please check your email and click on the verification link '+
'before logging in',
redirectTo: '/',
redirectToLinkText: 'Log in'
});
});
//send password reset link when requested
MrUser.on('resetPasswordRequest', function(info) {
var url = 'http://' + config.host + ':' + config.port + '/reset-password';
var html = 'Click <a href="' + url + '?access_token=' +
info.accessToken.id + '">here</a> to reset your password';
MrUser.app.models.Email.send({
to: info.email,
from: senderAddress,
subject: 'Password reset',
html: html
}, function(err) {
if (err) return console.log('> error sending password reset email');
console.log('> sending password reset email to:', info.email);
});
});
//render UI page after password change
MrUser.afterRemote('changePassword', function(context, user, next) {
context.res.render('response', {
title: 'Password changed successfully',
content: 'Please login again with new password',
redirectTo: '/',
redirectToLinkText: 'Log in'
});
});
//render UI page after password reset
MrUser.afterRemote('setPassword', function(context, user, next) {
context.res.render('response', {
title: 'Password reset success',
content: 'Your password has been reset successfully',
redirectTo: '/',
redirectToLinkText: 'Log in'
});
});
};
这直接来自示例,但不清楚是否应该在 /boot 中注册它。在将自定义用户从 /common/models 移动到 /server/boot 之前,我无法让自定义用户发送电子邮件。