npm package: @psyrenpark/auth
Description: viba auth
Author: psyrenpark
Latest version: 3.0.0

## @psyrenpark/auth

### 설명

인증, 토큰, email 발송등 auth에 관하여 클라이언트 (cms/web) 재사용을 위해 공통모듈로 분리 \
cognito라는 회원관리 시스템을 사용함으로써 패스워드등을 별도 보관하여 높은 보안 및 인증에 관한 효율적 사용성을 얻을수 있음

- react 지원
- react-native 지원
- vue.js 지원
- gatsby.JS 지원
- next.js(테스트중)

---

### 업데이트 내용

- v3.0.0

라이브러리 최신화
제거된 기능 제거

- v1.0.79

회원 가입시 이미 가입되어 있고 인증도 된경우의 에러 코드 변경

code : InvalidParameterException
message : User is already confirmed.
=>
code : UsernameExistsException
message : User is already confirmed.

---

### 필요 파일

각 프로젝트의 개발 환경에 맞는 aws-exports.js 요청

```
React- native는
<project root>/aws-exports.js

web/cms은
<project root>/src/aws-exports.js
```

### 설정 및 설치

```properties
yarn add @psyrenpark/auth
yarn add @psyrenpark/api
yarn add @psyrenpark/storage
yarn add amazon-cognito-identity-js
yarn add aws-amplify

// react-native 추가 설치
yarn add @react-native-community/netinfo
```

### 최신 버전 적용 방법

새버전 공지시 npm에 맞는것 재설치

```properties
yarn add @psyrenpark/auth
yarn add @psyrenpark/api
yarn add @psyrenpark/storage
```

- 이후 최신버전으로 변경되었는지 확인할것.

### DB tb_user 의 필수 컬럼

**프로젝트마다 사용될 유저 테이블에 들어가야할 요소를 서버 개발자에게 요청할것 (중요)**

- email or phone (필수) // 고유키
- reg_dt
- up_dt
- del_dt
- signup_type // 회원가입 방법
- user_type // 프로젝트마다 고유한 유저 타입이 있다면 정할것
- user_name
- ...

---

### 루트에서 세팅

```javascript
//--------------------------------------------------
// 각 프로젝트 루트
import { Auth } from "@psyrenpark/auth";
import { Api } from "@psyrenpark/api";
import { Storage } from "@psyrenpark/storage";
import awsmobile from "./aws-exports";
Auth.setConfigure(awsmobile);
Api.setConfigure(awsmobile);
Storage.setConfigure(awsmobile);

//--------------------------------------------------
// 혹 import가 지원 되지 않는 javascript 버전에서 사용시

// aws-exports
// export default awsmobile;
module.exports = awsmobile; // 로 변경

// 각 프로젝트 루트
const { apiObject } = require("./api");
const { Api } = require("@psyrenpark/api");
const { Auth, AuthType } = require("@psyrenpark/auth");
const { Storage } = require("@psyrenpark/storage");
const awsmobile = require("./aws-exports");

Auth.setConfigure(awsmobile);
Api.setConfigure(awsmobile);
Storage.setConfigure(awsmobile);
```

### 로딩 콜백 함수 샘플

로딩에 대한 불편함을 해결하기위해 콜백형식의 함수를 주입가능하도록 되어있음

```javascript
// 아래 auth 함수가 실행될때 로딩함수를 넣으면
// 시작시 isLoading => true
// 종료시 isLoading => false
// 로 주입한 함수가 호출됨
const loadingFunction = (isLoading) => {
  dispatch({ type: "SET_IS_LOADING", payload: isLoading });
  // or
  setIsLoading(isLoading);
};
```

---

### 로그인 여부 체크

- 이미 로그인한적이 있는지 체크하는 함수
- 이 과정을 한 후에 인증이 필요한 api 사용가능
- 비동기, 콜백 방식중 필요한 방식을 선택하여 사용할 것
- **cms라면 루트 라우팅에서 로그인창을 띄울지 말지를 결정하는 프로세스(중요)**

