我们如何在不使用 Cognito 的忘记密码流程的情况下重置 Cognito 用户的密码?

How can we reset a Cognito User's password without using Cognito's forgot password flow?

我正在使用 node.js 和 AWS Lambda 开发无服务器项目。 对于身份验证,我使用的是 AWS Cognito。 (前端是 AWS Amplify 上 Vue.js 中的一个网络应用程序)。

我想编写自己的实现来重置忘记密码的用户的密码。

基本上,最终用户使用他们的电子邮件填写表格。如果电子邮件在系统中,我会向他们发送重置 link(它具有我在数据库中设置的唯一代码)。

我知道 Cognito's Forgot Password flow and also a solution in which I can capture Cognito's "email sending" code and over-ride the email with my own template passing the code in the URL mentioned

我偶然发现了 adminSetUserPassword API,我确信它会起作用——但无论我做什么,我的 lambda 函数都没有获得执行此操作的权限。

这是我的 nodejs 代码:

import AWS from 'aws-sdk';
const COGNITO_POOL_ID = process.env.COGNITO_USERPOOL_ID;

const csp = new AWS.CognitoIdentityServiceProvider();

export async function resetUserPassword(username, newPassword) {
  // Constructing request to send to Cognito
  const params = {
    Password: newPassword,
    UserPoolId: COGNITO_POOL_ID,
    Username: username,
    Permanent: true,
  };

  await csp.adminSetUserPassword(params).promise();
  return true;
}

这是我对 lambda 函数的 IAM 权限(它是无服务器 yml 格式):

CognitoResetPasswordIAM:
  Effect: Allow
  Action:
    - cognito-idp:*
  Resource:
    - arn:aws:cognito-idp:us-east-1::*

(一旦成功,我会微调权限)

以下是我收到的错误信息。 我开始觉得我这样做的方法不是推荐的做事方式。

User: arn:aws:sts::[XXXXXXX]:assumed-role/[YYYYYYYYY]-us-east-1-lambdaRole/web-app-service-dev-resetPassword is not authorized to perform: cognito-idp:AdminSetUserPassword on resource: arn:aws:cognito-idp:us-east-1:[[XXXXXXX]]:userpool/us-east-1_ZZZZZZZZ

(Serverless 可以使用对 * 资源的 * 权限访问我的 AWS 访问密钥——所以我不认为我在那里缺少任何权限)。

我的问题:

事实证明,您需要使用 Amplify API 而不是 Cognito API。 这涉及几个步骤:

1.为 Auth 配置您的 Cognito Amplify 服务。

import Amplify, { Auth } from 'aws-amplify';

export function configureCognitoAuth() {
  Amplify.configure({
    Auth: {
      region: process.env.COGNITO_REGION,
      userPoolId: process.env.COGNITO_USERPOOL_ID,
      mandatorySignIn: false,
      userPoolWebClientId: process.env.COGNITO_CLIENT_ID,
      authenticationFlowType: 'USER_PASSWORD_AUTH',
      oauth: {
        domain: process.env.COGNITO_APP_DOMAIN,
        scope: ['phone', 'email', 'profile', 'openid', 'aws.cognito.signin.user.admin'],
        responseType: 'code', // or 'token', note that REFRESH token will only be generated when the responseType is code
      },
    },
  });

  // You can get the current config object
  Auth.configure();
}

2。调用Auth.forgotPassword服务将实际密码发送到这里

import { Auth } from 'aws-amplify';

async function sendUserPasswordResetEmail(event) {
  // Any validation checks, rate limits you want to check here, etc.

  try {
    configureCognitoAuth();
    await Auth.forgotPassword(userId);
  } catch (error) {
    // An error occurred while sending the password reset email
  }
}

3。写一个 forgotPasswordEmailTrigger Cognito Hook 这会将默认的 Cognito 重置密码电子邮件替换为您自己的自定义电子邮件。

这也是一个 lamdba 方法,您需要将其附加到 Cognito 自定义消息触发器(从 Cognito > 常规设置 > 触发器)

我的代码如下所示:

async function forgotPasswordEmailTrigger(event, context, callback) {
  // Confirm it is a PreSignupTrigger
  if (event.triggerSource === 'CustomMessage_ForgotPassword') {
    const { userName } = event;
    const passwordCode = event.request.codeParameter;
    const resetUrl = `${BASE_URL}/password_reset/${userName}/${passwordCode}`;

    let message = 'Your HTML email template goes here';
    message = message
      .replace(/{{passwordResetLink}}/g, resetUrl);

    event.response.emailSubject = 'Email Subject here';
    event.response.emailMessage = message;
  }

  // Return to Amazon Cognito
  callback(null, event);
}

event.request.codeParameter 是代码从 Cognito 返回的地方。我认为有办法改变这一点,但我没有费心。我在下一步中使用相同的代码进行验证。

4。当密码重置请求发送到您的后端时,从 Amplify Auth 服务调用 forgotPasswordSubmit 方法

当用户点击 URL 时,他们来到网站,我从 URL 中获取代码和用户 ID(来自第 3 步),然后验证代码 + 重置密码如下:

async function resetPassword(event) {
  const { token, password, user_id } = event.body;

  // Do your validations & checks

  // Getting to here means everything is in order. Reset the password
  try {
    configureCognitoAuth(); // See step 1
    await Auth.forgotPasswordSubmit(user_id, token, password);
  } catch (error) {
    // Error occurred while resetting the password
  }

  const result = {
    result: true,
  };

  return {
    statusCode: 200,
    body: JSON.stringify(result),
  };
}