Skip to content
Snippets Groups Projects
Commit 5f05e07c authored by DongJae Oh's avatar DongJae Oh
Browse files

Merge branch 'anak' into 'main'

refactor(userController, jwtController, db config) : fix logical errors in jwt...

See merge request !2
parents 22d39eb4 bb097283
No related branches found
No related tags found
1 merge request!2refactor(userController, jwtController, db config) : fix logical errors in jwt...
ACCESS_TOKEN_PRIVATE_KEY
REFRESH_TOKEN_PRIVTATE_KEY
MONGODB_URL
\ No newline at end of file
# .git ignore
/node_modules
.env
\ No newline at end of file
const express = require("express");
require('dotenv').config();
const app = express();
const port = 3000;
const port = 8080;
// const db = require("./db");
const routes = require("./src/route");
const authRouter = require("./src/modules/auth/auth.routes");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/api", routes);
app.use("/api/auth", authRouter);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
......
This diff is collapsed.
......@@ -7,7 +7,11 @@
"start": "node app.js"
},
"dependencies": {
"dotenv": "^16.3.1",
"express": "^4.17.1",
"jsonwebtoken": "^9.0.2",
"mongodb": "^6.2.0",
"mongoose": "^8.0.0",
"mysql2": "^3.6.1"
},
"author": "",
......
......@@ -2,10 +2,10 @@ const mysql = require("mysql2");
const connection = mysql.createConnection({
host: "127.0.0.1",
port: 4040,
port: 3000,
user: "root",
password: "1234",
database: "cakaotalk",
database: "cakaatalk",
});
connection.connect((err) => {
......
const jwt = require('jsonwebtoken')
const AuthToken = require('../../modules/auth/AuthToekn.js')
const jwtService = require('./jwt.service.js')
const ACCESS_SECRET_KEY = process.env.ACCESS_TOKEN_PRIVATE_KEY;
const REFRESH_SECRET_KEY = process.env.REFRESH_TOKEN_PRIVTATE_KEY;
exports.generateAccessToken = (email) => {
return jwt.sign(
payload = {
type: 'JWT',
time: Date(),
email: email
},
secret = ACCESS_SECRET_KEY,
options = {
expiresIn: '15m'
}
);
}
exports.generateRefreshToken = (email) => {
return jwt.sign(
payload = {
type: 'JWT',
time: Date(),
email: email
},
secret = REFRESH_SECRET_KEY,
options = {
expiresIn: '30d'
}
)
}
exports.generateTokens = async (user) => {
try {
const accessToken = this.generateAccessToken(user.email);
const refreshToken = this.generateRefreshToken(user.email);
// DB에서 Token 있는지 검사
const existRefreshToken = await jwtService.checkRefreshToken(refreshToken);
if (existRefreshToken) await jwtService.deleteRefreshToken(existRefreshToken);
return Promise.resolve({ accessToken, refreshToken });
} catch (err) {
return Promise.reject(err);
}
};
exports.validateToken = (accessToken, refreshToken) => {
}
function verifyAccessToken(accessToken) {
return new Promise((resolve, reject) => {
try {
resolve(jwt.verify(accessToken, ACCESS_SECRET_KEY));
}
catch (error) {
// AccessToken이 만료되었을 경우, RefreshToken을 DB에서 조회한다.
if (error.name === 'TokenExpiredError') {
console.log('Access Token이 만료되었습니다.');
if (refreshToken) {
// RefreshToken이 있을 경우 검증한다
jwtService.verifyRefreshToken(refreshToken)
// 검증이 유효할 경우 AccessToken을 재발급하고 정상 처리한다
.then(() => {
const newAccessToken = this.generateAccessToken(jwt.decode(accessToken).email);
resolve(newAccessToken);
})
// 검증이 유효하지 않을 경우 RefreshToken을 삭제하고 사용자가 로그인하도록 유도한다
.catch(() => {
reject('RefreshToken이 유효하지 않습니다. 다시 로그인해주세요.');
});
}
}
if (error.name === 'JsonWebTokenError') {
reject('유효하지 않은 Access Token입니다.');
}
}
});
}
exports.verifyRefreshToken = (refreshToken) => {
return new Promise((resolve, reject) => {
try {
// DB에 RefreshToken이 있나 체크
jwtService.checkRefreshToken(refreshToken);
resolve(jwt.verify(refreshToken, REFRESH_SECRET_KEY));
}
catch (error) {
if (error.name === 'TokenExpiredError') {
jwtService.deleteRefreshToken(refreshToken);
reject('Refresh Token이 만료되었습니다.');
}
if (error.name === 'JsonWebTokenError') {
jwtService.deleteRefreshToken(refreshToken);
reject('유효하지 않은 Refresh Token입니다.');
}
}
});
};
\ No newline at end of file
const mysql = require("../../common/database");
// RefreshToken 검사 함수
exports.checkRefreshToken = (refreshToken) => {
return new Promise((resolve, reject) => {
mysql.query('SELECT * FROM AUTH WHERE refresh_token = ?', [refreshToken], (err, results) => {
if (err) {
reject(err);
} else {
if (results.length > 0) {
resolve(refreshToken); // Token이 존재함
} else {
resolve(false); // Token이 존재하지 않음
}
}
});
});
}
// RefreshToken 삭제 함수
exports.deleteRefreshToken = (refreshToken) => {
return new Promise((resolve, reject) => {
connection.query('DELETE FROM AUTH WHERE refresh_token = ?', [refreshToken], (err, results) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
\ No newline at end of file
const jwtController = require('../../common/jwt/jwt.controller');
const userService = require('../user/user.service');
exports.signUpAndGiveToken = async (req, res) => {
try {
const { email, password } = req.body;
}
}
exports.loginAndGiveToken = async (req, res) => {
try {
const email = req.query.email;
const result = await userService.findUserByEmail(email);
const accessToken = jwtController.generateAccessToken(result[0].email);
res.status(200).json({ accessToken: accessToken });
} catch (error) {
console.error('Error occurred while finding user by email:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
// TODO: 다른 사람이 로그인 했을 때 강제 로그아웃
exports.forceLogout = () => { }
exports.logoutAndDestroyToken = async (req, res) => {
const email = req.query.email;
const result = await userService.findUserByEmail(email);
jwtController.destroyAccessToken(req, res);
res.status(200).json({ message: "Successfuly logout" });
}
// TODO: next 넣어서 미들웨어로 만들기
exports.checkUserSession = async (req, res) => {
try {
const accessToken = req.headers.authorization;
const refreshToken = extractRefreshTokenFromCookie(req);
const result = await jwtController.validateToken(accessToken, refreshToken);
console.log('Successfuly Authenticateed');
res.status(200).json({ email: result.email });
} catch (error) {
res.status(500).json({ error: error })
}
}
// Http Only Cookie에서 RefreshToken을 가져오는 함수
function extractRefreshTokenFromCookie(req) {
// TODO: Http Only Cookie에서 RefreshToken을 가져오는 로직을 작성해주세요.
// 쿠키에서 RefreshToken을 추출하고 반환해야 합니다.
return req.cookies.refreshToken;
}
const express = require('express');
const app = express();
const authController = require("./auth.controller");
const authRouter = express.Router();
authRouter.get('/login', authController.loginAndGiveToken);
authRouter.post('/logout', authController.logoutAndDestroyToken);
authRouter.get('/session', authController.checkUserSession);
module.exports = authRouter;
\ No newline at end of file
# 기능 요구사항
## 사용자의 Authentication
### 처음 로그인
- <b> 사용자의 로그인 정보가 있는지 검증 </b>
- 로그인 정보가 없을 시, 이메일 존재하지 않는다는 응답
- 로그인 정보가 있을 시, RefreshToken 생성, AccessToken 생성, RefreshToken은 DB와 HttpOnly 쿠키에 저장 AccessToken은 응답으로 전달 (프론트에서 헤더의 Authorization에 저장 필요)
### 이후 로그인 or 검증
- Access Token을 Request의 Authorization Header 에서 추출해서 검증
- 유효할 시 정상 응답
- 무효할 시 Refresh Token 검사
- Refresh Token 유효할 시, Access Token 재발급, 정상 응답 처리
- Refresh Token이 무효할 시 로그인 필요하다는 응답
- Refresh Token 생성
### JWT 토큰 파괴
- 사용자가 로그아웃 또는 Token이 존재하는 상태에서 로그인 시 기존 토큰 파괴
### 추가 기능
- 특정 URL 요청에 api/auth/session 을 자동으로 실행하도록 미들웨어 만들기
- 이미 Token을 발행한 유저가 로그인을 시도할 시, 기존 토큰 파괴 & 강제 로그아웃 => <b> DB에 Token을 저장해햐하는 이유 </b>
### JWT 를 사용하는 이유
<b>Session</b>
- Session을 사용하여 처리할 경우, 사용자가 앱을 이용할 때마다 서버 메모리에 접근해야한다
- Session은 서버 메모리에 저장되기 때문에, 분산 서비스를 구축할 때 세션을 공유할 수 있도록 DB 서버를 만들어야한다.
<b>JWT</b>
- JWT는 토큰 자체에 정보가 저장되어있기 때문에 서버 메모리 접근이 필요없다. (토큰을 회수할 때 제외)
### DB 결과 처리
- SEELCT 결과 값이 null : length == 0
- DELETE, UPDATE, INSERT 결과 : result.affectedRows
### JWT 를 DB에 저장해야하는 이유
- 다른 브라우저, 다른 환경에선 토큰이 없어서 로그인 시도를 할 것이다. 로그인에 성공하면 토큰을 주고 기존에 발행했던 토큰을 삭제해야하는데, 삭제하기 위해 DB에 저장해두고 관리할 필요가 있다.
- 만약 토큰을 삭제하지 않는다면, 2명의 유저가 하나의 아이디로 동시에 로그인할 수 있는 일이 발생한다.
const signUpBodyValidation = (email, body) => {
return new Promise((resolve, reject) => {
}
)
};
\ No newline at end of file
......@@ -38,6 +38,7 @@ exports.addFriend = (req, res) => {
const query = "INSERT INTO FRIENDS (user_id, friend_id) VALUES (?, ?)";
db.query(query, [userId, friendId], (error) => {
if (error) {
console.log(results);
res.status(500).json({ error: error.message });
return;
}
......@@ -45,6 +46,33 @@ exports.addFriend = (req, res) => {
});
};
exports.findUserByEmail = (req, res) => {
const email = req.query.email;
const query = `SELECT * FROM USER WHERE email = ?`;
db.query(query, [email], (error, results) => {
if (error) {
res.status(500).json({ error: error.message });
return;
}
if (results.length != 0) {
res.json({ data: results });
} else {
res.status(404).send({ error: 'User not found' });
}
})
}
exports.findAllUser = (req, res) => {
const query = `SELECT * FROM USER`;
db.query(query, [userId], (error, results) => {
if (error) {
res.status(500).json({ error: error.message });
}
console.log(results);
res.json({ data: results });
})
}
exports.updateProfile = (req, res) => {
const userId = req.headers.userid;
const imageUrl = req.body.imageUrl;
......
const db = require("../../common/database");
exports.findUserByEmail = (email) => {
return new Promise((resolve, reject) => {
const query = `SELECT * FROM USER WHERE email = ?`;
db.query(query, [email], (error, rows) => {
if (error) {
return reject(error);
}
if (rows.length > 0) {
return resolve(rows);
} else {
return reject('User not found');
}
});
})
}
// 동재 코드
exports.getFriendsList = (userId) => {
const query = `
SELECT p.image_url, u.user_name, p.comment
......
......@@ -10,5 +10,7 @@ router.post("/updateProfile", userController.updateProfile);
router.get("/searchUser", userController.searchUser);
router.post("/addUser", userController.addUser);
router.post("/deleteUser/:userId", userController.deleteUser);
router.get("/findUser", userController.findUserByEmail);
router.get("/findAll", userController.findAllUser);
module.exports = router;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment