middleware.ts 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import { type NextRequest, NextResponse } from 'next/server';
  2. function isTokenValid(token: string): boolean {
  3. try {
  4. const payload = JSON.parse(atob(token.split('.')[1]));
  5. return payload.exp * 1000 > Date.now();
  6. } catch {
  7. return false;
  8. }
  9. }
  10. export default function middleware(req: NextRequest)
  11. {
  12. // 인증 페이지는 로그인 상태에서도 접근 가능
  13. const skipRoutes = ['/approval', '/verify-email'];
  14. if (skipRoutes.some((path) => req.nextUrl.pathname.startsWith(path))) {
  15. return NextResponse.next();
  16. }
  17. const { pathname } = req.nextUrl;
  18. const accessToken = req.cookies.get('accessToken')?.value;
  19. const refreshToken = req.cookies.get('refreshToken')?.value;
  20. const isLoggedIn = !!accessToken && isTokenValid(accessToken);
  21. const publicRoutes = ['/', '/login', '/register', '/forgot-password', '/reset-password', '/welcome'];
  22. if (publicRoutes.includes(pathname) || pathname.startsWith('/login/google/')) {
  23. if (isLoggedIn && publicRoutes.filter(r => r !== '/').includes(pathname)) {
  24. return NextResponse.redirect(new URL('/', req.url)); // 로그인 상태에서 접근 불가
  25. }
  26. return NextResponse.next();
  27. }
  28. // 로그인이 필요한 경로 (startsWith로 하위 경로까지 포함)
  29. const protectedRoutes = [
  30. '/profile', // 내 정보
  31. '/change-email', // 이메일 변경
  32. '/change-name',// 별명 변경
  33. '/change-password', // 비밀번호 변경
  34. '/change-approve', // 알림 설정
  35. '/change-thumb', // 사진 변경
  36. '/change-summary', // 한마디 변경
  37. '/change-intro', // 자기소개 변경
  38. '/certificate', // 본인 인증
  39. '/my-post', // 작성 게시글
  40. '/my-comment', // 작성 댓글
  41. '/exp-log', // 경험치 내역
  42. '/login-log', // 로그인 기록
  43. '/withdraw', // 회원탈퇴
  44. '/valid-email', // 이메일 변경 인증
  45. '/studio' // 크리에이터 스튜디오
  46. ];
  47. const isProtected = protectedRoutes.some(route => pathname === route || pathname.startsWith(route + '/'));
  48. if (!isProtected) {
  49. return NextResponse.next(); // 404
  50. }
  51. // 보호된 경로에 토큰이 없으면 로그인으로
  52. if (!accessToken || !refreshToken) {
  53. return NextResponse.redirect(new URL('/login', req.url));
  54. }
  55. return NextResponse.next();
  56. }
  57. export const config =
  58. {
  59. matcher: [
  60. '/((?!_next|api|static|public|resources|editor|favicon.ico).*)'
  61. ]
  62. }