728x90
반응형

전 글에서 클라이언트 쪽 데이터 전송까지 다루었다.

이번 글에서는 백엔드쪽을 다루어보겠다.

 

1. 전체 코드( email.js )

//Express
const express = require('express');
const router = express.Router();
//Module
const nodemailer = require('nodemailer');
const bcrypt = require('bcrypt');
//Schemas
const {modelQuery} = require('../schemas/query')
const {COLLECTION_NAME, QUERY} = require('../const/consts');

// -- Start Code -- //

// nodemailer 설정
const smtpTransport = nodemailer.createTransport({
  service: "Gmail",
  auth: {
      user: "사용자 이메일주소(구글)",
      pass: process.env.gmail
  },
  tls: {
      rejectUnauthorized: false
  }
});

// 이메일 전송
router.post('/send', async (req, res, next) => {
  const reademailaddress = req.body.EA;
  
  const exEA = await modelQuery(QUERY.Findone,COLLECTION_NAME.Company,{ "EA" : reademailaddress },{});
  try {
    // 이메일이 중복됐을 때
    if(exEA) {
      return res.send({ result : 'exist' });
    }
    else {
      let authNum = Math.random().toString().substr(2,6);
      const hashAuth = await bcrypt.hash(authNum, 12);
      console.log(authNum);
      res.cookie('hashAuth', hashAuth,{
        maxAge: 300000
      });
      const mailOptions = {
        from: "mk.manager2020@gmail.com",
        to: reademailaddress,
        subject: "OASIS 인증번호 관련 메일 입니다.",
        text: "인증번호는 " + authNum + " 입니다.",
        html: "<div style='font-family: 'Apple SD Gothic Neo', 'sans-serif' !important; width: 540px; height: 600px; border-top: 4px solid #348fe2; margin: 100px auto; padding: 30px 0; box-sizing: border-box;'>"+
              "<h1 style='margin: 0; padding: 0 5px; font-size: 28px; font-weight: 400;'>"+
              "<span style='font-size: 15px; margin: 0 0 10px 3px;'>MK_</span><br />"+
              "<span style='color: #348fe2;'>인증번호</span> 안내입니다."+
              "</h1>"+
              "<p style='font-size: 16px; line-height: 26px; margin-top: 50px; padding: 0 5px;'>"+
              "안녕하세요.<br />"+
              "요청하신 인증번호가 생성되었습니다.<br />"+
              "감사합니다."+
              "</p>"+
              
              "<p style='font-size: 16px; margin: 40px 5px 20px; line-height: 28px;'>"+
              "인증번호: <br />"+
              "<span style='font-size: 24px;'>"+authNum+"</span>"+
              "</p>"+
              "<div style='border-top: 1px solid #DDD; padding: 5px;'>"+
              "</div>"+
              "</div>",
      };
      await smtpTransport.sendMail(mailOptions, (err, res) => {
        if(err) {
          console.log(err);
        } else{
          console.log('success');
        }
        smtpTransport.close();
      });
      
      return res.send({ result : 'send' });
    }
  } catch (err) {
    res.send({ result : 'fail' });
    console.error(err);
    next(err);
  }
});

// 이메일 인증
router.post('/cert', async (req, res, next) => {
  const CEA = req.body.CEA;
  const hashAuth = req.cookies.hashAuth;
  
  try {
    if(bcrypt.compareSync(CEA, hashAuth)) {
      res.send({ result : 'success' });
    }
    else {
      res.send({ result : 'fail' });
    }
  } catch(err) {
    res.send({ result : 'fail' });
    console.error(err);
    next(err);
  }
});

module.exports = router;

 

2. 먼저 nodemailer를 사용하기 전 설정을 해준다.

// nodemailer 설정
const smtpTransport = nodemailer.createTransport({
  service: "Gmail",
  auth: {
      user: "사용자 이메일주소(구글)",
      pass: process.env.gmail
  },
  tls: {
      rejectUnauthorized: false
  }
});

 

3. 그다음 ajax로 보낸 데이터를 받을 라우터를 설정해준다. 먼저 mongoose 쿼리로 이미 가입된 이메일이 있는지를 확인하고, 있을 경우에 랜덤으로 인증번호를 생성해 준 뒤 쿠키에 bcrypt로 암호화된 인증번호를 담는식으로 설정하였다.

이메일 전송이 성공하면 클라이언트쪽에서 인증번호 입력칸, 인증버튼이 생성된다.

 

 

 

이메일을 인증하는 방법은 너무나 다양하다. 각자 본인의 입맛에 맞게 설계하면 된다.

 

 

 

구글로 이메일을 보낼 경우에 구글 계정에서 설정해야 될 것들이 있다.

자세한 내용은 아래 주소 참고바란다.

https://ant-programmer.tistory.com/70

728x90
반응형
728x90
반응형

화면 구성

 

nodemailer 라는 좋은 npm모듈이 있다. 해당 서비스는 클라이언트에서 백엔드(node.js)에 이메일을 보낼거란 신호를 주고, 백엔드 쪽에서 여러 설정( 어떤 메일을 어떤 주소로 보낼건지 등 )을 해놓은 상태에서 메일을 전송하게 되고,

자신이 보낸 메일 인증번호와 클라이언트에서 입력한 인증번호가 같으면 인증이 완료되는 시스템이다.

 

1. 클라이언트 html쪽에선 먼저 이메일 주소 입력창, 인증번호 입력창, 보내기버튼, 인증버튼이 존재한다.

    class 에 d-none 옵션을 추가해서 이메일을 보내지 않은 상태에선 인증번호 입력칸, 인증 버튼이 없도록 했다.

