관리 메뉴

SIMPLE & UNIQUE

4-3강 : 이메일 인증을 사용한, 비밀번호 재설정 구현 본문

탈잉 강의 자료/2020_비전공자도 가능한 웹 프로젝트

4-3강 : 이메일 인증을 사용한, 비밀번호 재설정 구현

착한코딩 2020. 8. 3. 23:59

4_3강 소스.zip
0.53MB
4_3강 소스_덮어쓰기.zip
0.26MB

4_3 목표 : 사용자가 비밀번호를 잊었을 때, 이메일 인증을 통해 새로운 비밀번호를 설정할 수 있게 한다. 이메일 템플릿을 html로 작성하고, 사용자 아이디와 토큰을 가공해 전송한다. 이메일에서 버튼을 누르면 다시 사이트로 돌아오고 아이디와 토큰으로 회원 인증을 한다.

 

1. 아이디와 사용자 이름을 입력하면 이메일 발송 API를 호출한다.

 

1) react 경로 C:\react200\client\src\components에 있는 LoginForm.js를 아래와 같이 수정한다. 이메일 발송 함수(

sendEmail)부분이 추가 됐다.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import axios from "axios";
import cookie from 'react-cookies';
import Swal from 'sweetalert2';
import $ from 'jquery';

class LoginForm extends Component {
    submitClick = (e) => {
        this.email_val = $('#email_val').val();
        this.pwd_val = $('#pwd_val').val();
        if(this.email_val === '' || this.pwd_val === ''){
            this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
        }else{
            axios.post('/api/LoginForm?type=signin', {
                is_Email: this.email_val,
                is_Password: this.pwd_val
            })
            .then( response => {
                var userid = response.data.json[0].useremail
                var username = response.data.json[0].username
                var upw = response.data.json[0].userpassword
                
                if(userid != null && userid != ''){
                    this.sweetalert('로그인 되었습니다.', '', 'info', '닫기')
                    const expires = new Date()
                    expires.setMinutes(expires.getMinutes() + 60)
                    
                    axios.post('/api/LoginForm?type=SessionState', {
                        is_Email: userid,
                        is_UserName: username,
                    })
                    .then( response => {
                        cookie.save('userid', response.data.token1
                        , { path: '/', expires })
                        cookie.save('username', response.data.token2
                        , { path: '/', expires })
                        cookie.save('userpassword', upw
                        , { path: '/', expires })
                    })  
                    .catch( error => {
                        this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');
                    });
                    
                    setTimeout(function() {
                        window.location.href = '/SoftwareList';
                    }.bind(this),1000);
                }else{
                    this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
                }
            })
            .catch( error => {this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')} );
        }
    }

    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    pwdResetClick = () => {
        $('.signin').hide();
        $('.chgpw').fadeIn();
        $('.chgpw').css('display','table-cell');
    }

    pwdResetCancleClick = () => {
        $('.chgpw').hide();
        $('.signin').fadeIn();
        $('.signin').css('display','table-cell');
    }

    pwdResetConfim = (e) => {
        this.reset_email = $('#reset_email_val').val();
        this.reset_name = $('#reset_name_val').val();
        if(this.reset_email === '' || this.reset_name === ''){
            this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
        }else{
            axios.post('/api/LoginForm?type=pwreset', {
                is_Email: this.reset_email,
                is_Name: this.reset_name,
            })
            .then( response => {
                var userpassword = response.data.json[0].userpassword
                userpassword = userpassword.replace(/\//gi,"가")
                
                if(userpassword != null && userpassword != ''){
                    this.sendEmail(this.reset_email, 'react200 비밀번호 재설정 메일', userpassword)
                }else{
                    this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
                }
            })
            .catch( error => {
                this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
            });
        }
    }

    sendEmail = (email, subject, password, e) => {
        axios.post('/api/mail', {
            is_Email : email,
            is_Subject : subject,
            is_Password: password
        })
        .then( response => {
            if(response.data == "succ"){
                this.sweetalert('입력하신 이메일로 비밀번호 \n'
                + '재설정 메일 보내드렸습니다.', '', 'info', '닫기')
            }else{
                this.sweetalert('작업중 오류가 발생하였습니다.', '', 'error', '닫기');
            }
        })
        .catch( error => {
            this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');
        });
    }

    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    render () {
        return (
            <section className="main">
                <div className="m_login signin">
                <h3><span><img src={require("../img/main/log_img.png")} alt="" />
                </span>LOGIN</h3>
                <div className="log_box">
                    <div className="in_ty1">
                        <span><img src={require("../img/main/m_log_i3.png")} alt="" /></span>
                        <input type="text" id="email_val" placeholder="이메일" />
                    </div>
                    <div  className="in_ty1">
                        <span className="ic_2">
                            <img src={require("../img/main/m_log_i2.png")} alt="" />
                        </span>
                        <input type="password" id="pwd_val" placeholder="비밀번호" />
                    </div>
                    <ul className="af">
                        <li><Link to={'/register'}>회원가입</Link></li>
                        <li className="pwr_b" onClick={this.pwdResetClick}><a href="#n">비밀번호 재설정</a></li>
                    </ul>
                    <div className="s_bt" type="" onClick={(e) => this.submitClick(e)}>로그인</div>
                </div>
                </div>
                <div className="m_login m_pw chgpw">
                <h3 className="pw_ls">비밀번호 재설정 <span className="compl1">완료</span></h3>
                <div className="log_box">
                    <div className="pw_one">
                        <div className="in_ty1">
                        <span><img src={require("../img/main/m_log_i3.png")} alt="" /></span>
                        <input type="text" id="reset_email_val" name="" placeholder="이메일"/>
                        </div>
                        <div  className="in_ty1">
                        <span className=""><img src={require("../img/main/m_log_i1.png")} alt="" /></span>
                        <input type="text" id="reset_name_val" name="" placeholder="성명"/>
                        </div>
                        <div className="btn_confirm btn_confirm_m">
                        <div className="bt_ty bt_ty_m bt_ty1 cancel_ty1" 
                        onClick={this.pwdResetCancleClick}>취소</div>
                        <a href="#n" className="bt_ty bt_ty_m bt_ty2 submit_ty1" 
                        onClick={this.pwdResetConfim}>확인</a>
                        </div>
                    </div>
                </div>
                </div>
            </section>
        );
    }
}

export default LoginForm;

2) node 경로 C:\react200에 있는 server.js를 아래와 같이 수정한다.

var express = require('express');

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var swtoolRouter = require("./routes/SwtoolRout");
var fileuploadRouter = require("./routes/UploadRout");
var usersRouter = require("./routes/UsersRout");
var MailRout = require("./routes/MailRout");
require("./routes/BatchRout");

var app = express();

app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use("/api/Swtool", swtoolRouter);
app.use("/api/upload", fileuploadRouter);
app.use(express.static("./uploads"));
app.use("/api/register", usersRouter);
app.use("/api/LoginForm", usersRouter);
app.use("/api/mail", MailRout);

const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));

 

