타입스크립트로 함께하는 웹 풀 사이클 개발(React, Node.js)/TIL

데브코스 8주차 day1 - http-status-code 모듈, 폴더구조, 암호화, user 컨트롤러 구현

슈크림 붕어빵 2024. 1. 5. 16:28

구현한 부분 (with 암호화)

  • 회원가입
  • 로그인
  • 비밀번호 수정 요청(id확인)
  • 비밀번호 수정

 

- 회원가입 / 로그인 / 비밀번호 수정 요청(id확인) / 비밀번호 수정

node.js 패키지 구조

- app.js 

메인 라우터 역할

const express = require("express");
const app = express();

const dotenv = require("dotenv");
dotenv.config();

app.listen(process.env.PORT);

const userRouter = require("./routes/users");
const booksRouter = require("./routes/books");
const likesRouter = require("./routes/likes");
const cartsRouter = require("./routes/carts");
const ordersRouter = require("./routes/orders");

app.use("/users", userRouter);
app.use("/books", booksRouter);
app.use("/likes", likesRouter);
app.use("/carts", cartsRouter);
app.use("/orders", ordersRouter);

- routes

 /users.js : user에 관련된 하위 라우터 역할

 /books.js : book과 관련된 하위 라우터 역할

...

//users.js
const express = require("express");
const router = express.Router();
const conn = require("../mariadb");
const {
  join,
  login,
  passwordReset,
  PasswordResetrequest,
} = 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;

 

routes안 파일에서는 경로만을 지정해주고 콜백함수는 controller에 저장되어있는 모듈을 불러와서 사용한다.

 

 

- controller

  • 프로젝트에서 매니저 역할을 하는 파일
  • 사용자의 요청(req)가 길(url)을 찾아오면 콜백함수(=controller)가 받아줌.
  • 즉, 해당 url이 들어왔을 때 실행될 내용을 저장하고 있다.
  • module.exports로 내보내고 꺼내와 사용.
  • 더보기
    const conn = require("../mariadb");
    const { StatusCodes } = require("http-status-codes");
    const crypto = require("crypto"); //내장 암호화 모듈
    //jwt 모듈
    const jwt = require("jsonwebtoken");
    
    const join = (req, res) => {
      const { email, name, password } = req.body;
    
      //비밀번호 암호화
      const salt = crypto.randomBytes(64).toString("base64");
      const hashPassword = crypto
        .pbkdf2Sync(password, salt, 10000, 10, "sha512")
        .toString("base64");
    
      let sql = "INSERT INTO users (email,name,password,salt) VALUES (?,?,?,?)";
      let values = [email, name, 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 = ?";
      let values = [email];
      conn.query(sql, values, (err, results) => {
        if (err) {
          console.log(err);
          return res.status(StatusCodes.BAD_REQUEST).end();
        }
        const loginUser = results[0];
    
        //암호화
        const salt = loginUser.salt;
        const hashPassword = crypto
          .pbkdf2Sync(password, salt, 10000, 10, "sha512")
          .toString("base64");
    
        //해시 password 비교
        if (loginUser && loginUser.password == hashPassword) {
          //토큰 발급
          const token = jwt.sign(
            {
              email: loginUser.email,
              name: loginUser.name,
            },
            process.env.PRIVATE_KEY,
            {
              expiresIn: "15m",
              issuer: "sungeun",
            }
          );
          console.log(token);
    
          res.cookie("token", token, {
            httpOnly: true,
          });
          return res
            .status(StatusCodes.OK)
            .json({ message: `${loginUser.name}님 환영합니다.` });
        } else {
          return res.status(StatusCodes.UNAUTHORIZED).json({
            message: "id 및 pw를 확인해주세요.",
          });
        }
      });
    };
    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();
        }
      });
    };
    const passwordReset = (req, res) => {
      const { email, password } = req.body;
      let sql = "UPDATE users SET password = ?, salt=? WHERE email =?";
    
      //새로운 salt와 비밀번호
      const salt = crypto.randomBytes(64).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) {
          return res.status(StatusCodes.OK).end();
        } else {
          return res.status(StatusCodes.BAD_REQUEST).json(results);
        }
      });
    };
    
    module.exports = { join, login, PasswordResetrequest, passwordReset };

 

 

암호화 구현 - 비밀번호 암호화

비밀번호 암호화는 단방향 암호화(hash)를 사용하는 내부모듈 crypto를 사용했으며 내용은 다음 포스트에 작성하였다.

 

https://bsu0404.tistory.com/98

 

암호화 (+crypto)

 

bsu0404.tistory.com

 

http-status-codes

 

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

 

http-status-codes

Constants enumerating the HTTP status codes. Based on the Java Apache HttpStatus API.. Latest version: 2.3.0, last published: 4 months ago. Start using http-status-codes in your project by running `npm i http-status-codes`. There are 2430 other projects in

www.npmjs.com

사용법:

const { StatusCodes } = require("http-status-codes");

 

예시 잘못된 요청) 

return res.status(StatusCodes.BAD_REQUEST).end();

예시 성공) 

return res.status(StatusCodes.OK).end();

예시 찾을 수 없는 경우) 

return res.status(StatusCodes.NOT_FOUND).end();

 

앞으로 주의할 점

 

return값, 즉 res가 무한 로딩으로 돌아오지 않을 경우

 

1. router.use(express.json());에서 json뒤에 () 확인

2. res.json()을 쓰지 않은 경우 end()라도 사용했는지 확인