[BookShop 프로젝트] Users API 구현 - http-status-codes 모듈, 컨트롤러, crypto 모듈(비밀번호 암호화)

회원가입

//회원가입
router.post('/join', (req, res)=> {
   const {email, password} = req.body;

   let sql = 'INSERT INTO users (email, password) VALUES (?, ?)';
   let values = [email, password];

   conn.query(sql, values,
      (err, results) => {
         if(err) {
            console.log(err);
            return res.status(400).end(); // BAD REQUEST
         }

         return res.status(200).json(results);
      }
   )
   return res.json('회원가입');
});

400 : 입력이 잘못된 것으로 처리

statuscode를 하드코딩 하는 것은 좋지 않기 때문에, 다른 방법으로 처리하도록 하여야합니다.


http-status-codes 모듈

https://www.npmjs.com/package/http-status-codes

CodeConstantReason Phrase

200 OK OK
201 CREATED Created
400 BAD_REQUEST Bad Request
401 UNAUTHORIZED Unauthorized 미인증 됨(비번/아이디 틀림)
402 PAYMENT_REQUIRED Payment Required
403 FORBIDDEN Forbidden 접근 권리 없음
404 NOT_FOUND Not Found
500 INTERNAL_SERVER_ERROR Internal Server Error
501 NOT_IMPLEMENTED Not Implemented
502 BAD_GATEWAY Bad Gateway
const {StatusCodes} = require('http-status-codes'); //status code 모듈
//회원가입
router.post('/join', (req, res)=> {
   const {email, password} = req.body;

   let sql = 'INSERT INTO users (email, password) VALUES (?, ?)';
   let values = [email, password];

   conn.query(sql, values,
      (err, results) => {
         if(err) {
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end(); // BAD REQUEST
         }

         return res.status(StatusCodes.CREATED).json(results);
      }
   )
});

 


현재에는 라우터가 로직까지 다 수행하고 있는데요!

 

  • 프로젝트 규모가 커질 수록 코드가 복잡해짐 -> 해결방법: 코드를 간결하고 가독성 높게 만들어주자. 
  • 가독성 X
  • 트러블 슈팅 X

=> 유지보수 하기 어려워집니다.

 

경로를 찾은 다음 역할 -> '콜백함수'만을 빼내보겠습니다.

컨트롤러

: 프로젝트에서 매니저 역할을 하는 파일 :  관장!

- 누군가에게 일을 어떻게 시켜야할 지 알고 있습니다. = 직접 일을 하진 않을 것

=> router 통해 사용자의 요청(req)이 url을 찾아오면, 컨트롤러가 일을 시키고, 다시 받아 사용자에게 res를 돌려줍니다. 

 

routes/users.js

const express = require('express');
const router = express.Router();
const conn = require('../mariadb'); //db 모듈
const {
   join,
   login,
   passwordResetRequest,
   passwordReset
} = require('../controller/UserController');  

router.use(express.json()); 
router.post('/join', join); //회원가입
router.post('/login', login); //로그인
router.post('/reset', passwordResetRequest); //비밀번호 초기화 요청
router.put('/reset', passwordReset); //비밀번호 초기화

module.exports = router;

길만 찾아주고 Controller와 연결해줌

 

controller/UserController.js

const conn = require('../mariadb'); //db 모듈
const {StatusCodes} = require('http-status-codes'); //status code 모듈

const join = (req, res)=> {
   const {email, password} = req.body;

   let sql = 'INSERT INTO users (email, password) VALUES (?, ?)';
   let values = [email, password];

   conn.query(sql, values,
      (err, results) => {
         if(err) {
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end(); // BAD REQUEST
         }

         return res.status(StatusCodes.CREATED).json(results);
      }
   )
};

const login = (req, res)=>{
   res.json('로그인');
};

const passwordResetRequest = (req, res)=>{
   res.json('비밀번호 초기화 요청');
};

const passwordReset = (req,res) => {
    res.json('비밀번호 초기화');
};

module.exports = {
    join, 
    login, 
    passwordResetRequest, 
    passwordReset
};

로그인

const login = (req, res)=>{
   const {email, password} = req.body;

   let sql = 'SELECT * FROM users WHERE email = ?';
   conn.query(sql, email,
    (err, results) => {
        if(err){
           console.log(err);
           return res.status(StatusCodes.BAD_REQUEST).end(); 
        }

        const loginUser = results[0];
        if(loginUser && loginUser.password == password){
            const token = jwt.sign({
                email : loginUser.email
            }, process.env.PRIVATE_KEY, {
                expiresIn : '5m',
                issuer : "kayoung"
            })
            
            //토큰 쿠키에 담기
            res.cookie("token", token, {
                httpOnly : true
            });
            console.log(token);

            return res.status(StatusCodes.OK).json(results);
        } else {
            return res.status(StatusCodes.UNAUTHORIZED).end() //401 : Unauthorized
        }

    })
};

 


비밀번호 초기화 요청 & 비밀번호 초기화

const passwordResetRequest = (req, res)=>{
const {email} = req.body;

   let sql = 'SELECT * FROM users WHERE email = ?';
   conn.query(sql, email,
    (err, results) => {
        if(err){
           console.log(err);
           return res.status(StatusCodes.BAD_REQUEST).end(); 
        }

        //이메일로 유저가 있는지 찾아보자
        const user = results[0];
        if(user) {
            return res.status(StatusCodes.OK).json({
                email : email
            });
        } else {
            return res.status(StatusCodes.UNAUTHORIZED).end() //401 : Unauthorized
        }

    })
};