3) node 경로 C:\react200\routes에 MailRout.js 파일을 추가하고 아래 내용을 붙여 넣는다.

var express = require('express');
var router = express.Router();
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
var fs = require('fs')

router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));

router.post('/', (req, res, next) => {
  let email = req.body.is_Email;
  let subject = req.body.is_Subject;
  var password = req.body.is_Password;
  password = password.substr(0, 20)
  
  let transporter = nodemailer.createTransport({
    service: 'gmail',
    host: 'smtp.gmail.com',
    port: 465,
    secure: true,
    auth: {
      user: '########@gmail.com',
      pass: '********!'
    }
  });

  home_url = 'http://localhost:3000'
  var toHtml = ''
  fs.readFile(__dirname+'/template/mail_template_pw.html', function (err, html) {
    toHtml = html.toString()
    toHtml = toHtml.replace('{replacetext}', home_url+'/PwChangeForm/'+ email +'/'+password)
  })

  setTimeout(function() {
    let mailOptions = {
      from: '#######@gmail.com',
      to: email,
      subject: subject,
      html : toHtml
    };
    transporter.sendMail(mailOptions, function(error, info){
      if (error) {
        console.log(error);
        res.send("error");
      }
      else {
        console.log('Email sent: ' + info.response);
        res.send("succ");
      }
    });
  }.bind(this),1000
  );
});

module.exports = router;

## 참고 ##

  G-mail 자원을 정상적으로 사용하려면 
https://myaccount.google.com/lesssecureapps?pli=1 에서 보안수준이 낮은 엑세스를 허용하고

