admin으로 가입했을때 role이 admin 으로 바뀌어야 하는데, USER로 저장되는(default)현상 -> ADMIN으로 나오도록 해결함
enum Role 을 추가하고 USER 와 ADMIN 을 추가해준다.
enum Role {
USER
ADMIN
}
-> 나중에 ADMIN테이블을 따로 만들어야 하는지는 한번 찾아보기.
formData.append("role", isAdmin ? "ADMIN" : "USER") ; 조건식 추가해줌
const formData = new FormData();
formData.append("userId", id);
formData.append("pwd", password);
formData.append("name", name);
formData.append("phone", phone);
formData.append("email", email);
formData.append("isAdmin", String(isAdmin));
formData.append("role", isAdmin ? "ADMIN" : "USER"); //추가
if (isAdmin) {
formData.append("adminPhoneNumber", adminPhone);
formData.append("adminBusinessFile", businessFile);
}
백엔드(/api/signup)에서 fields.role을 값을 받아서 User 생성 시 data에 role: fields.role[0]을 추가하면, DB에 올바르게 저장됨.
const {
userId,
pwd,
name,
phone,
email,
isAdmin,
adminPhoneNumber,
role, //추가
} = fields;
let adminBusinessFileBuffer = null;
if (files.adminBusinessFile) {
const filePath = files.adminBusinessFile[0].filepath;
adminBusinessFileBuffer = await readFile(filePath);
}
try {
const newUser = await prisma.user.create({
data: {
userId: userId[0],
pwd: pwd[0],
name: name[0],
phone: phone[0],
email: email[0],
role: role[0], //추가
isAdmin: isAdmin[0] === "true",
adminPhoneNumber: adminPhoneNumber?.[0] || null,
adminBusinessFile: adminBusinessFileBuffer,
},
});
pwd가 MySQL에 평문으로 저장되고 있어서, 암호문으로 Hash처리해서 저장하는 방법 찾아보았다.
해시 처리는 일반적으로 bcrypt 모듈을 사용한다. 해시된 비밀번호는 MySQL Workbench에서도 평문으로 볼 수 없고, 보안이 유지된다.
✅1. bcrypt 설치하기
npm install bcrypt
✅ 2. handler 함수에서 비밀번호 해시하기
import bcrypt from "bcrypt"; // 추가
..
try {
// 비밀번호 암호화
const hashedPwd = await bcrypt.hash(pwd[0], 10); // 10은 saltRounds
const newUser = await prisma.user.create({
data: {
userId: userId[0],
pwd: hashedPwd, // 암호화된 비밀번호 저장
✔️ saltRounds란?
bcrypt.hash(비밀번호, saltRounds)에서 saltRounds는 해시를 얼마나 복잡하게 만들지 결정하는 숫자.
salt는 해커가 미리 계산한 해시값(DB 탈취 후 비교하는 방식 등)으로부터 비밀번호를 보호하기 위한 랜덤한 추가값임.
saltRounds가 높을수록 더 복잡하고 안전한 해시가 만들어지지만, 계산 속도는 느려진다.
(pwd[0], 10)는 10번 반복해서 암호화하는 해시 복잡도 수준을 의미함.
✅ 3. 로그인 시에는 bcrypt.compare() 사용
bcrypt.compare() 로 비밀번호 비교하는 로직은 login API 파일에 구현해야 한다.
즉, /api/login.js에서 로그인 요청을 처리할 때 평문 비밀번호와 DB에 저장된 해시 비밀번호를 비교해주는 역할을 한다.
user.pwd !== pwd
위처럼, 평문을 비교하던 코드를 아래처럼,bcrypt로 비밀번호를 비교하도록 수정한다.
const isMatch = await bcrypt.compare(pwd, user.pwd);
if (!isMatch) {
return res.status(401).json({ message: "아이디 또는 비밀번호가 틀렸습니다." });
}
이렇게 수정하고 나면, 비번을 평문으로 저장했던 계정들은 비교가 불가해지면서, 로그인이 안된다. 계정삭제 필요 함.
🔐 비밀번호 암호화는 회원가입 API (/api/signup)에서 bcrypt.hash() 사용
🔍 비밀번호 비교는 로그인 API (/api/login)에서 bcrypt.compare() 사용

localStorage 대신 fetch 로 수정하기
계속해서 me.js에서 401에러가 나는바람에 로그인이 안됬다.
(지긋지긋한 에러표시)
GET /api/me 401 in 17769ms

-> GET /api/me 200 in 24ms

🌷중요한점은 credentials: "include"를 꼭 써주어야한다.
TypeError: Cannot read properties of undefined (reading 'parse')
at handler (src\pages\api\me.js:7:20)

약 두세시간동안 헤맨부분. 분명 cookie를 import 했는데도 불구하고, undefined라고 계~속 에러가 나는것이다.. 그래서 import 방식을 처음에는 모두 import 로 했다가, 또 모두 const 로 수정했는데도 같은 에러가 났다.
그래서 마지막으로 import 를 대부분 쓰고, cookie 만 const로 require 를 사용했더니 드디어 정상으로 작동했다.
import prisma from "../../../lib/prisma";
const cookie = require("cookie"); // ← 여기만 require로 변경
import jwt from "jsonwebtoken";
// import cookie from "cookie";
Next.js의 API Routes (pages/api/…)는 Node.js 환경이기 때문에 cookie 패키지는 CommonJS 방식으로 require() 해야 정상 작동하는 경우가 있다.
import cookie from "cookie";
이 코드는 ES Modules 방식이다. 그러나 cookie 패키지는 기본적으로 CommonJS (CJS) 모듈 형식으로 배포되고 있어, 일부 환경에서는 import로 제대로 불러와지지 않는다.
그 결과, cookie 자체가 undefined로 로드되어 .parse를 호출할 수 없다는 에러가 발생했던것.
아래처럼 CommonJS 방식으로 불러오면 문제없이 동작한다:
const cookie = require("cookie");
⚠️ 왜 일부 import는 되는데 cookie는 안될까?
Next.js는 ES Module과 CommonJS를 혼용해서 지원하지만,
특정 라이브러리(특히 오래된 CommonJS 기반 라이브러리)는 import 방식에서 default export가 undefined로 로드될 수 있다.
예: cookie, jsonwebtoken, bcrypt 등
👉 이럴 땐 안전하게 require()를 쓰는 게 확실하다.
// useEffect(() => {
// const user = JSON.parse(localStorage.getItem("currentUser"));
// setCurrentUser(user);
// // 스토리지 변경 감지
// const handleStorageChange = () => {
// const updatedUser = JSON.parse(localStorage.getItem("currentUser"));
// setCurrentUser(updatedUser);
// };
// window.addEventListener("storage", handleStorageChange);
// return () => window.removeEventListener("storage", handleStorageChange);
// }, []);
useEffect(() => {
const fetchUser = async () => {
try {
const res = await fetch("/api/me", { credentials: "include" });
if (!res.ok) throw new Error("로그인 필요");
const user = await res.json();
setCurrentUser(user);
} catch (error) {
console.error("유저 정보 불러오기 실패", error);
// setCurrentUser(null); // 로그인 안 한 상태
}
};
fetchUser();
}, []);
// const handleLogout = () => {
// localStorage.removeItem("currentUser");
// setCurrentUser(null);
// alert("로그아웃되었습니다.");
// };
const handleLogout = async () => {
try {
await fetch("/api/logout", { method: "POST" });
setCurrentUser(null);
alert("로그아웃되었습니다.");
} catch (err) {
alert("로그아웃 실패");
}
};