개발일지

개발일지 46일차

index.ys 2023. 5. 6. 23:52

Nodemailer로 이메일 인증기능 구현

- 회원가입시 body 입력 받은 이메일에 인증코드를 전송하고 전송받은 코드를 input에 입력시 회원가입이 가능하게 하는 코드 구현

ejs, dotenv, nodemailer 패키지 설치

 npm install dotenv nodemailer ejs

모듈 require

const nodemailer = require('nodemailer');
const ejs = require('ejs');

메일에 ejs를 첨부하여 전송코드 작성

 let emailTemplete;
      ejs.renderFile(appDir + '/template/authMail.ejs', { authCode: authNum }, function (err, data) {
         if (err) { console.log(err) }
         emailTemplete = data;
      });

createTransport 메소드로 발신자 정보 설정

  • service: 구글이나 네이버 등 서비스명 기입 ex) Naver, Gmail
  • user: 보내는 사람의 이메일 설정 ex)  sdnfie@naver.com으로 설정하면 보내는 사람의 이메일로 설정 여기서는 환경변수로 이메일을 설정, 실제 네이버에 가입된 이메일로 작성해야함
  • password: 보내는 사람의 비밀번호 설정, 실제 네이버에 가입된 비밀번호로 작성
 let transporter = nodemailer.createTransport({ // 보내는사람 메일 설정입니다.
         service: 'Naver', // 보낼 메일서비스명
         auth: {
            user: process.env.NODEMAILER_USER, // 사용자의 아이디
            pass: process.env.NODEMAILER_PASS, // 사용자의 패스워드
         }
      });

받는 사람의 메일 설정

  • from: 보내는 사람의 이메일 주소 설정, 환경변수로 설정함
  • to: 받는 사람의 이메일 주소 설정, body로 받아온 nickname인자에 할당된 이메일로 인증 메일 발송
  • subject: "메일의 제목부분을 작성"
  • html: 메일 발송시 ejs파일에 작성된 html전송
   let mailOptions = {  // 받는사람 메일 설정입니다.
         from: process.env.NODEMAILER_USER, // 발송 메일 주소
         to: nickname, // 받는사람 이메일
         subject: '회원가입을 위한 인증번호를 입력해주세요.',
         html: emailTemplete,
      }

sendMail 메소드로 메일 전송

  • sendMail메소드의 첫번째 인자로 전송할 메일의 설정을 전달 여기서는 변수 mailOpions에 할당, 두번째 인자로 전송완료시메세지 출력
 transporter.sendMail(mailOptions, (error, info) => { // 이메일 발송
         res.status(200).json({ "message": `${nickname}이메일 발송 성공` }).console.log('이메일 발송에 성공했습니다: ' + info.response); // 성공
      })

발생한 오류

  • 네이버에 가입되지 않은 이메일을 발신자로 설정하였을때 메일 발송 불가
  • router를 분리하지 않고 signup 라우터에 발송부터, 회원가입까지 입력했을때 회원가입이 불가능 => signup라우터와 authMail 라우터를 분리하여 authMail 라우터에 post요청 후 발송된 인증코드를 signup라우터에서 처리 할수 있도록 분리

authMail 라우터

6자리 랜덤 인증코드 생성

- authMail로 post 요청시 인증 코드 생성

let authNum = Math.random().toString().substr(2, 6);

생성된 인증코드

- 535303

router.post('/authMail', async (req, res) => {
   const { nickname } = req.body
   try {
      let emailTemplete;
      ejs.renderFile(appDir + '/template/authMail.ejs', { authCode: authNum }, function (err, data) {
         if (err) { console.log(err) }
         emailTemplete = data;
      });

      let transporter = nodemailer.createTransport({ // 보내는사람 메일 설정입니다.
         service: 'Naver', // 보낼 메일서비스명
         port: 587,
         secure: false,
         auth: {
            user: process.env.NODEMAILER_USER, // 사용자의 아이디
            pass: process.env.NODEMAILER_PASS, // 사용자의 패스워드
         }
      });
      let mailOptions = {
         from: process.env.NODEMAILER_USER,
         to: nickname,
         subject: '회원가입을 위한 인증번호를 입력해주세요.',
         html: emailTemplete,
      }

      transporter.sendMail(mailOptions, (error, info) => { // 이메일 발송
         res.status(200).json({ "message": `${nickname}이메일 발송 성공` }).console.log('이메일 발송에 성공했습니다: ' + info.response); // 성공
         transporter.close()
      })
   } catch (error) {
      console.error(`${req.method} ${req.originalUrl} : ${error.message}`);
   }

})

전송된 이메일

signup 라우터

- authMail 라우터에서 인증코드를 발급 받은 후, 사용자에게 입력받은 인증번호를 authcode에 할당하고 생성된 인증코드와 비교하여 true일때 에러처리 실행 

// 회원 가입 API
router.post('/signup', async (req, res) => {
   const { nickname, password, confirm, authcode } = req.body
   console.log(nickname, password, confirm, authcode)
   console.log(authcode, authNum)
   try {
      if (authcode === authNum) {
         const isExistuser = await UserSchema.findOne({ nickname });
         if (isExistuser) {
            res.status(412).json({
               errorMessage: "중복된 닉네임입니다."
            })
            return
         };

         // 닉네임 최소 3글자 이상, 알파벳 대소문자, 숫자 외 에러메세지
         if ((!/^[a-zA-Z0-9@]+$/.test(nickname)) || (nickname.length < 4)) {
            res.status(412).json({
               errorMessage: "닉네임의 형식이 일치하지 않습니다."
            })
            return
         };

         // 패스워드, 확인패스워드 일치 검증
         if (password !== confirm) {
            res.status(412).json({
               errorMessage: "패스워드가 일치하지 않습니다."
            })
            return  // 패스워드 검증이 실패하면 뒤에는 실행시키지 않도록 return으로 브레이크
         };

         // 패스워드 닉네임을 포함시키면 에러메세지
         if (password.includes(nickname)) {
            res.status(412).json({
               errorMessage: "패스워드에 닉네임이 포함되어 있습니다."
            })
            return
         };

         // 패스워드 4글자 이하이면 에러메세지 
         if (password.length <= 4) {
            res.status(412).json({
               errorMessage: "패스워드 형식이 일치하지 않습니다."
            })
            return
         }
      }

      console.log(authcode, authNum)
      if (authcode !== authNum) {
         res.status(412).json({
            errorMessage: "인증코드가 일치하지 않습니다"
         })
         return


      };
      // 닉네임 DB 중복 검증
      const user = new UserSchema({ nickname, password });
      await user.save();
      return res.status(201).json({ message: "회원 가입에 성공하였습니다." });

   } catch (error) {
      console.error(`${req.method} ${req.originalUrl} : ${error.message}`);
      res.status(400).json({ "message": "이메일 발송 실패" })
   }

});

수정할 수 있는 부분

  • authMail 라우터에서 transporter 변수를 모듈파일로 따로 빼고 require로 불러오기
  • 이메일 유효성검사 정규표현식 수정 => @가 포함되게
  • 입력된 authcode와 생성된 authNum이 같을떄 작성한 조건문 삼항 연산자로 수정하기
  • 에러처리 부분 수정하기