Skip to content

Commit

Permalink
fix - 데이터베이스에 저장된 비밀번호가 해싱되지 않은 경우, 비밀번호를 해싱하여 비교 로직 작성, 리프레시 토큰 저장 기…
Browse files Browse the repository at this point in the history
…능 구현 (#18)

Co-authored-by: teauk03 <[email protected]>
  • Loading branch information
teauk03 and teauk03 authored Jun 27, 2024
1 parent d7af084 commit 0089352
Show file tree
Hide file tree
Showing 8 changed files with 726 additions and 238 deletions.
463 changes: 451 additions & 12 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test": "jest"
},
"dependencies": {
"@prisma/client": "^5.15.1",
"@prisma/client": "^5.16.0",
"aws-sdk": "^2.1632.0",
"axios": "^1.7.2",
"bcryptjs": "^2.4.3",
Expand Down
10 changes: 10 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ model users {
password String
reviews book_review[]
}
model refreshToken {
id Int @id @default(autoincrement())
token String
userId Int
user users @relation(fields: [userId], references: [uid])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}



model orders {
oid String @id
Expand Down
48 changes: 27 additions & 21 deletions src/controllers/auth/authControllers.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {authenticateUser, verifyToken, addToBlacklist} from "../../service/auth/authService.js";
import {authenticateUser} from "../../service/auth/authService.js";

// 공통 에러 처리 함수
const handleCommonError = (res, errorCode, errorMessage, description ) => {
Expand All @@ -7,30 +7,36 @@ const handleCommonError = (res, errorCode, errorMessage, description ) => {


// 로그인
/**
* 사용자 로그인 요청을 처리합니다.
*
* @param {object} req - Express 요청 객체
* @param {object} res - Express 응답 객체
*/
export const signIn = async (req, res) => {
const {email, password} = req.body;

const { email, password } = req.body;
try {
// 필수 입력 필드 확인
if (!email || !password) {
return handleCommonError(res, 400, "Bad Request Exception", "Email and password are required");
}

// 사용자 인증, 토큰 생성
const {user, token} = await authenticateUser(email, password);

// 로그인 성공
res.status(200).json({
message: 'Login successful',
user: user,
token: token
});

// 필수 입력 필드 확인
if (!email || !password) {
return handleCommonError(res, 400, 'Bad Request Exception', 'Email and password are required');
}
// 사용자 인증, 토큰 생성
const { user, accessToken } = await authenticateUser(email, password);
// 로그인 성공
console.log(`사용자 ${email}이(가) 로그인하였습니다.`);
res.status(200).json({
message: 'Login successful',
user: user,
accessToken: accessToken,
});
} catch (error) {
console.error(error)
handleCommonError(res, 500, "Unexpected Error", "Failed to login user");
console.error(`사용자 ${email} 로그인 중 에러 발생:`, error.message);
handleCommonError(res, 500, 'Unexpected Error', 'Failed to login user');
}
}
};


// 로그아웃
Expand Down
3 changes: 2 additions & 1 deletion src/routes/auth/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import express from "express";
import {registerUser, signIn, signOut} from "../../controllers/authControllers.js";
import {signIn, signOut} from "../../controllers/auth/authControllers.js";
import {registerUser} from "../../controllers/auth/registerController.js";

const router = express.Router();
router.post("/login", signIn);
Expand Down
149 changes: 68 additions & 81 deletions src/service/auth/authService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,6 @@ import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";
import { validateEmailAndPassword } from "./validation.js";
import prisma from "../../utils/prismaClient.js";
import UserModel from "../../models/UserModel.js";

// JWT 블랙리스트를 메모리에 저장
const blacklist = [];


/**
* 토큰을 JWT_SECRET 을 사용하여 검증한다.
* 토큰이 유효하지 않거나 만료된 경우 에러가 발생한다.
*
* @param {string} token - 검증할 JWT 토큰
* @returns {object} - 디코딩된 JWT 토큰 페이로드
* @throws {JsonWebTokenError} - 토큰이 유효하지 않거나 만료된 경우 발생
*/
export const verifyToken = (token) => {
return jwt.verify(token, process.env.JWT_SECRET);
}


/**
* 토큰을 블랙리스트에 추가하며, 이후 요청시 사용이 거부된다.
* 사용자가 로그아웃할 때 해당 토큰을 더 이상 사용하지 못하도록 하기 위해 사용된다.
*
* @param {string} token - 블랙리스트에 추가할 JWT 토큰
*/
export const addToBlacklist = (token) => {
blacklist.push(token);
}


/**
* 주어진 토큰이 블랙리스트에 있는지 확인한다.
*
* @param {string} token - 확인할 JWT 토큰
* @returns {boolean} - 블랙리스트에 있으면 true, 아니면 false
*/
export const isBlacklisted = (token) => {
return blacklist.includes(token);
}


/**
* 사용자 인증을 처리하고 JWT 토큰을 생성한다.
Expand All @@ -55,49 +15,76 @@ export const authenticateUser = async (email, password) => {
// 이메일과 비밀번호 검증
validateEmailAndPassword(email, password);

// 사용자 찾기
const user = await prisma.users.findUnique({ where: { email } });
if (!user) throw new Error('Invalid email or password');
try {
// 사용자 찾기
console.log("이메일로 사용자 찾기 시도:", email);
const user = await prisma.users.findUnique({ where: { email } });
console.log("찾은 사용자:", user);

// 비밀번호 검증
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) throw new Error('Invalid email or password');

// JWT 생성
/*const token = jwt.sign(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: process.env.TOKEN_EXPIRATION }
);*/

// 사용자 모델 생성
const userModel = new UserModel(
user.id,
user.uid,
user.name,
user.email
)
if (!user) {
throw new Error('Invalid email');
}

// JWT 액세스 토큰 생성
const accessToken = jwt.sign(
{ user: userModel },
process.env.JWT_SECRET,
{ expiresIn: process.env.ACCESS_TOKEN_EXPRIATION }
)
// 데이터베이스에 저장된 비밀번호가 해싱되지 않은 경우, 비밀번호를 해싱하여 비교
let hashedPasswordFromDB = user.password;
console.log("해싱된 비밀번호 (DB에서):", hashedPasswordFromDB);

if (!hashedPasswordFromDB.startsWith("$2a$")) {
// 데이터베이스에 저장된 비밀번호가 해싱되지 않은 경우에만 해싱 처리
console.log("비밀번호가 해싱되지 않았음. 해싱 중...");
hashedPasswordFromDB = await bcrypt.hash(user.password, 10);
console.log("해싱된 비밀번호:", hashedPasswordFromDB);

// 데이터베이스에 해싱된 비밀번호 업데이트
await prisma.users.update({
where: { email },
data: { password: hashedPasswordFromDB },
});
console.log("데이터베이스에 해싱된 비밀번호 업데이트 완료.");
}

// JWT 리프레시 토큰 생성
const refreshToken = jwt.sign(
{ user: userModel },
process.env.JWT_SECRET,
{ expiresIn: process.env.REFRESH_TOKEN_EXPIRATION }
)
// 비밀번호 비교
const isPasswordValid = await bcrypt.compare(password, hashedPasswordFromDB);
console.log("비밀번호 유효성 검사 결과:", isPasswordValid);

// 데이터 베이스에 리프레시 토큰 저장
await prisma.refreshToken.create({
data: {
token: refreshToken,
userId: user.id
if (!isPasswordValid) {
throw new Error('Invalid email or password');
}
})
return { user, token };
}

// JWT 액세스 토큰 생성 (토큰 페이로드에 필요한 정보만 포함)
const accessToken = jwt.sign(
{
id: user.id,
email: user.email,
},
process.env.JWT_SECRET,
{ expiresIn: process.env.ACCESS_TOKEN_EXPIRATION }
);
console.log("액세스 토큰 생성 완료:", accessToken);

// JWT 리프레시 토큰 생성
const refreshToken = jwt.sign(
{
id: user.id,
email: user.email,
},
process.env.JWT_SECRET,
{ expiresIn: process.env.REFRESH_TOKEN_EXPIRATION }
);
console.log("리프레시 토큰 생성 완료:", refreshToken);

// 데이터베이스에 리프레시 토큰 저장
await prisma.refreshToken.create({
data: {
token: refreshToken,
userId: user.id
}
});
console.log("리프레시 토큰 데이터베이스 저장 완료.");

return { user, accessToken, refreshToken };
} catch (error) {
console.error("사용자 인증 중 에러 발생:", error.message);
throw new Error("인증 실패");
}
};
4 changes: 2 additions & 2 deletions src/utils/prismaClient.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {PrismaClient} from "@prisma/client";
import { PrismaClient } from "@prisma/client";

// 프리즈마 객체 생성
const prisma = new PrismaClient();
export default prisma;
export default prisma;
Loading

0 comments on commit 0089352

Please sign in to comment.