Skip to content

Commit

Permalink
Merge branch 'main' into feature/inv-29
Browse files Browse the repository at this point in the history
  • Loading branch information
xilucks authored Jul 22, 2024
2 parents e5206d4 + ed9ee80 commit 6cc56b3
Show file tree
Hide file tree
Showing 28 changed files with 1,350 additions and 6 deletions.
29 changes: 26 additions & 3 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,4 +1,27 @@
# database
## @see https://console.neon.tech/app/projects/wild-union-33027706
DATABASE_URL=
NEXT_PUBLIC_KAKAOMAP_BASE_URL =
NEXT_PUBLIC_DAUMCDN_POSTOCDE_URL =
NEXT_PUBLIC_KAKAO_MAP_API_KEY=

# OAUTH
## GOOGLE
## @see https://console.cloud.google.com/apis/credentials?project=invi-428615
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=

## KAKAO
### @see https://developers.kakao.com/console/app/1102339/config/appKey
KAKAO_CLIENT_ID=
### @see https://developers.kakao.com/console/app/1102339/product/login/security
KAKAO_CLIENT_SECRET=
KAKAO_REDIRECT_URI=

KAKAO_MAP_BASE_URL=
KAKAO_MAP_API_KEY=
DAUMCDN_POSTOCDE_URL=

## NAVER
### @see https://developers.naver.com/apps/#/myapps/f4vceVr1QiIcw1FITmMo/overview
NAVER_CLIENT_ID=
NAVER_CLIENT_SECRET=
NAVER_REDIRECT_URI=
Binary file modified bun.lockb
Binary file not shown.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
"ui": "bunx shadcn-ui@latest",
"ui:add": "bunx shadcn-ui@latest add",
"ui:lint": "bunx prettier src/components/ui/* --write",
"db:lint": "bunx prettier src/lib/db/migrations/**/*.json --write",
"db:lint": "bunx prettier src/lib/db/migrations/**/*.{json,ts} --write",
"db:generate": "drizzle-kit generate && bun db:lint",
"db:migrate": "drizzle-kit migrate && bun db:lint",
"db:push": "drizzle-kit push",
"db:pull": "drizzle-kit introspect",
"db:pull": "drizzle-kit introspect && bun db:lint",
"db:drop": "drizzle-kit drop",
"db:studio": "drizzle-kit studio",
"db:check": "drizzle-kit check"
},
"dependencies": {
"@lucia-auth/adapter-drizzle": "^1.0.7",
"@neondatabase/serverless": "^0.9.4",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-alert-dialog": "^1.1.1",
Expand All @@ -38,10 +39,13 @@
"@tanstack/react-form": "^0.26.1",
"@tanstack/react-query": "^5.50.1",
"@tanstack/zod-form-adapter": "^0.25.3",
"arctic": "^2.0.0-next.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"drizzle-orm": "^0.31.3",
"es-hangul": "^1.4.2",
"ky": "^1.4.0",
"lucia": "^3.2.0",
"next": "14.2.4",
"next-themes": "^0.3.0",
"react": "^18.3.1",
Expand Down
65 changes: 65 additions & 0 deletions src/app/(auth)/sign-in/google/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { OAuth2RequestError } from "arctic";
import ky from "ky";
import { cookies } from "next/headers";
import { google } from "~/lib/auth/lucia";
import { createSession } from "~/lib/auth/utils";
import { findOrCreateUser } from "~/lib/db/schema/users.query";

export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const storedState = cookies().get(`google_oauth_state`)?.value;
const codeVerifier = cookies().get(`google_oauth_code_verifier`)?.value;

if (!code || !storedState || !codeVerifier || state !== storedState) {
return new Response(null, {
status: 400,
});
}

try {
// @see https://arctic.js.org/providers/google
const tokens = await google.validateAuthorizationCode(code, codeVerifier);
const accessToken = tokens.accessToken();

// @see https://developers.google.com/identity/openid-connect/openid-connect#an-id-tokens-payload
const user = await ky("https://openidconnect.googleapis.com/v1/userinfo", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
}).json<{
sub: string;
name: string;
given_name: string;
picture: string;
email: string;
}>();
console.log(":user", user);

const dbUser = await findOrCreateUser({
name: user.name,
email: user.email,
provider: "google",
});

await createSession(dbUser.id);

return new Response(null, {
status: 302,
headers: {
Location: "/playground/sign-in",
},
});
} catch (e) {
console.log(e);
if (e instanceof OAuth2RequestError) {
return new Response(e.message, {
status: 400,
});
}
return new Response(null, {
status: 500,
});
}
}
33 changes: 33 additions & 0 deletions src/app/(auth)/sign-in/google/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { generateCodeVerifier, generateState } from "arctic";
import { cookies } from "next/headers";
import { google } from "~/lib/auth/lucia";
import { env } from "~/lib/env";

export async function GET(): Promise<Response> {
const state = generateState();
const codeVerifier = generateCodeVerifier();

// @see https://developers.google.com/identity/openid-connect/openid-connect?hl=ko#an-id-tokens-payload
const url = google.createAuthorizationURL(state, codeVerifier, [
"openid",
"profile",
"email",
]);

// @see https://arcticjs.dev/guides/oauth2-pkce
cookies().set("google_oauth_state", state, {
path: "/",
secure: env.NODE_ENV === "production",
httpOnly: true,
maxAge: 60 * 10,
sameSite: "lax",
});
cookies().set("google_oauth_code_verifier", codeVerifier, {
path: "/",
secure: env.NODE_ENV === "production",
httpOnly: true,
maxAge: 60 * 10,
});

return Response.redirect(url);
}
66 changes: 66 additions & 0 deletions src/app/(auth)/sign-in/kakao/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { OAuth2RequestError } from "arctic";
import ky from "ky";
import { kakao } from "~/lib/auth/lucia";
import { createSession } from "~/lib/auth/utils";
import { findOrCreateUser } from "~/lib/db/schema/users.query";

