일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 옥길동요거트
- 옥길동파티룸
- 스컬킹점수
- mysql
- 보드게임점수계산
- 그릭요거트
- 휴식
- 스컬킹점수계산
- 옥길요거트
- 개발자
- 일
- 존경하는위인
- 가장존경하는인물
- 보드게임점수
- 코딩
- 파티룸
- 웹개발
- 취미
- 스컬킹
- 부천파티룸
- MBTI
- 구로파티룸
- 해외여행
- 옥길파티룸
- 부천공간대여
- 서울파티룸
- 착한코딩
- 스페이스우일
- 광명파티룸
- 옥길그릭요거트
- Today
- Total
SIMPLE & UNIQUE
5회차_3강 : 이메일 인증을 통한 비밀번호 재설정 기능을 구현한다. 본문
5회차_3강 : 이메일 인증을 통한 비밀번호 재설정 기능을 구현한다.
착한코딩 2020. 2. 14. 03:005_3 목표 : 이메일 HTML 탬플릿을 작성한다. 사용자 아이디와 토큰을 함께 전송한다. 토큰을 이용해 사용자 인증을 구현한다.
1. 비밀번호 재설정 요청을 하면 이메일을 발송하는 기능을 구현한다.
1) react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components에 있는 LoginForm.js를 아래와 같이 수정한다.
import React, { Component } from 'react';
import {Redirect, Link} from 'react-router-dom'
import cookie from 'react-cookies';
import $ from 'jquery';
import axios from "axios";
import Swal from 'sweetalert2'
class LoginForm extends Component {
constructor (props) {
super(props);
this.state = {
prop:props
}
}
submitClick = async 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 => {
try {
var userid = response.data.json[0].useremail
var username = response.data.json[0].username
var userflag = response.data.json[0].userflag
var upw = response.data.json[0].userpassword
if(userid != null && userid != ''){
this.sweetalert('로그인 되었습니다.', '', 'info', '닫기')
//로그인 id 세션에 저장
const expires = new Date()
expires.setMinutes(expires.getMinutes() + 60)
//userid 와 username을 비밀키로 암호화해 쿠키값에 세팅한다.
axios.post('/api/LoginForm?type=SessionState', {
is_Email: userid,
is_UserName: username,
})
.then( response => {
cookie.save('userid', response.data.token1
, {
path: '/',
expires,
// httpOnly: true // 도메인 연결 후 주석해제
})
cookie.save('username', response.data.token2
, {
path: '/',
expires,
// httpOnly: true // 도메인 연결 후 주석해제
})
cookie.save('userpassword', upw
, {
path: '/',
expires,
// httpOnly: true // 도메인 연결 후 주석해제
})
cookie.save('user_flag', 'Y'
, {
path: '/',
expires
}
);
})
.catch( error => {this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기'); return false; } );
setTimeout(function() {
window.location.href = '/';
}.bind(this),1000
);
}else{
this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
}
} catch (error) {
this.sweetalert('이메일과 비밀번호를 확인해주세요.', '', 'info', '닫기')
}
})
.catch( response => { alert(response);return false; } );
}
}
// input value state
handleChange = (e) => {
this.setState({
[e.target.name]: e.target.value
})
}
// login form post
handleSubmit = (e) => {
// submit 페이지 리로딩 방지
e.preventDefault();
}
// 비밀번호 재설정
pwdResetClick = () => {
$('.m_login').hide();
$('.m_pw').fadeIn();
$('.m_pw').css('display','table-cell');
}
//비밀전호 재설정용 메일 발송
sendEmail = (email, subject, text, e) => {
axios.post('/api/message?type=email&roll=resetpw', {
is_Email : email,
is_Subject : subject,
is_Text: text
})
.then( response => {
this.sweetalert('입력하신 이메일로 비밀번호 \n 재설정 메일 보내드렸습니다.', '', 'info', '닫기')
})
.catch( error => {this.sweetalert('작업중 오류가 발생하였습니다.', error, 'error', '닫기');return false;});
}
// //비밀번호 재설정 확인버튼
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 userid = response.data.json[0].useremail
var username = response.data.json[0].username
var userpassword = response.data.json[0].userpassword
if(userid != null && userid != ''){
this.sendEmail(userid, 'rtrod 비밀번호 재설정 메일', userpassword)
}else{
this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기')
}
})
.catch( response => {this.sweetalert('이메일과 성명을 확인해주세요.', '', 'info', '닫기');return false; } );
}
}
//alert 기본 함수
sweetalert = (title, contents, icon, confirmButtonText) => {
Swal.fire({
title: title,
text: contents,
icon: icon,
confirmButtonText: confirmButtonText
})
}
render () {
return (
<section className="main">
{/* <!-- 로그인폼 --> */}
<div className="m_login">
<h3><span><img src={require("../img/main/log_img.png")} alt="" /></span>LOGIN</h3>
<div className="log_box">
<form onSubmit={this.handleSubmit}>
<div className="in_ty1">
<span><img src={require("../img/main/m_log_i3.png")} alt="" /></span>
<input type="text" id="email_val" name="email" placeholder="이메일" onChange={this.handleChange} />
</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" name="password" placeholder="비밀번호" onChange={this.handleChange} />
</div>
<ul className="af">
<li><Link to={'/register_check'}>회원가입</Link></li>
<li className="pwr_b" onClick={this.pwdResetClick}><a href="#n">비밀번호 재설정</a></li>
</ul>
{/* <input className="s_bt" type="submit" value="로그인" /> */}
<button className="s_bt" type="submit" onClick={this.submitClick}>로그인</button>
</form>
</div>
</div>
{/* <!-- 비밀번호 재설정 --> */}
<div className="m_login m_pw">
<h3 className="pw_ls">비밀번호 재설정 <span className="compl1">완료</span></h3>
<div className="log_box">
{/* <!-- 1단 --> */}
{/* <form method="post"> */}
<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>
{/* <!-- 2단 가려둠--> */}
<div className="pw_two">
<div className="in_ty1">
<span className="ic_2"><img src={require("../img/main/m_log_i2.png")} alt="" /></span>
<input type="password" name="" placeholder="새 비밀번호" />
</div>
<div className="in_ty1">
<span className="ic_2"><img src={require("../img/main/m_log_i2.png")} alt="" /></span>
<input type="password" name="" placeholder="새 비밀번호 확인" />
</div>
<div className="btn_confirm btn_confirm_m">
<div className="bt_ty bt_ty_m bt_ty1 cancel_ty1">취소</div>
<a href="#n" className="bt_ty bt_ty_m bt_ty2 submit_ty1">재설정</a>
</div>
</div>
{/* <!-- 3단 가려둠 --> */}
<div className="pw_tree">
<div className="">
<p>
'<span>홍길동</span>'
님의 비밀번호가 재설정되었습니다.
</p>
</div>
<input className="s_bt" type="submit" value="로그인 이동" />
</div>
{/* </form> */}
</div>
</div>
</section>
);
}
}
LoginForm.defaultProps = {
}
export default LoginForm;
2) node 경로 C:\Users\ljung\OneDrive\문서\taling0102에 있는 server.js를 아래와 같이 수정한다.
var express = require('express');
var indexRouter = require('./routes/index');
var swtoolRouter = require("./routes/SwtoolRout");
var fileuploadRouter = require("./routes/UploadRout");
var BatchRout = require("./routes/BatchRout");
var usersRouter = require("./routes/UsersRout");
var MessageRoutRouter = require("./routes/MessageRout");
var app = express();
app.use('/', indexRouter);
//sw Tool 조회
app.use("/api/Swtool", swtoolRouter);
//파일 업로드
app.use("/api/upload", fileuploadRouter);
//파일 업로드 경로 설정
app.use(express.static("./uploads"));
//로그인 조회
app.use("/api/LoginForm", usersRouter);
//회원가입 정보 입력
app.use("/api/register", usersRouter);
//시스템 배치
app.use("/api/BatchRout", BatchRout);
//알림, 메일 전송
app.use("/api/message", MessageRoutRouter);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
3) node 경로 C:\Users\ljung\OneDrive\문서\taling0102\routes에 MessageRout.js 파일을 추가하고 아래 내용을 붙여 넣는다.
var express = require('express');
var router = express.Router();
const bodyParser = require('body-parser');
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
var usersModule = require('../modules/MessageModule');
router.get('/', function(req, res, next){
router.use('/', usersModule);
next('route')
});
//post 테스트
router.post('/', (req, res, next) => {
router.use('/', usersModule);
next('route')
});
module.exports = router;
4) node 경로 C:\Users\ljung\OneDrive\문서\taling0102\modules 에 MessageModule.js 파일을 추가하고 아래 내용을 붙여 넣는다.
var express = require('express');
var router = express.Router();
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
var fs = require('fs')
var ip = require('ip');
const axios = require('axios');
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
router.post('/', (req, res, next) => {
var m_typ = req.query.type;
if(m_typ == 'email'){
//이메일 발송
var m_roll = req.query.roll;
let email = req.body.is_Email;
let subject = req.body.is_Subject;
var text = req.body.is_Text;
var postpone_txt = text
text = text.substr(0, 20)
let transporter = nodemailer.createTransport({
service: 'gmail',
host: 'smtp.gmail.com',
port: 465,
secure: true,
auth: {
user: '0103288****a@gmail.com', // gmail 계정 아이디를 입력
pass: '0103288****' // gmail 계정의 비밀번호를 입력
}
});
var home_url = ''
if(ip.address() == '172.17.0.2'){
home_url = 'http://15.164.5.237:3000'
}else{//운영서버 ip가 아닌 경우 모두 로컬처리
home_url = 'http://localhost:3000'
}
var toHtml = ''
if(m_roll == 'resetpw'){
// 비밀번호 재설정 메일 발송
fs.readFile(__dirname+'/template/mail_template_pw.html', function (err, html) {
toHtml = html.toString()
toHtml = toHtml.replace('{replacetext}', home_url+'/PwChangeForm/'+ email +'/'+text)
})
}else if(m_roll == '2daybefore'){
// 프로젝트 종료 2일전 메일 발송
var pjtName = req.body.is_PjtName;
fs.readFile(__dirname+'/template/mail_template_notice.html', function (err, html) {
toHtml = html.toString()
toHtml = toHtml.replace('{replacetext}', pjtName)
})
}else if(m_roll == 'basic'){
// 단순 알림 메일 발송
fs.readFile(__dirname+'/template/mail_template_basic.html', function (err, html) {
toHtml = html.toString()
toHtml = toHtml.replace('{replacetext}', postpone_txt)
})
}
var email_use = true
axios.post(home_url+'/api/system?type=system', {
//사이트 이메일 사용유무 체크
})
.then( response => {
if(response.data.json[0].email_ym == 'Y'){
email_use = true
}else if(response.data.json[0].email_ym == 'N'){
email_use = false
}
})
.catch( response => {console.log('fail1'+response)} );
setTimeout(function() {
let mailOptions = {
from: 'rtrodemail@gmail.com', // 발송 메일 주소 (위에서 작성한 gmail 계정 아이디)
to: email , // 수신 메일 주소
subject: subject, // 제목
html : toHtml
};
if(email_use){
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
}
else {
console.log('Email sent: ' + info.response);
}
});
}else{
console.log('Email sent: 이메일 사용유무를 Y로 변경하세요 email_use :'+email_use);
}
res.redirect("/");
}.bind(this),1000
);
}
});
module.exports = router;
## 참고 ##
G-mail 자원을 정상적으로 사용하려면
https://myaccount.google.com/lesssecureapps?pli=1 에서 보안수준이 낮은 엑세스를 허용하고
https://accounts.google.com/DisplayUnlockCaptcha 에서 계정에대한 엑세스를 허용해야한다.
5) node 경로 C:\Users\ljung\OneDrive\문서\taling0102\modules 에 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;">Distributed biohealth integrated analysis platform</span></p>
<p style="font-size: 14px; line-height: 150%;"><span style="font-size: 20px; line-height: 30px;"><strong>centered on immune disease research</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> </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;">Real Time-Research On Demand</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;">Manage Your Project Easily</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">
<!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="border-spacing: 0; border-collapse: collapse; mso-table-lspace:0pt; mso-table-rspace:0pt;font-family:arial,helvetica,sans-serif;"><tr><td style="font-family:arial,helvetica,sans-serif;" align="center"><v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="" style="height:85px; v-text-anchor:middle; width:304px;" arcsize="59%" stroke="f" fillcolor="#28e0bf"><w:anchorlock/><center style="color:#FFF;font-family:arial,helvetica,sans-serif;"><![endif]-->
<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>
</a>
<!--[if mso]></center></v:roundrect></td></tr></table><![endif]-->
</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">
<div style="color: #999999; line-height: 120%; text-align: left; word-wrap: break-word;">
<p style="font-size: 14px; line-height: 120%;">주소 : [16499] 경기도 수원시 영통구 월드컵로 164 홍재관 503호Tel : 031-219-4471.</p>
<p style="font-size: 14px; line-height: 120%;"> </p>
<p style="font-size: 14px; line-height: 120%;">COPYRIGHT © 2019 RT-ROD, ALL RIGHTS RESERVED.</p>
</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>
<!--[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 axios -- save
npm install fs -- save
2. 이메일에서 [비밀번호 변경하기]를 누르면 비밀번호 변경 가능 화면으로 이동시킨다.
1) react 경로 C:\Users\ljung\OneDrive\문서\taling0102\client\src\components 에 App.js 를 아래와 같이 수정한다.
import React, { Component } from 'react';
import { Router, Route, Switch } from "react-router";
import {Redirect} from 'react-router-dom'
import Api_test from './Api_test'
import cookie from 'react-cookies';
import axios from "axios";
// css
import '../css/new.css';
import '../css/owl.carousel.min.css';
import '../css/owl.theme.default.min.css';
// header
import HeaderAdmin from './Header/Header admin';
// footer
import Footer from './Footer/Footer';
// login
import LoginForm from './LoginForm';
import PwChangeForm from './PwChangeForm';
// admin floatingPopulationList
import floatingPopulationList from './Floating_population/floatingPopulationList';
// admin softwareinfo
import AdminSoftwareList from './SoftwareToolsManage/AdminSoftwareList';
import AdminSoftwareView from './SoftwareToolsManage/AdminSoftwareView';
// register
import Register from './Register/Register';
import RegisterCheck from './Register/RegisterCheck';
class App extends Component {
constructor (props) {
super(props);
this.state = {
}
}
componentDidMount() {
// 비밀번호 재설정 패이지를 제외하고, 세션이 유효하지 않으면 home url로 이동.
if(window.location.pathname.indexOf('/PwChangeForm') == -1){
//쿠키에서 userid, username을 가져와 복호화한다.
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){
password = ''
}
axios.post('/api/LoginForm?type=pwemail', {
is_Email: this.state.userid,
is_Token : password
})
.then( response => {
if(response.data.json[0] !== undefined){
var userid = response.data.json[0].useremail
if(userid == undefined || password == undefined){
window.location.href = '/admin';
}
}else{
// 계정정보가 유효하지 않다면 세션값 삭제후, 홈으로 이동
if(window.location.hash != 'nocookie'){
this.remove_cookie();
window.location.href = '/nocookie/#nocookie';
}
}
})
.catch( response => {
this.remove_cookie()
window.location.href = '/nocookie/#nocookie';
} );
})
.catch( response => {window.location.href = '/nocookie/#nocookie';} );
}
}
//쿠키 초기화
remove_cookie = (e) => {
cookie.remove('userid', { path: '/'});
cookie.remove('username', { path: '/'});
cookie.remove('user_flag', { path: '/'});
cookie.remove('userpassword', { path: '/'});
}
render () {
return (
<div className="App">
<HeaderAdmin/>
<Switch>
{
(cookie.load('userid') !== undefined) ? (
<Route exact path='/' component={AdminSoftwareList} />
):(
<Route exact path='/admin' component={LoginForm} />
)
}
<Route exact path='/' component={LoginForm} />
<Route path='/nocookie' component={LoginForm} />
<Route path='/Api_test' component={Api_test} />
<Route path='/floatPopulationList' component={floatingPopulationList} />
<Route path='/AdminSoftwareList' component={AdminSoftwareList} />
<Route path='/AdminSoftwareView/:swtcode' component={AdminSoftwareView} />
<Route path='/register' component={Register} />
<Route path='/register_check' component={RegisterCheck} />
<Route path='/PwChangeForm/:email/:token' component={PwChangeForm} />
</Switch>
<Footer
footer_address={this.props.footer_address}
footer_tel={this.props.footer_tel}
footer_email={this.props.footer_email}
footer_mobile={this.props.footer_mobile}
/>
</div>
);
}
}
App.defaultProps = {
// footer value
footer_address: '[34234] 서울특별시 강남구 삼성동 111-114',
footer_tel: '02-1234-5678',
footer_email: 'ljung5@naver.com',
footer_mobile: '010-3288-3398',
};
export default App
2) react 경로 C:\Users\ljung\OneDrive\문서\taling0102\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 LoginForm extends Component {
constructor (props) {
super(props);
this.state = {
email: props.match.params.email, //이메일
token: props.match.params.token, //이메일 token
}
}
componentDidMount() {
axios.post('/api/LoginForm?type=pwemail', {
is_Email : this.state.email,
is_Token : this.state.token,
})
.then( response => {
if(response.data.json[0].usercode == undefined){
window.location.replace('about:blank')
}
})
.catch( error => {
this.sweetalert('유효한 접속이 아닙니다.', error, 'error', '닫기')
setTimeout(function() {
window.location.replace('about:blank')
}.bind(this),1000
);
});
}
// 회원가입 버튼 클릭시 validate check
submitClick = async (type, 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 = /[~!@#$%^&*()_+|<>?:{}]/; // 특수문자
// ## pwd check start
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');
// ## pwd check end
return true;
}
//회원 정보 유효성 체크
if(this.fnValidate()){
//form type To Json
var type = 'pwdmodify'
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='+type, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
//한글 디코딩
body: Json_form,
});
const body = await response.text();
if(body == "succ"){
this.sweetalertSucc('비밀번호 수정이 완료되었습니다.', false)
//home url 호출
setTimeout(function() {
this.props.history.push('/');
}.bind(this),1500
);
}else{
this.sweetalert('작업 중 오류가 발생하였습니다.', '', 'error', '닫기')
}
} catch (error) {
this.sweetalert('작업 중 오류가 발생하였습니다.', error, 'error', '닫기')
}
}//fnValidate end
};
//alert 기본 함수
sweetalert = (title, contents, icon, confirmButtonText) => {
Swal.fire({
title: title,
text: contents,
icon: icon,
confirmButtonText: confirmButtonText
})
}
//alert 성공 함수
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('modify', e)}>재설정</a>
</div>
</div>
</form>
</div>
</section>
);
}
}
LoginForm.defaultProps = {
}
export default LoginForm;
3) 비밀번호 재설정을 요청하면 id와 동일한 메일로 아래와 같은 템플릿으로 발송된다.
여기서 변경하기를 누르면, 사이트 url로 이동해 비밀번호 초기화를 할 수 있다.
https://taling.me/Talent/Detail/19341
'탈잉 강의 자료 > react.js(프론트) + node.js(백앤드) 개발에서 배포까지' 카테고리의 다른 글
5회차_2강 : 로그인을 구현한다. 쿠키로 사용자 세션을 관리한다. (0) | 2020.02.14 |
---|---|
5회차_1강 : 단방향 암호화를 이용해, 회원가입을 구현한다. (0) | 2020.02.13 |
2회차_3강 : 생성한 스키마에 한국시간, 한국 인코딩 설정을 하고 더미데이터를 삽입한다. (0) | 2020.02.09 |
4회차_3강 : NODE 스케줄러를 개발해, EC2 서버에 배포한다. (0) | 2020.01.28 |
4회차_2강 : EC2 서버에 소스파일을 전송 후, 클라우드 서버를 구동한다. (0) | 2020.01.27 |