Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
EXPO_PUBLIC_KAKAO_REST_API_KEY=fcc79e9199b5dbcaedfc00bb30b3d4af
EXPO_PUBLIC_SERVER_BASE_URL=https://api.dailysnap.app
EXPO_PUBLIC_NAVER_CLIENT_ID=tCdEIPDTDBnfdVRdRgoO
EXPO_PUBLIC_NAVER_CLIENT_SECRET=IX3znje6Hl
EXPO_PUBLIC_NAVER_REDIRECT_URI=https://dailysnap.xyz/api/auth/naver
EXPO_PUBLIC_GOOGLE_CLIENT_ID=735338487068-kh9kfnrrus8fdmucfek7996ml15jiell.apps.googleusercontent.com.apps.googleusercontent.com
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Google 클라이언트 ID 수정 필요

Google 클라이언트 ID에 .apps.googleusercontent.com이 중복으로 포함되어 있습니다. 올바른 형식으로 수정해야 합니다.

다음과 같이 수정하세요:

-EXPO_PUBLIC_GOOGLE_CLIENT_ID=735338487068-kh9kfnrrus8fdmucfek7996ml15jiell.apps.googleusercontent.com.apps.googleusercontent.com
+EXPO_PUBLIC_GOOGLE_CLIENT_ID=735338487068-kh9kfnrrus8fdmucfek7996ml15jiell.apps.googleusercontent.com
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
EXPO_PUBLIC_GOOGLE_CLIENT_ID=735338487068-kh9kfnrrus8fdmucfek7996ml15jiell.apps.googleusercontent.com.apps.googleusercontent.com
EXPO_PUBLIC_GOOGLE_CLIENT_ID=735338487068-kh9kfnrrus8fdmucfek7996ml15jiell.apps.googleusercontent.com
🤖 Prompt for AI Agents
In the .env file at line 6, the Google client ID contains a duplicated suffix
'.apps.googleusercontent.com'. Remove the extra '.apps.googleusercontent.com' so
that the client ID ends with a single '.apps.googleusercontent.com' to correct
the format.

EXPO_PUBLIC_GOOGLE_REDIRECT_URI=https://dailysnap.xyz/auth/google/callback
21 changes: 11 additions & 10 deletions app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ import { View, ActivityIndicator } from "react-native";

export default function App() {
const router = useRouter();
const { userInfo } = useAuth();
// const { userInfo } = useAuth();

useEffect(() => {
const timer = setTimeout(() => {
if (userInfo) {
// 로그인된 사용자면, 홈 네비게이션으로 이동
router.replace("/(tabs)/home");
} else {
// 로그인되지 않았으면 로그인 페이지로 이동
router.replace("/login");
}
router.replace("/(tabs)/home");
// if (userInfo) {
// // 로그인된 사용자면, 홈 네비게이션으로 이동
// router.replace("/(tabs)/home");
// } else {
// // 로그인되지 않았으면 로그인 페이지로 이동
// router.replace("/login");
// }
}, 100);

return () => clearTimeout(timer);
}, [userInfo, router]);
}, [router]);
Comment on lines +9 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

인증 우회에 대한 우려사항

사용자 인증 로직이 주석 처리되어 모든 사용자가 로그인 없이 홈으로 이동합니다. 이는 의도된 변경인지 확인이 필요합니다.

만약 개발 중 임시 변경이라면, 배포 전 반드시 인증 로직을 복원해야 합니다.

인증이 필요한 앱이라면 다음과 같이 복원하세요:

-  // const { userInfo } = useAuth();
+  const { userInfo } = useAuth();

-      router.replace("/(tabs)/home");
-      // if (userInfo) {
-      //   // 로그인된 사용자면, 홈 네비게이션으로 이동
-      //   router.replace("/(tabs)/home");
-      // } else {
-      //   // 로그인되지 않았으면 로그인 페이지로 이동
-      //   router.replace("/login");
-      // }
+      if (userInfo) {
+        // 로그인된 사용자면, 홈 네비게이션으로 이동
+        router.replace("/(tabs)/home");
+      } else {
+        // 로그인되지 않았으면 로그인 페이지로 이동
+        router.replace("/login");
+      }

