🎯 프로젝트 리브랜딩: Kumamoto → Travel Planner v2.0
✨ 주요 변경사항: - 프로젝트 이름: kumamoto-travel-planner → travel-planner - 버전 업그레이드: v1.0.0 → v2.0.0 - 멀티유저 시스템 구현 (JWT 인증) - PostgreSQL 마이그레이션 시스템 추가 - Docker 컨테이너 이름 변경 - UI 브랜딩 업데이트 (Travel Planner) - API 서버 및 인증 시스템 추가 - 여행 공유 기능 구현 - 템플릿 시스템 추가 🔧 기술 스택: - Frontend: React + TypeScript + Vite - Backend: Node.js + Express + JWT - Database: PostgreSQL + 마이그레이션 - Infrastructure: Docker + Docker Compose 🌟 새로운 기능: - 사용자 인증 및 권한 관리 - 다중 여행 계획 관리 - 여행 템플릿 시스템 - 공유 링크 및 댓글 시스템 - 관리자 대시보드
This commit is contained in:
179
server/routes/auth.js
Normal file
179
server/routes/auth.js
Normal file
@@ -0,0 +1,179 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcrypt');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { query } = require('../db');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// JWT 시크릿 (환경변수에서 가져오거나 기본값 사용)
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key-change-this-in-production';
|
||||
|
||||
// 회원가입
|
||||
router.post('/register', async (req, res) => {
|
||||
try {
|
||||
const { email, password, name } = req.body;
|
||||
|
||||
// 이메일 중복 확인
|
||||
const existingUser = await query('SELECT id FROM users WHERE email = $1', [email]);
|
||||
if (existingUser.rows.length > 0) {
|
||||
return res.status(400).json({ success: false, message: '이미 사용 중인 이메일입니다' });
|
||||
}
|
||||
|
||||
// 비밀번호 해시
|
||||
const saltRounds = 10;
|
||||
const passwordHash = await bcrypt.hash(password, saltRounds);
|
||||
|
||||
// 사용자 생성
|
||||
const result = await query(
|
||||
'INSERT INTO users (email, password_hash, name) VALUES ($1, $2, $3) RETURNING id, email, name, role, created_at',
|
||||
[email, passwordHash, name]
|
||||
);
|
||||
|
||||
const user = result.rows[0];
|
||||
|
||||
// JWT 토큰 생성
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email, role: user.role },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
created_at: user.created_at
|
||||
},
|
||||
token,
|
||||
message: '회원가입이 완료되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Registration error:', error);
|
||||
res.status(500).json({ success: false, message: '회원가입 중 오류가 발생했습니다' });
|
||||
}
|
||||
});
|
||||
|
||||
// 로그인
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
// 사용자 조회
|
||||
const result = await query(
|
||||
'SELECT id, email, password_hash, name, role, is_active FROM users WHERE email = $1',
|
||||
[email]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(400).json({ success: false, message: '등록되지 않은 이메일입니다' });
|
||||
}
|
||||
|
||||
const user = result.rows[0];
|
||||
|
||||
if (!user.is_active) {
|
||||
return res.status(400).json({ success: false, message: '비활성화된 계정입니다' });
|
||||
}
|
||||
|
||||
// 비밀번호 확인
|
||||
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({ success: false, message: '비밀번호가 올바르지 않습니다' });
|
||||
}
|
||||
|
||||
// 마지막 로그인 시간 업데이트
|
||||
await query('UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = $1', [user.id]);
|
||||
|
||||
// JWT 토큰 생성
|
||||
const token = jwt.sign(
|
||||
{ userId: user.id, email: user.email, role: user.role },
|
||||
JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
is_active: user.is_active
|
||||
},
|
||||
token,
|
||||
message: '로그인되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({ success: false, message: '로그인 중 오류가 발생했습니다' });
|
||||
}
|
||||
});
|
||||
|
||||
// 토큰 검증
|
||||
router.get('/verify', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
const result = await query(
|
||||
'SELECT id, email, name, role, is_active, last_login FROM users WHERE id = $1',
|
||||
[req.user.userId]
|
||||
);
|
||||
|
||||
if (result.rows.length === 0) {
|
||||
return res.status(404).json({ success: false, message: '사용자를 찾을 수 없습니다' });
|
||||
}
|
||||
|
||||
const user = result.rows[0];
|
||||
|
||||
if (!user.is_active) {
|
||||
return res.status(403).json({ success: false, message: '비활성화된 계정입니다' });
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
is_active: user.is_active,
|
||||
last_login: user.last_login
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Token verification error:', error);
|
||||
res.status(500).json({ success: false, message: '토큰 검증 중 오류가 발생했습니다' });
|
||||
}
|
||||
});
|
||||
|
||||
// JWT 토큰 인증 미들웨어
|
||||
function authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ success: false, message: '액세스 토큰이 필요합니다' });
|
||||
}
|
||||
|
||||
jwt.verify(token, JWT_SECRET, (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({ success: false, message: '유효하지 않은 토큰입니다' });
|
||||
}
|
||||
req.user = user;
|
||||
next();
|
||||
});
|
||||
}
|
||||
|
||||
// 관리자 권한 확인 미들웨어
|
||||
function requireAdmin(req, res, next) {
|
||||
if (req.user.role !== 'admin') {
|
||||
return res.status(403).json({ success: false, message: '관리자 권한이 필요합니다' });
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
router,
|
||||
authenticateToken,
|
||||
requireAdmin
|
||||
};
|
||||
Reference in New Issue
Block a user