API 구현하기, crypto 모듈, http-status-codes 모듈 활용해보기

ssomae·2024년 10월 2일

DevCourse

목록 보기
26/29

회원가입 API

const express = require('express');
const router = express.Router();
const conn = require('../mariadb');

router.use(express.json());

//회원가입
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();
            }
            res.status(201).json(results);
        }
    )
    res.json('회원가입');
});
  • HTTP status code를 이렇게 직접 하드코딩해서 넣는건 좋은 방법이 아니다.
  • http-status-codes 모듈을 활용해보자

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

const express = require('express');
const router = express.Router();
const conn = require('../mariadb');
const { StatusCodes } = require('http-status-codes');

router.use(express.json());

//회원가입
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();
            }
            res.status(StatusCodes.CREATED).json(results);
        }
    )
    res.json('회원가입');
});

node.js 패키지(파일) 구조

app.js : 프로젝트의 메인 라우터 역할

/routes

  • /users.js : 하위 라우터 역할
  • /books.js : 하위 라우터 역할

현재 하위 라우터 파일들에서 로직까지 다 수행하고 있는데 이는 단점이 있다.

라우터가 로직까지 모두 수행할 때 단점

  • 프로젝트 규모가 커질수록, 코드가 매우 복잡해짐 → 코드를 간결하고 가독성이 높게 만들어주자
  • 가독성X
  • 트러블 슈팅X
    • 유지 보수하기 어렵다

해결 방안

  • 경로를 찾은 다음 역할 = ‘콜백함수’를 빼내자 (by controller)

컨트롤러

  • 프로젝트에서 매니저 역할을 하는 파일
  • 누군가에게 일을 어떻게 시켜야할지 알고 있다.
    • 직접 일을 하진 않을 것.
  • 라우터를 통해서 “사용자의 요청(req)이” 길(url)을 찾아오면
    매니저(콜백함수 = controller)가 맞아 줄것이다.
    → 알바생한테 일을 시키고, 결과물을 매니저에게 전달
    매니저(콜백함수 = controller)가 사용자에게 res를 돌려줍니다.

  • 다음과 같이 controller 폴더를 생성후 안에 파일에다가 기존 콜백함수를 잘라내어 작성해준다.

  • 모듈화 시킨 이후 원래 코드에서 모듈을 불러온 후 사용해주면 된다.
const express = require('express');
const router = express.Router();
const conn = require('../mariadb');
const { StatusCodes } = require('http-status-codes');
const join = require('../controller/userController');

router.use(express.json());

//회원가입
router.post('/join',join );

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

//비밀번호 초기화 요청
router.post('/reset',(req, res) => {
    res.json('비밀번호 초기화 요청');
});

//비밀번호 초기화
router.put('/reset', (req, res) => {
    res.json('비밀번호 초기화');
});

module.exports = router;

로그인 API

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();
            }
            console.log(results, results[0]);
            const loginUser = results[0];
            if (loginUser && loginUser.password == password) {
                //토큰 발행
                const token = jwt.sign({
                    email: loginUser.email
                }, process.env.PRIVATE_KEY, {
                    expiresIn: '10m',
                    issuer: 'ssomae'
                });

                //토큰 쿠키에 담기
                res.cookie("token", token, {
                    httpOnly: true
                });
                console.log(token);

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

비밀번호 초기화 요청

  • 비밀번호를 잊었을때, 이메일을 입력하여 비밀번호 초기화를 요청한다.
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();
            }
            //이메일이 DB에 있는 정보인지 체크
            const user = results[0];
            if (user) {
                return res.status(StatusCodes.OK).json({
                    email : email
                });
            } else {
                return res.statuts(StatusCodes.UNAUTHORIZED).end();
            }
        }
    )
};
  • 이후 이메일이 DB에 존재한다면 비밀번호 초기화할때 사용해야 하므로, email 을 결과 값으로 던져준다.

비밀번호 업데이트

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);
        }
    )
};

회원가입 시 비밀번호 암호화

node.js의 기본 모듈인 crypto 를 이용할 것이다.

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

dotenv.config();

const join = (req, res) => {
    const { email, password } = req.body;
    
    //password 암호화
    const salt = crypto.randomBytes(64).toString('base64');
    const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64');

    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();
            }
            return res.status(StatusCodes.CREATED).json(results);
        }
    )
};
 const salt = crypto.randomBytes(64).toString('base64');
  • 64의 길이로 랜덤하게 바이트를 만든후 base64형식의 문자열로 변환한다.
const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 64, 'sha512').toString('base64');

  • 10000 → 해쉬 함수를 반복하는 횟수

  • 64 → 만들 암호화 비밀번호 길이

  • 회원가입시 비밀번호를 암호화해서 암호화된 비밀번호와, salt 값을 같이 저장

  • 로그인시 , 이메일 과 비밀번호(날 것) ⇒ salt 값 꺼내서 비밀번호 암호화 해보고 ⇒ db에 저장된 값과 비교

const join = (req, res) => {
    const { email, password } = req.body;
    let sql = 'INSERT INTO users (email, password, salt) VALUES(?, ?, ?)';

    //password 암호화
    const salt = crypto.randomBytes(10).toString('base64');
    const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 10, 'sha512').toString('base64');

    let values = [email, hashPassword, salt];
    //로그인시 , 이메일 과 비밀번호(날 것) ⇒ salt 값 꺼내서 비밀번호 암호화 해보고 ⇒ db에 저장된 값과 비교
    conn.query(sql, values,
        (err, results) => {
            if (err) {
                console.log(err);
                return res.status(StatusCodes.BAD_REQUEST).end();
            }
            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();
            }
            console.log(results, results[0]);
            const loginUser = results[0];
            
            //로그인시 , 이메일 과 비밀번호(날 것) ⇒ salt 값 꺼내서 비밀번호 암호화 해보고 ⇒ db에 저장된 값과 비교
            const hashPassword = crypto.pbkdf2Sync(password, loginUser.salt, 10000, 10, 'sha512').toString('base64');
            
            if (loginUser && loginUser.password == hashPassword) {
                //토큰 발행
                const token = jwt.sign({
                    email: loginUser.email
                }, process.env.PRIVATE_KEY, {
                    expiresIn: '10m',
                    issuer: 'ssomae'
                });

                //토큰 쿠키에 담기
                res.cookie("token", token, {
                    httpOnly: true
                });
                console.log(token);

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

비밀번호 초기화시 암호화

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

    let sql = 'UPDATE users SET password = ?, salt = ? WHERE email = ?';
    const salt = crypto.randomBytes(10).toString('base64');
    const hashPassword = crypto.pbkdf2Sync(password, salt, 10000, 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);
        }
    )
};
profile
성장해나갈 개발자

0개의 댓글