<!--이메일 인증-->
<label class="control-label">{{__('email')}} {{__('auth')}}<span class="text-danger"> *</span> </label>
  <div class="form-group m-b-20">
    <input id="EA" name="EA" type="email" class="form-control form-control-lg inverse-mode" value ="{{email}}" placeholder="{{__('email')}}" data-parsley-error-message=null required >
    <input id="CEA" name="CEA" type="text" class="form-control form-control-lg inverse-model mt-3 d-none" placeholder="{{__('authnum')}}" data-parsley-error-message="{{__('required_detail')}}" data-parsley-errors-container="#err-msg2" required >
  <div class="d-flex justify-content-between align-items-center mt-2">
  <div id="err-msg2" class="text-realred"></div>
    <input type="button" id="sendBtn" class="btn btn-primary btn-block btn-lg ml-auto custombtn" value="{{__('send')}}" onclick="emailSend('EA')">
    <input type="button" id="cerBtn" class="btn btn-primary btn-block btn-lg mt-0 ml-auto custombtn d-none" value="{{__('auth')}}" onclick="emailCer('CEA')">
    <input type="hidden" id="hideCK" name="hideCK" value="">
  </div>
</div>

 

2. 클라이언트 javascript 데이터 검증부분에선 먼저 버튼에 onclick에 붙어있는 함수들을 선언한다. parsley를 사용해 input에 제대로 된 데이터가 들어갔는지 먼저 검증하고 맞으면 아래 함수를 실행하도록 되어있다. 

	// 이메일 전송 기능
	function emailSend(email) {
		var EA = document.getElementsByName(email)[0].value;
		var validateEA = $('#'+email).parsley();
		
		if(validateEA.isValid() == true) {
			emailSendAjax(EA);
		}
		else {
			return alert("{{__('register_email_valid_msg')}}");
		}
	}
	
	// 이메일 인증 기능
	function emailCer(cerNum) {
		var CEA = document.getElementsByName(cerNum)[0].value;
		var validateCEA = $('#'+cerNum).parsley();
		
		if(validateCEA.isValid() == true) {
			emailCerAjax(CEA);
		}
		else {
			alert("{{__('register_auth_enter')}}");
		}
	}

 

3. 클라이언트 javascript 함수 실행부분에선 검증된 데이터가 들어왔을 때, ajax로 백엔드에 데이터를 전송하도록 하였다.

// 이메일 전송 기능
function emailSendAjax(email) {
    $.ajax({
		type: 'POST',
		url: '/email/send',
		dataType: 'json',
		data: {
			EA: email
		}
	}).done(function(data) {
		if (data.result == 'exist') {
			alert(i18nconvert('register_already_msg'));
		}
		else if (data.result == 'send') {
			alert(i18nconvert('register_auth_com_msg'));
			document.getElementsByName('EA')[0].readOnly = true;
			document.getElementById('CEA').classList.remove('d-none');
			document.getElementById('cerBtn').classList.remove('d-none');
			document.getElementById('sendBtn').classList.add('d-none');
			
			clearTimeout(timer);
			stopWatch(300);
		}
		else {
			alert(i18nconvert('register_email_send_fail'));
		}
	});
}

// 이메일 인증 기능
function emailCerAjax(cerNum) {
    $.ajax({
		type: 'POST',
		url: '/email/cert',
		dataType: 'json',
		data: {
			CEA: cerNum
		}
	}).done(function(data) {
		if (data.result == 'success') {
			alert(i18nconvert('register_auth_success'));
			clearTimeout(timer);
			document.getElementsByName('CEA')[0].readOnly = true;
			document.getElementById('err-msg2').innerHTML = i18nconvert('register_auth_success');
			document.getElementsByName('hideCK')[0].value = 'true';
		}
		else {
			alert(i18nconvert('register_auth_fail'));
			document.getElementsByName('CEA')[0].value = null;
			document.getElementsByName('hideCK')[0].value = null;
		}
	});
}

 

백엔드 쪽은 다음 글에서 설명하겠다.

 

@ 인증번호 타이머 기능

   setInterval 함수를 사용해 1초에 한번씩 바꿔주었다.

function stopWatch(TimeSet) {
	timer = setInterval(function(){
		sec = (TimeSet)%60;
		document.getElementById('err-msg2').innerHTML =i18nconvert('register_auth_time') + '&nbsp;' + parseInt(TimeSet/60) + i18nconvert('register_auth_minute') + sec + i18nconvert('register_auth_second') + "." + "<br><a href='javascript:;' id='resend' class='text-white' onclick=emailSend('EA')><u>"+i18nconvert('register_auth_resend')+"</u></a>";
		TimeSet--;
		
		if(TimeSet < 0){
			clearTimeout(timer);
			alert(i18nconvert('register_auth_timeout'));
			document.getElementsByName('EA')[0].value = null;
			document.getElementsByName('CEA')[0].value = null;
			document.getElementsByName('EA')[0].readOnly = false;
			document.getElementsByName('CEA')[0].readOnly = false;
			document.getElementsByName('hideCK')[0].value = null;
			document.getElementsByName('hideCNU')[0].value = null;
			document.getElementById('CEA').classList.add('d-none');
			document.getElementById('cerBtn').classList.add('d-none');
			document.getElementById('sendBtn').classList.remove('d-none');
			document.getElementById('err-msg2').innerHTML = i18nconvert('register_reauth_msg');
		}
    }, 1000);
}
728x90
반응형

+ Recent posts