-  }, [router]);
+  }, [router, userInfo]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// const { userInfo } = useAuth();
useEffect(() => {
const timer = setTimeout(() => {
if (userInfo) {
// 로그인된 사용자면, 홈 네비게이션으로 이동
router.replace("/(tabs)/home");
} else {
// 로그인되지 않았으면 로그인 페이지로 이동
router.replace("/login");
}
router.replace("/(tabs)/home");
// if (userInfo) {
// // 로그인된 사용자면, 홈 네비게이션으로 이동
// router.replace("/(tabs)/home");
// } else {
// // 로그인되지 않았으면 로그인 페이지로 이동
// router.replace("/login");
// }
}, 100);
return () => clearTimeout(timer);
}, [userInfo, router]);
}, [router]);
const { userInfo } = useAuth();
useEffect(() => {
const timer = setTimeout(() => {
if (userInfo) {
// 로그인된 사용자면, 홈 네비게이션으로 이동
router.replace("/(tabs)/home");
} else {
// 로그인되지 않았으면 로그인 페이지로 이동
router.replace("/login");
}
}, 100);
return () => clearTimeout(timer);
}, [router, userInfo]);
🤖 Prompt for AI Agents
In app/index.tsx between lines 9 and 24, the user authentication logic is
commented out, causing all users to be redirected to the home page without
login. To fix this, restore the authentication check by uncommenting the
userInfo usage and conditionally redirect users to the home page if logged in or
to the login page if not. Ensure this logic is active before deployment to
enforce proper authentication flow.


// 로딩 화면 표시. TODO: 추후에 디자인 된 로딩스피너로 수정
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator size="large" color="#195B35" />
</View>
);
Expand Down
92 changes: 92 additions & 0 deletions features/auth/api/googleAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import * as WebBrowser from "expo-web-browser";
import axios from "axios";

const GOOGLE_CLIENT_ID = process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID ?? "";
const GOOGLE_REDIRECT_URI = process.env.EXPO_PUBLIC_GOOGLE_REDIRECT_URI ?? "";
const SERVER_BASE_URL = process.env.EXPO_PUBLIC_SERVER_BASE_URL ?? "";
Comment on lines +4 to +6
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

환경변수 검증 추가 필요

환경변수들이 빈 문자열로 초기화되어 있지만, 실제 런타임에서 이 값들이 유효한지 검증하는 로직이 없습니다.

다음과 같이 환경변수 검증을 추가하는 것을 권장합니다:

const GOOGLE_CLIENT_ID = process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID ?? "";
const GOOGLE_REDIRECT_URI = process.env.EXPO_PUBLIC_GOOGLE_REDIRECT_URI ?? "";
const SERVER_BASE_URL = process.env.EXPO_PUBLIC_SERVER_BASE_URL ?? "";

