diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx index 350a5a15..13c4d836 100644 --- a/src/app/(auth)/login/page.tsx +++ b/src/app/(auth)/login/page.tsx @@ -65,12 +65,16 @@ export default function LoginPage() {
구글 로그인 카카오 로그인 diff --git a/src/app/(auth)/signup/applicant/page.tsx b/src/app/(auth)/signup/applicant/page.tsx index c79e0d31..807fbf9f 100644 --- a/src/app/(auth)/signup/applicant/page.tsx +++ b/src/app/(auth)/signup/applicant/page.tsx @@ -109,14 +109,14 @@ export default function ApplicantSignupPage() {
구글 회원가입 카카오 회원가입 diff --git a/src/app/(auth)/signup/owner/page.tsx b/src/app/(auth)/signup/owner/page.tsx index aed11e2b..a55cf24a 100644 --- a/src/app/(auth)/signup/owner/page.tsx +++ b/src/app/(auth)/signup/owner/page.tsx @@ -131,14 +131,14 @@ export default function OwnerSignupPage() {
구글 회원가입 카카오 회원가입 diff --git a/src/app/api/oauth/callback/google/route.ts b/src/app/api/oauth/callback/google/route.ts index 3fa90861..64d21043 100644 --- a/src/app/api/oauth/callback/google/route.ts +++ b/src/app/api/oauth/callback/google/route.ts @@ -1,33 +1,27 @@ import { NextRequest, NextResponse } from "next/server"; import axios from "axios"; import { decodeJwt } from "@/middleware"; -import { OauthUser } from "@/types/oauth/oauthReq"; import apiClient from "@/lib/apiClient"; +import { OauthLoginUser, OauthResponse, OauthSignupUser } from "@/types/oauth/oauth"; +import { cookies } from "next/headers"; -export const GET = async (req: NextRequest) => { - const searchParams = req.nextUrl.searchParams; +export const GET = async (request: NextRequest) => { + const searchParams = request.nextUrl.searchParams; const code = searchParams.get("code"); const state = searchParams.get("state"); - if (!code) { - return NextResponse.json({ message: "Code not found" }, { status: 400 }); + if (!code || !state) { + return NextResponse.json({ message: `${!code ? "Code" : "State"} not found` }, { status: 400 }); } - if (!state) { - return NextResponse.json({ message: "State not found" }, { status: 400 }); - } - - // `state`를 JSON으로 파싱 let parsedState; try { parsedState = JSON.parse(decodeURIComponent(state)); - } catch (error) { - console.error("Failed to parse state:", error); + } catch { return NextResponse.json({ message: "Invalid state format" }, { status: 400 }); } - const { provider, role } = parsedState; - + const { provider, action, role } = parsedState; const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token"; const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID; const clientSecret = process.env.NEXT_PUBLIC_GOOGLE_SECRET; @@ -38,8 +32,8 @@ export const GET = async (req: NextRequest) => { } try { - // Access Token 요청 - const tokenResponse = await axios.post(GOOGLE_TOKEN_URL, null, { + // Google Access Token 요청 + const { data: tokenResponse } = await axios.post(GOOGLE_TOKEN_URL, null, { params: { code, client_id: clientId, @@ -49,41 +43,76 @@ export const GET = async (req: NextRequest) => { }, }); - const { id_token } = tokenResponse.data; - - // id_token 디코딩 + const { id_token } = tokenResponse; const decodedIdToken = decodeJwt(id_token); if (!decodedIdToken) { return NextResponse.json({ message: "Invalid ID token" }, { status: 400 }); } - const googleUser: OauthUser = { - role: role, - name: decodedIdToken.name, - token: id_token, + const googleUser: { signup: OauthSignupUser; login: OauthLoginUser } = { + signup: { + role, + name: decodedIdToken.name, + token: id_token, + }, + login: { + token: id_token, + redirectUri, + }, }; - console.log("Google user:", googleUser); - // OAuth 회원가입 API로 회원가입 요청 - try { - const googleSignupResponse = await apiClient.post(`/oauth/sign-up/${provider}`, googleUser); - console.log("구글 회원가입 성공:", googleSignupResponse.data); - } catch (error) { - const errorMessage = (error as any).response?.data; - console.error("구글 회원가입 에러:", errorMessage); - } - // 사용자 정보를 클라이언트에 반환 - const response = NextResponse.redirect("http://localhost:3000"); - response.cookies.set("user", JSON.stringify(googleUser), { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 60 * 60 * 24, // 1일 - path: "/", - }); - return response; - } catch (error) { - console.error("Google login error:", error); - return NextResponse.json({ message: "서버에러" }, { status: 500 }); + const processUser = async () => { + if (action === "signup") { + try { + const response = await apiClient.post(`/oauth/sign-up/${provider}`, googleUser.signup); + console.log("구글 회원가입 성공:", response.data); + } catch (error: any) { + if (error.response?.status === 400) { + console.log("이미 등록된 사용자입니다. 로그인 시도 중..."); + await loginUser(); + } else { + throw new Error("회원가입 중 서버 오류"); + } + } + } else if (action === "login") { + await loginUser(); + } else { + throw new Error("잘못된 작업 에러"); + } + }; + + const loginUser = async () => { + const { data: loginResponse } = await axios.post( + `${process.env.NEXT_PUBLIC_DOMAIN_URL}/api/oauth/login/${provider}`, + googleUser.login + ); + console.log("구글 로그인 성공:", loginResponse); + + // 쿠키 저장 + const { accessToken, refreshToken } = loginResponse; + setCookies(accessToken, refreshToken); + }; + + const setCookies = (accessToken: string, refreshToken: string) => { + cookies().set("accessToken", accessToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); + cookies().set("refreshToken", refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); + }; + + await processUser(); + } catch (error: any) { + console.error("OAuth 처리 중 오류:", error.message || error); + return NextResponse.json({ message: error.message || "서버 오류" }, { status: 500 }); } + + return NextResponse.redirect(new URL("/", request.url)); }; diff --git a/src/app/api/oauth/callback/kakao/route.ts b/src/app/api/oauth/callback/kakao/route.ts index 1694d590..bc7a322a 100644 --- a/src/app/api/oauth/callback/kakao/route.ts +++ b/src/app/api/oauth/callback/kakao/route.ts @@ -1,70 +1,102 @@ import { NextRequest, NextResponse } from "next/server"; -import { OauthUser } from "@/types/oauth/oauthReq"; +import axios from "axios"; import apiClient from "@/lib/apiClient"; +import { OauthLoginUser, OauthResponse, OauthSignupUser } from "@/types/oauth/oauth"; +import { cookies } from "next/headers"; -export const GET = async (req: NextRequest) => { - const searchParams = req.nextUrl.searchParams; +export const GET = async (request: NextRequest) => { + const searchParams = request.nextUrl.searchParams; const code = searchParams.get("code"); const state = searchParams.get("state"); - if (!code) { - return NextResponse.json({ message: "Code not found" }, { status: 400 }); - } - - if (!state) { - return NextResponse.json({ message: "State not found" }, { status: 400 }); + if (!code || !state) { + return NextResponse.json({ message: `${!code ? "Code" : "State"} not found` }, { status: 400 }); } let parsedState; try { parsedState = JSON.parse(decodeURIComponent(state)); - } catch (error) { - console.error("Failed to parse state:", error); + } catch { return NextResponse.json({ message: "Invalid state format" }, { status: 400 }); } - const { provider, role } = parsedState; - const clientId = process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY; + const { provider, action, role } = parsedState; const redirectUri = process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI; - if (!clientId || !redirectUri) { + if (!redirectUri) { return NextResponse.json({ message: "Environment variables not set" }, { status: 500 }); } - const kakaoUser: OauthUser = { - role: role, - name: "", // 기본값 설정 (빈 문자열) - token: code, // 인가코드 그대로 전달 - redirectUri: redirectUri, + const kakaoUser: { signup: OauthSignupUser; login: OauthLoginUser } = { + signup: { + role: role || "user", // 기본 역할 설정 + name: "", // Kakao는 이름을 제공하지 않으므로 기본값 + token: code, // 인가 코드 전달 + }, + login: { + token: code, // 인가 코드 전달 + redirectUri, // 리다이렉트 URI 포함 + }, }; - try { - // 인가코드를 포함한 데이터를 백엔드로 전달 - const kakaoSignupResponse = await apiClient.post(`/oauth/sign-up/${provider}`, kakaoUser); - console.log("카카오 회원가입 성공:", kakaoSignupResponse.data); + const processUser = async () => { + if (action === "signup") { + try { + const response = await apiClient.post(`/oauth/sign-up/${provider}`, kakaoUser.signup); + console.log("카카오 회원가입 성공:", response.data); + } catch (error: any) { + if (error.response?.status === 400) { + console.log("이미 등록된 사용자입니다. 로그인 시도 중..."); + await loginUser(); + } else { + throw new Error("회원가입 중 서버 오류"); + } + } + } else if (action === "login") { + await loginUser(); + } else { + throw new Error("Invalid action"); + } + }; - // 사용자 정보를 클라이언트에 반환 - // return NextResponse.json(kakaoSignupResponse.data); - } catch (error: any) { - // 에러 타입 명시 - console.error("카카오 회원가입 에러:", error.response?.data || error.message); + const loginUser = async () => { + try { + const { data: loginResponse } = await axios.post( + `${process.env.NEXT_PUBLIC_DOMAIN_URL}/api/oauth/login/${provider}`, + kakaoUser.login + ); + console.log("카카오 로그인 성공:", loginResponse); - // return NextResponse.json({ message: error.response?.data || "Error during Kakao signup" }, { status: 500 }); - } + // 쿠키 저장 + const { accessToken, refreshToken } = loginResponse; + setCookies(accessToken, refreshToken); + } catch (error: any) { + console.error("카카오 로그인 중 오류:", error.message || error); + throw new Error("로그인 중 서버 오류"); + } + }; - try { - // 사용자 정보를 클라이언트에 반환 - const response = NextResponse.redirect("http://localhost:3000"); - response.cookies.set("user", JSON.stringify(kakaoUser), { + const setCookies = (accessToken: string, refreshToken: string) => { + cookies().set("accessToken", accessToken, { httpOnly: true, secure: process.env.NODE_ENV === "production", - sameSite: "strict", - maxAge: 60 * 60 * 24, // 1일 + sameSite: "lax", path: "/", }); - return response; + cookies().set("refreshToken", refreshToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + path: "/", + }); + }; + + try { + await processUser(); } catch (error: any) { - console.error("카카오 회원가입 에러:", error.response?.data || error.message); - return NextResponse.json({ message: error.response?.data || "서버에러" }, { status: 500 }); + console.error("OAuth 처리 중 오류:", error.message || error); + return NextResponse.json({ message: error.message || "서버 오류" }, { status: 500 }); } + + return NextResponse.redirect(new URL("/", request.url)); }; diff --git a/src/app/api/oauth/login/[provider]/route.ts b/src/app/api/oauth/login/[provider]/route.ts index b766e3ea..08bfc0db 100644 --- a/src/app/api/oauth/login/[provider]/route.ts +++ b/src/app/api/oauth/login/[provider]/route.ts @@ -6,6 +6,7 @@ import apiClient from "@/lib/apiClient"; // OAuth 로그인 API export async function POST(request: Request, { params }: { params: { provider: string } }) { try { + console.log("/api/oauth/login"); const provider = params.provider; // provider 유효성 검사 @@ -15,28 +16,10 @@ export async function POST(request: Request, { params }: { params: { provider: s // 요청 본문 파싱 const body = await request.json(); - + console.log("Received body:", body); // 요청 본문 로그 출력 // OAuth 로그인 요청 const response = await apiClient.post(`/oauth/sign-in/${provider}`, body); - // 응답에서 토큰 추출 - const { accessToken, refreshToken } = response.data; - - // 쿠키에 토큰 저장 - cookies().set("accessToken", accessToken, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - path: "/", - }); - - cookies().set("refreshToken", refreshToken, { - httpOnly: true, - secure: process.env.NODE_ENV === "production", - sameSite: "lax", - path: "/", - }); - return NextResponse.json(response.data); } catch (error: unknown) { if (error instanceof AxiosError) { @@ -45,6 +28,6 @@ export async function POST(request: Request, { params }: { params: { provider: s return NextResponse.json({ message: error.response.data.message }, { status: error.response.status }); } } - return NextResponse.json({ message: "Internal Server Error" }, { status: 500 }); + return NextResponse.json({ message: "서버오류" }, { status: 500 }); } } diff --git a/src/types/oauth/oauth.d.ts b/src/types/oauth/oauth.d.ts new file mode 100644 index 00000000..ca4ca5a8 --- /dev/null +++ b/src/types/oauth/oauth.d.ts @@ -0,0 +1,22 @@ +export interface OauthSignupUser { + location?: string; + phoneNumber?: string; + storePhoneNumber?: string; + storeName?: string; + role: string; + nickname?: string; + name: string; + redirectUri?: string; + token: string; +} + +export interface OauthLoginUser { + redirectUri: string; + token: string; +} + +export interface OauthResponse { + use: KakaoSignupUser; + refreshToken: string; + accessToken: string; +} diff --git a/src/types/oauth/oauthReq.ts b/src/types/oauth/oauthReq.ts deleted file mode 100644 index 10df9aa6..00000000 --- a/src/types/oauth/oauthReq.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface OauthUser { - location?: string; - phoneNumber?: string; - storePhoneNumber?: string; - storeName?: string; - role: string; - nickname?: string; - name: string; - redirectUri?: string; - token: string; -} diff --git a/tsconfig.json b/tsconfig.json index 69d63471..89c2d401 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,5 +24,5 @@ "typeRoots": ["./node_modules/@types"] }, "include": ["next-env.d.ts", "src/**/*.ts", "src/**/*.tsx", ".next/types/**/*.ts", "custom.d.ts"], - "exclude": ["node_modules"] + "exclude": ["node_modules", ".next", ".storybook", "storybook-static"] }