```javascript
import { Auth } from "@psyrenpark/auth";

//--------------------------------------
// 비동기 방식
const isCheckToLoginFunction = async (props) => {
  try {
    var auth = await Auth.currentSession();
    // 로그인 상태
    return true;
  } catch (error) {
    // 로그아웃 상태
    return false;
  }
};

//--------------------------------------
// 콜백 방식
const checkToLoginFunction = async (props) => {
  Auth.currentSessionProcess(
    async (data) => {
      // 성공처리 및 로그인 된 상태
      // 자동 로그인 필요
      // 서버에서 유저 정보 필요시 여기서 비동기로 가져올것
    },
    (error) => {
      // 실패처리 및 로그아웃 상태
      // 로그인 화면으로 이동 필요
    },
    loadingFunction
  );
};
```

### 특정 그룹 포함 여부 체크

- 이 유저가 특정 그룹에 속해있는지 체크하는 함수
- 이로직은 토큰안에 유저 그룹을 포함시키는 방법으로써 각 프로젝트마다 다르니 필요시 요청 (높은 보안 요구시)
- user, admin, manager 등이 있을수 있다. (기능필요시 요청 할것)

```javascript
import { Auth } from "@psyrenpark/auth";

//--------------------------------------
// 비동기 방식
const isIncludeGroupFunction = async (props) => {
  try {
    var isIncludeGroup = await Auth.isIncludeGroup("admin");

    if (!isIncludeGroup) {
      await Auth.signOut();
      // 로그아웃하고 홈으로 리다이렉트  및 유저 관련 리덕스 초기화
      return;
    }

    // 정상로직
  } catch (error) {}
};
```

### cms등 특정 유저 로그인을 막기위한 처리 아닌경우 강제 로그아웃

- cms나 특정 유저만 사용가능 해야할 경우
- 로그인후 await Auth.currentSession() 로 로그인체크
- Auth.isIncludeGroup("특정 그룹 key"); 로 토큰안에 key가 포함되어 있는지 체크
- false 일경우 강제 로그아웃 및 유저 정보 관련된 정보 초기화 할것

```javascript
const isIncludeGroupFunction = async (props) => {
  try {
    var auth = await Auth.currentSession();
    console.log("checkAuth -> auth", auth);

    var isAdmin = await Auth.isIncludeGroup("admin");

    console.log("checkAuth -> isAdmin", isAdmin);

    if (!isAdmin) {
      // 로그아웃후 리덕스나 내부 저장소 날리는 작업필요
      await Auth.signOut();

      //clear()
      return;
    }
    // 성공후 자기 관리자 정보 가져오기
  } catch (error) {
    // 에러시 강제 로그아웃 후 리덕스나 내부 저장소 날리는 작업필요
    await Auth.signOut();

    //clear()
  }
};
```

---

### 회원가입

- 회원가입 함수
- **[선인증] 필요시 (휴대폰 인증, pass등) 화면 플로우에 맞게 클라이언트에서 미인증 서버 api 호출을 \
  진행후 이결과를 cognitoRegComm안에 포함시켜준다. (중요)**
- 현재 이메일 또는 전화번호 회원가입만 지원한다. (아이디등은 차후)
- 프로젝트에 따라 이메일을 자동 인증하는 경우와 인증을 체크하는 경우 2가지가 있다.
- **[후인증] 필요시 이메일 인증을 하는경우 이메일에 발송된 코드 입력창으로 이동시켜줘야한다.**
- **[자동인증] 되는 경우 성공시 로그인화면 또는 자동로그인 화면으로 이동해야한다.**
- email은 소문자로 변경해주며, 앞뒤 트림도 해줘야한다.

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식

