Skip to content

Commit

Permalink
Merge pull request #109 from Dev-FE-1/feat/is-valid-token-100
Browse files Browse the repository at this point in the history
[Feature] 토큰 유효성 검사
  • Loading branch information
devdeun authored Jul 6, 2024
2 parents 1f3d2aa + bdf0eba commit a536a23
Show file tree
Hide file tree
Showing 9 changed files with 128 additions and 158 deletions.
42 changes: 19 additions & 23 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,27 +22,21 @@ const generateToken = (user) => {
return jwt.sign(payload, SECRET_KEY, { expiresIn: '1h' });
};

const authenticateToken = (req, res, next) => {
app.post('/api/verify-token', async (req, res) => {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(' ')[1];

if (!token) {
res.sendStatus(401);
return;
return res.json({ valid: false });
}

jwt.verify(token, SECRET_KEY, (err, user) => {
if (err) {
res.sendStatus(403);
return;
}

/* eslint-disable no-param-reassign */
req.user = user;
/* eslint-enable no-param-reassign */
next();
});
};
try {
await jwt.verify(token, SECRET_KEY);
return res.json({ valid: true });
} catch (err) {
return res.json({ valid: false });
}
});

app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
Expand Down Expand Up @@ -79,14 +73,16 @@ app.post('/api/login', async (req, res) => {
}

// 비밀번호가 틀린 경우: STATUS 401
return res.status(401).json({ status: 'Error', error: '로그인 실패!' });
return res.status(401).json({
status: 'Error',
error:
'로그인에 실패하였습니다. 이메일과 비밀번호를 다시 확인해 주시기 바랍니다!',
});
});

// 반환 값을 명시적으로 추가하여 콜백 함수가 종료되었음을 알립니다.
return null;
});

