Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/pages/Bestseller.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ const Bestseller = () => {
src={book.cover}
alt={book.title}
className="book-cover"
onClick={() => navigate(`/book/${encodeURIComponent(book.title)}`)}
onClick={() => navigate(`/book/${encodeURIComponent(book.title.replace(/[^a-zA-Z0-9 ]/g, ""))}`)}
style={{ cursor: "pointer" }}
/>
<p
className="book-title"
onClick={() => navigate(`/book/${encodeURIComponent(book.title)}`)}
onClick={() => navigate(`/book/${encodeURIComponent(book.title.replace(/[^a-zA-Z0-9 ]/g, ""))}`)}
style={{ cursor: "pointer", fontSize: "15px" }}
>
{book.title.split("-")[0].trim()}
Expand Down
2 changes: 1 addition & 1 deletion corner4/client/src/pages/BookDetail.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const BookDetail = () => {

{/* 도서 상세 정보 */}
<div className="book-detail-container">
<img src={book.cover} alt={book.title} className="book-cover" />
<img src={book.cover && book.cover.startsWith('http') ? book.cover : ''} alt={book.title} className="book-cover" />
<div className="book-info">
<h2>{book.title}</h2>
<p><strong>저자:</strong> {book.author}</p>
Expand Down
8 changes: 6 additions & 2 deletions corner4/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const dotenv = require('dotenv');
const path = require('path');
const nunjucks = require('nunjucks');
const passport = require('passport');
const helmet = require('helmet');
const csurf = require('csurf');

dotenv.config();
const indexRouter = require('./routes');
Expand Down Expand Up @@ -46,6 +48,7 @@ sequelize.sync({ force: false })
});

//미들웨어 설정
app.use(helmet());
app.use(morgan('dev'));
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.json());
Expand All @@ -57,10 +60,11 @@ app.use(session({
secret: process.env.COOKIE_SECRET,
cookie: {
httpOnly: true,
secure: false,
secure: true, // Set secure to true
},
name: 'session-cookie',
}));
app.use(csurf()); // Enable CSRF protection

app.use(passport.initialize());
app.use(passport.session());
Expand All @@ -79,7 +83,7 @@ app.use('/api/bookRecommendation', bookRecommendationRouter);
const fs = require('fs');

app.get('/:page', (req, res) => {
const page = req.params.page;
const page = path.basename(req.params.page); // Sanitize input
const filePath = path.join(__dirname, 'views', `${page}.html`);

fs.access(filePath, fs.constants.F_OK, (err) => {
Expand Down
11 changes: 9 additions & 2 deletions corner4/server/controllers/page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
const { User, Post, Hashtag } = require('../models');
const rateLimit = require('express-rate-limit');

// Apply rate limiting to expensive operations
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});

exports.renderProfile = (req, res) => {
res.render('profile', { title: '나의 서랍' });
Expand All @@ -16,7 +23,7 @@ exports.renderProfile = (req, res) => {
});
};

exports.renderHashtag = async (req, res, next) => {
exports.renderHashtag = [limiter, async (req, res, next) => {
const query = req.query.hashtag;
if (!query) {
return res.redirect('/');
Expand All @@ -36,4 +43,4 @@ exports.renderHashtag = async (req, res, next) => {
console.error(error);
return next(error);
}
};
}];
9 changes: 8 additions & 1 deletion corner4/server/routes/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
const express = require('express');
const path = require('path');
const rateLimit = require('express-rate-limit');

const router = express.Router();

// Define rate limiting middleware
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100 // limit each IP to 100 requests per windowMs
});

// GET / -> index.html 파일 제공
router.get('/', (req, res) => {
router.get('/', limiter, (req, res) => {
res.sendFile(path.join(__dirname, '../public/index.html'));
});

Expand Down
1 change: 1 addition & 0 deletions corner4/server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const express = require('express');
const app = express();
const test = require('./Router/test');

app.disable('x-powered-by');
app.use('/api', test);

const port = 3002; //node 서버가 사용할 포트 번호, 리액트의 포트번호(3000)와 충돌하지 않게 다른 번호로 할당
Expand Down
22 changes: 14 additions & 8 deletions server/public/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,32 @@ async function getWeather() {

// 에러 처리
if (data.error) {
document.getElementById('weather-result').innerHTML = `<p>${data.error}</p>`;
document.getElementById('weather-result').textContent = data.error;
return;
}

// 🌤 날씨 정보 표시
document.getElementById('weather-result').innerHTML = `
<h2>${data.weather.name} (${data.weather.main.temp}°C)</h2>
<p>날씨: ${data.weather.weather[0].description}</p>
<h2>${sanitizeHTML(data.weather.name)} (${sanitizeHTML(data.weather.main.temp)}°C)</h2>
<p>날씨: ${sanitizeHTML(data.weather.weather[0].description)}</p>
`;

// 📚 책 추천 정보 표시
const book = data.book_recommendation;
document.getElementById('book-recommendation').innerHTML = `
<h3>📚 추천 도서: ${book.title}</h3>
<p>저자: ${book.author}</p>
<p>설명: ${book.description}</p>
<p><strong>추천 이유:</strong> ${book.reason}</p>
<h3>📚 추천 도서: ${sanitizeHTML(book.title)}</h3>
<p>저자: ${sanitizeHTML(book.author)}</p>
<p>설명: ${sanitizeHTML(book.description)}</p>
<p><strong>추천 이유:</strong> ${sanitizeHTML(book.reason)}</p>
`;
} catch (error) {
console.error("❌ API 요청 실패:", error);
document.getElementById('weather-result').innerHTML = "<p>날씨 정보를 가져오는데 실패했습니다.</p>";
document.getElementById('weather-result').textContent = "날씨 정보를 가져오는데 실패했습니다.";
}
}

function sanitizeHTML(str) {
const temp = document.createElement('div');
temp.textContent = str;
return temp.innerHTML;
}
18 changes: 16 additions & 2 deletions server/routes/auth.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const express = require('express');
const passport = require('passport');
const rateLimit = require('express-rate-limit'); // Add rate limiting

const { isLoggedIn, isNotLoggedIn } = require('../middlewares');
const { join, login, logout } = require('../controllers/auth');
Expand All @@ -8,6 +9,19 @@ const User = require('../models/user'); // 모델 경로에 맞게 수정

const router = express.Router();

// Rate limiter for login and delete operations
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per windowMs
message: "Too many login attempts from this IP, please try again after 15 minutes"
});

const deleteLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // Limit each IP to 5 requests per windowMs
message: "Too many delete requests from this IP, please try again after 15 minutes"
});

// POST /auth/join - 회원가입 처리
// router.post('/join', isNotLoggedIn, join);
router.get('/join', isNotLoggedIn, (req, res) => {
Expand All @@ -25,7 +39,7 @@ router.get('/login', isNotLoggedIn, (req, res) => {
});


router.post('/login', isNotLoggedIn, passport.authenticate('local', {
router.post('/login', isNotLoggedIn, loginLimiter, passport.authenticate('local', {
failureRedirect: '/auth/login', // 로그인 실패 시 다시 로그인 페이지로 리다이렉트
failureFlash: true,
}), (req, res) => {
Expand Down Expand Up @@ -76,7 +90,7 @@ router.get('/logout', isLoggedIn, logout);
// }
// });

router.post("/delete", isLoggedIn, async (req, res, next) => {
router.post("/delete", isLoggedIn, deleteLimiter, async (req, res, next) => {
try {
await User.destroy({
where: { id: req.user.id }
Expand Down
13 changes: 12 additions & 1 deletion server/routes/posts.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,25 @@ const express = require("express");
const router = express.Router();
const { Post, User, Like } = require("../models"); // ✅ Like 모델 추가
const { Op } = require("sequelize");
const rateLimit = require("express-rate-limit"); // Import rate limiting middleware

// Apply rate limiting to all routes
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // Limit each IP to 100 requests per windowMs
message: "Too many requests from this IP, please try again later."
});

router.use(limiter); // Apply the rate limiter to all routes

// 📌 [1] 게시글 목록 조회 + 검색 기능 (GET /posts)
router.get("/", async (req, res) => {
try {
const { search } = req.query;
let whereCondition = {};

if (search) {
// Ensure search is a string
if (search && typeof search === 'string') {
whereCondition = {
[Op.or]: [
{ bookTitle: { [Op.like]: `%${search}%` } }, // 책 제목 검색
Expand Down
20 changes: 14 additions & 6 deletions server/routes/user.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
const express = require('express');
const rateLimit = require('express-rate-limit'); // Import the rate limiting middleware

const { isLoggedIn } = require('../middlewares');
const { follow } = require('../controllers/user');
const { User } = require('../models');

const router = express.Router();

router.post('/:id/follow', isLoggedIn, async (req, res, next) => {
// Define rate limiting rules
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests, please try again later.'
});

router.post('/:id/follow', isLoggedIn, limiter, async (req, res, next) => { // Apply rate limiter
try {
console.log("✅ 팔로우 요청 받음:", req.params.id); // 요청 ID 확인
console.log("✅ 현재 로그인한 사용자:", req.user); // 로그인 상태 확인
Expand Down Expand Up @@ -36,7 +44,7 @@ router.post('/:id/follow', isLoggedIn, async (req, res, next) => {


// 팔로우 취소 처리
router.post('/:id/unfollow', isLoggedIn, async (req, res, next) => {
router.post('/:id/unfollow', isLoggedIn, limiter, async (req, res, next) => { // Apply rate limiter
try {
const { id } = req.params;
const me = await User.findByPk(req.user.id);
Expand All @@ -60,7 +68,7 @@ router.post('/:id/unfollow', isLoggedIn, async (req, res, next) => {
});

// 모든 사용자 목록 조회 (로그인한 경우에만 접근 가능)
router.get('/list', isLoggedIn, async (req, res, next) => {
router.get('/list', isLoggedIn, limiter, async (req, res, next) => { // Apply rate limiter
try {
const users = await User.findAll();
res.render('layout', {
Expand All @@ -77,7 +85,7 @@ router.get('/list', isLoggedIn, async (req, res, next) => {


// 사용자 목록 조회 API 수정
router.get('/users', isLoggedIn, async (req, res) => {
router.get('/users', isLoggedIn, limiter, async (req, res) => { // Apply rate limiter
try {
const users = await User.findAll();
const followingIds = req.user.Followings.map(f => f.id); // 로그인한 사용자의 팔로잉 ID 목록
Expand All @@ -100,7 +108,7 @@ router.get('/users', isLoggedIn, async (req, res) => {
//닉네임 변경 api
//수정수정

router.post('/update-nickname', isLoggedIn, async (req, res) => {
router.post('/update-nickname', isLoggedIn, limiter, async (req, res) => { // Apply rate limiter
try {
const { nickname } = req.body;
const userId = req.user.id; // 로그인된 사용자 ID 사용
Expand Down Expand Up @@ -159,7 +167,7 @@ router.post('/update-nickname', isLoggedIn, async (req, res) => {



router.get('/user_profile/:username', async (req, res, next) => {
router.get('/user_profile/:username', limiter, async (req, res, next) => { // Apply rate limiter
try {
console.log("🔹 현재 로그인한 사용자:", req.user); // 로그 추가
console.log("🔹 요청된 사용자 프로필:", req.params.username);
Expand Down