สิทธิ์ตามบทบาทใน 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 ในพจนานุกรมซ้อนกัน

แนวคิดก็คือเราต้องพิจารณาสามสิ่งนี้ก่อน:

  1. ผู้ใช้แก้ไขเองหรือไม่ (บูล)
  2. ผู้แก้ไขเป็นผู้ใช้ประเภทใด (ผู้ดูแลระบบ ฯลฯ )
  3. กำลังแก้ไขผู้ใช้ประเภทใด (ผู้ดูแลระบบ ฯลฯ )

และจากสามสิ่งนั้นเราสามารถค้นหา 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();
  }
}