보안 전공생의 공부

게시판 만들기 / User 생성, 수정 시 error 처리 (1) 본문

WEB/Node

게시판 만들기 / User 생성, 수정 시 error 처리 (1)

수잉 2021. 12. 1. 15:33

· flash : 변수처럼 이름과 값(문자열, 숫자, 배열, 객체 등 어떠한 형태의 값이라도 사용 가능)을 저장할 수 있는데,

한 번 생성되면 사용될 때까지 서버 메모리상에 저장이 되어 있다가 한 번 사용되면 사라지는 형태의 data

  -> connect-flash package를 이용하여 flash

 

· regex(Regular Expression, 정규 표현식) : 특정규칙을 가진 문자열의 집할을 표현하는 데 사용하는 형식 언어

(출처 :https://ko.wikipedia.org/wiki/%EC%A0%95%EA%B7%9C_%ED%91%9C%ED%98%84%EC%8B%9D)

  문자열이 특정한 형식을 가지고 있는지 아닌지를 판단하기 위해 사용함

  ->User의 username, password, name, email 항목들이 특정한 형식의 값만 저장할 수 있도록 함


· package 설치

express-session package
connect-falsh package

express-sessionconnect-flash를 실행하기 위해 필요한 package이다.

 

 

· User.js 수정

- User schema

수정 전
수정 후

trim은 문자열 앞뒤에 빈칸이 있는 경우 제거해주는 옵션

match에 regex가 들어가서 값이 regex에 부합하지 않으면 에러메시지를 낸다.


◆ 정규표현식 : 문자열에 특정한 규칙에 맞는 문자열이 있는지 알아보는 표현식

 

1. 정규식 리터럴("/"로 감싸는 패턴) 사용

/^.{4,12}$/
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/

  스크립트가 불러와질 때 컴파일됨 

  -> 정규식이 상수일 때 사용

 

2. RegExp객체의 생성자 함수 호출

RegExp("^.{4,12}$")
RegExp("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$")

  정규식이 실행되는 시점에 컴파일됨

  -> 정규식의 패턴이 변경될 수 있는 경우 or 사용자 입력과 같이 다른 출처로부터 패턴을 가져와야 하는 경우에 사용

 

◆ 정규표현식에서의 특수문자

^ : 문자열의 시작 위치에 대응(줄 바꿈 문자 바로 다음 부분과도 대응)

$ : 문자열의 끝 위치에 대응(줄 바꿈 문자 바로 앞 부분과도 대응)

. : 어떠한 문자열이라도 상관없음 의미

{n} : 앞 표현식이 n번 나타나는 부분에 대응 (n은 반드시 양의 정수)

      ex) /a{2}/ ->caandy의 a에 대응O , candy의 a에 대응X

\w : 영숫자 문자에 대응

       [A-Za-z0-9_]와 동일

[xyz] : 문자셋(Character set)

        괄호 안의 어떤 문자(escape sequence까지 포함)와도 대응

        하이픈(-)으로 문자의 범위 지정 가능

        ex) [a-d] ->[abcd]와 똑같이 작동

 

- 출처 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Regular_Expressions

 

정규 표현식 - JavaScript | MDN

정규 표현식은 문자열에 나타는 특정 문자 조합과 대응시키기 위해 사용되는 패턴입니다. 자바스크립트에서, 정규 표현식 또한 객체입니다.  이 패턴들은 RegExp의 exec 메소드와 test 메소드  ,

developer.mozilla.org


regex(/^.{4,12}$/) : 문자열의 시작 위치에 길이 4이상 12이하인 문자열이 있고, 바로 다음이 문자열의 끝이여야 함
regex(/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/)  :  문자열의 시작위치에 영문자, 숫자, 특수문자( . , _ , % , +, - ) 중에서 최소 1자 이상의 문자열+"@"+영문자, 숫자, 특수문자( . , - ) 중에서 최소 1자 이상의 문자열 +"."+ 영문자 최소 2자 이상의 문자열이 끝이여야 함

 

- password validation

수정 전

 ▷수정 후의 코드

