NodeJs

[NODE JS] nodemailer를 사용한 gmail 인증(3)

dbsxo4083 2024. 8. 28. 16:11

✍🏻 index.ejs

<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <div>
        <input type="email" id="email" placeholder="Enter email">
        <button onclick="sendEmail()">이메일 전송</button>
    </div>

    <!-- 인증번호 입력 필드와 제출 버튼을 숨기고 있다가 이메일 전송 후에 표시 -->
    <div id="verification-section" style="display: none;">
        <input type="text" id="verification-code" placeholder="Enter verification code">
        <button onclick="verifyCode()">Verify Code</button>
        <p id="timer"></p>
        <p id="verification-message"></p> <!-- 인증 메시지를 표시할 위치 -->
    </div>

    <script>
        let verificationExpiryTime; // 만료 시간 저장 변수
        let email; // 이메일 저장 변수

        function sendEmail() {
            email = document.getElementById('email').value;
            
            fetch('/send-email', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ email: email })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    console.log('Email sent:', data.message);
                    // 이메일 전송 성공 시 인증번호 입력 필드와 타이머 표시
                    document.getElementById('verification-section').style.display = 'block';
                    
                    // 인증번호 만료 시간 설정 (현재 시간 + 1분)
                    verificationExpiryTime = Date.now() + 1 * 60 * 1000;
                    startTimer();
                } else {
                    console.error('Error:', data.message);
                }
            })
            .catch((error) => {
                console.error('Error:', error);
            });
        }

        function verifyCode() {
            const code = document.getElementById('verification-code').value;

            fetch('/verify-code', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({ email: email, code: code })
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    // 인증 성공 시 메시지 표시 및 타이머와 버튼 숨기기
                    document.getElementById('verification-message').textContent = 'Authentication successful!';
                    document.getElementById('verification-section').style.display = 'none'; // 인증 입력 필드와 타이머 숨김
                } else {
                    console.error('Error:', data.message);
                }
            })
            .catch((error) => {
                console.error('Error:', error);
            });
        }

        function startTimer() {
            const timerElement = document.getElementById('timer');
            const updateTimer = () => {
                const now = Date.now();
                const timeLeft = verificationExpiryTime - now;
                
                if (timeLeft <= 0) {
                    timerElement.textContent = 'Verification code has expired.';
                    // 인증번호 만료 후 처리
                    document.getElementById('verification-section').style.display = 'none'; // 인증 입력 필드와 타이머 숨김
                } else {
                    const minutes = Math.floor(timeLeft / 60000);
                    const seconds = Math.floor((timeLeft % 60000) / 1000);
                    timerElement.textContent = `Time left: ${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
                    setTimeout(updateTimer, 1000); // 1초마다 업데이트
                }
            };
            updateTimer();
        }
    </script>
</body>
</html>

 

 

  • <input type="email" id="email" placeholder="Enter email">: 사용자로부터 이메일 주소를 입력받는 필드입니다. id="email"은 자바스크립트에서 이 필드를 참조하기 위한 식별자입니다.
  • <button onclick="sendEmail()">이메일 전송</button>: 사용자가 이메일을 전송하려고 클릭하는 버튼입니다. 클릭 시 sendEmail() 함수가 호출됩니다.

 

  • <div id="verification-section" style="display: none;">: 이 섹션은 처음에는 숨겨져 있으며, 사용자가 이메일을 전송한 후에 표시됩니다. display: none; 스타일 속성으로 숨겨져 있습니다.
  • <input type="text" id="verification-code" placeholder="Enter verification code">: 사용자가 이메일로 받은 인증 코드를 입력할 수 있는 필드입니다.
  • <button onclick="verifyCode()">Verify Code</button>: 사용자가 인증 코드를 제출하는 버튼입니다. 클릭 시 verifyCode() 함수가 호출됩니다.
  • <p id="timer"></p>: 인증 코드의 남은 시간을 표시할 위치입니다.
  • <p id="verification-message"></p>: 인증 성공 또는 실패 메시지가 표시될 위치입니다.

 

  • let verificationExpiryTime;: 인증 코드의 만료 시간을 저장하는 변수입니다.
  • let email;: 사용자가 입력한 이메일 주소를 저장하는 변수입니다.

 

sendEmail()

  • function sendEmail(): 이메일 전송을 처리하는 함수입니다.
  • email = document.getElementById('email').value;: 사용자가 입력한 이메일 주소를 가져와 email 변수에 저장합니다.
  • fetch('/send-email', { ... }): 서버에 이메일 전송을 요청하는 POST 요청을 보냅니다. body에 이메일 주소가 포함됩니다.
  • .then(response => response.json()): 서버의 응답을 JSON 형식으로 파싱합니다.
  • .then(data => { ... }): 이메일 전송 성공 여부에 따라 처리합니다. 성공하면 인증번호 입력 필드가 나타나고 타이머가 시작됩니다.
  • verificationExpiryTime = Date.now() + 1 * 60 * 1000;: 현재 시간으로부터 1분 후를 만료 시간으로 설정합니다.
  • startTimer();: 타이머를 시작합니다.

verifyCode()

 

  • function verifyCode(): 인증 코드를 검증하는 함수입니다.
  • const code = document.getElementById('verification-code').value;: 사용자가 입력한 인증 코드를 가져와 code 변수에 저장합니다.
  • fetch('/verify-code', { ... }): 서버에 인증 코드 검증을 요청하는 POST 요청을 보냅니다. 이메일과 인증 코드가 body에 포함됩니다.
  • .then(response => response.json()): 서버의 응답을 JSON 형식으로 파싱합니다.
  • .then(data => { ... }): 인증 성공 여부에 따라 처리합니다. 성공하면 인증 메시지를 표시하고 인증번호 입력 필드를 숨깁니다.

startTimer()

 

  • function startTimer(): 인증 코드의 남은 시간을 카운트다운하는 타이머를 시작하는 함수입니다.
  • const timerElement = document.getElementById('timer');: 타이머가 표시될 HTML 요소를 가져옵니다.
  • const updateTimer = () => { ... };: 타이머를 업데이트하는 함수입니다.
  • const now = Date.now();: 현재 시간을 가져옵니다.
  • const timeLeft = verificationExpiryTime - now;: 인증 코드의 남은 시간을 계산합니다.
  • if (timeLeft <= 0) { ... } else { ... }: 만료 시간이 지났는지 확인하고, 만료되면 타이머를 숨기고 메시지를 표시합니다. 그렇지 않으면 남은 시간을 표시하고 1초마다 업데이트합니다.

 

✍🏻index.js

const express = require('express');
const app = express();
const path = require('path');
const port = 3000;
const nodemailer = require('nodemailer');
require('dotenv').config(); // 환경 변수 로드

app.set("view engine", "ejs");
app.set('views', path.join(__dirname, 'views'));
app.use(express.json());

const transporter = nodemailer.createTransport({
    service: "gmail",
    port: 587,
    auth: {
        user: process.env.user, // 환경 변수로 설정
        pass: process.env.pass  // 환경 변수로 설정
    }
});

let verificationData = {}; // 이메일과 인증코드를 저장할 객체


app.post('/send-email', (req, res) => {
    const { email } = req.body;

    // 생성할 인증번호 (6자리 랜덤 숫자)
    const verificationCode = Math.floor(100000 + Math.random() * 900000).toString();

    verificationData[email] = { code: verificationCode, expiry: Date.now() + 1 * 60 * 1000 }; // 1분 만료

    const mailOptions = {
        to: email,
        subject: 'Your Verification Code',
        text: `Your verification code is ${verificationCode}`
    };

    transporter.sendMail(mailOptions, (error, info) => {
        if (error) {
            console.error('Error sending email:', error);
            return res.status(500).json({ success: false, message: 'Failed to send email' });
        }
        console.log('Email sent:', info.response);
        res.json({ success: true, message: 'Email sent successfully' });
    });
});

app.post('/verify-code', (req, res) => {
    const { email, code } = req.body;

    const data = verificationData[email];

    if (data) {
        if (Date.now() > data.expiry) {
            delete verificationData[email]; // 만료된 코드 제거
            return res.json({ success: false, message: 'Verification code has expired.' });
        }
        if (code === data.code) {
            delete verificationData[email]; // 인증 성공 후 코드 제거
            res.json({ success: true, message: 'Code verified successfully' });
        } else {
            res.json({ success: false, message: 'Invalid code' });
        }
    } else {
        res.json({ success: false, message: 'No code found for this email' });
    }
});

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});

 

 

 

 

 

Nodemailer 설정

 

  • nodemailer.createTransport(): 이메일 전송을 위한 SMTP 설정을 구성합니다.
  • service: "gmail": 이메일을 전송할 서비스로 Gmail을 사용합니다.
  • port: 587: SMTP 서버의 포트 번호입니다. 587은 TLS(Transport Layer Security)를 사용한 이메일 전송에 주로 사용됩니다.
  • auth: 이메일 인증 정보를 설정합니다. user와 pass는 환경 변수로 저장된 Gmail 계정의 이메일과 비밀번호입니다.

 

인증 데이터 저장

  • verificationData: 이메일과 인증 코드를 저장하는 객체입니다. 키로는 이메일 주소, 값으로는 인증 코드와 만료 시간을 저장합니다

이메일 전송 엔드포인트

 

  • app.post('/send-email', (req, res): 이메일 전송을 처리하는 POST 요청의 엔드포인트입니다.
  • const { email } = req.body;: 클라이언트로부터 받은 이메일 주소를 추출합니다.
  • const verificationCode = Math.floor(100000 + Math.random() * 900000).toString();: 6자리 랜덤 숫자를 생성하여 인증 코드로 사용합니다.
  • verificationData[email] = { code: verificationCode, expiry: Date.now() + 1 * 60 * 1000 };: 이메일을 키로 사용하여 인증 코드와 만료 시간을 저장합니다. 만료 시간은 현재 시간으로부터 1분 후로 설정됩니다.
  • const mailOptions = { ... };: 이메일 전송 옵션을 설정합니다. to는 수신자 이메일 주소, subject는 이메일 제목, text는 이메일 내용입니다.
  • transporter.sendMail(mailOptions, (error, info): 설정한 이메일을 전송합니다. 전송 성공 여부에 따라 클라이언트에 응답합니다. 성공 시 JSON 형식으로 성공 메시지를 반환합니다. 실패 시 오류 메시지를 반환합니다.

인증 코드 검증 엔드포인트

 

  • app.post('/verify-code', (req, res): 인증 코드를 검증하는 POST 요청의 엔드포인트입니다.
  • const { email, code } = req.body;: 클라이언트로부터 받은 이메일과 인증 코드를 추출합니다.
  • const data = verificationData[email];: verificationData 객체에서 이메일에 해당하는 인증 정보를 가져옵니다.
  • if (data): 인증 정보가 존재하는지 확인합니다.
    • if (Date.now() > data.expiry): 인증 코드가 만료되었는지 확인합니다. 만료되었다면, 해당 이메일의 인증 정보를 삭제하고 만료 메시지를 반환합니다.
    • if (code === data.code): 사용자가 입력한 코드가 서버에 저장된 코드와 일치하는지 확인합니다. 일치하면 인증 성공 메시지를 반환하고, 인증 정보를 삭제합니다. 일치하지 않으면 오류 메시지를 반환합니다.
  • else: 인증 정보가 없을 경우 오류 메시지를 반환합니다.