สิทธิ์ตามบทบาทใน Express.js
Aug 28 2020
นี่เป็นสิ่งที่ฉันเคยทำมาสองสามครั้งแล้ว แต่ฉันพบว่ามันรู้สึกผิดพลาดได้ง่ายด้วยเงื่อนไขมากมายและฉันสงสัยว่าจะมีใครชี้ฉันไปในทิศทางที่สะอาดกว่านี้ได้หรือไม่ นี่คือเส้นทาง PATCH สำหรับแก้ไขผู้ใช้ ผู้ดูแลระบบขั้นสูงและผู้ใช้ผู้ดูแลระบบสามารถเปลี่ยนผู้ใช้รายอื่นได้ (โดยมีข้อ จำกัด บางประการ) ในขณะที่ผู้ใช้ประเภทอื่นแก้ไขได้ด้วยตนเองเท่านั้น
router.patch('/:userId', async (req, res) => {
const patcher = (req as AuthRequest).user;
const otherUser = await database.getUserById(req.params.userId);
const requestedUpdate = req.body;
// 404 if user is not found.
if (!otherUser) {
return sendCannotFind(res);
}
// Basic validation of requestedUpdate
if (requestedUpdate.userType && !isUserTypeValid(requestedUpdate.userType)) {
return sendInvalidUserType(res);
}
if (patcher.userType === 'superAdmin') {
// Super admin cannot demote self.
if (otherUser.id === patcher.id && requestedUpdate.userType) {
return sendCannotSetUserType(res);
}
// Super admin cannot edit other super admins
if (otherUser.userType === 'superAdmin' && otherUser.id !== patcher.id) {
return sendCannotEdit(res);
}
} else if (patcher.userType === 'admin') {
// Admin cannot edit super admins
if (otherUser.userType === 'superAdmin') {
return sendCannotEdit(res);
}
// Admin cannot edit other admins
if (otherUser.userType === 'admin' && otherUser.id !== patcher.id) {
return sendCannotEdit(res);
}
// Admin cannot promote or demote themselves
if (otherUser.id === patcher.id && requestedUpdate.userType) {
return sendCannotSetUserType(res);
}
// Admin cannot promote anyone to admin or superAdmin
if (requestedUpdate.userType === 'admin' || requestedUpdate.userType === 'superAdmin') {
return sendCannotSetUserType(res);
}
} else {
// Non-admins cannot edit anyone but themselves
if (otherUser.id !== patcher.id) {
return sendCannotEdit(res);
}
// Non-admins cannot promote or demote themselves
if (requestedUpdate.userType && requestedUpdate.userType !== otherUser.userType) {
return sendCannotSetUserType(res);
}
}
await doEdit(otherUser, requestedUpdate);
return res.json(otherUser);
});
```
คำตอบ
Aurast Sep 08 2020 at 23:25
ฉันคิดว่าฉันมีวิธีที่ดีกว่าและเปิดเผยมากขึ้นโดยใช้ Joi schemas ในพจนานุกรมซ้อนกัน
แนวคิดก็คือเราต้องพิจารณาสามสิ่งนี้ก่อน:
- ผู้ใช้แก้ไขเองหรือไม่ (บูล)
- ผู้แก้ไขเป็นผู้ใช้ประเภทใด (ผู้ดูแลระบบ ฯลฯ )
- กำลังแก้ไขผู้ใช้ประเภทใด (ผู้ดูแลระบบ ฯลฯ )
และจากสามสิ่งนั้นเราสามารถค้นหา Joi schema ที่ถูกต้องเพื่อใช้
ดังนั้นพจนานุกรมสคีมาจึงเป็นเช่นนี้สำหรับเส้นทาง PATCH:
// No one is allowed to edit their own user type, everyone can edit their own name.
const selfEditSchemas: { [key in UserType]?: Joi.Schema } = {
[UserType.superAdmin]: Joi.object({
userType: Joi.forbidden(),
name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(), }), [UserType.admin]: Joi.object({ userType: Joi.forbidden(), name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(),
}),
[UserType.user]: Joi.object({
userType: Joi.forbidden(),
name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(), }), }; // We only allow admins and super admins to edit others, and we use different schemas // depending on what type of user they are trying to edit. const otherEditSchemas: { [key in UserType]: { [key in UserType]?: Joi.Schema } } = { [UserType.superAdmin]: { [UserType.admin]: Joi.object({ // (superAdmin can edit admins according to this schema) userType: Joi.string().valid(...Object.values(UserType)).optional(), name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(),
}),
[UserType.user]: Joi.object({ // (superAdmin can edit regular users according to this chema)
userType: Joi.string().valid(...Object.values(UserType)).optional(),
name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(), }), }, [UserType.admin]: { [UserType.user]: Joi.object({ // (admin can edit regular users according to this schema) userType: Joi.forbidden(), // (admin cannot promote regular users) name: Joi.string().pattern(/^[a-zA-Z0-9_\-. ]{3,100}$/).optional(),
}),
},
[UserType.user]: {}, // (user is not allowed to edit anyone else)
};
และตอนนี้ตรรกะในการตรวจสอบความถูกต้องของการแก้ไขกลายเป็น IMO ที่ง่ายขึ้นและมีข้อผิดพลาดน้อยลง
function validateUserEdit(req: express.Request, res: express.Response, next: express.NextFunction) {
const userReq = req as UserRequest;
const isSelf = userReq.user.id === userReq.otherUser.id;
const schema = isSelf
? selfEditSchemas[userReq.user.userType]
: otherEditSchemas[userReq.user.userType][userReq.otherUser.userType];
if (!schema) {
return res.status(403).json({
message: 'Not allowed to edit that user.',
code: ErrorCodes.FORBIDDEN_TO_EDIT_USER,
});
}
const validateRes = schema.validate(req.body, { stripUnknown: true });
if (validateRes.error) {
res.status(400).json({
message: `Invalid arguments: ${validateRes.error}`,
code: ErrorCodes.INVALID_ARGUMENTS,
});
} else {
req.body = validateRes.value;
next();
}
}