// password validation
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/;
const passwordRegexErrorMessage = 'Should be minimum 8 characters of alphabet and number combination!';
userSchema.path('password').validate(function(v) {
  var user = this;

  // create user
  if(user.isNew){
    if(!user.passwordConfirmation){
      user.invalidate('passwordConfirmation', 'Password Confirmation is required.');
    }

    if(!passwordRegex.test(user.password)){
      user.invalidate('password', passwordRegexErrorMessage);
    }
    else if(user.password !== user.passwordConfirmation) {
      user.invalidate('passwordConfirmation', 'Password Confirmation does not matched!');
    }
  }

  // update user
  if(!user.isNew){
    if(!user.currentPassword){
      user.invalidate('currentPassword', 'Current Password is required!');
    }
    else if(!bcrypt.compareSync(user.currentPassword, user.originalPassword)){
      user.invalidate('currentPassword', 'Current Password is invalid!');
    }

    if(user.newPassword && !passwordRegex.test(user.newPassword)){
      user.invalidate("newPassword", passwordRegexErrorMessage);
    }
    else if(user.newPassword !== user.passwordConfirmation) {
      user.invalidate('passwordConfirmation', 'Password Confirmation does not matched!');
    }
  }
});

 

const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,16}$/;
8-16자리의 문자열 중에 숫자랑 영문자가 반드시 하나 이상 존재해야 함
 
const passwordRegexErrorMessage = 'Should be minimum 8 characters of alphabet and number combination!';
에러메세지->변수로 선언
 

앞서 정규표현식을 변수 passwordRegex로 선언하였다.

정규표현식.test(문자열)함수 : 문자열에 정규표현식을 통과하는 부분이 있으면 -> true,

                                       아니면 false를 반환

->false가 반환되면 model.invalidate 함수가 호출된다.

 

 

· index.js 수정

index.js

새로 설치한 package로부터 flash, session을 선언하였다.

 

req.flash(문자열, 저장할 값)형태로 저장할 값(숫자, 문자열, 오브젝트 등 가능)을 해당 문자열에 저장한다.

이때 배열로 저장되기 때문에 같은 문자열을 중복해서 사용하면 순서대로 배열에 저장된다.

req.flash(문자열)인 경우 - 해당 문자열에 저장된 값들을 배열로 불러온다. 

<- 저장된 값이 없으면 빈 배열([ ])을 return

 

sesssion은 서버에서 접속자를 구분시키는 역할을 한다.

서로 다른 사용자를 구분하여 서버에서 필요한 값들을 따로 관리하게 된다.