export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const code = url.searchParams.get("code");

if (!code) {
return new Response(null, {
status: 400,
});
}

try {
const tokens = await kakao.validateAuthorizationCode(code);
const accessToken = tokens.accessToken();

// @see https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
const user = await ky("https://kapi.kakao.com/v2/user/me", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
}).json<{
id: string;
name: string;
// TODO: 비즈앱 전환
email: string;
phone_number: string;
kakao_account: {
profile: {
nickname: string;
profile_image_url: string;
};
};
}>();
console.log(":user", user);

const dbUser = await findOrCreateUser({
name: user.name,
email: user.email,
provider: "kakao",
});

await createSession(dbUser.id);

return new Response(null, {
status: 302,
headers: {
Location: "/playground/sign-in",
},
});
} catch (e) {
console.log(e);
if (e instanceof OAuth2RequestError) {
return new Response(e.message, {
status: 400,
});
}
return new Response(null, {
status: 500,
});
}
}
11 changes: 11 additions & 0 deletions src/app/(auth)/sign-in/kakao/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { generateState } from "arctic";
import { kakao } from "~/lib/auth/lucia";

export async function GET(): Promise<Response> {
const state = generateState();

// @see https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite#consent-item
const url = kakao.createAuthorizationURL(state, ["account_email"]);

return Response.redirect(url);
}
67 changes: 67 additions & 0 deletions src/app/(auth)/sign-in/naver/callback/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { OAuth2RequestError } from "arctic";
import ky from "ky";
import { naver } from "~/lib/auth/lucia";
import { createSession } from "~/lib/auth/utils";
import { findOrCreateUser } from "~/lib/db/schema/users.query";

export async function GET(request: Request): Promise<Response> {
const url = new URL(request.url);
const code = url.searchParams.get("code");
const state = url.searchParams.get("state");
const errorCode = url.searchParams.get("error");
const errorDescription = url.searchParams.get("error_description");

if (!code || !state) {
return new Response(errorDescription, {
status: errorCode ? parseInt(errorCode) : 400,
});
}

try {
const tokens = await naver.validateAuthorizationCode(code, state);
const accessToken = tokens.accessToken();

// @see https://developers.naver.com/docs/login/devguide/devguide.md#3-4-5-접근-토큰을-이용하여-프로필-api-호출하기
const res = await ky("https://openapi.naver.com/v1/nid/me", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
}).json<{
resultcode: string;
message: string;
response: {
id: string;
nickname: string;
profile_image: string;
email: string;
name: string;
};
}>();
console.log(":res", res);

const dbUser = await findOrCreateUser({
name: res.response.name,
email: res.response.email,
provider: "naver",
});

await createSession(dbUser.id);

return new Response(null, {
status: 302,
headers: {
Location: "/playground/sign-in",
},
});
} catch (e) {
console.log(e);
if (e instanceof OAuth2RequestError) {
return new Response(e.message, {
status: 400,
});
}
return new Response(null, {
status: 500,
});
}
}
10 changes: 10 additions & 0 deletions src/app/(auth)/sign-in/naver/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { generateState } from "arctic";
import { naver } from "~/lib/auth/lucia";

export async function GET(): Promise<Response> {
const state = generateState();

const url = naver.createAuthorizationURL(state);

return Response.redirect(url);
}
28 changes: 28 additions & 0 deletions src/app/(auth)/sign-out/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"use server";

import { headers } from "next/headers";
import { redirect } from "next/navigation";
import {
getAuth,
invalidateAuth,
invalidateSessionCookie,
} from "~/lib/auth/utils";

export async function signOutAction(redirectUrl = "/playground/sign-in") {
const { session } = await getAuth();
if (!session) {
invalidateSessionCookie();
return { error: "Unauthorized" };
}

await invalidateAuth(session.id);

const headersList = headers();
const referer = headersList.get("referer") || "/";
const currentUrl = new URL(referer).pathname;
if (currentUrl === redirectUrl) {
return { refresh: true };
} else {
redirect(redirectUrl);
}
}
28 changes: 28 additions & 0 deletions src/app/(auth)/sign-out/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { signOutAction } from "~/app/(auth)/sign-out/actions";

export async function GET(): Promise<Response> {
const result = await signOutAction();

if (result.error) {
return new Response(null, {
status: 401,
});
}

if (result.refresh) {
return new Response(null, {
status: 200,
headers: {
"Content-Type": "text/html",
Refresh: "0",
},
});
}

return new Response(null, {
status: 302,
headers: {
Location: "/playground/sign-in",
},
});
}
2 changes: 1 addition & 1 deletion src/app/(playground)/playground/inner-tools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export function AMain(props: React.HTMLAttributes<HTMLDivElement>) {
<main
{...props}
className={cn(
"mx-auto w-full max-w-2xl space-y-20 px-10 pt-20",
"mx-auto w-full max-w-2xl space-y-20 px-10 py-20",
props.className,
)}
/>
Expand Down
Loading

0 comments on commit 6cc56b3

Please sign in to comment.