const signUpFunction = async (formData) => {
  Auth.signUpProcess(
    {
      email: formData.userEmail,
      password: formData.userPassword,
      authType: AuthType.EMAIL,
      lang: "en", // 가입시 유저 선택 언어
      cognitoRegComm: {
        // 서버에서 회원가입시 처리할 필요 파라미터를 첨부한다. 위의 [DB tb_user 의 필수 컬럼] 참고
        // 패스워드 같은 정보는 포함시키지 않는다.
        name: formData.userName,
        age: formData.userAge,
        // ...
      },
    },
    async (data) => {
      // 성공처리 및 회원가입 성공
      // 자동인증인 경우 로그인 화면으로 이동  및 자동로그인 필요
      // 인증을 체크하는 경우 인증코드 입력창 화면으로 이동 필요
    },
    (error) => {
      // 회원가입 실패
    },
    loadingFunction
  );
};
```

### 회원가입 후 인증 확인 함수

- 회원인증 함수
- 인증을 체크하는 경우 코드입력 화면에서 코드값을 넣어 아래 함수를 실행한다.

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const confirmSignUpFunction = async (formData) => {
  Auth.confirmSignUpProcess(
    {
      // 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
      email: formData.userEmail,
      password: formData.userPassword,
      authType: AuthType.EMAIL,
      code: formData.userCode, // 이메일에 있는 인증코드
    },
    async (data) => {
      // 성공처리 및 인증 성공
      // 로그인 화면이나 자동로그인 필요
    },
    (error) => {
      // 실패처리,
      // 인증코드가 틀렸을경우 및 만료 된경우
      // 인증 메일 재전송이 필요하다.
    },
    loadingFunction
  );
};
```

### 회원가입 후 인증 재전송 함수

- 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const resendSignUpFunction = async (formData) => {
  Auth.resendSignUpProcess(
    {
      // 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
      email: formData.userEmail,
    },
    async (data) => {
      // 성공처리
      // 성공적으로 인증 메일 재전송
    },
    (error) => {
      // 실패처리,
    },
    loadingFunction
  );
};
```

---

---

### 로그인

- 로그인 함수
- 로그아웃 상태일 경우 이 과정을 한후에 인증이 필요한 api 사용가능
- 인증안하고 종료후 다시 로그인할경우 3번째 파라미터 부분 참고할것

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";

//--------------------------------------
// 콜백 방식
const signInFuntion = async (formData) => {
  Auth.signInProcess(
    {
      email: formData.userEmail,
      password: formData.userPassword,
      authType: AuthType.EMAIL,
    },
    async (data) => {
      // 성공처리 및 로그인 된 상태
      // 서버에서 유저 정보 필요시 여기서 비동기로 가져올것
      // 서버로 자기 정보 가져오는 api를 호출해야한다.
    },
    async (data) => {
      // 회원가입은 되었으나 인증을 안했을경우
      // 인증 안하고 강제 종료 했거나 화면 나갔을경우 타는 함수
      // 인증 화면으로 이동필요
      // 자동으로 인증 메일 재발송됨
    },
    (error) => {
      // 실패처리,
    },
    loadingFunction
  );
};
```

---

---

### 소셜 로그인 관련