https://accounts.google.com/DisplayUnlockCaptcha 에서 계정에대한 엑세스를 허용해야한다. 

출처 : https://velog.io/@npcode9194/NodeJS-nodemailer-%EB%AA%A8%EB%93%88%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-Gmail-API-%EC%82%AC%EC%9A%A9-hhjwgcmhsh

 

## 20220715 업데이트 ##

구글 보안 정책 변경으로 위의 방법으로 허용되지 않는다.

https://myaccount.google.com/security?pli=1 

에 접속해서 2단계 인증 추가 후, 앱 비밀번호를 생성한다
이 비밀번호를 nodemailer 패스워드로 입력해야한다.

 

4) node 경로 C:\react200\routes template 폴더를 추가하고 mail_template_pw.html 파일을 만들어 아래 내용을 붙여 넣는다.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!--[if gte mso 9]>
<xml>
  <o:OfficeDocumentSettings>
    <o:AllowPNG/>
    <o:PixelsPerInch>96</o:PixelsPerInch>
  </o:OfficeDocumentSettings>
</xml>
<![endif]-->
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="x-apple-disable-message-reformatting">
  <!--[if !mso]><!--><meta http-equiv="X-UA-Compatible" content="IE=edge"><!--<![endif]-->
  <title></title>
  <style type="text/css">
   body {
  margin: 0;
  padding: 0;
}

table, tr, td {
  vertical-align: top;
  border-collapse: collapse;
}

p, ul {
  margin: 0;
}

.ie-container table, .mso-container table {
  table-layout: fixed;
}

* {
  line-height: inherit;
}

a[x-apple-data-detectors=true] {
  color: inherit !important;
  text-decoration: none !important;
}

.ExternalClass, .ExternalClass p, .ExternalClass span, .ExternalClass font, .ExternalClass td, .ExternalClass div {
  line-height: 100%;
}

[owa] .email-row .email-col {
  display: table-cell;
  float: none !important;
  vertical-align: top;
}

.ie-container .email-col-100, .ie-container .email-row, [owa] .email-col-100, [owa] .email-row { width: 650px !important; }
.ie-container .email-col-17, [owa] .email-col-17 { width: 110.50000000000001px !important; }
.ie-container .email-col-25, [owa] .email-col-25 { width: 162.5px !important; }
.ie-container .email-col-33, [owa] .email-col-33 { width: 214.5px !important; }
.ie-container .email-col-50, [owa] .email-col-50 { width: 325px !important; }
.ie-container .email-col-67, [owa] .email-col-67 { width: 435.5px !important; }

@media only screen and (min-width: 670px) {
  .email-row { width: 650px !important; }
  .email-row .email-col { vertical-align: top; }
  .email-row .email-col-100 { width: 650px !important; }
  .email-row .email-col-67 { width: 435.5px !important; }
  .email-row .email-col-50 { width: 325px !important; }
  .email-row .email-col-33 { width: 214.5px !important; }
  .email-row .email-col-25 { width: 162.5px !important; }
  .email-row .email-col-17 { width: 110.50000000000001px !important; }
}

@media (max-width: 670px) {
  .hide-mobile { display: none !important; }
  .email-row-container {
    padding-left: 0px !important;
    padding-right: 0px !important;
  }
  .email-row .email-col {
    min-width: 320px !important;
    max-width: 100% !important;
    display: block !important;
  }
  .email-row { width: calc(100% - 40px) !important; }
  .email-col { width: 100% !important; }
  .email-col > div { margin: 0 auto; }
  .no-stack .email-col { min-width: 0 !important; display: table-cell !important; }
  .no-stack .email-col-50 { width: 50% !important; }
  .no-stack .email-col-33 { width: 33% !important; }
  .no-stack .email-col-67 { width: 67% !important; }
  .no-stack .email-col-25 { width: 25% !important; }
  .no-stack .email-col-17 { width: 17% !important; }
} 

  </style>

<!--[if mso]>
<style type="text/css">
  ul li {
    list-style:disc inside;
    mso-special-format:bullet;
  }
</style>
<![endif]-->

<!--[if !mso]><!--><link href="https://fonts.googleapis.com/css?family=Montserrat:400,700" rel="stylesheet" type="text/css">
<style type="text/css">
  @import url('https://fonts.googleapis.com/css?family=Montserrat:400,700');
