Просмотр исходного кода

Merge pull request #3228 from weseek/feat/add-validation-for-users

Feat/add validation for users
Yuki Takei 5 лет назад
Родитель
Сommit
c93efb1326

+ 2 - 0
src/client/js/services/AdminUsersContainer.js

@@ -132,6 +132,8 @@ export default class AdminUsersContainer extends Container {
       sortOrder: this.state.sortOrder,
       selectedStatusList: Array.from(this.state.selectedStatusList),
       searchText: this.state.searchText,
+      // Even if email is hidden, it will be displayed on admin page.
+      forceIncludeAttributes: ['email'],
     };
     const { data } = await this.appContainer.apiv3.get('/users', params);
 

+ 3 - 0
src/server/models/serializers/user-serializer.js

@@ -1,6 +1,9 @@
 function omitInsecureAttributes(user) {
   // omit password
   delete user.password;
+  // omit apiToken
+  delete user.apiToken;
+
   // omit email
   if (!user.isEmailPublished) {
     delete user.email;

+ 3 - 2
src/server/routes/apiv3/personal-setting.js

@@ -128,10 +128,11 @@ module.exports = (crowi) => {
     try {
       const user = await User.findUserByUsername(username);
 
-      // return email whether it's private
-      const { email } = user;
+      // return email and apiToken
+      const { email, apiToken } = user;
       const currentUser = user.toObject();
       currentUser.email = email;
+      currentUser.apiToken = apiToken;
 
       return res.apiv3({ currentUser });
     }

+ 62 - 24
src/server/routes/apiv3/users.js

@@ -8,6 +8,7 @@ const router = express.Router();
 
 const { body, query } = require('express-validator');
 const { isEmail } = require('validator');
+const { serializeUserSecurely } = require('../../models/serializers/user-serializer');
 
 const ErrorV3 = require('../../models/vo/error-apiv3');
 
@@ -88,21 +89,27 @@ module.exports = (crowi) => {
   };
 
   validator.statusList = [
-    // validate status list status array match to statusNo
-    query('selectedStatusList').custom((value) => {
-      const error = [];
-      value.forEach((status) => {
-        if (!Object.keys(statusNo)) {
-          error.push(status);
-        }
-      });
-      return (error.length === 0);
+    query('selectedStatusList').if(value => value != null).custom((value, { req }) => {
+
+      const { user } = req;
+
+      if (user != null && user.admin) {
+        return value;
+      }
+      throw new Error('the param \'selectedStatusList\' is not allowed to use by the users except administrators');
     }),
     // validate sortOrder : asc or desc
     query('sortOrder').isIn(['asc', 'desc']),
     // validate sort : what column you will sort
     query('sort').isIn(['id', 'status', 'username', 'name', 'email', 'createdAt', 'lastLoginAt']),
     query('page').isInt({ min: 1 }),
+    query('forceIncludeAttributes').toArray().custom((value, { req }) => {
+      // only the admin user can specify forceIncludeAttributes
+      if (value.length === 0) {
+        return true;
+      }
+      return req.user.admin;
+    }),
   ];
 
   validator.recentCreatedByUser = [
@@ -156,11 +163,13 @@ module.exports = (crowi) => {
    *                      $ref: '#/components/schemas/PaginateResult'
    */
 
-  router.get('/', loginRequiredStrictly, validator.statusList, apiV3FormValidator, async(req, res) => {
+  router.get('/', accessTokenParser, loginRequired, validator.statusList, apiV3FormValidator, async(req, res) => {
 
     const page = parseInt(req.query.page) || 1;
     // status
-    const { selectedStatusList } = req.query;
+    const { forceIncludeAttributes } = req.query;
+    const selectedStatusList = req.query.selectedStatusList || ['active'];
+
     const statusNoList = (selectedStatusList.includes('all')) ? Object.values(statusNo) : selectedStatusList.map(element => statusNo[element]);
 
     // Search from input
@@ -172,27 +181,56 @@ module.exports = (crowi) => {
       [sort]: (sortOrder === 'desc') ? -1 : 1,
     };
 
-    try {
-      const paginateResult = await User.paginate(
+    const orConditions = [
+      { name: { $in: searchWord } },
+      { username: { $in: searchWord } },
+    ];
+
+    const query = {
+      $and: [
+        { status: { $in: statusNoList } },
         {
-          $and: [
-            { status: { $in: statusNoList } },
-            {
-              $or: [
-                { name: { $in: searchWord } },
-                { username: { $in: searchWord } },
-                { email: { $in: searchWord } },
-              ],
-            },
-          ],
+          $or: orConditions,
         },
+      ],
+    };
+
+    try {
+      if (req.user != null) {
+        orConditions.push(
+          {
+            $and: [
+              { isEmailPublished: true },
+              { email: { $in: searchWord } },
+            ],
+          },
+        );
+      }
+      if (forceIncludeAttributes.includes('email')) {
+        orConditions.push({ email: { $in: searchWord } });
+      }
+
+      const paginateResult = await User.paginate(
+        query,
         {
           sort: sortOutput,
           page,
           limit: PAGE_ITEMS,
-          select: User.USER_PUBLIC_FIELDS,
         },
       );
+
+      paginateResult.docs = paginateResult.docs.map((doc) => {
+
+        // return email only when specified by query
+        const { email } = doc;
+        const user = serializeUserSecurely(doc);
+        if (forceIncludeAttributes.includes('email')) {
+          user.email = email;
+        }
+
+        return user;
+      });
+
       return res.apiv3({ paginateResult });
     }
     catch (err) {