- 옵션부분 : {secret:'MySecret', resave:true, saveUninitialized:true}

  -> secret : session을 hash화하는 데 사용되는 값(일종의 비밀번호)

                 아무값이나 넣으면 된다.

      resave : 재저장을 계속 할 것인지에 대한 옵션 

                 매 request마다 세션에 변동이 있든 없든 무조건 다시 저장하는 것

                 default : true

      saveUninitialized :  세션이 세션store에 저장되기 전 uninitialized한 상태(request가 들어올 때, 해당 request에서 새로 생성된 session에 아무 작업이 이루어지지 않은 상황)의 session을 강제 저장

                                 아무 내용 없는 session이 계속 저장될 수 있음

                                 default : true-> client의 서버 방문 횟수에 따라 등급을 구분 짓는 기능 구현 가능 

 (참조 : https://dalkomit.tistory.com/72 , https://fierycoding.tistory.com/36 )

 

 

 

· user.js 수정

// routes/users.js

const express = require('express');
const router = express.Router();
const User = require('../models/User');

// Index
router.get('/', function(req, res){
  User.find({})
    .sort({username:1})
    .exec(function(err, users){
      if(err) return res.json(err);
      res.render('users/index', {users:users});
    });
});

// New
router.get('/new', function(req, res){
  const user = req.flash('user')[0] || {};
  const errors = req.flash('errors')[0] || {};
  res.render('users/new', { user:user, errors:errors });
});

// create
router.post('/', function(req, res){
  User.create(req.body, function(err, user){
    if(err){
      req.flash('user', req.body);
      req.flash('errors', parseError(err));
      return res.redirect('/users/new');
    }
    res.redirect('/users');
  });
});


// show
router.get('/:username', function(req, res){
  User.findOne({username:req.params.username}, function(err, user){
    if(err) return res.json(err);
    res.render('users/show', {user:user});
  });
});

// edit
router.get('/:username/edit', function(req, res){
  const user = req.flash('user')[0];
  const errors = req.flash('errors')[0] || {};
  if(!user){
    User.findOne({username:req.params.username}, function(err, user){
      if(err) return res.json(err);
      res.render('users/edit', { username:req.params.username, user:user, errors:errors });
    });
  }
  else {
    res.render('users/edit', { username:req.params.username, user:user, errors:errors });
  }
});

// update
router.put('/:username', function(req, res, next){
  User.findOne({username:req.params.username})
    .select('password')
    .exec(function(err, user){
      if(err) return res.json(err);

      // update user object
      user.originalPassword = user.password;
      user.password = req.body.newPassword? req.body.newPassword : user.password;
      for(const p in req.body){
        user[p] = req.body[p];
      }

      // save updated user
      user.save(function(err, user){
        if(err){
          req.flash('user', req.body);
          req.flash('errors', parseError(err));
          return res.redirect('/users/'+req.params.username+'/edit');
        }
        res.redirect('/users/'+user.username);
      });
  });
});


// destroy
router.delete('/:username', function(req, res){
  User.deleteOne({username:req.params.username}, function(err){
    if(err) return res.json(err);
    res.redirect('/users');
  });
});

module.exports = router;

// functions
function parseError(errors){
  const parsed = {};
  if(errors.name == 'ValidationError'){
    for(const name in errors.errors){
      const validationError = errors.errors[name];
      parsed[name] = { message:validationError.message };
    }
  }
  else if(errors.code == '11000' && errors.errmsg.indexOf('username') > 0) {
    parsed.username = { message:'This username already exists!' };
  }
  else {
    parsed.unhandled = JSON.stringify(errors);
  }
  return parsed;
}

new route

user 생성 시에 에러가 있는 경우 new페이지에 에러와 기존에 입력했던 값을 보여준다.

( 이때, 이 값들은 create route에서 생성된 flash로부터 받아옴 )

 

flash는 배열이 오는데 이 프로그램에서는 하나 이상의 값이 저장되는 경우가 없고, 있더라도 오류이므로 무조건 [0]의 값을 읽어오게 하였다.

값이 없다면 (처음 new 페이지에 들어온 경우) | | { }을 사용하여 빈 오브젝트를 넣어 user/new 페이지를 생성한다.

 

create route

 

 

user 생성시 오류가 있으면 user, error flash를 만들고 new페이지로 redirect 한다.

user 생성시 발생하는 오류는 

1. User model의 userSchema에 설정해둔 validation을 통과하지 못한 경우

2. mongoDB에서 오류를 내는 경우

 -> 두 경우의 error객체의 형식은 상이하므로 parseError라는 함수를 따로 만들어서 err을 분석하고 일정한 형식으로 만든다

 

edit route

user flash가 있으면 -> 1. update에서 오류가 생겨 edit으로 다시 돌아온 경우

                             (그래서 기존에 입력했던 값으로 form에 값들을 생성해야 함 -> | | { } 사용 X )

                없으면 -> 2. 처음 들어온 경우로 가정

더보기

req.flash(문자열)인 경우 - 해당 문자열에 저장된 값들을 배열로 불러온다. 

<- 저장된 값이 없으면 빈 배열([ ])을 return

 

 

2. 처음 접속하는 경우 (!user : user 변수에 값이 없음) -> DB에서 값을 찾아 form에 기본 값을 생성(=form을 user의 DB값들로 채움)

1. update에서 오류가 생겨 edit 페이지로 돌아온 경우 -> user 변수의 값을 flash에서 받아옴

user을 사용해 값을 생성함(=update에서 입력했던 값으로 form을 채움)

 

username : req.params.username

render시 username을 따로 req.params.username로 보내준다.

user.username이 (1) 해당 user의 username일수도, (2) user flash에서 받는 username일수도 있기 때문에

username은 주소에서 찾은 username을 따로 보내준다.

 

function

mongoose에서 내는 에러와 mongoDB에서 내는 에러의 형태가 다르기 때문에

에러의 형태를 항목이름: { message: "에러메세지" }로 통일시켜주는 함수이다.

 

if - mongoose의 model validation error

else if - mongoDB에서 username이 중복되는 error

           (error code 11000 : key값이 중복될 때 발생하는 error)

else - 그 외의 error을 처리함

 

 

 

 

출처및참조 : https://www.a-mean-blog.com/ko/blog/Node-JS-%EC%B2%AB%EA%B1%B8%EC%9D%8C/%EA%B2%8C%EC%8B%9C%ED%8C%90-%EB%A7%8C%EB%93%A4%EA%B8%B0/%EA%B2%8C%EC%8B%9C%ED%8C%90-User-Error-%EC%B2%98%EB%A6%AC

 

Node JS 첫걸음/게시판 만들기: 게시판 - User Error 처리 - A MEAN Blog

소스코드 이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실

www.a-mean-blog.com

 

Comments