npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@psyrenpark/auth

v1.0.81

Published

viba auth

Downloads

79

Readme

@psyrenpark/auth

설명

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

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

업데이트 내용

  • 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

설정 및 설치

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에 맞는것 재설치

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
  • ...

루트에서 세팅

//--------------------------------------------------
// 각 프로젝트 루트
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);

로딩 콜백 함수 샘플

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

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

로그인 여부 체크

  • 이미 로그인한적이 있는지 체크하는 함수
  • 이 과정을 한 후에 인증이 필요한 api 사용가능
  • 비동기, 콜백 방식중 필요한 방식을 선택하여 사용할 것
  • cms라면 루트 라우팅에서 로그인창을 띄울지 말지를 결정하는 프로세스(중요)
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 등이 있을수 있다. (기능필요시 요청 할것)
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 일경우 강제 로그아웃 및 유저 정보 관련된 정보 초기화 할것
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은 소문자로 변경해주며, 앞뒤 트림도 해줘야한다.
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
  );
};

회원가입 후 인증 확인 함수

  • 회원인증 함수
  • 인증을 체크하는 경우 코드입력 화면에서 코드값을 넣어 아래 함수를 실행한다.
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
  );
};

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

  • 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행
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번째 파라미터 부분 참고할것
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 (테스트중)
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단계

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

  • 유저가 가입한 이메일로 인증메일 발송됨
  • 발송된 유저 코드 입력 와 새로운 패스워드 입력 필요
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단계 인증 코드 재전송

  • 유저가 잘못 코드를 입력한경우 또는 재요청 버튼을 클릭한경우 아래 함수 실행
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
  );
};


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

  • 로그인된 상태에서 비밀번호를 변경할 경우
  • 여기서는 메일이 발송되지않는다.
  • 이미 로그인된 상태에서 실행해야한다.
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에서 실패남
  • 로그아웃후 로그인화면으로 강제 이동필요(중요)
  • 소셜 로그인일경우 성공 처리 사이에 소설 로그인을 넣어서 처리한다.
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에서 주는 코드 외
  • 이 라이브러리에서 던져 주는 에러코드도 있다
  • 이 목록에 없는 코드 발견시 제보 바람

// 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/ 참고
//-------------------------------------------
// 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>
  );
};