Skip to content
Merged
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
8 changes: 6 additions & 2 deletions src/app/(auth)/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,16 @@ export default function LoginPage() {
</div>
<div className="flex justify-center space-x-6">
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&state=${encodeURIComponent(
JSON.stringify({ provider: "google", action: "login" })
)}`}
>
<Image src="/icons/social/social_google.svg" width={72} height={72} alt="구글 로그인" />
</Link>
<Link
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&response_type=code`}
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&response_type=code&state=${encodeURIComponent(
JSON.stringify({ provider: "kakao", action: "login" })
)}`}
>
<Image src="/icons/social/social_kakao.svg" width={72} height={72} alt="카카오 로그인" />
</Link>
Expand Down
4 changes: 2 additions & 2 deletions src/app/(auth)/signup/applicant/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ export default function ApplicantSignupPage() {
<div className="flex justify-center space-x-4">
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&state=${encodeURIComponent(
JSON.stringify({ provider: "google", role: "APPLICANT" })
JSON.stringify({ provider: "google", action: "signup", role: "APPLICANT" })
)}`}
>
<Image src="/icons/social/social_google.svg" width={72} height={72} alt="구글 회원가입" />
</Link>
<Link
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&response_type=code&state=${encodeURIComponent(
JSON.stringify({ provider: "kakao", role: "APPLICANT" })
JSON.stringify({ provider: "kakao", action: "signup", role: "APPLICANT" })
)}`}
>
<Image src="/icons/social/social_kakao.svg" width={72} height={72} alt="카카오 회원가입" />
Expand Down
4 changes: 2 additions & 2 deletions src/app/(auth)/signup/owner/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,14 +131,14 @@ export default function OwnerSignupPage() {
<div className="flex justify-center space-x-4">
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&state=${encodeURIComponent(
JSON.stringify({ provider: "google", role: "OWNER" })
JSON.stringify({ provider: "google", action: "signup", role: "OWNER" })
)}`}
>
<Image src="/icons/social/social_google.svg" width={72} height={72} alt="구글 회원가입" />
</Link>
<Link
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API_KEY}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI}&response_type=code&state=${encodeURIComponent(
JSON.stringify({ provider: "kakao", role: "OWNER" })
JSON.stringify({ provider: "kakao", action: "signup", role: "OWNER" })
)}`}
>
<Image src="/icons/social/social_kakao.svg" width={72} height={72} alt="카카오 회원가입" />
Expand Down
119 changes: 74 additions & 45 deletions src/app/api/oauth/callback/google/route.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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,
Expand All @@ -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<OauthResponse>(`/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<OauthResponse>(
`${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));
};
110 changes: 71 additions & 39 deletions src/app/api/oauth/callback/kakao/route.ts
Original file line number Diff line number Diff line change
@@ -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<OauthResponse>(
`${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));
};
23 changes: 3 additions & 20 deletions src/app/api/oauth/login/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 유효성 검사
Expand All @@ -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) {
Expand All @@ -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 });
}
}
Loading
Loading