From 71459c464328331934b26e71997b8eb01e1ae883 Mon Sep 17 00:00:00 2001 From: logan <2220110150@office.kopo.ac.kr> Date: Sat, 21 Jun 2025 13:33:01 +0900 Subject: [PATCH 1/3] feat: implement JWT authentication system --- src/middleware/auth.middleware.ts | 153 +++++++ src/pages/_app.tsx | 20 +- src/pages/api/auth/[...nextauth].ts | 175 +++++++- src/pages/api/auth/sync-user.ts | 161 +++++++ src/pages/api/project/[projectName]/index.ts | 59 ++- .../api/project/[projectName]/start-flow.ts | 77 +++- src/pages/api/project/create.ts | 268 ++++++----- src/pages/api/project/dashboard_projects.ts | 119 ++++- src/pages/dashboard.tsx | 422 +++++++++++------- src/pages/index.tsx | 323 ++++++++------ src/pages/project/[projectName]/index.tsx | 116 ++++- src/services/auth/user-sync.service.ts | 86 ++++ src/types/next-auth.d.ts | 55 ++- tsconfig.json | 25 +- 14 files changed, 1551 insertions(+), 508 deletions(-) create mode 100644 src/middleware/auth.middleware.ts create mode 100644 src/pages/api/auth/sync-user.ts create mode 100644 src/services/auth/user-sync.service.ts diff --git a/src/middleware/auth.middleware.ts b/src/middleware/auth.middleware.ts new file mode 100644 index 00000000..7fea0b18 --- /dev/null +++ b/src/middleware/auth.middleware.ts @@ -0,0 +1,153 @@ +// src/middleware/auth.middleware.ts - 디버깅 강화 버전 +import { NextApiRequest, NextApiResponse } from 'next'; +import { getToken } from 'next-auth/jwt'; +import prisma from '@/lib/prisma'; + +// 인증된 사용자 정보를 요청에 추가 +declare module 'next' { + interface NextApiRequest { + user?: { + id: number; + email: string; + name: string; + githubId: string; + }; + } +} + +/** + * JWT 토큰을 검증하고 사용자 정보를 요청에 추가하는 미들웨어 + */ +export async function authenticateUser( + req: NextApiRequest, + res: NextApiResponse, + next: () => void | Promise +) { + try { + console.log(`[Auth Middleware] ${req.method} ${req.url} - Starting authentication`); + + // NextAuth JWT 토큰 가져오기 + const token = await getToken({ + req, + secret: process.env.NEXTAUTH_SECRET, + // 디버깅을 위해 다양한 토큰 소스 시도 + secureCookie: process.env.NODE_ENV === 'production', + }); + + console.log('[Auth Middleware] Token:', { + exists: !!token, + email: token?.email, + sub: token?.sub, + iat: token?.iat, + exp: token?.exp, + }); + + if (!token || !token.email) { + console.log('[Auth Middleware] No valid token found'); + return res.status(401).json({ + error: 'Unauthorized', + message: '로그인이 필요합니다.', + debug: { + tokenExists: !!token, + hasEmail: !!token?.email, + cookieHeader: req.headers.cookie ? 'present' : 'missing' + } + }); + } + + // 데이터베이스에서 사용자 조회 + console.log(`[Auth Middleware] Looking up user with email: ${token.email}`); + const user = await prisma.user.findUnique({ + where: { email: token.email }, + select: { + id: true, + email: true, + name: true, + avatarUrl: true, + } + }); + + if (!user) { + console.log(`[Auth Middleware] User not found in DB: ${token.email}`); + return res.status(401).json({ + error: 'User not found', + message: '사용자를 찾을 수 없습니다. 다시 로그인해주세요.', + debug: { + tokenEmail: token.email, + suggestion: 'Try logging out and logging in again' + } + }); + } + + // 요청 객체에 사용자 정보 추가 + req.user = { + id: user.id, + email: user.email, + name: user.name, + githubId: token.sub || '', // GitHub ID + }; + + console.log(`[Auth Middleware] Authentication successful for user ID: ${user.id}`); + await next(); + } catch (error) { + console.error('[Auth Middleware Error]', error); + return res.status(500).json({ + error: 'Internal Server Error', + message: '인증 처리 중 오류가 발생했습니다.', + debug: process.env.NODE_ENV === 'development' ? String(error) : undefined + }); + } +} + +/** + * 사용자 ID를 요청에서 안전하게 가져오는 헬퍼 함수 + */ +export function getUserIdFromRequest(req: NextApiRequest): number | null { + return req.user?.id || null; +} + +/** + * 프로젝트 소유권을 확인하는 미들웨어 + */ +export async function checkProjectOwnership( + req: NextApiRequest, + res: NextApiResponse, + projectId: number +): Promise { + if (!req.user) { + res.status(401).json({ error: 'Unauthorized' }); + return false; + } + + try { + const project = await prisma.project.findFirst({ + where: { + id: projectId, + OR: [ + { ownerId: req.user.id }, + { + contributors: { + some: { + userId: req.user.id + } + } + } + ] + } + }); + + if (!project) { + res.status(403).json({ + error: 'Forbidden', + message: '이 프로젝트에 대한 권한이 없습니다.' + }); + return false; + } + + return true; + } catch (error) { + console.error('[Project Ownership Check Error]', error); + res.status(500).json({ error: 'Internal Server Error' }); + return false; + } +} \ No newline at end of file diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index f870fce8..c319adfd 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,19 +1,23 @@ +// src/pages/_app.tsx - 헤더 중복 제거 버전 import '@/styles/globals.css'; import '../styles/docker-analysis.css'; import type { AppProps } from 'next/app'; -import { SessionProvider } from 'next-auth/react' -import { GithubProvider } from '../context/GithubContext' -import Footer from '../components/Footer' -import Header from '../components/Header' +import { SessionProvider } from 'next-auth/react'; +import { GithubProvider } from '../context/GithubContext'; +import Footer from '../components/Footer'; +// import Header from '../components/Header'; -export default function MyApp({ Component, pageProps: { session, ...pageProps } }: AppProps) { +export default function MyApp({ + Component, + pageProps: { session, ...pageProps } +}: AppProps) { return ( -
+ {/*
제거 - 각 페이지에서 필요에 따라 개별 구현 */}