@Wethread 회원가입 / 로그인 기능구현

usnim·2023년 10월 20일
0

project

목록 보기
2/20
post-thumbnail

📌 회원가입 / 로그인

📍 Router

//routers / index.js
const express = require("express");
const router = express.Router();

//localhost:3000/users/ ---
const userRouter = require("./userRouter");
router.use("/users", userRouter.router);

module.exports = router;
//routers / userRouter.js
const express = require("express");
const router = express.Router();
const userController = require("../controller/userController");

router.post("/signup", userController.signUp);

router.post("/login", userController.login);

module.exports = {router};

front와 연결할때 필요한 uri를 위해 router 작성

회원가입 : /users/signup
로그인 : /users/login
기능별로 나누기 위해 공통적인 부분은 index.js의 users로 지정하고 나머지 signup / login을 지정했다.

📍 Controller

//controllers / userController
const userService = require("../services/userService");
const etc = require("../middleware/etc");

const signUp = async (req, res) => {
  try {
    const { nickname, email, password } = req.body;
    if (!nickname || !email || !password) {
      return res
        .status(400)
        .json({ message: "필수 항목을 꼭 기입해야 합니다." });
    }
    const result = await userService.signUp(nickname, email, password);
    return res.status(200).json({ message: "sign-up successfully" , result });
  } catch (error) {
    return res.status(error.statusCode || 500).json({ message: error.message });
  }
};

const login = async (req, res) => {
  try {
    const { email, password } = req.body;
    if (!email || !password) {
      return res
        .status(400)
        .json({ message: "이메일 주소와 패스워드를 확인해 주세요." });
    }
    // 이메일 체크
    const userCheck = await userService.login(email);
    if (!userCheck) {
      throw new Error(401, "not found email");
    }
    //비밀번호 체크
    const passwordCheck = userCheck[0].password;
    const result = await etc.checkHash(password, passwordCheck);
    //결과가 나오면 토큰 생성
    if (result) {
      const userId = userCheck[0].id;
      const userNickname = userCheck[0].nickname;
      const jwtToken = etc.generateToken(email , userId);
      if (!jwtToken) {
        throw new Error(401, "token generation failed");
      }
      return res.status(200).json({
        message: "login success",
        token: `${jwtToken}`,
        id: userId,
        nickname: userNickname,
      });
    }
  } catch (error) {
    return res.status(error.statusCode || 500).json({ message: error.message });
  }
};

module.exports = {
  signUp,
  login,
};

회원가입 : front에서 건네받은 닉네임과 이메일 , 패스워드를 한번 확인하고 값이 정상적으로 들어오지 않으면 필수 항목이라 꼭 기입해야 한다는 message를 내보낸다. 그 후 Service 부분에 signUp 함수 사용

로그인 : 회원가입을 마치고 DB에 회원정보가 저장된 후 로그인을 할때 회원가입과 마찬가지 값을 정상적으로 입력하지 않고 로그인 시도를 하면 이메일과 패스워드를 확인하라는 메세지 출력. 그 후 Service 부분에 login 함수 사용

📍 Middleware

//middlewares / etc.js
const jwt = require("jsonwebtoken");
const bcrypt = require("bcrypt");
const secretKey = process.env.SECRET_KEY;

//hash 만들기
const makeHash = async (password, saltRounds) => {
  return bcrypt.hash(password, saltRounds);
};

//hash 검증
const checkHash = async (password, hashedPassword) => {
  return bcrypt.compare(password, hashedPassword);
};

//이메일 형식 정규식
const validateEmail = (email) => {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email);
};
//패스워드 정규식
const validatePassword = (password) => {
  const passwordRegex = /^.{10,}$/;
  return passwordRegex.test(password);
};

//token 만들기
const generateToken = (email, userId) => {
  return jwt.sign({ email, userId }, secretKey);
};

//디코딩 token
const decoded = (token, secretKey) => {
  const result = jwt.verify(token, secretKey);
  return result;
};

