import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; /** * ๐Ÿ”ต Next.js ๋‚ด๋ถ€ API - ๋กœ๊ทธ์•„์›ƒ ํ”„๋ก์‹œ (PHP ๋ฐฑ์—”๋“œ๋กœ ์ „๋‹ฌ) * * โšก ์„ค๊ณ„ ๋ชฉ์ : * - ์™„์ „ํ•œ ๋กœ๊ทธ์•„์›ƒ: PHP ๋ฐฑ์—”๋“œ ํ† ํฐ ๋ฌดํšจํ™” + ์ฟ ํ‚ค ์‚ญ์ œ * - ๋ณด์•ˆ: HttpOnly ์ฟ ํ‚ค์—์„œ ํ† ํฐ์„ ์ฝ์–ด ๋ฐฑ์—”๋“œ๋กœ ์ „๋‹ฌ * - ์„ธ์…˜ ์ •๋ฆฌ: ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์–‘์ชฝ ๋ชจ๋‘ ์„ธ์…˜ ์ข…๋ฃŒ * * ๐Ÿ”„ ๋™์ž‘ ํ๋ฆ„: * 1. ํด๋ผ์ด์–ธํŠธ โ†’ Next.js /api/auth/logout * 2. Next.js: HttpOnly ์ฟ ํ‚ค์—์„œ access_token ์ฝ๊ธฐ * 3. Next.js โ†’ PHP /api/v1/logout (ํ† ํฐ ๋ฌดํšจํ™” ์š”์ฒญ) * 4. Next.js: access_token, refresh_token ์ฟ ํ‚ค ์‚ญ์ œ * 5. Next.js โ†’ ํด๋ผ์ด์–ธํŠธ (๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต ์‘๋‹ต) * * ๐Ÿ” ๋ณด์•ˆ ํŠน์ง•: * - ๋ฐฑ์—”๋“œ์—์„œ ํ† ํฐ ๋ธ”๋ž™๋ฆฌ์ŠคํŠธ ์ฒ˜๋ฆฌ (์žฌ์‚ฌ์šฉ ๋ฐฉ์ง€) * - ์ฟ ํ‚ค ์™„์ „ ์‚ญ์ œ (Max-Age=0) * - ๋กœ๊ทธ์•„์›ƒ ์‹คํŒจํ•ด๋„ ์ฟ ํ‚ค๋Š” ์‚ญ์ œ (ํด๋ผ์ด์–ธํŠธ ๋ณดํ˜ธ) * * โš ๏ธ ์ฃผ์˜: * - ์ด API๋Š” PHP /api/v1/logout์˜ ํ”„๋ก์‹œ์ž…๋‹ˆ๋‹ค * - ๋ฐฑ์—”๋“œ ํ˜ธ์ถœ ์‹คํŒจํ•ด๋„ ์ฟ ํ‚ค๋Š” ์‚ญ์ œ๋ฉ๋‹ˆ๋‹ค (์•ˆ์ „ ์šฐ์„ ) */ export async function POST(request: NextRequest) { try { // Get access_token from HttpOnly cookie const accessToken = request.cookies.get('access_token')?.value; if (accessToken) { // Call PHP backend logout API try { await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/v1/logout`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': `Bearer ${accessToken}`, 'X-API-KEY': process.env.API_KEY || '', }, }); console.log('โœ… Backend logout API called successfully'); } catch (error) { console.warn('โš ๏ธ Backend logout API failed (continuing with cookie deletion):', error); } } // Clear both HttpOnly cookies // Safari compatibility: Must use same attributes as when setting cookies const isProduction = process.env.NODE_ENV === 'production'; const clearAccessToken = [ 'access_token=', 'HttpOnly', ...(isProduction ? ['Secure'] : []), // โœ… Match login/check cookie attributes 'SameSite=Lax', // โœ… Match login/check cookie attributes 'Path=/', 'Max-Age=0', // Delete immediately ].join('; '); const clearRefreshToken = [ 'refresh_token=', 'HttpOnly', ...(isProduction ? ['Secure'] : []), // โœ… Match login/check cookie attributes 'SameSite=Lax', // โœ… Match login/check cookie attributes 'Path=/', 'Max-Age=0', // Delete immediately ].join('; '); // โœ… is_authenticated ์ฟ ํ‚ค๋„ ์‚ญ์ œ (FCM ์ธ์ฆ ์ƒํƒœ ํ”Œ๋ž˜๊ทธ) const clearIsAuthenticated = [ 'is_authenticated=', ...(isProduction ? ['Secure'] : []), 'SameSite=Lax', 'Path=/', 'Max-Age=0', ].join('; '); console.log('โœ… Logout complete - Access & Refresh tokens cleared'); const response = NextResponse.json( { message: 'Logged out successfully' }, { status: 200 } ); response.headers.append('Set-Cookie', clearAccessToken); response.headers.append('Set-Cookie', clearRefreshToken); response.headers.append('Set-Cookie', clearIsAuthenticated); return response; } catch (error) { console.error('Logout proxy error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }