- Why Validate?
- How to Validate
- 프로젝트에 적용하기
다양한 유저가 우리 웹사이트에 들어올 수 있다. 이때 Input을 바로 서버에 있는 Database 나 File로 저장하는 것이 아닌 Controller나 Middleware 에서 검증하는 단계를 거치자 즉, 잘못된 Input을 한번 거르는 단계를 추가해보자
- Client 단에서 실행하는 JavaScript 코드는 실행되지 않거나 변조 될 수 있다.
- 따라서 선택적으로 사용하고 대신 Server (Node App) 에서 검증하는 방법을 사용해야 한다.
-
$ npm i --save express-validator공식 문서 : https://express-validator.github.io/docs/ -
routes/auth.js에 위 패키지 가져오고 적용시키기
// JavaScript에서 제공하는 Destructuring 을 사용하여 check 함수를 가져오자 const { check } = require('express-validator/check'); // 라우터에는 넣고싶은 만큼 라우터를 추가할 수 있다. 따라서 check() 를 넣어주고 인자로 form 을 넣어주자 router.post('/signup', check('email').isEmail(), authController.postSignup);
-
controllers/auth.js 수정하기
-
express-validator가져오기// routes/auth.js에서 미리 설정했던 isEmail 함수에서 받아 오류 생성 const { validationResult } = require('express-validator/check');
-
postSignup에서 오류 설정하기exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; const confirmPassword = req.body.confirmPassword; // client 사이드에서 오류가 발생하면 req로 전달 const errors = validationResult(req); if (!errors.isEmpty()) { console.log(errors.array()); return res.status(422).render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: errors.array()[0].msg }); } ...
-
-
Error 메세지 수정하기 위해 routes/auth.js에
postSignup수정하기router.post( '/signup', check('email') .isEmail() .withMessage('Please Enter a Valid Email'), authController.postSignup );
-
express-validator커스터마이징 하기 아래 처럼 custom 함수를 활용 할 수 있다.router.post( '/signup', check('email') .isEmail() .withMessage('Please Enter a Valid Email') .custom((value, { req }) => { if (value === 'test@test.com') { throw new Error('This Email Address if forbidden.'); } }), authController.postSignup );
-
controllers/admin.js에 있는
express-validator에 body 추가하기const { check, body } = require('express-validator/check');
-
controllers/admin.js에 있는
postSignup라우터에 비밀번호 체크 기능 넣기router.post( '/signup', [ check('email') .isEmail() .withMessage('Please Enter a Valid Email') .custom((value, { req }) => { if (value === 'test@test.com') { throw new Error('This Email Address if forbidden.'); } // default case return true; }), //body(인자1, 인자2) => req.body로 날라오는 값을 검증 // 인자1 : 검증할 값, 인자2: default 에러 메세지 body( 'password', 'Please Enter a Password wiht only numbers and text and at least 5 characters.' ) .isLength({ min: 5 }) // 알파벳만 가능 .isAlphanumeric() ], authController.postSignup );
-
패스워드 재입력 기능 만들기 위해 다시
postSignup수정router.post( '/signup', [ check('email') .isEmail() .withMessage('Please Enter a Valid Email') .custom((value, { req }) => { if (value === 'test@test.com') { throw new Error('This Email Address if forbidden.'); } // default case return true; }), body( 'password', 'Please Enter a Password wiht only numbers and text and at least 5 characters.' ) .isLength({ min: 5 }) .isAlphanumeric(), body('confirmPassword').custom(value => { if (!value === req.body.password) { throw new Error('Passwords have to match!'); } return true; }) ], authController.postSignup );
-
routes/auth.js에 models/user.js 불러오기
const User = require('../models/user');
-
routes/auth.js에
postSign에 Email 검증 로직 추가하기 -
기존 controllers/auth.js에
postSignup에서 Email 검증 기능 빼기exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; // client 사이드에서 오류가 발생하면 req로 전달 const errors = validationResult(req); if (!errors.isEmpty()) { console.log(errors.array()); return res.status(422).render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: errors.array()[0].msg }); } return bcrypt .hash(password, 12) .then(hashedPassword => { const user = new User({ email: email, password: hashedPassword, cart: { items: [] } }); return user.save(); }) .then(result => { res.redirect('/login'); return sgMail.send({ to: email, from: 'nodeshop@shop.com', subject: 'Signup suceeded!', html: '<h1>Hi! 회원가입을 축하합니다.</h1>' }); }) .catch(err => { console.log(err); }); };
-
controllers/auth.js 사용자
Input을 유지하기 위해render할 때 사용자Input다시 보내주기exports.getSignup = (req, res, next) => { let message = req.flash('error'); if (message.length > 0) { message = message[0]; } else { message = null; } res.render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: message, // 처음엔 빈 값 보내주기 oldInput: { email: '', password: '', confirmPassword: '' } }); }; exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; // client 사이드에서 오류가 발생하면 req로 전달 const errors = validationResult(req); if (!errors.isEmpty()) { console.log(errors.array()); return res.status(422).render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: errors.array()[0].msg, // 이전 Input 다시 보내줘서 form value로 넣기 oldInput: { email: email, password: password } }); }
-
views/auth/signup.ejs form value 로 값 전달하기
<form class="login-form" action="/signup" method="POST" novalidate> <div class="form-control"> <label for="email">E-Mail</label> <input type="email" name="email" id="email" value="<%= oldInput.email %>"> </div> <div class="form-control"> <label for="password">Password</label> <input type="password" name="password" id="password" value="<%= oldInput.password %>"> </div> <div class="form-control"> <label for="confirmPassword">Confirm Password</label> <input type="password" name="confirmPassword" id="confirmPassword" value="<%= oldInput.confirmPassword %>"> </div> <input type="hidden" name="_csrf" value="<%= csrfToken %>" /> <button class="btn" type="submit">SignUP!</button> </form>
잘못 된 Input을 줬을 때 해당 box 빨간색 테두리 넣기
-
controllers/auth.js
postSignuperrors 배열 전체 보내주기exports.getSignup = (req, res, next) => { let message = req.flash('error'); if (message.length > 0) { message = message[0]; } else { message = null; } res.render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: message, oldInput: { email: '', password: '', confirmPassword: '' }, validationErrors: [] }); }; exports.postSignup = (req, res, next) => { const email = req.body.email; const password = req.body.password; // client 사이드에서 오류가 발생하면 req로 전달 const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).render('auth/signup', { path: '/signup', pageTitle: 'Signup', errorMessage: errors.array()[0].msg, // 이전 Input 다시 보내줘서 form value로 넣기 oldInput: { email: email, password: password, confirmPassword: req.body.confirmPassword }, // 오류 생겼을 때 errors 보내주기 validationErrors: errors.array() }); }
-
views/auth/signup.ejs 각
input에cssclass추가하기<input class="<%= validationErrors.find(e => e.param === 'email') ? 'invalid' : '' %>" type="email" name="email" id="email" value="<%= oldInput.email %>" >
-
public/css/form.css 에 추가하기
.form-control input.invalid { border-color: red; }
사용자들은 회원가입 할 때 Email에 대문자나 공백을 넣을 수 있다. 이를 방지하기 위해 router 단에서 미리 사용자 Input을 검증하자
-
routes/auth.js
-
postLogin에 정규식 추가하기router.post( '/login', [ body('email') .isEmail() .withMessage('Email형식을 입력해주세요') .normalizeEmail(), // 이메일 형식 만들기 body('password', '패스워드를 5글자 이상 또는 알파벳만 넣어주세요.') .isLength({ min: 5 }) .isAlphanumeric() .trim() // 공백 지우기 ], authController.postLogin );
-
postSignup에도 적용하기router.post( '/signup', [ body('email', 'E-mail exists already!') .isEmail() .withMessage('Please Enter a Valid Email') .custom((value, { req }) => { // 유저가 입력한 Eamil 존재하는지 DB에서 찾기 return User.findOne({ email: value }).then(userDoc => { if (userDoc) { // Error 던지기 return Promise.reject('E-mail exists already!!'); } }); }) .normalizeEmail(), //body(인자1, 인자2) => req.body로 날라오는 값을 검증 // 인자1 : 검증할 값, 인자2: default 에러 메세지 body( 'password', 'Please Enter a Password wiht only numbers and text and at least 5 characters.' ) .isLength({ min: 5 }) // 알파벳만 가능 .isAlphanumeric() .trim(), body('confirmPassword') .trim() .custom((value, { req }) => { if (value !== req.body.password) { throw new Error('Passwords have to match!'); } return true; }) ], authController.postSignup );
1.routes/admin.js
-
express-validator불러오기const { body } = require('express-validator/check');
-
postAddProduct에 검증 로직 추가하기router.post( '/add-product', [ body('title', 'Title에 최소한 5글자 이상 넣어주세요') .isLength({ min: 3 }) .isAlphanumeric() .trim(), body('imageUrl', 'URL 형식을 넣어주세요').isURL(), body('price').isFloat(), body('description') .isLength({ min: 5, max: 400 }) .trim() ], isAuth, adminController.postAddProduct );
2.controllers/admin.js
-
express-validator불러오기const { body } = require('express-validator/check');
-
postAddProduct검증 로직 추가하기
3.views/admin/edit-product.ejs
-
editing 상태 구별하고
erorrMessage넣기<main> <% if (errorMessage) { %> <div class="user-message user-message--error"><%= errorMessage %></div> <% } %> <form class="product-form" action="/admin/<% if (editing) { %>edit-product<% } else { %>add-product<% } %>" method="POST"> <div class="form-control"> <label for="title">Title</label> <input type="text" name="title" id="title" value="<% if (editing || hasError) { %><%= product.title %><% } %>"> </div> <div class="form-control"> <label for="imageUrl">Image URL</label> <input type="text" name="imageUrl" id="imageUrl" value="<% if (editing || hasError) { %><%= product.imageUrl %><% } %>"> </div> <div class="form-control"> <label for="price">Price</label> <input type="number" name="price" id="price" step="0.01" value="<% if (editing || hasError) { %><%= product.price %><% } %>"> </div> <div class="form-control"> <label for="description">Description</label> <textarea name="description" id="description" rows="5"><% if (editing || hasError) { %><%= product.description %><% } %></textarea> </div> <% if (editing) { %> <input type="hidden" value="<%= product._id %>" name="productId"> <% } %> <input type="hidden" name="_csrf" value="<%= csrfToken %>" /> <button class="btn" type="submit"><% if (editing) { %>Update Product<% } else { %>Add Product<% } %></button> </form> </main>
-
-
controllers/admin.js
-
postEditProductexports.postEditProduct = (req, res, next) => { const prodId = req.body.productId; const updatedTitle = req.body.title; const updatedPrice = req.body.price; const updatedImageUrl = req.body.imageUrl; const updatedDesc = req.body.description; const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).render('admin/edit-product', { pageTitle: 'Edit Product', path: '/admin/edit-product', editing: true, hasError: true, product: { title: updatedTitle, imageUrl: updatedImageUrl, price: updatedPrice, description: updatedDesc, _id: prodId // 꼭 추가해주기, getEditProduct 에는 이 값이 있다. }, errorMessage: errors.array()[0].msg }); }
-
참고: Edit 버튼을 보면 URI 인자로
product._id와 쿼리로edit=true를 줄 수 있는 것을 볼 수 있다. 이렇게 포스트 요청을 하면 각각req.params와req.query로 받을 수 있다.<a href="/admin/edit-product/<%= product._id %>?edit=true" class="btn">Edit</a>
-