/* 암호화된 패스워드와 발행받는 토큰의 형태는 이러한 형태를 띄고있다.
회원가입 : 암호화된 패스워드 ($2b$10$GmbKm5JIU2czUm0c6YCjR.ar64FVuZP0Je3BI/Cm.ONIQcCZYC1Q.)
로그인 : 토근 발행 ("token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVvaXNvYXVuZGFrc0B3ZWNvZGUuY28ua3IiLCJ1c2VySWQiOjM3LCJpYXQiOjE2OTc3MDcyNTZ9.UhqosTgsGtIaZpl375ZfWPM7mqihX4kZMh7lnxH6Dcg")*/

패스워드 암호화 및 로그인 후 다른 항목을 엑세스를 위한 Token 발행과 검증을 하는 부분

📍 Service

//Services / userService.js
const userDao = require("../models/userDao");
const etc = require("../middleware/etc");

const signUp = async (nickname, email, password) => {
  //패스워드 예외처리
  if (!etc.validatePassword(password)) {
    const err = new Error("패스워드는 10자리 이상이어야 합니다.");
    err.status = 409;
    throw err;
  }
  //이메일 예외처리
  if (!etc.validateEmail(email)) {
    const err = new Error("이메일 형식이 올바르지 않습니다.");
    err.status = 409;
    throw err;
  }

  //중복 이메일 예외처리
  const checkEmail = await userDao.checkEmail(email);
  if (checkEmail) {
    const err = new Error("이미 사용 중인 이메일입니다.");
    err.status = 409;
    throw err;
  }

  const hashedPassword = await etc.makeHash(password, 10);

  const insertHash = await userDao.signUp(nickname, email, hashedPassword);

  const result = await etc.checkHash(password, hashedPassword);

  return insertHash;
};

const login = async (email) => {
  const users = await userDao.login(email);

  return users;
};
module.exports = {
  signUp,
  login,
};

Controller에서 받은 인자값을 DB에 접근하기 전에 예외처리 및 검증하는 Service 부분

📍 Model (db)

//Models / userDao.js
const { DataSource } = require("typeorm");

const appDataSource = new DataSource({
  type: process.env.TYPEORM_CONNECTION,
  host: process.env.TYPEORM_HOST,
  port: process.env.TYPEORM_PORT,
  username: process.env.TYPEORM_USERNAME,
  password: process.env.TYPEORM_PASSWORD,
  database: process.env.TYPEORM_DATABASE,
});

try {
  appDataSource.initialize().then(() => {
    console.log("USER Data Source has been initialized");
  });
} catch (err) {
  console.log(err);
}

const signUp = async (nickname, email, hashedpassword) => {
  try {
    const result = await appDataSource.query(
      `
    insert into users (nickname , email , password) values (?,?,?)
    `,
      [nickname, email, hashedpassword]
    );
    return result;
  } catch (error) {
    const err = new Error("Data insert error");
    err.status = 500;
    throw err;
  }
};

const checkEmail = async (email) => {
  try {
    const result = await appDataSource.query(
      `
    select email from users where email = ?
    `,
      [email]
    );
    return result.length > 0;
  } catch (error) {
    const err = new Error("Data read error");
    err.status = 500;
    throw err;
  }
};

const login = async (email) => {
  try {
    const result = await appDataSource.query(
      `
    select * from users where email = ?
    `,
      [email]
    );
    return result;
  } catch (error) {
    const err = new Error("Data read error");
    err.status = 500;
    throw err;
  }
};

module.exports = {
  signUp,
  login,
  checkEmail,
};

DB에 직접적으로 연결하는 부분이며 Service 부분에서 인자로 받은 값들을 DB에 저장 및 select 문으로 확인하고 이메일 중복이 있는지 확인하고 그 값을 return하는 역할

로그인 부분은 인자로 받은 이메일이 회원정보가 담겨있는 users라는 테이블에 저장이 되어있는지 판단 후 다시 Service 부분으로 return 하는 역할

이렇게 회원가입과 로그인을 진행 할 수 있게 된다.

📍 One-line reflection

회원가입과 로그인 기능구현이 시작하기전에는 간단하다고 생각했지만 예외처리와 기능별로 pattern을 나누는것이 쉽지 않았다. 다음에는 게시물 , 댓글 CRUD 기능을 작성해보려한다.

profile
🤦‍♂️

0개의 댓글