app.get('/api/members/:page', authenticateToken, (req, res) => {
app.get('/api/members/:page', (req, res) => {
let { page } = req.params;
const { max = 10 } = req.query;
const limit = parseInt(max, 10);
Expand Down Expand Up @@ -127,7 +123,7 @@ app.get('/api/members/:page', authenticateToken, (req, res) => {
});
});

app.get('/api/member/:employeeNumber', authenticateToken, (req, res) => {
app.get('/api/member/:employeeNumber', (req, res) => {
const { employeeNumber } = req.params;
const { isAdmin } = req.query;

Expand Down Expand Up @@ -239,7 +235,7 @@ app.get('/api/user', (req, res) => {
});

// eslint-disable-next-line consistent-return
app.get('/api/attendance/:page', authenticateToken, (req, res) => {
app.get('/api/attendance/:page', (req, res) => {
let { page } = req.params;
const { employeeNumber, max = 10 } = req.query;

Expand Down Expand Up @@ -284,7 +280,7 @@ app.get('/api/attendance/:page', authenticateToken, (req, res) => {
});

// eslint-disable-next-line consistent-return
app.get('/api/vacationRequests/:page', authenticateToken, (req, res) => {
app.get('/api/vacationRequests/:page', (req, res) => {
let { page } = req.params;
const { employeeNumber, max = 10 } = req.query;

Expand Down Expand Up @@ -347,7 +343,7 @@ app.get('/api/announcements', (req, res) => {
});
});

app.get('/api/announcements/:id', authenticateToken, (req, res) => {
app.get('/api/announcements/:id', (req, res) => {
const { id } = req.params;
const sql = 'SELECT * FROM Announcements WHERE announcementId = ?';

Expand Down
44 changes: 0 additions & 44 deletions src/components/API/ApiClient.js

This file was deleted.

116 changes: 70 additions & 46 deletions src/components/API/AuthService.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,78 @@
import ApiClient from './ApiClient.js';
const login = async (email, password, showError) => {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email, password }),
});

export default class AuthService {
constructor() {
this.apiClient = new ApiClient(this.getToken);
}
if (!response.ok) {
const errorData = await response.json();
if (response.status === 400) {
showError(`잘못된 요청입니다. ${errorData.error}`);
} else if (response.status === 401) {
showError(
'로그인에 실패하였습니다. 이메일과 비밀번호를 다시 확인해 주시기 바랍니다',
);
} else {
showError(
`오류: ${errorData.message || '알 수 없는 오류가 발생했습니다.'}`,
);
}
return;
}

login(email, password, showError) {
return this.apiClient
.post('/api/login', {
email,
password,
})
.then((response) => {
if (response.data.status === 'OK') {
localStorage.setItem('token', response.data.token);
window.location.href = '/';
} else {
showError(response.data.error);
}
})
.catch((error) => {
if (error.response && error.response.status) {
if (error.response.status === 400) {
showError(`잘못된 요청입니다. ${error.response.data.error}`);
} else if (error.response.status === 401) {
showError(
'로그인에 실패하였습니다. 이메일과 비밀번호를 다시 확인해 주시기 바랍니다',
);
} else {
showError(`오류: ${error.message}`);
}
} else {
showError('알 수 없는 오류가 발생했습니다.');
}
});
}
const data = await response.json();

// eslint-disable-next-line class-methods-use-this
logout() {
localStorage.removeItem('token');
window.location.href = '/signin';
if (data.status === 'OK') {
localStorage.setItem('token', data.token);
window.location.href = '/';
} else {
showError(
`로그인에 실패하였습니다: ${data.message || '알 수 없는 오류가 발생했습니다.'}`,
);
}
} catch (error) {
showError('알 수 없는 오류가 발생했습니다.');
}
};

// eslint-disable-next-line class-methods-use-this
isLoggedIn() {
return localStorage.getItem('token') !== null;
const logout = () => {
localStorage.removeItem('token');
window.location.href = '/signin';
};

const isLoggedIn = async () => {
const token = localStorage.getItem('token');
if (!token) {
return false;
}

// eslint-disable-next-line class-methods-use-this
getToken() {
return localStorage.getItem('token');
try {
const response = await fetch('/api/verify-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
},
});

if (!response.ok) {
throw new Error('Token is not valid');
}

const data = await response.json();
return data.valid;
} catch (error) {
console.error('Token verification failed:', error);
return false;
}
}
};

const getToken = () => {
return localStorage.getItem('token');
};

export { login, logout, isLoggedIn, getToken };
32 changes: 15 additions & 17 deletions src/components/API/MemberService.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import ApiClient from './ApiClient.js';
const loadPage = async (page, max) => {
try {
const response = await fetch(`/api/members/${page}?max=${max}`, {
method: 'GET',
});

export default class MemberService {
constructor() {
this.apiClient = new ApiClient(this.getToken);
}
if (!response.ok) {
throw new Error(`Failed to load data: ${response.statusText}`);
}

loadPage(page, max) {
return this.apiClient
.get(`/api/members/${page}?max=${max}`)
.then((response) => response.data.data)
.catch((error) => {
throw error;
});
const data = await response.json();
return data.data;
} catch (error) {
console.error('Error loading page:', error);
throw error;
}
};

// eslint-disable-next-line class-methods-use-this
getToken() {
return localStorage.getItem('token');
}
}
export { loadPage };
5 changes: 2 additions & 3 deletions src/components/Header/Header.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import AuthService from '../API/AuthService.js';
import { logout } from '../API/AuthService.js';
import Button from '../Button/Button.js';
import './Header.css';

Expand All @@ -12,7 +12,6 @@ export default class Header {
content: '로그아웃',
size: 'small',
});
this.handleLogout = new AuthService().logout;
this.render();
}

Expand All @@ -33,6 +32,6 @@ export default class Header {
`;
document
.querySelector('.logout-btn-wrapper button')
.addEventListener('click', this.handleLogout);
.addEventListener('click', logout);
}
}
5 changes: 2 additions & 3 deletions src/components/SignInForm/SignInForm.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Input from '../Input/Input.js';
import Button from '../Button/Button.js';
import './SignInForm.css';
import AuthService from '../API/AuthService.js';
import { login } from '../API/AuthService.js';

export default class SignInForm {
constructor() {
Expand All @@ -23,7 +23,6 @@ export default class SignInForm {
content: '로그인',
disabled: true,
});
this.authService = new AuthService();
this.isValidEmail = false;
this.isValidPassword = false;
}
Expand All @@ -44,7 +43,7 @@ export default class SignInForm {
'비밀번호는 8자 이상, 30자 이하로 작성해 주시기 바랍니다.',
);
} else {
this.authService.login(this.email, this.password, this.showErrorMessage);
login(this.email, this.password, this.showErrorMessage);
}
document.querySelector('.button.btn-primary').removeAttribute('disabled');
};
Expand Down
18 changes: 9 additions & 9 deletions src/pages/Members/Members.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import './Members.css';
import Icon from '../../components/Icon/Icon.js';
import { magnifyingGlass } from '../../utils/icons.js';
import Pagination from '../../components/Pagination/Pagination.js';
import MemberService from '../../components/API/MemberService.js';
import AuthService from '../../components/API/AuthService.js';
import { loadPage } from '../../components/API/MemberService.js';
import { isLoggedIn } from '../../components/API/AuthService.js';

export default class MembersPage extends Container {
constructor() {
Expand All @@ -23,18 +23,19 @@ export default class MembersPage extends Container {
this.input = new Input({ placeholder: '이름을 입력하세요' });
this.currentPage = 1;
this.maxProfile = 7;
this.memberService = new MemberService();
this.contents = [];
this.auth = new AuthService();
this.pagination = new Pagination({
currentPage: this.currentPage,
maxPage: 8,
onPageChange: this.handlePageChange,
});

if (this.auth.isLoggedIn()) {
this.loadData(this.currentPage, this.maxProfile);
}
// 비동기 로그인 체크
isLoggedIn().then((loggedIn) => {
if (loggedIn) {
this.loadData(this.currentPage, this.maxProfile);
}
});
}

handlePageChange = (newPage) => {
Expand All @@ -43,8 +44,7 @@ export default class MembersPage extends Container {
};

loadData(page, max) {
this.memberService
.loadPage(page, max)
loadPage(page, max)
.then((data) => {
this.contents = data;
this.renderTable();
Expand Down
Loading

0 comments on commit a536a23

Please sign in to comment.