</style><!--<![endif]-->

</head>

<body class="clean-body" style="margin: 0;padding: 0;-webkit-text-size-adjust: 100%;background-color: #f2f2f2">
  <!--[if IE]><div class="ie-container"><![endif]-->
  <!--[if mso]><div class="mso-container"><![endif]-->
  <table class="nl-container" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;min-width: 320px;Margin: 0 auto;background-color: #f2f2f2;width:100%" cellpadding="0" cellspacing="0">
  <tbody>
  <tr style="vertical-align: top">
    <td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top">
    <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color: #f2f2f2;"><![endif]-->

<div class="email-row-container" style="padding: 25px 10px 0px;background-color: rgba(255,255,255,0)">
  <div style="Margin: 0 auto;min-width: 320px;max-width: 650px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;" class="email-row">
    <div style="border-collapse: collapse;display: table;width: 100%;background-color: #ffffff;">
      <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 25px 10px 0px;background-color: rgba(255,255,255,0);" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:650px;"><tr style="background-color: #ffffff;"><![endif]-->
      <!--[if (mso)|(IE)]></td><![endif]-->
      <!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
    </div>
  </div>
</div>

<div class="email-row-container" style="padding: 0px 10px;background-color: rgba(255,255,255,0)">
  <div style="Margin: 0 auto;min-width: 320px;max-width: 650px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: #ffffff;" class="email-row">
    <div style="border-collapse: collapse;display: table;width: 100%;background-color: #ffffff;">
      <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 0px 10px;background-color: rgba(255,255,255,0);" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:650px;"><tr style="background-color: #ffffff;"><![endif]-->

<!--[if (mso)|(IE)]><td align="center" width="650" style="width: 650px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
<div class="email-col email-col-100" style="max-width: 320px;min-width: 650px;display: table-cell;vertical-align: top;">
  <div style="width: 100% !important;">
  <!--[if (!mso)&(!IE)]><!--><div style="padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;"><!--<![endif]-->

<table id="u_content_text_3" class="u_content_text" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:40px 20px 10px;font-family:arial,helvetica,sans-serif;" align="left">

  <div style="color: #000; line-height: 150%; text-align: center; word-wrap: break-word;">
    <p style="font-size: 14px; line-height: 150%;"><span style="font-size: 20px; line-height: 30px;">Lorem Ipsum is simply dummy text of the printing</span></p>
<p style="font-size: 14px; line-height: 150%;"><span style="font-size: 20px; line-height: 30px;"><strong>It is a long established fact that a reader will be distracted</strong>.</span></p>
  </div>

      </td>
    </tr>
  </tbody>
</table>

<table id="u_content_text_4" class="u_content_text" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:10px 20px;font-family:arial,helvetica,sans-serif;" align="left">

  <div style="color: #000; line-height: 150%; text-align: center; word-wrap: break-word;">
    <p style="font-size: 14px; line-height: 150%;"><span style="font-size: 20px; line-height: 30px;">Please feel free to contact us if you have any questions.</span></p>
  </div>

      </td>
    </tr>
  </tbody>
</table>

<table id="u_content_divider_1" class="u_content_divider" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:10px 20px;font-family:arial,helvetica,sans-serif;" align="left">

  <table height="0px" align="center" border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;table-layout: fixed;border-spacing: 0;mso-table-lspace: 0pt;mso-table-rspace: 0pt;vertical-align: top;border-top: 1px solid #CCC;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
    <tbody>
      <tr style="vertical-align: top">
        <td style="word-break: break-word;border-collapse: collapse !important;vertical-align: top;font-size: 0px;line-height: 0px;mso-line-height-rule: exactly;-ms-text-size-adjust: 100%;-webkit-text-size-adjust: 100%">
          <span>&#160;</span>
        </td>
      </tr>
    </tbody>
  </table>

      </td>
    </tr>
  </tbody>
</table>

<table id="u_content_text_5" class="u_content_text" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:10px 20px;font-family:arial,helvetica,sans-serif;" align="left">

  <div style="color: #07a88b; line-height: 150%; text-align: center; word-wrap: break-word;">
    <p style="font-size: 14px; line-height: 150%;"><strong><span style="font-size: 30px; line-height: 45px;">REACT 200, Reset password</span></strong></p>
<p style="font-size: 14px; line-height: 150%;"><span style="font-size: 16px; line-height: 24px; color: #000000;"><span style="line-height: 24px; font-size: 16px;">Click the button below.</span></span></p>
  </div>

      </td>
    </tr>
  </tbody>
</table>

<table id="u_content_button_1" class="u_content_button" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:20px 20px 50px;font-family:arial,helvetica,sans-serif;" align="left">

<div align="center">
    <a href="{replacetext}" target="_blank" style="display: inline-block;font-family:arial,helvetica,sans-serif;text-decoration: none;-webkit-text-size-adjust: none;text-align: center;color: #FFF; background-color: #28e0bf; border-radius: 50px; -webkit-border-radius: 50px; -moz-border-radius: 50px; width: auto; padding: 20px 50px; mso-border-alt: none;">
      <span style="line-height:150%;">
        <strong>
          <span style="font-size: 30px; line-height: 45px;">비밀번호 변경하기</span>
        </strong>
      </span>
    </a>
</div>

      </td>
    </tr>
  </tbody>
</table>

  <!--[if (!mso)&(!IE)]><!--></div><!--<![endif]-->
  </div>
</div>
<!--[if (mso)|(IE)]></td><![endif]-->
      <!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
    </div>
  </div>
</div>

<div class="email-row-container" style="padding: 10px;background-color: rgba(255,255,255,0)">
  <div style="Margin: 0 auto;min-width: 320px;max-width: 650px;overflow-wrap: break-word;word-wrap: break-word;word-break: break-word;background-color: transparent;" class="email-row">
    <div style="border-collapse: collapse;display: table;width: 100%;background-color: transparent;">
      <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding: 10px;background-color: rgba(255,255,255,0);" align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:650px;"><tr style="background-color: transparent;"><![endif]-->

<!--[if (mso)|(IE)]><td align="center" width="650" style="width: 650px;padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;" valign="top"><![endif]-->
<div class="email-col email-col-100" style="max-width: 320px;min-width: 650px;display: table-cell;vertical-align: top;">
  <div style="width: 100% !important;">
  <!--[if (!mso)&(!IE)]><!--><div style="padding: 0px;border-top: 0px solid transparent;border-left: 0px solid transparent;border-right: 0px solid transparent;border-bottom: 0px solid transparent;"><!--<![endif]-->

<table id="u_content_text_6" class="u_content_text" style="font-family:arial,helvetica,sans-serif;" role="presentation" cellpadding="0" cellspacing="0" width="100%" border="0">
  <tbody>
    <tr>
      <td style="overflow-wrap:break-word;word-break:break-word;padding:20px;font-family:arial,helvetica,sans-serif;" align="left">
      </td>
    </tr>
  </tbody>
</table>

  <!--[if (!mso)&(!IE)]><!--></div><!--<![endif]-->
  </div>
</div>
<!--[if (mso)|(IE)]></td><![endif]-->
      <!--[if (mso)|(IE)]></tr></table></td></tr></table><![endif]-->
    </div>
  </div>
</div>

    <!--[if (mso)|(IE)]></td></tr></table><![endif]-->
    </td>
  </tr>
  </tbody>
  </table>
  <!--[if (mso)|(IE)]></div><![endif]-->
</body>

</html>

node 경로에서 필요한 패키지들을 설치해준다.

npm install nodemailer -- save
npm install fs -- save

 

2. 이메일에서 [비밀번호 변경하기]를 누르면 비밀번호 변경 가능 화면으로 이동시킨다.

 

1) react 경로 C:\react200\client\src\components 에 App.js 를 아래와 같이 수정한다. 쿠키에 저장된 사용자 정보가 유효하지 않을 때, 로그인 화면으로 강제 페이지 이동을 한다. 이때, 비밀번호 변경 화면(PwChangeForm)인 경우 예외 처리한다. 

import React, { Component } from 'react';
import { Route } from "react-router-dom";
import cookie from 'react-cookies';
import axios from "axios";

// css
import '../css/new.css';

// header
import HeaderAdmin from './Header/Header admin';

// footer
import Footer from './Footer/Footer';

// login
import LoginForm from './LoginForm';

import SoftwareList from './SoftwareToolsManage/SoftwareList';
import SoftwareView from './SoftwareToolsManage/SoftwareView';

import Register from './Register/Register';
import PwChangeForm from './PwChangeForm';

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
    }
  }

  componentDidMount() {
    if(window.location.pathname.indexOf('/PwChangeForm') == -1){
      axios.post('/api/LoginForm?type=SessionConfirm', {
        token1 : cookie.load('userid') 
        , token2 : cookie.load('username') 
      })
      .then( response => {
          this.state.userid = response.data.token1
          let password = cookie.load('userpassword')
          if(password !== undefined){
            axios.post('/api/LoginForm?type=SessionSignin', {
              is_Email: this.state.userid,
              is_Token : password
            })
            .then( response => {
              if(response.data.json[0].useremail === undefined){
                this.noPermission()
              }
            })
            .catch( error => {
              this.noPermission()
            });
          }else{
            this.noPermission()
          }
      })
      .catch( response => this.noPermission());
    }
  }

  noPermission = (e) => {
    if(window.location.hash != 'nocookie'){
      this.remove_cookie();
      window.location.href = '/login/#nocookie';
    }
  };

  remove_cookie = (e) => {
    cookie.remove('userid', { path: '/'});
    cookie.remove('username', { path: '/'});
    cookie.remove('userpassword', { path: '/'});
  }

  render () {
    return (
      <div className="App">
        <HeaderAdmin/> 
        <Route exact path='/' component={LoginForm} />
        <Route path='/login' component={LoginForm} />
        <Route path='/SoftwareList' component={SoftwareList} />
        <Route path='/SoftwareView/:swtcode' component={SoftwareView} />
        <Route path='/register' component={Register} />
        <Route path='/PwChangeForm/:email/:token' component={PwChangeForm} />
        <Footer/>
      </div>
    );
  }
}

export default App;

2) react 경로 C:\react200\client\src\components 에 PwChangeForm.js 파일을 생성하고 아래코드를 붙여넣는다.

import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import Swal from 'sweetalert2'
import $ from 'jquery';
import axios from "axios";

class PwChangeForm extends Component {
    constructor (props) {
    super(props);
        this.state = {
            email: props.match.params.email,
            token: props.match.params.token,
        }
    }

    componentDidMount() {
        let token = this.state.token.replace(/가/gi, "/"); 
        axios.post('/api/LoginForm?type=emailtoken', {
            is_Email : this.state.email,
            is_Token : token,
        })
        .then( response => {
            if(response.data.json[0].username == undefined){
                window.location.replace('about:blank')
            }
        })
        .catch( error => {
            this.sweetalert('유효한 접속이 아닙니다.', error, 'error', '닫기')
            setTimeout(function() {
                window.location.replace('about:blank')    
                }.bind(this),1000
            );
        });
    }

