Это последняя статья из серии Аутентификация с помощью AWS Cognito, если вы не читали предыдущие статьи, вы можете найти их в моем профиле.

Итак, в прошлой статье мы рассмотрели пользовательские сеансы и функции выхода из системы. Теперь давайте сделаем еще один шаг и предоставим нашим пользователям возможность изменять свои пароли и обновлять свои атрибуты, такие как имена пользователей. Итак, приступим к делу и начнем!

Изменить пароль

Начнем с создания новой формы для смены пароля пользователя:

const ChangePassword = () => {
  const [password, setPassword] = useState("");
  const [newPassword, setNewPassword] = useState("");

 const [showPassword, toggleShowPassword] = useToggle(false);
  const [showNewPassword, toggleShowNewPassword] = useToggle(false);

  const handleShowPassword = (type: "current" | "new") => {
    if (type === "current") {
      toggleShowPassword(!showPassword);
    } else {
      toggleShowNewPassword(!showNewPassword);
    }
  };

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

  };

  return (
    <form onSubmit={handleSubmit}>
      <Flex
        minH={"100vh"}
        align={"center"}
        justify={"center"}
        bg={useColorModeValue("gray.50", "gray.800")}
      >
        <Stack
          spacing={4}
          w={"full"}
          maxW={"md"}
          bg={useColorModeValue("white", "gray.700")}
          rounded={"xl"}
          boxShadow={"lg"}
          p={6}
          my={12}
        >
          <Heading lineHeight={1.1} fontSize={{ base: "2xl", sm: "3xl" }}>
            Change Password
          </Heading>
          <FormControl id="password">
            <FormLabel>Current password</FormLabel>
            <InputGroup size="md">
              <Input
                pr="4.5rem"
                type={showPassword ? "text" : "password"}
                placeholder="Enter current password"
                value={password}
                onChange={(event) => setPassword(event.target.value)}
              />
              <InputRightElement width="4.5rem">
                <Button
                  h="1.75rem"
                  size="sm"
                  onClick={() => handleShowPassword("current")}
                >
                  {showPassword ? "Hide" : "Show"}
                </Button>
              </InputRightElement>
            </InputGroup>
          </FormControl>
          <FormControl id="newPassword">
            <FormLabel>New password</FormLabel>
            <InputGroup size="md">
              <Input
                pr="4.5rem"
                type={showNewPassword ? "text" : "password"}
                placeholder="Enter new password"
                value={newPassword}
                onChange={(event) => setNewPassword(event.target.value)}
              />
              <InputRightElement width="4.5rem">
                <Button
                  h="1.75rem"
                  size="sm"
                  onClick={() => handleShowPassword("new")}
                >
                  {showNewPassword ? "Hide" : "Show"}
                </Button>
              </InputRightElement>
            </InputGroup>
          </FormControl>
          <Stack spacing={6} direction={["column", "row"]}>
            <Button
              bg={"blue.400"}
              color={"white"}
              w="full"
              _hover={{
                bg: "blue.500",
              }}
              type="submit"
            >
              Submit
            </Button>
          </Stack>
        </Stack>
      </Flex>
    </form>
  );
};

export default ChangePassword;

Вот простая форма с двумя контролируемыми входами для смены паролей. Кроме того, мы добавили кнопку, позволяющую переключать видимость поля пароля. Ниже скриншот того, как это выглядит:

Теперь давайте внесем несколько дополнений в наш контекст Auth:

  1. Я добавил переменную состояния пользователя, чтобы гарантировать, что мы можем получить доступ к объекту CognitoUser во всем нашем приложении.
// Auth.tsx
const [user, setUser] = useState<CognitoUser | null>(null);

//... other code

const authenticate = useCallback(
    async (Username: string, Password: string) => {
      const user = new CognitoUser({
        Username,
        Pool: UserPool,
      });

      const authDetails = new AuthenticationDetails({
        Username,
        Password,
      });

      user.authenticateUser(authDetails, {
        onSuccess: (data) => {
          setSession(data);
          setUser(user); // <-- Set user after succesfull authentication
        },
        onFailure: (error) => {
          if (error) {
            toast({
              title: error.message,
              status: "error",
              isClosable: true,
            });
          }
        },
      });
    },
    [toast]
  );

2. После входа в систему у нас теперь есть доступ к объекту CognitoUser, который позволяет нам создать функцию для смены паролей:

// Auth.tsx

const changeUserPassword = useCallback(
    ({ password, newPassword }: { password: string; newPassword: string }) => {
      if (user) {
        user.changePassword(password, newPassword, (error, result) => {
          if (error) {
            toast({
              title: error.message,
              status: "error",
              isClosable: true,
            });
          } else if (result) {
            toast({
              title: result,
              status: "success",
              isClosable: true,
            });
          }
        });
      }
    },
    [user, toast]
  );

Давайте вызовем эту функцию в обработчике отправки нашей формы ChangePassword:

// ChangePassword.tsx
const { changeUserPassword } = useAuth();

const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

    changeUserPassword({ password, newPassword }); // <-- pass input state to the function
  };

Большой! Теперь пришло время попробовать. Просто введите свой текущий пароль и новый пароль, который вы хотите установить, и давайте посмотрим, что произойдет.

Если все настроено правильно, теперь вы сможете успешно изменить свой пароль. Также, если вы введете неправильный пароль, он покажет вам ошибку, не стесняйтесь попробовать это самостоятельно.

Обновить атрибуты пользователя

