본문 바로가기
프로젝트/로그인, 회원가입

5. nodemailer, 인증코드

by 갱생angel 2024. 5. 27.

nodemailer로 인증코드를 보내 비밀번호 찾기 기능 구현

 

-백엔드-

 

nodemailer : node.js에서 이메일을 보낼 수 있게 해주는 애플리케이션 모듈

>> npm install nodemailer //nodemailer 설치

 

<.env> : 송신할 이메일 주소 지정

MAIL_USER = '발신자 이메일 주소'
MAIL_PWD = '발신자 이메일 비밀번호'

 

model - <authModel.js> : 인증코드 document를 저장할 스키마 생성

  -expires : document 만료 시간 지정

const mongoose = require("mongoose");

const Schema = mongoose.Schema;

const AuthSchema = new Schema({
  code: { //인증코드
    type: String,
    require: true,
  },
  timeOut: { //제한시간
    type: Date,
    default: Date.now,
    expires: 180, // 3분 후 자동 삭제
  },
});

module.exports = mongoose.model("Authcode", AuthSchema);

 

controller - <userController.js>

※nodemailer

  1) createTransport() : 메일을 보내기 위한 송신 객체인 트랜스포트 생성 함수

      -service : 서버 서비스 종류

      -host  : 서버 호스트명 

      -port  : 서버 포트 넘버

      -auth  : 발신자 인증 정보(user : 발신자 이메일 주소, pass : 발신자 이메일 비밀번호)

  2) sendMail() : 이메일 송신 함수

     -from : 발신자 이메일 주소

     -to : 수신자 이메일 주소

     -subject : 이메일 제목

     -text : 이메일 내용

(...)

const Authcode = require("../model/authModel"); //인증코드 스키마 등록
const nodemailer = require("nodemailer"); //nodemailer 모듈 등록

//메일 발송 트랜스포트
const mailPoster = nodemailer.createTransport({
  service: "naver", //서버 서비스 종류
  host: "smtp.naver.com", //서버 호스트명
  port: 587, //서버 포트 번호
  auth: { user: process.env.MAIL_USER, pass: process.env.MAIL_PWD }, //발신자 인증 정보
});

//Post Find User_pwd, /find_pwd : 비밀번호 찾기
const findPwd = asynchHandler(async (req, res) => {
  const { username, email } = req.body;
  const user = await User.findOne({ username });

  if (!user) {
    return res.status(401).json({ nameMessage: "일치하는 아이디가 없습니다." });
  } else if (user.email !== email) {
    return res.status(401).json({ emailMessage: "이메일이 틀립니다." });
  } else {
    const code = Math.floor(100000 + Math.random() * 900000); //인증코드 6자리 생성
    await Authcode.create({ code }); //인증코드 document 생성

    const mailOption = { //메일 옵션 지정
      from: process.env.MAIL_USER, //발신자 이메일 주소
      to: email, //수신자 사람 이메일 주소
      subject: "비밀번호 재설정 인증코드", //이메일 제목
      text: `인증 코드는 ${code}입니다.`, //이메일 내용
    };

    mailPoster.sendMail(mailOption, (error, info) => { //이메일 송신
      if (error) {
        console.log(error);
        res.status(400).json({ message: "인증코드 전송에 실패햇습니다." });
      } else {
        res.status(201).json({ message: "인증코드가 전송되었습니다." });
      }
    });
  }
});

//Post Auth Code, /authcode : 인증코드 확인
const checkAuthCode = asynchHandler(async (req, res) => {
  const { code } = req.body;
  const authCode = await Authcode.findOne({ code });
  if (!authCode) {
    return res.status(400).json({ message: "인증 코드가 올바르지 않습니다." });
  }
  res.status(200).json({ message: "인증 코드가 확인되었습니다." });
});

(...)

module.exports = { findPwd, checkAuthCode };

 

route - <userRoute.js>

const express = require("express");
const router = express.Router();
const {
  getUserData,
  loginUser,
  findId,
  findPwd, //비밀번호 찾기 함수
  checkAuthCode, //인증코드 확인 함수
  registerUser,
  addImageScore,
  addCombineScore,
} = require("../controller/userController");
const { authUser } = require("../middleware/authMiddleware");

router.route("/login").get(authUser, getUserData).post(loginUser);
router.route("/find_id").post(findId);
router.route("/find_pwd").post(findPwd); //비밀번호 찾기 라우터
router.route("/authcode").post(checkAuthCode); //인증코드 확인 라우터
router.route("/register").post(registerUser);
router.route("/imageScore").post(authUser, addImageScore);
router.route("/combineScore").post(authUser, addCombineScore);

module.exports = router;

-프론트엔드-

 

page - <PwdFind.js> : 비밀번호 찾기 페이지

  -isFind가 false면 비밀번호 찾기 페이지, true면 인증코드 확인 페이지

import axios from "axios";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import "../../css/user.css";

