Nodejs: Signing up and signing in with a refresh token and an access token

2023. 11. 22. 23:34Javascript Node.js

Main video

Code

// ./routes/auth.router.js

import { Router } from "express";
import bcrypt from "bcrypt";
import db from "../models/index.js";
import jwt from "jsonwebtoken";
import {
    PASSWORD_SALT_ROUNDS,
    JWT_ACCESS_TOKEN_SECRET,
    JWT_REFRESH_TOKEN_SECRET,
    JWT_ACCESS_TOKEN_EXPIRES_IN,
    JWT_REFRESH_TOKEN_EXPIRES_IN,
} from "../constants/security.constant.js";
const { User } = db;
const authRouter = Router();

// 생년월일(정규식)
const datePattern = /^(19|20)\d{2}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/;

// 회원가입 /api/auth/signup
authRouter.post("/signup", async (req, res, next) => {
    try {
        const { email, name, myIntro, password, passwordConfirm, birth } = req.body;

        // 이메일 정보가 없는 경우
        if (!email) {
            return res.status(400).json({
                ok: false,
                message: "이메일 입력이 필요합니다.",
            });
        }
		// 이름을 작성하지 않았을 경우
        if (!name) {
            return res.status(400).json({
                ok: false,
                message: "이름 입력이 필요합니다.",
            });
        }
		// 생년월일을 입력하지 않았을 경우
        if (!birth) {
            return res.status(400).json({
                ok: false,
                message: "생년월일 입력이 필요합니다.",
            });
        }
		// 생년월일을 잘못된 형식으로 작성했을 경우(정규식 사용)
        if (!datePattern.test(birth)) {
            return res.status(400).json({
                ok: false,
                message: "생년월일을 잘못 입력하셨습니다.",
            });
        }
        // 비밀번호가 없었을 경우
        if (!password) {
            return res.status(400).json({
                ok: false,
                message: "비밀번호 입력이 필요합니다.",
            });
        }
		// 패스워드 확인란을 작성하지 않았을 경우
        if (!passwordConfirm) {
            return res.status(400).json({
                ok: false,
                message: "비밀번호 확인 입력이 필요합니다.",
            });
        }
		// 패스워드가 서로 일치하지 않았을 경우
        if (password !== passwordConfirm) {
            return res.status(400).json({
                ok: false,
                message: "입력한 비밀번호가 서로 일치하지 않습니다.",
            });
        }
        // 비밀번호의 형식: 최소 7자 이상 작성
        if (password.length < 7) {
            return res.status(400).json({
                ok: false,
                message: "비밀번호는 최소 7자리 이상입니다.",
            });
        }
		// 이메일에 알맞은 형식 규정
        let emailValidationRegex = new RegExp("[a-z0-9._]+@[a-z]+.[a-z]{2,3}");
        const isValidEmail = emailValidationRegex.test(email);
		// 이메일 형식이 잘못되었을 경우
        if (!isValidEmail) {
            return res.status(400).json({
                ok: false,
                message: "올바른 이메일 형식이 아닙니다.",
            });
        }
		// 이미 가입된 유저를 찾을 때
        const existedUser = await User.findOne({ where: { email } });
		// 가입된 유저가 있을 경우 
        if (existedUser) {
            return res.status(409).json({
                ok: false,
                message: "이미 가입된 이메일입니다.",
            });
        }
		// 비밀번호 hash화
        const hashedPassword = bcrypt.hashSync(password, PASSWORD_SALT_ROUNDS);
		// 유저 회원가입 성공
        const newUser = (
            await User.create({ email, name, birth, myIntro, password: hashedPassword })
        ).toJSON();
        delete newUser.password;

        return res.status(201).json({
            ok: true,
            message: "회원가입에 성공하였습니다.",
            data: newUser,
        });
    } catch (error) {
        next(error);
    }
});

// 로그인/api/auth/signin
authRouter.post("/signin", async (req, res, next) => {
    try {
		//로그인시 req.body에서 값을 서버에 요청
		const { email, password } = req.body;
		// 이메일을 작성하지 않았을 경우
        if (!email) {
            return res.status(400).json({
                ok: false,
                message: "이메일 입력이 필요합니다.",
            });
        }
		// 비밀번호를 작성하지 않았을 경우
        if (!password) {
            return res.status(400).json({
                ok: false,
                message: "비밀번호 입력이 필요합니다.",
            });
        }
		// 로그인 요청시 해당 유저가 있는지 확인
        const user = (await User.findOne({ where: { email } }))?.toJSON();
        // 유가가 있을시 비밀번호
        const hashedPassword = user?.password;
		// hash화된 회원가입시 비밀번호화 로그인 비밀번호화 비교
        const isPasswordMatched = bcrypt.compareSync(password, hashedPassword);
		// 회원가입된 유저와 비밀번호가 확인된 유저
        const isCorrectUser = user && isPasswordMatched;
		// 회원가입, 즉 가입된 유저 정보가 없는 경우
        if (!isCorrectUser) {
            return res.status(400).json({
                ok: false,
                message: "일치하는 인증 정보가 없습니다.",
            });
        }
		// JWT ACCESS TOKEN: 로그인시 서버측에서 사용자에게 제공
        const accessToken = jwt.sign({ userId: user.id }, JWT_ACCESS_TOKEN_SECRET, {
            expiresIn: JWT_ACCESS_TOKEN_EXPIRES_IN,
        });
        // JWT REFRESH TOKEN: 로그인시 서버측에서 사용자에게 제공
        // 이 토큰은 JWT ACCESS TOKEN이 만료되었을 경우 대체 인증 수단으로 사용
        const refreshToken = jwt.sign({ userId: user.id }, JWT_REFRESH_TOKEN_SECRET, {
            expiresIn: JWT_REFRESH_TOKEN_EXPIRES_IN,
        });
		// 로그인에 성공했을 경우
        return res.status(200).json({
            ok: true,
            message: "로그인에 성공하였습니다.",
            data: { accessToken, refreshToken },
        });
    } catch (error) {
        next(error);
    }
});

export { authRouter };