+// 환경변수 검증
+if (!GOOGLE_CLIENT_ID || !GOOGLE_REDIRECT_URI || !SERVER_BASE_URL) {
+  throw new Error("구글 로그인에 필요한 환경변수가 설정되지 않았습니다.");
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const GOOGLE_CLIENT_ID = process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID ?? "";
const GOOGLE_REDIRECT_URI = process.env.EXPO_PUBLIC_GOOGLE_REDIRECT_URI ?? "";
const SERVER_BASE_URL = process.env.EXPO_PUBLIC_SERVER_BASE_URL ?? "";
const GOOGLE_CLIENT_ID = process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID ?? "";
const GOOGLE_REDIRECT_URI = process.env.EXPO_PUBLIC_GOOGLE_REDIRECT_URI ?? "";
const SERVER_BASE_URL = process.env.EXPO_PUBLIC_SERVER_BASE_URL ?? "";
// 환경변수 검증
if (!GOOGLE_CLIENT_ID || !GOOGLE_REDIRECT_URI || !SERVER_BASE_URL) {
throw new Error("구글 로그인에 필요한 환경변수가 설정되지 않았습니다.");
}
🤖 Prompt for AI Agents
In features/auth/api/googleAuth.ts around lines 4 to 6, the environment
variables are assigned default empty strings without runtime validation. Add
validation logic after these assignments to check if any of the variables are
empty or invalid, and throw an error or handle the case appropriately to ensure
the app does not proceed with missing critical configuration.


// 브라우저 로그인 결과 처리를 위한 타입
export interface GoogleAuthResult {
type: "success" | "error" | "cancel";
params?: {
code?: string;
error?: string;
};
error?: Error;
}

// 구글 로그인 인증 브라우저를 열고 인증 코드를 받아오는 함수
export async function signInWithGoogle(): Promise<GoogleAuthResult> {
try {
// OAuth URL
const authUrl =
`https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${GOOGLE_CLIENT_ID}` +
`&redirect_uri=${encodeURIComponent(GOOGLE_REDIRECT_URI)}` +
`&response_type=code` +
`&scope=${encodeURIComponent("profile email")}` +
`&prompt=select_account`;

// 기본 브라우저로 구글 로그인 페이지 열기. 구글로그인은 Webview보다 WebBroswer 권장.
const result = await WebBrowser.openAuthSessionAsync(authUrl, GOOGLE_REDIRECT_URI);

// 브라우저 결과 처리
if (result.type === "success") {
// URL에서 code 파라미터 추출
const url = new URL(result.url);
const code = url.searchParams.get("code");

if (code) return { type: "success", params: { code } };

const error = url.searchParams.get("error");
return {
type: "error",
params: { error: error || "인증 코드를 받아오지 못했습니다." },
};
}

return { type: "cancel" };
} catch (error) {
console.error("Google Auth Error:", error);
return {
type: "error",
error: error instanceof Error ? error : new Error("알 수 없는 오류가 발생했습니다."),
};
}
}

// 백엔드와 통신해 사용자 정보 받아오는 함수
export async function exchangeGoogleCodeForUserInfo(code: string) {
try {
const response = await axios.post(`${SERVER_BASE_URL}/api/auth/google`, { code });
return response.data;
} catch (error) {
console.error("Google Token Exchange Error:", error);
throw error;
}
}
Comment on lines +59 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

백엔드 API 에러 처리 개선 필요

현재 백엔드 API 호출에서 HTTP 상태 코드별 에러 처리가 부족합니다.

export async function exchangeGoogleCodeForUserInfo(code: string) {
  try {
    const response = await axios.post(`${SERVER_BASE_URL}/api/auth/google`, { code });
+    
+    if (response.status !== 200) {
+      throw new Error(`서버 응답 오류: ${response.status}`);
+    }
+    
    return response.data;
  } catch (error) {
    console.error("Google Token Exchange Error:", error);
+    
+    if (axios.isAxiosError(error)) {
+      if (error.response?.status === 401) {
+        throw new Error("인증에 실패했습니다.");
+      } else if (error.response?.status === 500) {
+        throw new Error("서버 오류가 발생했습니다.");
+      }
+    }
+    
    throw error;
  }
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function exchangeGoogleCodeForUserInfo(code: string) {
try {
const response = await axios.post(`${SERVER_BASE_URL}/api/auth/google`, { code });
return response.data;
} catch (error) {
console.error("Google Token Exchange Error:", error);
throw error;
}
}
export async function exchangeGoogleCodeForUserInfo(code: string) {
try {
const response = await axios.post(`${SERVER_BASE_URL}/api/auth/google`, { code });
if (response.status !== 200) {
throw new Error(`서버 응답 오류: ${response.status}`);
}
return response.data;
} catch (error) {
console.error("Google Token Exchange Error:", error);
if (axios.isAxiosError(error)) {
if (error.response?.status === 401) {
throw new Error("인증에 실패했습니다.");
} else if (error.response?.status === 500) {
throw new Error("서버 오류가 발생했습니다.");
}
}
throw error;
}
}
🤖 Prompt for AI Agents
In features/auth/api/googleAuth.ts around lines 59 to 67, the current error
handling for the backend API call does not differentiate based on HTTP status
codes. Improve error handling by checking the HTTP status code in the catch
block and handle different cases accordingly, such as logging specific messages
or throwing custom errors for different status codes to provide clearer feedback
on the failure reason.


// 구글 로그인 전체 플로우 처리
export async function handleGoogleSignIn() {
try {
const authResult = await signInWithGoogle();

if (authResult.type === "success" && authResult.params?.code) {
const userInfo = await exchangeGoogleCodeForUserInfo(authResult.params.code);
return { success: true, user: userInfo };
} else if (authResult.type === "cancel") {
return { success: false, error: "로그인이 취소되었습니다." };
} else {
return {
success: false,
error: authResult.params?.error || "로그인 중 오류가 발생했습니다.",
};
}
} catch (error) {
console.error("Google Sign In Error:", error);
return {
success: false,
error: error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.",
};
}
}
41 changes: 37 additions & 4 deletions features/auth/ui/GoogleLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,44 @@
import React from "react";
import { TouchableOpacity, Text } from "react-native";
import React, { useState } from "react";
import { TouchableOpacity, Text, Alert, ActivityIndicator } from "react-native";
import type { SocialLoginProps } from "../model/types";
import { handleGoogleSignIn } from "../api/googleAuth";

export const GoogleLoginButton: React.FC<SocialLoginProps> = ({ onLoginSuccess, onLoginError }) => {
const [isLoading, setIsLoading] = useState(false);

const handleLogin = async () => {
try {
setIsLoading(true);

const result = await handleGoogleSignIn();

if (result.success && result.user) {
onLoginSuccess(result.user);
} else {
onLoginError(result.error || "구글 로그인 중 오류가 발생했습니다.");
Alert.alert("로그인 실패", result.error || "구글 로그인 중 오류가 발생했습니다.");
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : "알 수 없는 오류가 발생했습니다.";
onLoginError(errorMessage);
Alert.alert("로그인 실패", errorMessage);
} finally {
setIsLoading(false);
}
};

return (
<TouchableOpacity>
<Text className="">구글로 시작하기</Text>
<TouchableOpacity
className="w-full bg-white border border-gray-300 py-4 rounded-xl items-center justify-center flex-row"
onPress={handleLogin}
disabled={isLoading}
>
{isLoading ? (
<ActivityIndicator size="small" color="#4285F4" />
) : (
<Text className="text-black font-semibold text-base">구글로 시작하기</Text>
)}
</TouchableOpacity>
);
};
12 changes: 6 additions & 6 deletions features/auth/ui/KakaoLoginButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,26 @@ import { KakaoLoginWebView } from "./KakaoLoginWebView";
import type { SocialLoginProps } from "../model/types";

export const KakaoLoginButton: React.FC<SocialLoginProps> = ({ onLoginSuccess, onLoginError }) => {
const [showWebView, setShowWebView] = useState(false);
const [isShowWebView, setIsShowWebView] = useState(false);

const handleKakaoLogin = () => {
setShowWebView(true);
setIsShowWebView(true);
};

const handleLoginSuccess = (userInfo: any) => {
console.log("카카오 로그인 성공:", userInfo);
setShowWebView(false);
setIsShowWebView(false);
onLoginSuccess(userInfo);
};

const handleLoginError = (error: any) => {
console.error("카카오 로그인 실패:", error);
setShowWebView(false);
setIsShowWebView(false);
onLoginError(typeof error === "string" ? error : "카카오 로그인 중 오류가 발생했습니다.");
};

const handleClose = () => {
setShowWebView(false);
setIsShowWebView(false);
};

return (
Expand All @@ -35,7 +35,7 @@ export const KakaoLoginButton: React.FC<SocialLoginProps> = ({ onLoginSuccess, o
<Text className="text-black font-semibold text-base">카카오로 시작하기</Text>
</TouchableOpacity>

<Modal visible={showWebView} animationType="slide" presentationStyle="pageSheet">
<Modal visible={isShowWebView} animationType="slide" presentationStyle="pageSheet">
<KakaoLoginWebView
onLoginSuccess={handleLoginSuccess}
onLoginError={handleLoginError}
Expand Down
46 changes: 41 additions & 5 deletions features/auth/ui/NaverLoginButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,47 @@
import React from "react";
import { TouchableOpacity, Text } from "react-native";
import React, { useState } from "react";
import { TouchableOpacity, Text, Modal } from "react-native";
import { NaverLoginWebView } from "./NaverLoginWebView";
import type { SocialLoginProps } from "../model/types";

export const NaverLoginButton: React.FC<SocialLoginProps> = ({ onLoginSuccess, onLoginError }) => {
const [showWebView, setShowWebView] = useState(false);

const handleNaverLogin = () => {
setShowWebView(true);
};

const handleLoginSuccess = (userInfo: any) => {
console.log("네이버 로그인 성공:", userInfo);
setShowWebView(false);
onLoginSuccess(userInfo);
};

const handleLoginError = (error: any) => {
console.error("네이버 로그인 실패:", error);
setShowWebView(false);
onLoginError(typeof error === "string" ? error : "네이버 로그인 중 오류가 발생했습니다.");
};

const handleClose = () => {
setShowWebView(false);
};

return (
<TouchableOpacity>
<Text>네이버로 시작하기</Text>
</TouchableOpacity>
<>
<TouchableOpacity
onPress={handleNaverLogin}
className="w-full bg-[#03C75A] py-4 rounded-xl items-center justify-center"
>
<Text className="text-white font-semibold text-base">네이버로 시작하기</Text>
</TouchableOpacity>

<Modal visible={showWebView} animationType="slide" presentationStyle="pageSheet">
<NaverLoginWebView
onLoginSuccess={handleLoginSuccess}
onLoginError={handleLoginError}
onClose={handleClose}
/>
</Modal>
</>
);
};
Loading