JavaScript 中 Amazon Cognito API 的所有用户 select 详尽无遗的安全且可扩展的方法是什么?

What is a safe and scalable way to exhaustively select all users from Amazon Cognito API in JavaScript?

我是一个小团队的一员,在一个有用户帐户的相当小的网站上工作;此时大约有 100 个用户。我们正在使用 Amazon Cognito 进行用户管理。在我们的网站上有一个摘要页面,其中显示了所有用户和各种属性的 list/table。但是,there's a hard limit 由 Amazon Cognito listUsers API 调用 return 编辑的项目数量,在本例中为 60。

幸运的是,API 调用还 return 是一个令牌,用于在有更多用户时进行后续调用。使用这些令牌,我们能够请求所有用户的完整列表。

另外,我们的网站使用了 react-redux 和 javascript 库 bluebird。在这个问题的上下文中,请求所有用户列表的组件也分派操作(redux 片段)并且来自亚马逊 aws-sdk 的 CognitoIdentityServiceProvider 被传递给 bluebird Promise.promisfyAll(将异步后缀添加到 listUser 调用)。我正在修改此组件以获取整个用户列表。

作为请求整个用户列表的粗略草稿,我使用递归函数将承诺链接在一起,直到来自亚马逊的分页令牌 return 未定义并将结果存储在一些 class 变量。我用这个 forum post as reference.

这行得通,但我对实施感觉不太好;现在在我们的用例中应该没问题,因为我们有大约 100 个用户,但我不知道这是否可以很好地扩展到数千个或更多用户。我知道递归足够危险,因为我不知道当 promises/calls 的数量增加时这种技术是否会导致问题。我想到了开销和内存管理的问题。但是,我们不必担心用户数量至少在短时间内飙升至数千,但我仍然想了解可能更可取的 and/or 更安全的方法来实现同样的目标。

以下片段来自请求用户列表的组件:

import Promise from 'bluebird';
import { CognitoIdentityServiceProvider } from './services/aws';

export const fetchRegisteredUsers = () => (dispatch) => {
...

  let allUsersTemp = [];
  let paginationToken;

  const getUserList = () => {
    let tempUserTest = CognitoIdentityServiceProvider.listUsersAsync({
      UserPoolId: process.env.COGNITO_USER_POOL_ID,
      PaginationToken: paginationToken
    });
    return tempUserTest.then((tempUser) => {
      allUsersTemp = allUsersTemp.concat(tempUser.Users);
      paginationToken = tempUser.PaginationToken;
      if(paginationToken) {
        return getUserList();
      } else {
        return;
      }
    })
  }

  const adminUsers = CognitoIdentityServiceProvider.listUsersInGroupAsync(adminParams);

  return Promise.all([adminUsers]).then(
    ([{ Users: adminUsers }]) => {
      getUserList().then((item) => {
        let allUsers = allUsersTemp;
        const adminUsernames = adminUsers.map(user => user.Username);

        allUsers.forEach(user => {
          let mappedAttributes = {};
          user.Attributes.forEach(attribute => mappedAttributes[attribute.Name] = attribute.Value);
          user.Attributes = mappedAttributes;
          user.isAdmin = adminUsernames.includes(user.Username);
        });

        dispatch({
          type: FETCH_REGISTERED_USERS_SUCCESS,
          payload: allUsers
        });
      });
    }, ...



根据我的理解,bluebird Promise.all 调用会等待传递给它的每个项目都可以解析,然后再执行 .then 部分中的内容;在对代码进行任何更改之前,有两个列表,一个用于用户,一个用于管理员。 Promise.all 等到两个承诺都完成后,对数据进行了一些基本处理,然后 return 在动作调度负载中编辑了数据。

经过我的更改,处理和return用户列表的逻辑在递归承诺链 (getUserList) 完成后执行。

我的问题: 这种技术可以吗?我可以按原样使用吗?还是它不安全,如果是,具体是什么问题?还有更好的方法吗?

我的弱点是递归和 promises/bluebird 所以请随时批评与这些主题相关的任何代码

I understand recursion just enough to be dangerous in that I do not know if this technique can cause problems when the number of promises/calls goes up. Questions of overhead and memory management come to mind

.

唯一可能的内存问题是您的 allUsersTemp 数组可能会变得非常大,直到超过浏览器的限制。然而,您应该很久以前就想知道在一个大的 table 中显示百分之一万的用户条目是否真的有用。在内存会出现问题的规模上,无论如何,您都需要更有效的工具来管理您的用户群,而不仅仅是列出所有用户。

关于代码风格,我建议不要将 allUsersTemppaginationToken 声明为 mutable 更高范围的变量。而是将它们作为递归函数的参数并用结果履行承诺:

function getUserList (paginationToken, allUsersTemp = []) => {
//                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  return tempUserTest = CognitoIdentityServiceProvider.listUsersAsync({
    UserPoolId: process.env.COGNITO_USER_POOL_ID,
    PaginationToken: paginationToken
  }).then(tempUser => {
    const allUsers = allUsersTemp.concat(tempUser.Users);
    const nextToken = tempUser.PaginationToken;
    if (nextToken) {
      return getUserList(nextToken, allUsers);
//                       ^^^^^^^^^^^^^^^^^^^
    } else {
      return allUsers;
//           ^^^^^^^^
    }
  });
}

The Promise.all waited until both promises were done

不,您将只有一个承诺的数组传递给 Promise.all,并在第一个函数完成后调用第二个函数。要同时制作 运行,它应该看起来像

return Promise.all([
  CognitoIdentityServiceProvider.listUsersInGroupAsync(adminParams),
  getUserList()
]).then(([{ Users: adminUsers }, allUsers]) => {
  …
});

我知道已经有一段时间了,但这就是我在 Cognito 中扫描所有用户的方式。希望这会有所帮助

const getListUserCognito = async () => {
  try {
    const params = {
      UserPoolId: userPoolID
    };

    console.log('params', JSON.stringify(params));

    const listUserResp = await getAllUserCognito(params);

    console.log('listUserResp', JSON.stringify(listUserResp));

    return listUserResp.Users;
  } catch (error) {
    return null;
  }
};

const getAllUserCognito = async (params) => {
  try {
    // string must not be empty
    let paginationToken = 'notEmpty';
    let itemsAll = {
      Users: []
    };
    while (paginationToken) {
      const data = await cognitoidentityserviceprovider
        .listUsers(params)
        .promise();

      const { Users } = data;
      itemsAll = {
        ...data,
        ...{ Users: [...itemsAll.Users, ...(Users ? [...Users] : [])] }
      };
      paginationToken = data.PaginationToken;
      if (paginationToken) {
        params.PaginationToken = paginationToken;
      }
    }
    return itemsAll;
  } catch (err) {
    console.error(
      'Unable to scan the cognito pool users. Error JSON:',
      JSON.stringify(err, null, 2)
    );
  }
};