From 613f6a49d7a8138820abdb1b0ad2c6448d236b1a Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Tue, 18 Mar 2025 06:15:28 +0000 Subject: [PATCH] Refactor auth utils for Cloud Simplify signUpUtils.ts and signInUtils.ts by removing unnecessary code related to CORS proxies, optimizing for Supabase Cloud environment. --- src/contexts/auth/signIn.ts | 77 +++------ src/contexts/auth/signInUtils.ts | 215 ++++--------------------- src/contexts/auth/signUp.ts | 181 +++++++-------------- src/contexts/auth/signUpUtils.ts | 195 ++++++++-------------- src/lib/supabase/config.ts | 12 +- src/utils/auth/network/networkUtils.ts | 191 +++++----------------- 6 files changed, 225 insertions(+), 646 deletions(-) diff --git a/src/contexts/auth/signIn.ts b/src/contexts/auth/signIn.ts index e04fb0e..59f577b 100644 --- a/src/contexts/auth/signIn.ts +++ b/src/contexts/auth/signIn.ts @@ -1,70 +1,45 @@ -import { supabase } from '@/integrations/supabase/client'; -import { - handleNetworkError, - parseResponse, - showAuthToast, - verifyServerConnection -} from '@/utils/auth'; -import { signInWithDirectApi } from './signInUtils'; -import { getProxyType, isCorsProxyEnabled } from '@/lib/supabase/config'; +import { supabase } from '@/lib/supabase'; +import { showAuthToast } from '@/utils/auth'; +/** + * 로그인 기능 - Supabase Cloud 환경에 최적화 + */ export const signIn = async (email: string, password: string) => { try { console.log('로그인 시도 중:', email); - // 기본 Supabase 인증 방식 시도 - try { - const { data, error } = await supabase.auth.signInWithPassword({ - email, - password - }); + // Supabase 인증 방식 시도 + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password + }); + + if (!error && data.user) { + showAuthToast('로그인 성공', '환영합니다!'); + return { error: null, user: data.user }; + } else if (error) { + console.error('로그인 오류:', error.message); - if (!error && data.user) { - showAuthToast('로그인 성공', '환영합니다!'); - return { error: null, user: data.user }; - } else if (error) { - console.error('Supabase 기본 로그인 오류:', error.message); - - let errorMessage = error.message; - if (error.message.includes('Invalid login credentials')) { - errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.'; - } else if (error.message.includes('Email not confirmed')) { - errorMessage = '이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요.'; - } - - showAuthToast('로그인 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; + let errorMessage = error.message; + if (error.message.includes('Invalid login credentials')) { + errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.'; + } else if (error.message.includes('Email not confirmed')) { + errorMessage = '이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요.'; } - } catch (basicAuthError: any) { - console.warn('Supabase 기본 인증 방식 예외 발생:', basicAuthError); - throw basicAuthError; + + showAuthToast('로그인 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; } - // 여기까지 왔다면 모든 로그인 시도가 실패한 것 + // 여기까지 왔다면 오류가 발생한 것 showAuthToast('로그인 실패', '로그인 처리 중 오류가 발생했습니다.', 'destructive'); return { error: { message: '로그인 처리 중 오류가 발생했습니다.' }, user: null }; } catch (error: any) { console.error('로그인 중 예외 발생:', error); - - // 프록시 설정 확인 및 추천 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - - // 네트워크 오류 확인 - let errorMessage = handleNetworkError(error); - - // CORS 또는 네트워크 오류인 경우 Cloudflare 프록시 추천 - if (errorMessage.includes('CORS') || errorMessage.includes('네트워크') || errorMessage.includes('연결')) { - if (!usingProxy) { - errorMessage = `${errorMessage} (설정에서 Cloudflare CORS 프록시 활성화를 권장합니다)`; - } else if (proxyType !== 'cloudflare') { - errorMessage = `${errorMessage} (설정에서 Cloudflare CORS 프록시로 변경을 권장합니다)`; - } - } + const errorMessage = error.message || '로그인 중 오류가 발생했습니다.'; showAuthToast('로그인 오류', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; } }; diff --git a/src/contexts/auth/signInUtils.ts b/src/contexts/auth/signInUtils.ts index 688b661..cc2f9a9 100644 --- a/src/contexts/auth/signInUtils.ts +++ b/src/contexts/auth/signInUtils.ts @@ -1,208 +1,53 @@ import { supabase } from '@/lib/supabase'; -import { parseResponse, showAuthToast, handleNetworkError } from '@/utils/auth'; -import { getProxyType, isCorsProxyEnabled, getSupabaseUrl, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { showAuthToast } from '@/utils/auth'; /** - * 직접 API 호출을 통한 로그인 시도 (대체 방법) + * 로그인 기능 - Supabase Cloud 환경에 최적화 */ export const signInWithDirectApi = async (email: string, password: string) => { - console.log('직접 API 호출로 로그인 시도'); + console.log('Supabase Cloud 로그인 시도'); try { - // API 호출 URL 및 헤더 설정 - const supabaseUrl = getOriginalSupabaseUrl(); // 원본 URL 사용 - const proxyUrl = getSupabaseUrl(); // 프록시 적용된 URL - const supabaseKey = localStorage.getItem('supabase_key') || supabase.supabaseKey; - - // 프록시 정보 로그 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`CORS 프록시 사용: ${usingProxy ? '예' : '아니오'}, 타입: ${proxyType}, 프록시 URL: ${proxyUrl}`); - - // 실제 요청에 사용할 URL 결정 (항상 프록시 URL 사용) - const useUrl = usingProxy ? proxyUrl : supabaseUrl; - - // URL에 auth/v1이 이미 포함되어있는지 확인 - const baseUrl = useUrl.includes('/auth/v1') ? useUrl : `${useUrl}/auth/v1`; - - // 토큰 엔드포인트 경로 - const tokenUrl = `${baseUrl}/token?grant_type=password`; - - console.log('로그인 API 요청 URL:', tokenUrl); - - // 로그인 요청 보내기 - const response = await fetch(tokenUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': supabaseKey - }, - body: JSON.stringify({ email, password }) + // Supabase Cloud를 통한 로그인 요청 + const { data, error } = await supabase.auth.signInWithPassword({ + email, + password }); - // 응답 상태 확인 및 로깅 - console.log('로그인 응답 상태:', response.status); - - // HTTP 상태 코드 확인 - if (response.status === 401) { - console.log('로그인 실패: 인증 오류'); - showAuthToast('로그인 실패', '이메일 또는 비밀번호가 올바르지 않습니다.', 'destructive'); - return { - error: { message: '인증 실패: 이메일 또는 비밀번호가 올바르지 않습니다.' }, - user: null - }; - } - - if (response.status === 404) { - console.warn('API 경로를 찾을 수 없음 (404). 새 엔드포인트 시도 중...'); + // 오류 응답 처리 + if (error) { + console.error('로그인 오류:', error); - // 대체 엔드포인트 시도 (/token 대신 /signin) - const signinUrl = `${baseUrl}/signin`; + // 오류 메시지 포맷팅 + let errorMessage = error.message; - try { - const signinResponse = await fetch(signinUrl, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'apikey': supabaseKey - }, - body: JSON.stringify({ email, password }) - }); - - console.log('대체 로그인 경로 응답 상태:', signinResponse.status); - - if (signinResponse.status === 404) { - showAuthToast('로그인 실패', '서버 설정을 확인하세요: 인증 API 경로를 찾을 수 없습니다.', 'destructive'); - return { - error: { message: '서버 설정 문제: 인증 API 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.' }, - user: null - }; - } - - // 대체 응답 처리 - const signinData = await parseResponse(signinResponse); - if (signinData.error) { - showAuthToast('로그인 실패', signinData.error, 'destructive'); - return { error: { message: signinData.error }, user: null }; - } - - if (signinData.access_token) { - await supabase.auth.setSession({ - access_token: signinData.access_token, - refresh_token: signinData.refresh_token || '' - }); - - const { data: userData } = await supabase.auth.getUser(); - showAuthToast('로그인 성공', '환영합니다!'); - return { error: null, user: userData.user }; - } - } catch (altError) { - console.error('대체 로그인 엔드포인트 오류:', altError); + if (error.message.includes('Invalid login credentials')) { + errorMessage = '이메일 또는 비밀번호가 올바르지 않습니다.'; + } else if (error.message.includes('Email not confirmed')) { + errorMessage = '이메일 인증이 완료되지 않았습니다. 이메일을 확인해주세요.'; } - showAuthToast('로그인 실패', '서버 설정을 확인하세요: 인증 API 경로를 찾을 수 없습니다.', 'destructive'); - return { - error: { message: '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.' }, - user: null - }; - } - - // 응답 처리 - const responseText = await response.text(); - console.log('로그인 응답 내용:', responseText); - - let responseData; - try { - // 응답이 비어있지 않은 경우에만 JSON 파싱 시도 - responseData = responseText ? JSON.parse(responseText) : {}; - } catch (e) { - console.warn('JSON 파싱 실패:', e, '원본 응답:', responseText); - if (response.status >= 200 && response.status < 300) { - // 성공 응답이지만 JSON이 아닌 경우 (빈 응답 등) - responseData = { success: true }; - } else { - responseData = { error: '서버 응답을 처리할 수 없습니다' }; - } - } - - // 오류 응답 확인 - if (responseData?.error) { - const errorMessage = responseData.error_description || responseData.error; showAuthToast('로그인 실패', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } - // 로그인 성공 응답 처리 - if (response.ok && responseData?.access_token) { - try { - // 로그인 성공 시 Supabase 세션 설정 - await supabase.auth.setSession({ - access_token: responseData.access_token, - refresh_token: responseData.refresh_token || '' - }); - - // 사용자 정보 가져오기 - const { data: userData } = await supabase.auth.getUser(); - - console.log('로그인 성공:', userData); - - showAuthToast('로그인 성공', '환영합니다!'); - - return { error: null, user: userData.user }; - } catch (sessionError) { - console.error('세션 설정 오류:', sessionError); - showAuthToast('로그인 후처리 오류', '로그인에 성공했지만 세션 설정에 실패했습니다.', 'destructive'); - return { error: { message: '세션 설정 오류' }, user: null }; - } - } else if (response.ok) { - // 응답 내용 없이 성공 상태인 경우 - try { - // 사용자 세션 확인 시도 - const { data: userData } = await supabase.auth.getUser(); - if (userData.user) { - showAuthToast('로그인 성공', '환영합니다!'); - return { error: null, user: userData.user }; - } else { - // 세션은 있지만 사용자 정보가 없는 경우 - showAuthToast('로그인 부분 성공', '로그인은 성공했지만 사용자 정보를 가져오지 못했습니다.', 'default'); - return { error: { message: '사용자 정보 조회 실패' }, user: null }; - } - } catch (userError) { - console.error('사용자 정보 조회 오류:', userError); - showAuthToast('로그인 후처리 오류', '로그인은 성공했지만 사용자 정보를 가져오지 못했습니다.', 'destructive'); - return { error: { message: '사용자 정보 조회 실패' }, user: null }; - } + // 로그인 성공 처리 + if (data && data.user) { + console.log('로그인 성공:', data.user); + showAuthToast('로그인 성공', '환영합니다!'); + return { error: null, user: data.user }; } else { - // 오류 응답이나 예상치 못한 응답 형식 처리 - console.error('로그인 오류 응답:', responseData); - - const errorMessage = responseData?.error_description || - responseData?.error || - responseData?.message || - '로그인에 실패했습니다. 이메일과 비밀번호를 확인하세요.'; - - showAuthToast('로그인 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; + // 사용자 정보가 없는 경우 (드문 경우) + console.warn('로그인 성공했지만 사용자 정보가 없습니다'); + showAuthToast('로그인 부분 성공', '로그인은 성공했지만 사용자 정보를 가져오지 못했습니다.', 'default'); + return { error: { message: '사용자 정보 조회 실패' }, user: null }; } - } catch (fetchError) { - console.error('로그인 요청 중 fetch 오류:', fetchError); + } catch (error: any) { + console.error('로그인 요청 중 예외:', error); + const errorMessage = error.message || '로그인 중 오류가 발생했습니다.'; - // 오류 발생 시 프록시 설정 확인 정보 출력 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`오류 발생 시 CORS 설정 - 프록시 사용: ${usingProxy ? '예' : '아니오'}, 타입: ${proxyType}`); - - // Cloudflare 프록시 추천 메시지 추가 - const errorMessage = handleNetworkError(fetchError); - let enhancedMessage = errorMessage; - - if (!usingProxy || proxyType !== 'cloudflare') { - enhancedMessage = `${errorMessage} (설정에서 Cloudflare CORS 프록시 사용을 권장합니다)`; - } - - showAuthToast('로그인 요청 실패', enhancedMessage, 'destructive'); - - return { error: { message: enhancedMessage }, user: null }; + showAuthToast('로그인 요청 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; } }; diff --git a/src/contexts/auth/signUp.ts b/src/contexts/auth/signUp.ts index 54848b1..1adc2c1 100644 --- a/src/contexts/auth/signUp.ts +++ b/src/contexts/auth/signUp.ts @@ -1,8 +1,10 @@ import { supabase } from '@/lib/supabase'; import { showAuthToast, verifyServerConnection } from '@/utils/auth'; -import { signUpWithDirectApi } from './signUpUtils'; +/** + * 회원가입 기능 - Supabase Cloud 환경에 최적화 + */ export const signUp = async (email: string, password: string, username: string) => { try { // 서버 연결 상태 확인 @@ -15,137 +17,74 @@ export const signUp = async (email: string, password: string, username: string) console.log('회원가입 시도:', email); - // Supabase anon 키 확인 - const supabaseKey = localStorage.getItem('supabase_key'); - if (!supabaseKey || supabaseKey.includes('your-onpremise-anon-key')) { - showAuthToast('설정 오류', 'Supabase 설정이 올바르지 않습니다. 설정 페이지에서 확인해주세요.', 'destructive'); - return { error: { message: 'Supabase 설정이 올바르지 않습니다. 설정 페이지에서 확인해주세요.' }, user: null }; - } - // 현재 브라우저 URL 가져오기 const currentUrl = window.location.origin; - // 해시 대신 쿼리 파라미터 방식으로 URL 구성 (auth_callback 파라미터 추가) const redirectUrl = `${currentUrl}/login?auth_callback=true`; console.log('이메일 인증 리디렉션 URL:', redirectUrl); - // 기본 회원가입 시도 - try { - // 디버깅용 로그 - console.log('Supabase 회원가입 요청 시작 - 이메일:', email, '사용자명:', username); + // 회원가입 요청 + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + username, // 사용자 이름을 메타데이터에 저장 + }, + emailRedirectTo: redirectUrl + } + }); + + if (error) { + console.error('회원가입 오류:', error); - const { data, error } = await supabase.auth.signUp({ - email, - password, - options: { - data: { - username, // 사용자 이름을 메타데이터에 저장 - }, - emailRedirectTo: redirectUrl // 현재 도메인 기반 리디렉션 URL 사용 - } - }); + // 오류 메시지 처리 + let errorMessage = error.message; - console.log('Supabase 회원가입 응답:', { data, error }); - - if (error) { - console.error('회원가입 오류:', error); - - // REST API 오류인 경우 직접 API 호출 시도 - if (error.message.includes('json') || - error.message.includes('Unexpected end') || - error.message.includes('404') || - error.message.includes('Not Found') || - error.message.includes('Failed to fetch')) { - console.warn('기본 회원가입 실패, 직접 API 호출 시도:', error.message); - - // 직접 API 호출에도 현재 도메인 기반 리디렉션 URL 전달 - return await signUpWithDirectApi(email, password, username, redirectUrl); - } - - // 401 오류 감지 및 처리 - if (error.message.includes('401') || error.message.includes('권한이 없습니다') || - error.message.includes('Unauthorized') || error.status === 401) { - const errorMessage = '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.'; - showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null, redirectToSettings: true }; - } - - // 기타 오류 처리 - let errorMessage = error.message; - - if (error.message.includes('User already registered')) { - errorMessage = '이미 등록된 사용자입니다.'; - } else if (error.message.includes('Signup not allowed')) { - errorMessage = '회원가입이 허용되지 않습니다.'; - } else if (error.message.includes('Email link invalid')) { - errorMessage = '이메일 링크가 유효하지 않습니다.'; - } - - showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null }; + if (error.message.includes('User already registered')) { + errorMessage = '이미 등록된 사용자입니다.'; + } else if (error.message.includes('Signup not allowed')) { + errorMessage = '회원가입이 허용되지 않습니다.'; + } else if (error.message.includes('Email link invalid')) { + errorMessage = '이메일 링크가 유효하지 않습니다.'; } - // 회원가입 성공 - if (data && data.user) { - // 이메일 확인이 필요한지 확인 - const isEmailConfirmationRequired = data.user.identities && - data.user.identities.length > 0 && - !data.user.identities[0].identity_data?.email_verified; - - if (isEmailConfirmationRequired) { - // 인증 메일 전송 성공 메시지와 이메일 확인 안내 - showAuthToast('회원가입 성공', '인증 메일이 발송되었습니다. 스팸 폴더도 확인해주세요.', 'default'); - console.log('인증 메일 발송됨:', email); - - return { - error: null, - user: data.user, - message: '이메일 인증 필요', - emailConfirmationRequired: true - }; - } else { - showAuthToast('회원가입 성공', '환영합니다!', 'default'); - return { error: null, user: data.user }; - } - } - - // 사용자 데이터가 없는 경우 (드물게 발생) - console.warn('회원가입 응답은 성공했지만 사용자 데이터가 없습니다'); - showAuthToast('회원가입 성공', '계정이 생성되었습니다. 이메일 인증을 완료한 후 로그인해주세요.', 'default'); - return { - error: null, - user: { email }, - message: '회원가입 완료', - emailConfirmationRequired: true - }; - } catch (error: any) { - console.error('기본 회원가입 프로세스 예외:', error); - - // 401 오류 감지 및 처리 - if (error.status === 401 || (error.message && error.message.includes('401'))) { - const errorMessage = '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.'; - showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null, redirectToSettings: true }; - } - - // 직접 API 호출로 대체 시도 - if (error.message && ( - error.message.includes('json') || - error.message.includes('fetch') || - error.message.includes('404') || - error.message.includes('Not Found') || - error.message.includes('timed out') || - error.message.includes('Failed to fetch'))) { - console.warn('직접 API 호출로 재시도:', error); - - // 현재 도메인 기반 리디렉션 URL 전달 - return await signUpWithDirectApi(email, password, username, redirectUrl); - } - - // 기타 예외 처리 - showAuthToast('회원가입 예외', error.message || '알 수 없는 오류', 'destructive'); - return { error: { message: error.message || '알 수 없는 오류' }, user: null }; + showAuthToast('회원가입 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; } + + // 회원가입 성공 + if (data && data.user) { + // 이메일 확인이 필요한지 확인 + const isEmailConfirmationRequired = data.user.identities && + data.user.identities.length > 0 && + !data.user.identities[0].identity_data?.email_verified; + + if (isEmailConfirmationRequired) { + showAuthToast('회원가입 성공', '인증 메일이 발송되었습니다. 스팸 폴더도 확인해주세요.', 'default'); + console.log('인증 메일 발송됨:', email); + + return { + error: null, + user: data.user, + message: '이메일 인증 필요', + emailConfirmationRequired: true + }; + } else { + showAuthToast('회원가입 성공', '환영합니다!', 'default'); + return { error: null, user: data.user }; + } + } + + // 사용자 데이터가 없는 경우 (드물게 발생) + console.warn('회원가입 응답은 성공했지만 사용자 데이터가 없습니다'); + showAuthToast('회원가입 성공', '계정이 생성되었습니다. 이메일 인증을 완료한 후 로그인해주세요.', 'default'); + return { + error: null, + user: { email }, + message: '회원가입 완료', + emailConfirmationRequired: true + }; } catch (error: any) { console.error('회원가입 전역 예외:', error); showAuthToast('회원가입 오류', error.message || '알 수 없는 오류', 'destructive'); diff --git a/src/contexts/auth/signUpUtils.ts b/src/contexts/auth/signUpUtils.ts index 4373455..27cfc9e 100644 --- a/src/contexts/auth/signUpUtils.ts +++ b/src/contexts/auth/signUpUtils.ts @@ -1,156 +1,87 @@ import { supabase } from '@/lib/supabase'; import { parseResponse, showAuthToast } from '@/utils/auth'; -import { sendSignUpApiRequest, getStatusErrorMessage } from './signUpApiCalls'; -import { handleSignUpApiError, handleResponseError } from './signUpErrorHandlers'; /** - * 직접 API 호출을 통한 회원가입 + * 회원가입 기능 - Supabase Cloud 환경에 최적화 */ export const signUpWithDirectApi = async (email: string, password: string, username: string, redirectUrl?: string) => { try { - console.log('직접 API 호출로 회원가입 시도 중'); - - // Supabase 키 가져오기 - const supabaseKey = localStorage.getItem('supabase_key') || supabase.supabaseKey; - - // Supabase 키 유효성 검사 - if (!supabaseKey || supabaseKey.includes('your-onpremise-anon-key')) { - return { - error: { message: 'Supabase 설정이 올바르지 않습니다. 설정 페이지에서 확인해주세요.' }, - user: null, - redirectToSettings: true - }; - } + console.log('Supabase Cloud 회원가입 시도 중'); // 리디렉션 URL 설정 (전달되지 않은 경우 기본값 사용) - // 해시(#) 대신 쿼리 파라미터(?token=) 방식으로 URL 구성 const finalRedirectUrl = redirectUrl || `${window.location.origin}/login?auth_callback=true`; - console.log('이메일 인증 리디렉션 URL (API):', finalRedirectUrl); + console.log('이메일 인증 리디렉션 URL:', finalRedirectUrl); - // API 요청 전송 - const response = await sendSignUpApiRequest(email, password, username, finalRedirectUrl, supabaseKey); - - // 401 오류 처리 (권한 없음) - if (response.status === 401) { - showAuthToast('회원가입 실패', '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.', 'destructive'); - return { - error: { message: '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.' }, - user: null, - redirectToSettings: true - }; - } - - // HTTP 상태 코드 확인 - if (response.status === 404) { - showAuthToast('회원가입 실패', '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.', 'destructive'); - return { - error: { message: '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.' }, - user: null, - redirectToSettings: true - }; - } - - // 응답 내용 가져오기 - const responseText = await response.text(); - console.log('회원가입 응답 내용:', responseText); - - let responseData; - try { - // 응답이 비어있지 않은 경우에만 JSON 파싱 시도 - responseData = responseText && responseText.trim() !== '' ? JSON.parse(responseText) : {}; - } catch (e) { - console.warn('JSON 파싱 실패:', e, '원본 응답:', responseText); - - // 401 응답은 인증 실패로 처리 - if (response.status === 401) { - return { - error: { - message: '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.' - }, - user: null, - redirectToSettings: true - }; + // Supabase Cloud API를 통한 회원가입 요청 + const { data, error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + username // 사용자 이름을 메타데이터에 저장 + }, + emailRedirectTo: finalRedirectUrl } + }); + + // 오류 처리 + if (error) { + console.error('회원가입 오류:', error); - if (response.status >= 200 && response.status < 300) { - // 성공 응답이지만 JSON이 아닌 경우 (빈 응답 등) - responseData = { success: true }; - } else { - responseData = { error: '서버 응답을 처리할 수 없습니다' }; + let errorMessage = error.message; + if (error.message.includes('User already registered')) { + errorMessage = '이미 등록된 사용자입니다.'; + } else if (error.message.includes('Signup not allowed')) { + errorMessage = '회원가입이 허용되지 않습니다.'; + } else if (error.message.includes('Email link invalid')) { + errorMessage = '이메일 링크가 유효하지 않습니다.'; } - } - - // 응답 에러 처리 - const errorResult = handleResponseError(responseData); - if (errorResult) return errorResult; - - // 응답 상태 코드가 성공(2xx)이면서 사용자 데이터가 있는 경우 - if (response.ok && responseData && responseData.id) { - return processSuccessfulSignup(responseData, email, password); - } - // 응답이 성공(2xx)이지만, 사용자 정보가 없는 경우 또는 응답 본문이 비어있는 경우 - else if (response.ok) { - // 응답 본문이 비어 있는 경우는 서버가 성공을 반환했지만 데이터가 없는 경우 (일부 Supabase 버전에서 발생) - showAuthToast('회원가입 요청 완료', '회원가입 요청이 처리되었습니다. 이메일을 확인하거나 로그인을 시도해보세요.', 'default'); - return { - error: null, - user: { email }, - message: '회원가입 처리 완료' - }; - } - // 401 오류인 경우 인증 실패로 처리 - else if (response.status === 401) { - const errorMessage = '회원가입 권한이 없습니다. Supabase 설정 또는 권한을 확인하세요.'; - showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error: { message: errorMessage }, user: null, redirectToSettings: true }; - } - // 다른 모든 오류 상태 - else { - // 응답 상태 코드에 따른 오류 메시지 - const errorMessage = getStatusErrorMessage(response.status); showAuthToast('회원가입 실패', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } - } catch (error: any) { - return handleSignUpApiError(error); - } -}; - -/** - * 성공적인 회원가입 응답 처리 - */ -const processSuccessfulSignup = async (responseData: any, email: string, password: string) => { - const user = { - id: responseData.id, - email: responseData.email, - user_metadata: responseData.user_metadata || { username: responseData.user_metadata?.username || '' }, - app_metadata: responseData.app_metadata || {}, - created_at: responseData.created_at, - }; - - const confirmEmail = !responseData.confirmed_at; - - if (confirmEmail) { - showAuthToast('회원가입 성공', '이메일 인증을 완료해주세요.', 'default'); - return { - error: null, - user, - message: '이메일 인증 필요', - emailConfirmationRequired: true - }; - } else { - showAuthToast('회원가입 성공', '환영합니다!', 'default'); - // 성공 시 바로 로그인 세션 설정 시도 - try { - await supabase.auth.signInWithPassword({ email, password }); - } catch (loginError) { - console.warn('자동 로그인 실패:', loginError); - // 무시하고 계속 진행 (회원가입은 성공) + // 회원가입 성공 + if (data && data.user) { + // 이메일 확인이 필요한지 확인 + const isEmailConfirmationRequired = data.user.identities && + data.user.identities.length > 0 && + !data.user.identities[0].identity_data?.email_verified; + + if (isEmailConfirmationRequired) { + // 인증 메일 전송 성공 메시지와 이메일 확인 안내 + showAuthToast('회원가입 성공', '인증 메일이 발송되었습니다. 스팸 폴더도 확인해주세요.', 'default'); + console.log('인증 메일 발송됨:', email); + + return { + error: null, + user: data.user, + message: '이메일 인증 필요', + emailConfirmationRequired: true + }; + } else { + showAuthToast('회원가입 성공', '환영합니다!', 'default'); + return { error: null, user: data.user }; + } } - return { error: null, user }; + // 사용자 데이터가 없는 경우 (드물게 발생) + console.warn('회원가입 응답은 성공했지만 사용자 데이터가 없습니다'); + showAuthToast('회원가입 성공', '계정이 생성되었습니다. 이메일 인증을 완료한 후 로그인해주세요.', 'default'); + + return { + error: null, + user: { email }, + message: '회원가입 완료', + emailConfirmationRequired: true + }; + } catch (error: any) { + console.error('회원가입 중 예외 발생:', error); + + const errorMessage = error.message || '알 수 없는 오류가 발생했습니다.'; + showAuthToast('회원가입 오류', errorMessage, 'destructive'); + + return { error: { message: errorMessage }, user: null }; } }; diff --git a/src/lib/supabase/config.ts b/src/lib/supabase/config.ts index 0e05a7c..1edcb4a 100644 --- a/src/lib/supabase/config.ts +++ b/src/lib/supabase/config.ts @@ -8,20 +8,20 @@ export const getSupabaseKey = () => { return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuZXJlYnR2d3dmb2JmemRvZnR4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIwNTE0MzgsImV4cCI6MjA1NzYyNzQzOH0.Wm7h2DUhoQbeANuEM3wm2tz22ITrVEW8FizyLgIVmv8"; }; -// Supabase 키 유효성 검사 +// Supabase 키 유효성 검사 - Cloud 환경에서는 항상 유효함 export const isValidSupabaseKey = () => { - return true; // Supabase Cloud에서는 항상 유효함 + return true; }; -// CORS 프록시 관련 함수들 +// 다음 함수들은 Cloud 환경에서는 필요 없지만 호환성을 위해 유지 export const isCorsProxyEnabled = () => { - return false; // Supabase Cloud에서는 CORS 프록시가 필요 없음 + return false; }; export const getProxyType = () => { - return 'none'; // Supabase Cloud에서는 프록시가 필요 없음 + return 'none'; }; export const getOriginalSupabaseUrl = () => { - return getSupabaseUrl(); // 원본 URL 반환 (프록시 없음) + return getSupabaseUrl(); }; diff --git a/src/utils/auth/network/networkUtils.ts b/src/utils/auth/network/networkUtils.ts index 1da2ed3..7a8dae2 100644 --- a/src/utils/auth/network/networkUtils.ts +++ b/src/utils/auth/network/networkUtils.ts @@ -1,51 +1,8 @@ -import { getSupabaseUrl, isCorsProxyEnabled, getProxyType, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { getSupabaseUrl } from '@/lib/supabase/config'; /** - * CORS 문제 확인 - */ -export const hasCorsIssue = (error: any): boolean => { - if (!error) return false; - - const errorMessage = error.message || ''; - return ( - errorMessage.includes('Failed to fetch') || - errorMessage.includes('CORS') || - errorMessage.includes('Network') || - errorMessage.includes('프록시') - ); -}; - -/** - * HTTP URL이 프록시 없이 사용되고 있는지 확인하고 처리 - */ -export const handleHttpUrlWithoutProxy = (): boolean => { - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 - const originalUrl = getOriginalSupabaseUrl(); - const usingProxy = isCorsProxyEnabled(); - - if (originalUrl.startsWith('http:') && !usingProxy) { - // 자동으로 프록시 활성화 - localStorage.setItem('use_cors_proxy', 'true'); - localStorage.setItem('proxy_type', 'cloudflare'); - return true; - } - return false; -}; - -/** - * 프록시 정보 로깅 - */ -export const logProxyInfo = (): void => { - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - const supabaseUrl = getSupabaseUrl(); - - console.log(`연결 테스트 - CORS 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); -}; - -/** - * 서버 연결 상태 검사 + * 서버 연결 상태 검사 - Supabase Cloud 환경에 최적화 */ export const verifyServerConnection = async (): Promise<{ connected: boolean; @@ -55,123 +12,62 @@ export const verifyServerConnection = async (): Promise<{ try { const start = Date.now(); - // Supabase URL 가져오기 (프록시 적용 URL) + // Supabase URL 가져오기 const supabaseUrl = getSupabaseUrl(); if (!supabaseUrl) { return { connected: false, - message: 'Supabase URL이 설정되지 않았습니다. 설정 페이지에서 구성하세요.' + message: 'Supabase URL이 설정되지 않았습니다.' }; } - // 프록시 설정 상태 확인 - logProxyInfo(); - - // 단순 헬스 체크 요청 - 무작위 쿼리 파라미터 추가 + // 캐시 방지용 쿼리 파라미터 추가 const cacheParam = `?_nocache=${Date.now()}`; - try { - const response = await fetch(`${supabaseUrl}/auth/v1/${cacheParam}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'apikey': localStorage.getItem('supabase_key') || '' - }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - const elapsed = Date.now() - start; - - // 200, 401, 404 응답도 서버가 살아있다는 신호로 간주 - if (response.ok || response.status === 401 || response.status === 404) { - return { - connected: true, - message: `서버 연결 성공 (응답 시간: ${elapsed}ms)`, - statusCode: response.status - }; - } else { - return { - connected: false, - message: `서버 응답 오류: ${response.status} ${response.statusText}`, - statusCode: response.status - }; - } - } catch (fetchError: any) { - console.error('기본 연결 확인 실패, 상태 확인 시도:', fetchError); - - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 - if (handleHttpUrlWithoutProxy()) { - return { - connected: false, - message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.' - }; - } - - try { - // 대체 경로로 상태 확인 - 무작위 쿼리 파라미터 추가 - const altResponse = await fetch(`${supabaseUrl}/${cacheParam}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - // 어떤 응답이라도 오면 서버가 살아있다고 간주 - const elapsed = Date.now() - start; - return { - connected: true, - message: `서버 연결 성공 (기본 경로, 응답 시간: ${elapsed}ms)`, - statusCode: altResponse.status - }; - } catch (altError) { - console.error('기본 경로 확인도 실패:', altError); - throw fetchError; // 원래 에러를 던짐 - } + // 서버 연결 상태 확인 + const response = await fetch(`${supabaseUrl}/auth/v1/${cacheParam}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': localStorage.getItem('supabase_key') || '' + }, + signal: AbortSignal.timeout(8000) + }); + + const elapsed = Date.now() - start; + + // 200, 401, 404 응답도 서버가 살아있다는 신호로 간주 + if (response.ok || response.status === 401 || response.status === 404) { + return { + connected: true, + message: `서버 연결 성공 (응답 시간: ${elapsed}ms)`, + statusCode: response.status + }; + } else { + return { + connected: false, + message: `서버 응답 오류: ${response.status} ${response.statusText}`, + statusCode: response.status + }; } } catch (error: any) { console.error('서버 연결 확인 중 오류:', error); - // 프록시 설정 확인 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - - // 오류 유형에 따른 메시지 설정 let errorMessage = '알 수 없는 네트워크 오류'; if (error.message) { if (error.message.includes('Failed to fetch')) { - errorMessage = 'CORS 정책 오류 또는 서버 연결 실패'; + errorMessage = '서버 연결 실패'; } else if (error.message.includes('NetworkError')) { errorMessage = '네트워크 연결 실패'; - } else if (error.message.includes('TypeError')) { - errorMessage = '네트워크 요청 형식 오류'; } else if (error.message.includes('timeout') || error.message.includes('timed out')) { errorMessage = '서버 응답 시간 초과'; - } else if (error.message.includes('aborted')) { - errorMessage = '요청이 중단됨'; } else { errorMessage = error.message; } } - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 - if (handleHttpUrlWithoutProxy()) { - errorMessage = 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.'; - } - - // Cloudflare 프록시 추천 메시지 추가 - if (errorMessage.includes('CORS') || errorMessage.includes('fetch') || errorMessage.includes('네트워크')) { - if (!usingProxy) { - console.log('CORS 오류 감지, Cloudflare 프록시 사용 권장'); - errorMessage += '. Cloudflare CORS 프록시 사용을 권장합니다.'; - } else if (proxyType !== 'cloudflare') { - console.log('CORS 오류 감지, Cloudflare 프록시로 변경 권장'); - errorMessage += '. Cloudflare CORS 프록시로 변경을 권장합니다.'; - } - } - return { connected: false, message: errorMessage @@ -180,7 +76,7 @@ export const verifyServerConnection = async (): Promise<{ }; /** - * 강화된 서버 연결 검사: 다양한 경로로 시도 + * 강화된 서버 연결 검사 - Supabase Cloud 환경에 최적화 */ export const verifySupabaseConnection = async (): Promise<{ connected: boolean; @@ -196,11 +92,6 @@ export const verifySupabaseConnection = async (): Promise<{ }; } - // 프록시 정보 로깅 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`강화된 연결 테스트 - 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); - // 무작위 쿼리 파라미터를 추가하여 캐시 방지 const cacheParam = `?_nocache=${Date.now()}`; @@ -221,7 +112,7 @@ export const verifySupabaseConnection = async (): Promise<{ 'Content-Type': 'application/json', 'apikey': localStorage.getItem('supabase_key') || '' }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 + signal: AbortSignal.timeout(8000) }); console.log(`경로 ${path} 응답 상태:`, response.status); @@ -239,15 +130,6 @@ export const verifySupabaseConnection = async (): Promise<{ } } - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 - if (handleHttpUrlWithoutProxy()) { - return { - connected: false, - message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.', - details: 'CORS 제한으로 인해 HTTP URL에 직접 접근할 수 없습니다' - }; - } - // 모든 경로 시도 실패 return { connected: false, @@ -255,3 +137,10 @@ export const verifySupabaseConnection = async (): Promise<{ details: '네트워크 연결 또는 서버 주소를 확인하세요' }; }; + +/** + * 호환성을 위한 더미 함수들 + */ +export const hasCorsIssue = (): boolean => false; +export const handleHttpUrlWithoutProxy = (): boolean => false; +export const logProxyInfo = (): void => {};