Skip to content

Commit

Permalink
feat: 로그인 버튼 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
Xvezda committed May 13, 2024
1 parent c30ba4d commit e6f7cea
Show file tree
Hide file tree
Showing 8 changed files with 302 additions and 5 deletions.
13 changes: 13 additions & 0 deletions apps/api/src/services/auth/v1/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Hono, MiddlewareHandler, Context } from 'hono';
import { cors } from 'hono/cors';
import { csrf } from 'hono/csrf'
import { HTTPException } from 'hono/http-exception';
import { setCookie, getCookie, deleteCookie } from 'hono/cookie';
import * as jose from 'jose';
Expand Down Expand Up @@ -270,6 +271,18 @@ app.use('*', cors({
credentials: true,
}));

app.get(
'/logout',
csrf({
origin: (origin, c) => {
if (c.env.DEV) {
return true;
}
return /https:\/\/(\w+\.)cheda\.kr/.test(origin);
},
})
);

app.get('/logout', withPrevUrl, async (c) => {
try {
// access token 갱신 요청으로 이전 토큰을 무효화
Expand Down
23 changes: 19 additions & 4 deletions apps/web/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
"use client";

import { Suspense } from 'react';
import { useSearchParams } from 'next/navigation';
import { useEffect, Suspense } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import NaverLoginButton from '@/components/naver-login-button/button';
import useAuth from '@/hooks/useAuth';

export default function LoginPage() {
return (
<div>
<div className="flex flex-col space-y-5 justify-center items-center min-h-[calc(100vh-60px)]">
<h2 className="text-3xl font-bold">
체다 서비스에 로그인
</h2>
<Suspense>
<LoginButton />
</Suspense>
Expand All @@ -15,7 +19,18 @@ export default function LoginPage() {
}

function LoginButton() {
const auth = useAuth();

const router = useRouter();
const searchParams = useSearchParams();

return <NaverLoginButton prevUrl={decodeURIComponent(searchParams.get('prevUrl') ?? '')} />;
const prevUrl = decodeURIComponent(searchParams.get('prevUrl') ?? '');

useEffect(() => {
if (auth.data?.loggedIn) {
router.push(prevUrl || '/');
}
}, [auth.data]);

return <NaverLoginButton prevUrl={decodeURIComponent(prevUrl)} />;
}
6 changes: 6 additions & 0 deletions apps/web/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import Image from 'next/image';
import Link from 'next/link';

Expand All @@ -6,6 +8,7 @@ import { Button } from "@/components/ui/button"
import ThemeToggle from "@/components/theme-toggle";
import { blackHanSans } from '@/app/fonts';
import { cn } from '@/lib/utils';
import LoginButton from './login-button';
import logoImage from './cheda-transparent.png';

export default function Header() {
Expand All @@ -26,6 +29,9 @@ export default function Header() {
<Github className="h-[1.2rem] w-[1.2rem]" />
</Link>
</Button>
<div className="flex justify-center w-20">
<LoginButton />
</div>
</nav>
</header>
);
Expand Down
38 changes: 38 additions & 0 deletions apps/web/components/header/login-button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client';

import Link from 'next/link';
import { Skeleton } from '@/components/ui/skeleton';
import { Button } from "@/components/ui/button";
// import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import useAuth from '@/hooks/useAuth';

export default function LoginButton() {
const auth = useAuth();

if (auth.isLoading) {
return <Skeleton className="w-20 h-10" />;
}

if (!auth.data?.loggedIn) {
return (
<Button variant="outline" className="w-20" asChild>
<Link href="/login">로그인</Link>
</Button>
);
}

// return (
// <Avatar>
// <AvatarImage src={auth.data.user.userImage} alt={auth.data.user.userName} />
// <AvatarFallback>{auth.data.user.userName.substring(0, 1)}</AvatarFallback>
// </Avatar>
// );

return (
<Button variant="outline" className="w-18" asChild>
<Link href={`${process.env.NEXT_PUBLIC_API_ORIGIN}/services/auth/v1/logout`}>
로그아웃
</Link>
</Button>
);
}
50 changes: 50 additions & 0 deletions apps/web/components/ui/avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"use client"

import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"

import { cn } from "@/lib/utils"

const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName

const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName

const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName

export { Avatar, AvatarImage, AvatarFallback }
91 changes: 91 additions & 0 deletions apps/web/hooks/useAuth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { useAtomValue } from 'jotai';
import { atomWithQuery } from 'jotai-tanstack-query';
import { importSPKI, jwtVerify } from 'jose';

const JWT_PREFIX = 'http:cheda.kr/';

type PrefixRoot<TPrefix extends string, TValue extends { [k: string]: any }> = {
[k in keyof TValue as k extends string ? `${TPrefix}${k}` : never]: TValue[k];
};

type SessionPayload = PrefixRoot<typeof JWT_PREFIX, {
user: {
userId: string;
userName: string;
userImage: string;
accessToken: string;
};
}>;

type SecuredSessionPayload = PrefixRoot<typeof JWT_PREFIX, {
user: {
refreshToken: string;
};
}>;

type StatePayload = PrefixRoot<typeof JWT_PREFIX, {
state: {
id: string;
url: string;
};
}>;

type Auth = {
loggedIn: false;
user: null;
} | {
loggedIn: true;
user: {
userId: string;
userName: string;
userImage: string;
};
};

const authAtom = atomWithQuery<Auth>(() => ({
queryKey: ['auth'],
queryFn: async () => {
const sessionId = document.cookie
.split('; ')
.find((cookie) => cookie.startsWith('session_id='))
?.match(/^([^=]+)=(.*)/)
?.[2];

try {
const publicKey = await importSPKI(process.env.NEXT_PUBLIC_JWT_KEY!, 'ES256');
const token = await jwtVerify<SessionPayload>(sessionId ?? '', publicKey);

return {
loggedIn: true,
user: token.payload['http:cheda.kr/user'],
};
} catch (e) {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_ORIGIN}/services/auth/v1/me`, {
credentials: 'include',
});

if (!response.ok) {
return {
loggedIn: false,
user: null,
};
}

const result = await response.json();

return {
loggedIn: true,
user: {
userId: result.id,
userName: result.name,
userImage: result.image,
},
};
}
},
}));

export default function useAuth() {
return useAtomValue(authAtom);
}

8 changes: 7 additions & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,24 @@
"dependencies": {
"@next/third-parties": "^14.2.1",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/query-core": "^5.36.0",
"@tanstack/react-query": "^5.29.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"jose": "^5.2.4",
"jotai": "^2.8.0",
"jotai-tanstack-query": "^0.8.5",
"lucide-react": "^0.368.0",
"next": "14.1.4",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"tailwind-merge": "^2.2.2",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"wonka": "^6.3.4"
},
"devDependencies": {
"@types/node": "^20",
Expand Down
Loading

0 comments on commit e6f7cea

Please sign in to comment.