    submitClick = async (e) => {
        this.pwd_val_checker = $('#pwd_val').val();
        this.pwd_cnf_val_checker = $('#pwd_cnf_val').val();

        this.fnValidate = (e) => {
            var pattern1 = /[0-9]/;
            var pattern2 = /[a-zA-Z]/;
            var pattern3 = /[~!@#$%^&*()_+|<>?:{}]/;

            if(this.pwd_val_checker ==='') {
                $('#pwd_val').addClass('border_validate_err');
                this.sweetalert('비밀번호를 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.pwd_val_checker !='') {
                var str = this.pwd_val_checker;
                if(str.search(/\s/) != -1) {
                    $('#pwd_val').addClass('border_validate_err');
                    this.sweetalert('비밀번호 공백을 제거해 주세요.', '', 'info', '닫기')
                    return false;
                } 
                if(!pattern1.test(str) || !pattern2.test(str) || !pattern3.test(str)
                || str.length < 8 || str.length > 16) {
                    $('#pwd_val').addClass('border_validate_err');
                    this.sweetalert('8~16자 영문 대 소문자, 숫자\n 특수문자를 사용하세요.', '', 'info', '닫기')
                    return false; 
                } 
            }
            $('#pwd_val').removeClass('border_validate_err');

            if(this.pwd_cnf_val_checker ==='') {
                $('#pwd_cnf_val').addClass('border_validate_err');
                this.sweetalert('비밀번호 확인을 입력해주세요.', '', 'info', '닫기')
                return false;
            }
            if(this.pwd_val_checker != this.pwd_cnf_val_checker) {
                $('#pwd_val').addClass('border_validate_err');
                $('#pwd_cnf_val').addClass('border_validate_err');
                this.sweetalert('비밀번호가 일치하지 않습니다.', '', 'info', '닫기')
                return false;
            }
            $('#pwd_cnf_val').removeClass('border_validate_err');
            return true;
        }

        if(this.fnValidate()){
            var jsonstr = $("form[name='frm']").serialize();
            jsonstr = decodeURIComponent(jsonstr);
            var Json_form = JSON.stringify(jsonstr).replace(/\"/gi,'')
            Json_form = "{\"" +Json_form.replace(/\&/g,'\",\"').replace(/=/gi,'\":"')+"\"}";
            
            try {
                const response = await fetch('/api/register?type=pwdmodify', {
                    method: 'POST',
                    headers: {
                    'Content-Type': 'application/json',
                    },
                    body: Json_form,
                });
                const body = await response.text();
                if(body == "succ"){
                    this.sweetalertSucc('비밀번호 수정이 완료되었습니다.', false)
                    setTimeout(function() {
                        this.props.history.push('/');
                        }.bind(this),1500
                    );
                }else{
                    this.sweetalert('작업 중 오류가 발생하였습니다.', '', 'error', '닫기')
                }  
            } catch (error) {
                this.sweetalert('작업 중 오류가 발생하였습니다.', error, 'error', '닫기')
            }
        }
    };

    sweetalert = (title, contents, icon, confirmButtonText) => {
        Swal.fire({
            title: title,
            text: contents,
            icon: icon,
            confirmButtonText: confirmButtonText
          })
    }

    sweetalertSucc = (title, showConfirmButton) => {
        Swal.fire({
            position: 'bottom-end',
            icon: 'success',
            title: title,
            showConfirmButton: showConfirmButton,
            timer: 1000
        })
    }

    render () {
        return (
            <section className="main">
                <div className="m_login">
                <h3 className="pw_ls">비밀번호 재설정 <span className="compl1">완료</span></h3>
                    <form method="post" name="frm" action="">
                        <input type="hidden" id="is_Useremail" name="is_Useremail" value={this.state.email}/>
                        <div className="log_box">
                            <div className="in_ty1">
                                <span className="ic_2">
                                    <img src={require("../img/main/m_log_i2.png")} alt="" />
                                </span>
                                <input type="password" id="pwd_val"
                                name="is_Password" placeholder="새 비밀번호" />
                                </div>
                                <div className="in_ty1">
                                <span className="ic_2">
                                    <img src={require("../img/main/m_log_i2.png")} alt="" />
                                </span>
                                <input type="password" id="pwd_cnf_val"
                                name="is_Password" placeholder="새 비밀번호 확인" />
                                </div>
                                <div className="btn_confirm btn_confirm_m">
                                <Link to={'/'}>
                                    <div className="bt_ty bt_ty_m bt_ty1 cancel_ty1">취소</div>
                                </Link>
                                <a href="#n" className="bt_ty bt_ty_m bt_ty2 submit_ty1" 
                                onClick={(e) => this.submitClick(e)}>재설정</a>
                            </div>
                        </div>
                    </form>
                </div>
            </section>
        );
    }
}

export default PwChangeForm;

 

3) 비밀번호 재설정을 요청하면 id와 동일한 메일로 아래와 같은 템플릿으로 발송된다. 
   비밀번호 변경하기를 누르면, 사이트 비밀번호 재설정 페이지(PwChangeForm) url로 이동해 비밀번호 초기화를 할 수 있다.

4) 비밀번호 재설정 페이지로 돌아와, 다음과 같은 화면에서 새 비밀번호를 입력해 변경할 수 있다. 주소창에 url을 보면 사용자 아이디(이메일)과 토큰이 전달된 것을 확인할 수 있다.

 

https://taling.me/Talent/Detail/19341

 

[비전공자도 가능한 웹 프로젝트] 개발부터 배포까지, 배우면서 완성(react,nodejs,mysql,aws)! | 탈잉

✔ 수업 목표 & 5주 뒤 결과물 만들고 싶은 서비스를 직접 구현하게 됩니다. 사람들이 방문하고 액션을 할 수 있는 웹 페이지를 제작 & 배포하는 것까지 진행됩니다. 여러분이 개발 경험이 있든 ��

taling.me:443

강의 문의 : ljung5@naver.com

Comments