function PwdFind() {
  const navigate = useNavigate();

  const [isFind, setIsFind] = useState(false);

  const [username, setUsername] = useState(""); //아이디 state
  const [email, setEmail] = useState(""); //이메일 state
  const [authCode, setAuthCode] = useState(""); //인증코드 state
  
  const [usernameError, setUsernameError] = useState(""); //아이디 에러 state
  const [emailError, setEmailError] = useState(""); //이메일 에러 state
  const [authCodeError, setAuthCodeError] = useState(""); //인증코드 에러 state

  const changeUsername = (e) => setUsername(e.target.value);
  const changeEmail = (e) => setEmail(e.target.value);
  const changeAuthCode = (e) => setAuthCode(e.target.value);

  const pwdFindSubmit = async (e) => { //비밀번호 확인 함수
    e.preventDefault();

    const pwdData = { username: username, email: email };

    if (username === "" || email === "") {
      alert("빈칸을 입력해주세요.");
    } else {
      try {
        await axios
          .post("http://localhost:5000/find_pwd", pwdData)
          .then((res) => {
            alert(res.data.message);
            setIsFind(true);
          });
        localStorage.setItem("username", username); //아이디를 localStorage에 저장
      } catch (err) {
        setUsernameError(err.response.data.nameMessage);
        setEmailError(err.response.data.emailMessage);
      }
    }
  };

  const submitAuthCode = async (e) => { //인증코드 확인 함수
    e.preventDefault();

    if (authCode === "") {
      alert("빈칸을 입력해주세요.");
    } else {
      try {
        await axios
          .post("http://localhost:5000/authcode", { code: authCode })
          .then((res) => {
            alert(res.data.message);
            navigate("/change_pwd"); //비밀번호 변경 페이지로 이동
          });
      } catch (err) {
        setAuthCodeError(err.response.data.message);
      }
    }
  };

  useEffect(() => { //토큰이 있으면 메인페이지로 이동
    if (localStorage.getItem("token") !== null) navigate("/main"); 
  }, [navigate]);

  return (
    <div className="userContainer">
      <h1>비밀번호 찾기</h1>
      {isFind ? (
        <div>
          <form onSubmit={submitAuthCode}>
            <div>
              <label>인증코드(4자리, 3분)</label>
              <input type="text" value={authCode} onChange={changeAuthCode} />
              <h4>{authCodeError}</h4>
            </div>
            <button type="submit" className="submitBtn">
              인증
            </button>
          </form>
        </div>
      ) : (
        <div>
          <form onSubmit={pwdFindSubmit}>
            <div>
              <label>ID</label>
              <input type="text" value={username} onChange={changeUsername} />
              <h4>{usernameError}</h4>
            </div>
            <div>
              <label>이메일</label>
              <input
                type="text"
                value={email}
                onChange={changeEmail}
                placeholder="ex) admin@aaa.com"
              />
              <h4>{emailError}</h4>
            </div>
            <button type="submit" className="submitBtn">
              다음
            </button>
          </form>
          <p onClick={() => navigate("/")}>-#로그인-</p>
        </div>
      )}
    </div>
  );
}

export default PwdFind;

 

<App.js>

import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Login from "./page/User/Login";
import Register from "./page/User/Register";
import PostAdd from "./page/Comunity/PostAdd";
import PostList from "./page/Comunity/PostList";
import PostDetail from "./page/Comunity/PostDetail";
import PostUpdate from "./page/Comunity/PostUpdate";
import Home from "./page/Home";
import ImageRegist from "./page/Game/ImageRegist";
import ImageGame from "./page/Game/ImageGame";
import CombineGame from "./page/Game/CombineGame";
import IdFind from "./page/User/IdFind";
import PwdFind from "./page/User/PwdFind";

function App() {
  return (
    <div>
      <BrowserRouter>
        <Routes>
          <Route index element={<Login />} />
          <Route path="/find_id" element={<IdFind />} />
          <Route path="/find_pwd" element={<PwdFind />} /> <!--비밀번호 찾기-->
          <Route path="/register" element={<Register />} />
          <Route path="/main" element={<Home />} />
          <Route path="/post" element={<PostList />} />
          <Route path="/post/add" element={<PostAdd />} />
          <Route path="/post/:id" element={<PostDetail />} />
          <Route path="/post/:id/update" element={<PostUpdate />} />
          <Route path="/image/add" element={<ImageRegist />} />
          <Route path="/imageGame" element={<ImageGame />} />
          <Route path="/combineGame" element={<CombineGame />} />
        </Routes>
      </BrowserRouter>
    </div>
  );
}

export default App;

'프로젝트 > 로그인, 회원가입' 카테고리의 다른 글

7. 인증코드 제한 시간  (0) 2024.06.06
6. 비밀번호 변경  (0) 2024.05.27
4. 아이디 찾기  (0) 2024.05.24
3. 이메일, 정규 표현식  (0) 2024.05.23
2. JWT 토큰 검증  (0) 2024.05.20