//passwordResetRequest에서 res한 email을 front가 passwordReset req으로 보내줄것임 

const passwordReset = (req,res) => {
    const {email, password} = req.body;

    let sql = 'UPDATE users SET password = ? WHERE email = ?';
    let values = [password, email]; 
    conn.query(sql, values,
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            if(results.affectedRows == 0) {
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            else {
                return res.status(StatusCodes.OK).json(results);    
            }   
        }
    )
};


비밀번호 암호화 - crypto 모듈

const crypto = require('crypto');

const password = "1111";

//비밀번호 암호화
const salt = crypto.randomBytes(10).toString('base64'); //매개변수로 들어오는 걸로 64길이의 랜덤바이트만들고 base64방식의 문자열로 담음
const hashPassword = crypto.pbkdf2Sync(password, salt, 1000, 10, 'sha512').toString('base64'); //암호화 알고리즘 10000 : 해시함수 반복횟수 / 64 : 길이

console.log(hashPassword);

출력 > V9Xi8ne8DVreRw== (실행할 때마다 다른 값이 출력됨)

<- password 1111이 암호화되어 출력된 것을 확인할 수 있습니다. 

하지만 이는 단방향이기 때문에 암호화만 가능하고 복호화가 불가능합니다. 

 

해결방법

1. randomBytes로 계속 바귀는 salt를 고정시켜 암호화된 코드를 고정시켜 활용하는 방법 

2. salt를 데이터베이스에 저장해두고, 활용 

-> 회원가입 시 비밀번호를 암호화해서 암호회된 비밀번호 & salt 값 저장

-> 로그인 시 이메일&비밀번호(날 것) => salt값 꺼내서 비밀번호 암호화 해보고 => DB 비밀번호와 비교

 

const conn = require('../mariadb'); //db 모듈
const {StatusCodes} = require('http-status-codes'); //status code 모듈
const jwt = require('jsonwebtoken'); //jwt 모듈
const crypto = require('crypto'); //crypto 모듈 : 암호화 
const dotenv = require('dotenv'); //dotenv 모듈
dotenv.config();

const join = (req, res)=> {
    const {email, password} = req.body;

    let sql = 'INSERT INTO users (email, password, salt) VALUES (?, ?, ?)';
    
    // 암호화된 비밀번호와 salt 값을 같이 DB에 저장
    const salt = crypto.randomBytes(10).toString('base64'); 
    const hashPassword = crypto.pbkdf2Sync(password, salt, 1000, 10, 'sha512').toString('base64'); 

    console.log(hashPassword);

    let values = [email, hashPassword, salt];
    conn.query(sql, values,
      (err, results) => {
         if(err) {
            console.log(err);
            return res.status(StatusCodes.BAD_REQUEST).end(); // BAD REQUEST
         }

         return res.status(StatusCodes.CREATED).json(results);
      }
   )
};

const login = (req, res)=>{
   const {email, password} = req.body;

   let sql = 'SELECT * FROM users WHERE email = ?';
   conn.query(sql, email,
    (err, results) => {
        if(err){
           console.log(err);
           return res.status(StatusCodes.BAD_REQUEST).end(); 
        }

        const loginUser = results[0];

        // salt값 꺼내서 날 것으로 들어온 비밀번호 암호화 해보고
        const hashPassword = crypto.pbkdf2Sync(password, loginUser.salt, 1000, 10, 'sha512').toString('base64'); 

        // => DB 비밀번호랑 비교 
        if(loginUser && loginUser.password == hashPassword){
            const token = jwt.sign({
                email : loginUser.email
            }, process.env.PRIVATE_KEY, {
                expiresIn : '5m',
                issuer : "kayoung"
            })
            
            //토큰 쿠키에 담기
            res.cookie("token", token, {
                httpOnly : true
            });
            console.log(token);

            return res.status(StatusCodes.OK).json(results);
        } else {
            return res.status(StatusCodes.UNAUTHORIZED).end() //401 : Unauthorized
        }

    })
};

 

onst passwordResetRequest = (req, res)=>{
    const {email} = req.body;

   let sql = 'SELECT * FROM users WHERE email = ?';
   conn.query(sql, email,
    (err, results) => {
        if(err){
           console.log(err);
           return res.status(StatusCodes.BAD_REQUEST).end(); 
        }

        //이메일로 유저가 있는지 찾아보자
        const user = results[0];
        if(user) {
            return res.status(StatusCodes.OK).json({
                email : email
            });
        } else {
            return res.status(StatusCodes.UNAUTHORIZED).end() //401 : Unauthorized
        }

    })
};


//passwordResetRequest에서 res한 email을 front가 passwordReset req으로 보내줄것임 

const passwordReset = (req,res) => {
    const {email, password} = req.body;

    let sql = 'UPDATE users SET password=?, salt=? WHERE email=?';
    
    // 암호화된 비밀번호와 salt 값을 같이 DB에 저장
    const salt = crypto.randomBytes(10).toString('base64'); 
    const hashPassword = crypto.pbkdf2Sync(password, salt, 1000, 10, 'sha512').toString('base64'); 

    let values = [hashPassword, salt, email]; 
    conn.query(sql, values,
        (err, results) => {
            if(err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            if(results.affectedRows == 0) {
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            else {
                return res.status(StatusCodes.OK).json(results);    
            }   
        }
    )
};