- 소셜 로그인 라이브러리 추가후 로그인후 나오는 인증 정보중
- email, password는 고유 아이디, name은 있으면 넣고 없으면 디폴트로 넣어줄것
- **password의 고유 아이디는 앱, 웹 에서 서로 같아야 각각에서 같은 계정으로 로그인이 가능함. (중요)**
- 앱을 여러개로 출시할경우 소셜 유형에 따라 (유저용앱, 관리자용앱) 고유 아이다가 서로 다를수 있으니 주의할것 (예씨 카카오톡)
- 각 라이브러리(웹, 앱) 로그인후 나오는 토큰 분해후 잘 체크해볼것
- user_info 필수 param / [email, password, name]
- **소셜특징중 이메일을 알아낼수 없는경우 고유한 키를 조합할것 예시 (고유키+@[소셜].com (중요)**
- 암호화가 필요할경우 isCrypto:true (테스트중)

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";

//--------------------------------------
// 콜백 방식
const signInProcess = async (
    params = {},
    successCallback,
    failCallback,
    loadingCallback,
  ) => {
    if (loadingCallback) {
      loadingCallback(true);
    }

    try {

      // 중요
      // 쇼설 로그인 라이브러리 추가후 로그인후 나오는 인증 정보중
      // email, password는 고유 아이디, name은 았으면 넣고 없으면 디폴트로 넣어줄것
      // user_info 필수 param / email, password, name
      var user_info = await appleLogin();  //or await googleLogin();
      // var user_info  =
      // {
      //   ...userInfo.user, // 디비에 넣을 필요정보들
      //   email: userInfo.user.email, // 필수
      //   password: userInfo.user.id, // 필수
      //   name: user.givenName,       // 필수
      // }

    } catch (error) {
      if (loadingCallback) {
        loadingCallback(false);
      }

      if (failCallback) {
        failCallback(error);
      return;
    }

    await Auth.signUpProcess(
      {
        email: user_info.email,
        password: user_info.password,
        authType: AuthType.APPLE,         // or  authType: AuthType.GOOGLE, // enum에 없을경우 요청할것
        lang: params.lang ? params.lang : 'en',
        cognitoRegComm: {
          ...user_info,
          ...params,
        },
      },
      async (data) => {
        if (successCallback) {
          await successCallback(data);
        }
      },
      (error) => {
        if (failCallback) {
          failCallback(error);
        }
      },
      (flag) => {
        // if (!flag && loadingCallback) {
        loadingCallback(flag);
        // }
      },
    );
  };
```

---

---

### 비밀번호 분실 1단계

- 로그아웃 상태에서 비밀번호를 모를경우
- 이메일 인증후 새로운 패스워드 설정이 가능하다.
- 비밀번호 변경은 이메일 타입만 가능하다. 그외에는 비활성

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";

//--------------------------------------
// 콜백 방식
const forgotPasswordFunction = async (formData) => {
  Auth.forgotPasswordProcess(
    {
      email: formData.userEmail,
      authType: AuthType.EMAIL,
    },
    async (data) => {
      // 성공처리 및 인증 메일 발송됨
      // 패스워드 분실 2단계로 이동
    },
    (error) => {
      // 실패처리,
    },
    loadingFunction
  );
};
```

### 비밀번호 분실 2단계

- 유저가 가입한 이메일로 인증메일 발송됨
- 발송된 유저 코드 입력 와 새로운 패스워드 입력 필요

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";

//--------------------------------------
// 콜백 방식

const confirmForgotPasswordFunction = async (formData) => {
  Auth.confirmForgotPasswordProcess(
    {
      // 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
      email: userEmail,
      code: formData.userCode,
      newPassword: formData.userPassword, //새로 지정할 newPassword 이다.
      authType: AuthType.EMAIL,
    },
    async (data) => {
      // 성공처리 및 패스워드 변경
      // 성공하면 자동으로 로그인 되니
      // 바로 메인으로 이동하면됨
    },
    (error) => {
      // 코드 잘못 입력
    },
    loadingFunction
  );
};
```

### 비밀번호 분실 2단계 인증 코드 재전송

- 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const resendForgotPasswordFunction = async (formData) => {
  Auth.resendForgotPasswordProcess(
    {
      // 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
      email: formData.userEmail,
      authType: AuthType.EMAIL,
    },
    async (data) => {
      // 성공처리
    },
    (error) => {
      // 실패처리,
    },
    loadingFunction
  );
};
```

---

---

### 로그인 상태에서 패스워드 변경

- 로그인된 상태에서 비밀번호를 변경할 경우
- 여기서는 메일이 발송되지않는다.
- 이미 로그인된 상태에서 실행해야한다.

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const changePasswordFunction = async (formData) => {
    Auth.changePasswordProcess(
      {
        // 만약 화면 이동을 하였다면 이 변수는 이전화면에서 가져와야할 필요가 있다. (라우팅 porps,redux, context등을 이용)
        email: formData.userEmail,
        oldPassword: formData.userOldPassword,
        newPassword: formData.userNewPassword,
        authType: AuthType.EMAIL,
      },
      async (data) => {
        // 성공처리
        // 정상적으로 패스워드 변경
        // 로그아웃 시켜 로그인 화면으로 이동시키는 편이 좋음
      },
      (error) => {
        // 실패처리

      },
      loadingFunction
    );
  );
};
```

---

---

### 로그아웃

- 로그아웃을 하면 토큰이 발급되지 않는다
- isCheckToLoginFunction or checkToLoginFunction에서 실패남
- **로그아웃후 로그인화면으로 강제 이동필요(중요)**
- 소셜 로그인일경우 성공 처리 사이에 소설 로그인을 넣어서 처리한다.

```javascript
import { Auth, CurrentAuthUiState, AuthType } from "@psyrenpark/auth";
//--------------------------------------
// 콜백 방식
const signOutFunction = async () => {
  Auth.signOutProcess(
    {
      authType: AuthType.EMAIL,
    },
    async (data) => {
      // 성공처리 및 로그아웃
      // 리덕스나, context의 저장된 정보 초기화 필요
      // 그후 로그인 화면으로 이동
    },
    (error) => {
      // 실패처리,
    },
    loadingFunction
  );
};
```

---

### 유저 탈퇴 관련

- 실제 amplify에서 삭제 기능은 없으므로 서버딤딩지에게 유저 삭제 api 기능 요청할것
- 서버 담장자는 express-lib-doc.md 참고
- 클라이언트는 이 api가 성공후 꼭 반드시 로그아웃후 redux, context안의 유저 정보등 정리 할것
- 이후 web, cms, react-native등 이후 강제 로그인화면으로 이동 필요

---

### 에러 코드 정의

- 실제 amplify에서 주는 코드 외
- 이 라이브러리에서 던져 주는 에러코드도 있다
- 이 목록에 없는 코드 발견시 제보 바람

```javascript

// amplify에서 error
error.code === "UsernameExistsException"        // email등이 중복되었을 경우
error.code === "InvalidParameterException"      // 필요 파라미터가 부족할경우
error.code === "NotAuthorizedException"         // 인증 권한이 없을경우
error.code === "UserNotFoundException"          // 헤딩 유저가 없을 경우
error.code === "CodeMismatchException"          // 인증 코드가 틀렸을경우
error.code === "LimitExceededException"         // 인증시도 초과시
error.code === "UserLambdaValidationException"  // 개발시난 에러 (api 담당자에게 버그수정 요청)


// custom error
error.code === "NotSignedException"             // 로그인 되지 않았을 경우
error.code === "NotImplementedException"        // 아직 미구현 일경우 (쇼셜 관련 기능)
error.code === "NotSupportAuthTypeException"    // 아직 지원되지 않는 쇼셜 타입
error.code === "SamePasswordException"          // 패스워드 변경시 이전패스워드와 현재 패스워드가 같을경우


// 에러 코드 처리
// - error.code로 검사할것 사용할것

(error) => {
  // 실패처리,
  console.log("changePasswordFunction -> error.code", error.code);
  console.log("changePasswordFunction -> error.name", error.name);
  console.log("changePasswordFunction -> error.message", error.message);
  alert(error.message);
},

// 패스워드를 잘못 입력했을경우 참고 자료
- https://github.com/aws-amplify/amplify-js/issues/1234

- ( 10번 이내 )
code    : NotAuthorizedException
message : "Incorrect username or password"

- ( 10번 이상 )
code    : NotAuthorizedException
message : "Password attempts exceeded"

// 무차별 대입 공격으로부터 보호

- https://stackoverflow.com/questions/37732970/how-aws-cognito-user-pool-defends-against-bruteforce-attacks

`로그인 시도는 5회 실패할 수 있습니다. 그 후 우리는 1초에서 시작하여 각 시도가 실패한 후 최대 약 15분까지 두 배로 증가하는 시간으로 임시 잠금을 시작합니다. 임시 잠금 기간 동안의 시도는 무시됩니다. 임시 잠금 기간이 지난 후 다음 시도가 실패하면 마지막 시간의 두 배 기간으로 새로운 임시 잠금이 시작됩니다. 시도하지 않고 약 15분을 기다리면 임시 잠금도 재설정됩니다. 이 동작은 변경될 수 있습니다.`

```

---

---

### 이메일 전송 양식 설명

- 변경필요시 아이콘과 맨트를 적어서 서버관리자에게 전달할것
- 다국어처리 필요시 서버관리자에게 전달 할것
- 이메일전송시 일단 기본적 양식으로 날라감

- 예시 맨트
  SBAA0404: "안녕하세요", \
  SBAA0405: "싸이페어에 오신 것을 환영합니다", \
  SBAA0406: "하단의 인증번호를 싸이페어 화면에 입력해주세요.", \
  SBAA0407: "이메일 인증번호", \
  SBAA0408: "(주)싸이페어", \
  SBAA0409: "", \
  SBAA0410: "이 메일은 (주)싸이페어에서 발송되었습니다.", \

추가로 이메일에 붙을 아이콘 첨부바람

- 예시 icon
  https://sf-prod-file.s3-ap-northeast-1.amazonaws.com/public/email/img/icon/square_logo_Blck_400.png

- 완성 예시
  https://sf-prod-file.s3-ap-northeast-1.amazonaws.com/public/email/email-example.jpg

---

---

### react-hook-form 예시

- 필수는 아니나 사용하면 편리
- form속성같이 auth화면에서 사용하면 편리하게 개발 가능
- 필수 속성, 입력값 정규식 체크 등이 편리하게 사용가능
- 자세한 내용은 https://react-hook-form.com/ 참고

```javascript
//-------------------------------------------
// react 버전

import React, { useState, useEffect, useContext } from "react";
import { Grid, Checkbox, TextField } from "@material-ui/core";
//-------------------------------------------
// redux
import { useDispatch, useSelector } from "react-redux";

//--------------------------------------------------
// auth
import {
  Auth,
  CurrentAuthUiState,
  AuthType,
  UserState,
} from "@psyrenpark/auth";

//--------------------------------------------------
// hook
import { useForm, useWatch, Controller } from "react-hook-form";
import { useTypedController } from "@hookform/strictly-typed";

//--------------------------------------------------

export const SignInComponent = (props) => {
  const reducer = useSelector((state) => state.reducer);
  const dispatch = useDispatch();

  // useForm 속성
  const {
    register,
    handleSubmit,
    watch,
    errors,
    formState,
    control,
    trigger,
  } = useForm();

  // material ui 사용시
  const TypedController = useTypedController({ control });

  const { toggle } = watch();

  const signInFuntion = async (data) => {
    console.log("signInFuntion -> data", data);

    var userEmail = data.userEmail;
    var userPassword = data.userPassword;
  };

  return (
    <Grid className="sign_in sign">
      {/* Input 사용시 */}
      <Grid className="input_wrap">
        <input
          name="userEmail"
          ref={register({
            required: true,
            pattern: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/,
          })}
          type="text"
          placeholder="이메일 주소"
        />
      </Grid>

      {/* material ui TextField 사용시 */}
      <Grid className="input_wrap">
        <TypedController
          name="userEmail"
          defaultValue=""
          render={(props) => <TextField {...props} />}
          rules={{
            required: true,
            pattern: /^[A-Za-z0-9_\.\-]+@[A-Za-z0-9\-]+\.[A-Za-z0-9\-]+/,
          }}
        />
      </Grid>

      {errors?.userEmail && (
        <p className="warning">email 정보가 올바르지 않습니다.</p>
      )}
      <Grid className="input_wrap">
        <input
          name="userPassword"
          ref={register({
            required: true,
            pattern: /(?=.*\d{1,50})(?=.*[~`!@#$%\^&*()-+=]{1,50})(?=.*[a-zA-Z]{2,50}).{8,50}$/,
          })}
          type="password"
          placeholder="비밀번호"
          onKeyUp={() => {
            if (window.event.keyCode === 13) {
              handleSubmit(signInFuntion);
            }
          }}
        />
      </Grid>
      {errors.userPassword && (
        <p className="warning">password 정보가 올바르지 않습니다.</p>
      )}
      <button
        type="button"
        className={
          watch("userEmail", false) && watch("userPassword", false)
            ? "btn_move on"
            : "btn_move"
        }
        // className={"btn_move"}
        onClick={handleSubmit(signInFuntion)}
      >
        다음
      </button>
    </Grid>
  );
};
```