Для обновления атрибутов пользователя я создал отдельную форму, которая позволяет вам изменять конкретные данные пользователя. Чтобы изменить атрибут username, давайте создадим простую форму с одним текстовым полем. Это позволит нам обновлять свойство username на основе пользовательского ввода. Рассмотрим реализацию формы.

import { FormEvent, useState } from "react";
import {
  Button,
  Flex,
  FormControl,
  FormLabel,
  Heading,
  Input,
  Stack,
  useColorModeValue,
} from "@chakra-ui/react";

import { useAuth } from "../../context/Auth";

const EditProfile = () => {
  const [username, setUsername] = useState("");

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();

  };

  return (
    <form onSubmit={handleSubmit}>
      <Flex align={"center"} justify={"center"}>
        <Stack
          spacing={4}
          w={"full"}
          maxW={"md"}
          bg={useColorModeValue("white", "gray.700")}
          rounded={"xl"}
          boxShadow={"lg"}
          p={6}
          my={12}
        >
          <Heading lineHeight={1.1} fontSize={{ base: "2xl", sm: "3xl" }}>
            Edit profile
          </Heading>
          <FormControl id="password">
            <FormLabel>New user name</FormLabel>
            <Input
              placeholder={profileInfo?.username}
              _placeholder={{ color: "gray.500" }}
              type="text"
            />
          </FormControl>
          <Stack spacing={6} direction={["column", "row"]}>
            <Button
              bg={"blue.400"}
              color={"white"}
              w="full"
              _hover={{
                bg: "blue.500",
              }}
              type="submit"
            >
              Submit
            </Button>
          </Stack>
        </Stack>
      </Flex>
    </form>
  );
};

export default EditProfile;

Следующее, что я собираюсь сделать, это переместить состояние profile в наш контекст аутентификации, чтобы сделать его глобальным, и создать функцию updateAttribute.

Затем я перемещу состояние profile в наш контекст Auth, чтобы сделать его глобально доступным для всего приложения. Дополнительно я создам функцию updateAttribute, которая будет отвечать за обновление атрибута пользователя. Это позволит нам легко изменить свойство имени пользователя.

// Auth.tsx

// 1. Add profile state
const [profileInfo, setProfileInfo] = useState<State["profileInfo"]>({
    username: "",
    email: "",
  });

// 2. Set profile state after successfull authentication 
const getSession: State["getSession"] = useCallback(async () => {
  // ...code above

            setProfileInfo({
              username: attributes.name,
              email: attributes.email,
            });

// ...code below
    });
  }, [user]);

// 3. Update user attributes
const updateUserAttribute = useCallback(
    ({ Name, Value }: { Name: string; Value: string }) => {
      const attributes = [
        new CognitoUserAttribute({
          Name,
          Value,
        }),
      ];

      user?.updateAttributes(attributes, (error, result) => {
        if (error) {
          toast({
            title: error.message,
            status: "error",
            isClosable: true,
          });
        } else {
// 4. Call get session for updating our profile state globally 
          getSession();
          toast({
            title: result,
            status: "success",
            isClosable: true,
          });
        }
      });
    },
    [getSession, user, toast]
  );

Теперь, когда у нас есть готовая функция updateUserAttribute, давайте используем ее в нашем компоненте EditProfile для обновления username:

// EditProfile.tsx
const EditProfile = () => {
// 1. Get state and function from the context
  const { updateUserAttribute, profileInfo } = useAuth();
  const [username, setUsername] = useState("");

  const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
    event.preventDefault();
// 2. call function with new `username`
    updateUserAttribute({ Name: "name", Value: username });
  };

  return (
    <form onSubmit={handleSubmit}>
      <Flex align={"center"} justify={"center"}>
        <Stack
          spacing={4}
          w={"full"}
          maxW={"md"}
          bg={useColorModeValue("white", "gray.700")}
          rounded={"xl"}
          boxShadow={"lg"}
          p={6}
          my={12}
        >
          <Heading lineHeight={1.1} fontSize={{ base: "2xl", sm: "3xl" }}>
            Edit profile
          </Heading>
          <FormControl id="password">
            <FormLabel>New user name</FormLabel>
            <Input
              placeholder={profileInfo?.username} // make current username as a placeholder
              _placeholder={{ color: "gray.500" }}
              type="text"
              value={username}
              onChange={(event) => setUsername(event.target.value)}
            />
          </FormControl>
          <Stack spacing={6} direction={["column", "row"]}>
            <Button
              bg={"blue.400"}
              color={"white"}
              w="full"
              _hover={{
                bg: "blue.500",
              }}
              type="submit"
            >
              Submit
            </Button>
          </Stack>
        </Stack>
      </Flex>
    </form>
  );
};

export default EditProfile;

После отправки нового имени пользователя в форме EditProfile мы должны увидеть обновленное имя пользователя, отраженное в нашей форме Profile. Это позволит пользователям сразу увидеть внесенные ими изменения.

Заключение

Поздравляем! Вы многого добились в этой серии. Мы рассмотрели различные аспекты, связанные с аутентификацией, включая управление сеансом и манипулирование атрибутами пользователя. Надеюсь, вы нашли эти темы интересными и полезными при создании приложения для аутентификации. Если у вас есть какие-либо вопросы или вам нужна дополнительная помощь, не стесняйтесь обращаться к нам. Молодец, что завершил это путешествие!

Идеи для самостоятельной реализации

  • Добавьте проверку на стороне клиента с помощью react-hook-form.
  • Добавьте react-router-dom и соедините все компоненты с помощью router

Ресурсы

  1. Проект на GitHub
  2. Похожий туториал на YouTube
  3. Официальные документы