summaryrefslogtreecommitdiff
path: root/lib/auth/custom-signout.ts
blob: 6f3a6b019b9c26f2a3e731af33df2fb5036a401a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
/**
 * 커스텀 로그아웃 함수
 * NextAuth의 signOut이 NEXTAUTH_URL로 강제 리다이렉트하는 문제를 해결하기 위해 직접 구현
 */

interface CustomSignOutOptions {
  callbackUrl?: string;
  redirect?: boolean;
}

/**
 * 커스텀 로그아웃 함수
 * 
 * @param options - callbackUrl: 로그아웃 후 이동할 URL (상대 경로 권장: "/ko/partners")
 * @param options - redirect: 자동 리다이렉트 여부 (기본: true)
 */
export async function customSignOut(options?: CustomSignOutOptions): Promise<void> {
  const { callbackUrl, redirect = true } = options || {};
  
  console.log('[customSignOut] 시작:', {
    currentOrigin: window.location.origin,
    currentHref: window.location.href,
    callbackUrl,
    redirect,
  });
  
  try {
    // 1. CSRF 토큰 가져오기
    const csrfResponse = await fetch('/api/auth/csrf');
    const { csrfToken } = await csrfResponse.json();
    
    console.log('[customSignOut] CSRF 토큰 획득');
    
    // 2. 서버에 로그아웃 요청 (리다이렉트 방지)
    const signoutResponse = await fetch('/api/auth/signout', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: new URLSearchParams({
        csrfToken,
        json: 'true',
      }),
      redirect: 'manual', // ⭐ 서버의 리다이렉트를 자동으로 따라가지 않음
    });
    
    console.log('[customSignOut] 서버 응답:', {
      status: signoutResponse.status,
      statusText: signoutResponse.statusText,
      redirected: signoutResponse.redirected,
      url: signoutResponse.url,
    });
    
    // 3. NextAuth 세션 쿠키 즉시 삭제 (middleware가 감지하도록)
    document.cookie = 'next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;';
    document.cookie = '__Secure-next-auth.session-token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT; Secure;';
    
    console.log('[customSignOut] 세션 쿠키 삭제 완료');
    
    // 4. 리다이렉트
    if (redirect) {
      // ⭐ URL 객체로 변환하여 상대 경로인지 확인
      let finalUrl: string;
      
      if (callbackUrl) {
        try {
          // callbackUrl이 절대 URL인 경우 (http:// 또는 https://로 시작)
          if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
            const urlObj = new URL(callbackUrl);
            // 같은 origin인 경우 pathname만 사용 (상대 경로로 변환)
            if (urlObj.origin === window.location.origin) {
              finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
            } else {
              finalUrl = callbackUrl;
            }
          } else {
            // 이미 상대 경로인 경우 (/, /ko/partners 등)
            finalUrl = callbackUrl;
          }
        } catch (error) {
          console.error('[customSignOut] callbackUrl 파싱 오류:', error);
          finalUrl = callbackUrl; // 오류 시 원본 사용
        }
      } else {
        // callbackUrl이 없는 경우 루트 경로로
        finalUrl = '/';
      }
      
      console.log('[customSignOut] 리다이렉트 실행:', finalUrl);
      window.location.href = finalUrl;
    }
  } catch (error) {
    console.error('Custom sign out error:', error);
    // 에러 발생 시에도 리다이렉트 (세션이 이미 만료되었을 수 있음)
    if (redirect) {
      let finalUrl = '/';
      if (callbackUrl) {
        try {
          if (callbackUrl.startsWith('http://') || callbackUrl.startsWith('https://')) {
            const urlObj = new URL(callbackUrl);
            if (urlObj.origin === window.location.origin) {
              finalUrl = urlObj.pathname + urlObj.search + urlObj.hash;
            } else {
              finalUrl = callbackUrl;
            }
          } else {
            finalUrl = callbackUrl;
          }
        } catch {
          finalUrl = callbackUrl;
        }
      }
      window.location.href = finalUrl;
    }
  }
}