import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; /** * πŸ”΅ Next.js λ‚΄λΆ€ API - 인증 μƒνƒœ 확인 (PHP λ°±μ—”λ“œ X) * * ⚑ 섀계 λͺ©μ : * - μ„±λŠ₯ μ΅œμ ν™”: 맀번 PHP λ°±μ—”λ“œ 호좜 λŒ€μ‹  둜컬 μΏ ν‚€λ§Œ 확인 * - λ°±μ—”λ“œ λΆ€ν•˜ κ°μ†Œ: κ°„λ‹¨ν•œ 인증 확인은 Next.jsμ—μ„œ 처리 * - μ‚¬μš©μž κ²½ν—˜: μ¦‰μ‹œ μ‘λ‹΅μœΌλ‘œ λΉ λ₯Έ νŽ˜μ΄μ§€ μ „ν™˜ * * πŸ“ μ‚¬μš© μœ„μΉ˜: * - LoginPage.tsx: 이미 둜그인된 μ‚¬μš©μžλ₯Ό λŒ€μ‹œλ³΄λ“œλ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈ * - SignupPage.tsx: 이미 둜그인된 μ‚¬μš©μžλ₯Ό λŒ€μ‹œλ³΄λ“œλ‘œ λ¦¬λ‹€μ΄λ ‰νŠΈ * - λ’€λ‘œκ°€κΈ° μ‹œ μΊμ‹œ 문제 λ°©μ§€ * * πŸ”„ λ™μž‘ 방식: * 1. HttpOnly μΏ ν‚€μ—μ„œ access_token, refresh_token 확인 * 2. access_token 있음 β†’ { authenticated: true } μ¦‰μ‹œ 응닡 * 3. refresh_token만 있음 β†’ PHP /api/v1/refresh ν˜ΈμΆœν•˜μ—¬ 토큰 κ°±μ‹  * 4. λ‘˜ λ‹€ μ—†μŒ β†’ { authenticated: false } 응닡 * * ⚠️ 주의: * - 이 APIλŠ” PHP λ°±μ—”λ“œμ— μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€ * - Next.js ν”„λ‘ νŠΈμ—”λ“œ 자체 μœ ν‹Έλ¦¬ν‹° APIμž…λ‹ˆλ‹€ * - μ‹€μ œ 인증 λ‘œμ§μ€ μ—¬μ „νžˆ PHP λ°±μ—”λ“œκ°€ λ‹΄λ‹Ήν•©λ‹ˆλ‹€ */ export async function GET(request: NextRequest) { try { // Get tokens from HttpOnly cookies const accessToken = request.cookies.get('access_token')?.value; const refreshToken = request.cookies.get('refresh_token')?.value; // No tokens at all - not authenticated if (!accessToken && !refreshToken) { return NextResponse.json( { error: 'Not authenticated' }, { status: 401 } ); } // Has access token - authenticated if (accessToken) { return NextResponse.json( { authenticated: true }, { status: 200 } ); } // Only has refresh token - try to refresh if (refreshToken && !accessToken) { console.log('πŸ”„ Access token missing, attempting refresh...'); console.log('πŸ” Refresh token exists:', refreshToken.substring(0, 20) + '...'); console.log('πŸ” Backend URL:', process.env.NEXT_PUBLIC_API_URL); // Attempt token refresh try { const refreshResponse = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/refresh`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-API-KEY': process.env.NEXT_PUBLIC_API_KEY || '', }, body: JSON.stringify({ refresh_token: refreshToken, }), }); console.log('πŸ” Refresh API response status:', refreshResponse.status); if (refreshResponse.ok) { const data = await refreshResponse.json(); // Set new tokens with Safari-compatible configuration // Safari compatibility: Secure only in production (HTTPS) const isProduction = process.env.NODE_ENV === 'production'; const accessTokenCookie = [ `access_token=${data.access_token}`, 'HttpOnly', // βœ… JavaScript cannot access ...(isProduction ? ['Secure'] : []), // βœ… HTTPS only in production (Safari fix) 'SameSite=Lax', // βœ… CSRF protection (Lax for better compatibility) 'Path=/', `Max-Age=${data.expires_in || 7200}`, ].join('; '); const refreshTokenCookie = [ `refresh_token=${data.refresh_token}`, 'HttpOnly', // βœ… JavaScript cannot access ...(isProduction ? ['Secure'] : []), // βœ… HTTPS only in production (Safari fix) 'SameSite=Lax', // βœ… CSRF protection (Lax for better compatibility) 'Path=/', 'Max-Age=604800', // 7 days (longer for refresh token) ].join('; '); console.log('βœ… Token auto-refreshed in auth check'); const response = NextResponse.json( { authenticated: true, refreshed: true }, { status: 200 } ); response.headers.append('Set-Cookie', accessTokenCookie); response.headers.append('Set-Cookie', refreshTokenCookie); return response; } else { const errorData = await refreshResponse.text(); console.error('❌ Refresh API failed:', refreshResponse.status, errorData); } } catch (error) { console.error('❌ Token refresh failed in auth check:', error); } // Refresh failed - not authenticated console.log('⚠️ Returning 401 due to refresh failure'); return NextResponse.json( { error: 'Token refresh failed' }, { status: 401 } ); } // Fallback - not authenticated return NextResponse.json( { error: 'Not authenticated' }, { status: 401 } ); } catch (error) { console.error('Auth check error:', error); return NextResponse.json( { error: 'Internal server error', authenticated: false }, { status: 500 } ); } }