diff --git a/src/app/routes/_index.tsx b/src/app/routes/_index.tsx index c61a7e3..66e2a28 100644 --- a/src/app/routes/_index.tsx +++ b/src/app/routes/_index.tsx @@ -1,6 +1,6 @@ -import type { MetaFunction } from '@remix-run/node'; +import type { LoaderFunction, MetaFunction } from '@remix-run/node'; import { json } from '@remix-run/node'; -import { withAuthenticated } from 'src/app/server/withAuthenticated'; +import { authenticate } from 'src/app/server/authenticate'; import { getAllInfo, info } from 'src/types'; import { InfoListPage } from 'src/pages/main/info_list/InfoListPage'; import { useLoaderData } from '@remix-run/react'; @@ -10,19 +10,23 @@ export const meta: MetaFunction = () => { return [{ title: '구구' }, { name: 'description', content: '내 사랑을 구해줄래? 구해줄게!' }]; }; -export const loader = withAuthenticated(async (_, accessToken) => { +export const loader: LoaderFunction = async ({ request }) => { + const accessToken = await authenticate(request); + const { data: profileList } = await getAllInfo({ headers: { Authorization: `Bearer ${accessToken}`, }, }); + const { data: userInfo } = await info({ headers: { Authorization: `Bearer ${accessToken}`, }, }); + return json({ userInfo, profileList }); -}); +}; export default function Index() { const { profileList, userInfo } = useLoaderData(); diff --git a/src/app/routes/api.$.tsx b/src/app/routes/api.$.tsx index 4736d45..4775018 100644 --- a/src/app/routes/api.$.tsx +++ b/src/app/routes/api.$.tsx @@ -1,43 +1,11 @@ import { ActionFunction, LoaderFunction } from '@remix-run/node'; -import { withAuthenticated } from 'src/app/server/withAuthenticated'; -// -// export const loader = withAuthenticated(async ({ request }: LoaderFunctionArgs, accessToken) => { -// const url = new URL(request.url); -// const newHeaders = new AxiosHeaders(); -// request.headers.forEach((v, k) => newHeaders.set(k, v)); -// request.headers.set('Authorization', `Bearer ${accessToken}`); -// try { -// return customInstance({ -// method: request.method, -// url: `${url.pathname}${url.search}`, -// headers: { -// Authorization: `Bearer ${accessToken}`, -// }, -// }); -// } catch (e) { -// console.error(e); -// return null; -// } -// }); -// -// export const action = withAuthenticated(async ({ request }: ActionFunctionArgs, accessToken) => { -// const url = new URL(request.url); -// const newHeaders = new AxiosHeaders(); -// request.headers.forEach((v, k) => newHeaders.set(k, v)); -// request.headers.set('Authorization', `Bearer ${accessToken}`); -// -// return customInstance({ -// method: request.method, -// url: `${url.pathname}${url.search}`, -// headers: { -// Authorization: `Bearer ${accessToken}`, -// }, -// }); -// }); +import { authenticate } from 'src/app/server/authenticate'; const apiURL = new URL(process.env.API_BASE_URL ?? ''); -export const loader: LoaderFunction = withAuthenticated((args, accessToken) => { +export const loader: LoaderFunction = async (args) => { + const accessToken = await authenticate(args.request); + const url = new URL(args.request.url); url.protocol = apiURL.protocol; url.host = apiURL.host; @@ -52,9 +20,11 @@ export const loader: LoaderFunction = withAuthenticated((args, accessToken) => { }, }), ); -}); +}; + +export const action: ActionFunction = async (args) => { + const accessToken = await authenticate(args.request); -export const action: ActionFunction = withAuthenticated((args, accessToken) => { const url = new URL(args.request.url); url.protocol = apiURL.protocol; url.host = apiURL.host; @@ -70,4 +40,4 @@ export const action: ActionFunction = withAuthenticated((args, accessToken) => { }, }), ); -}); +}; diff --git a/src/app/routes/auth.kakao.tsx b/src/app/routes/auth.kakao.tsx index 8130b86..e663b66 100644 --- a/src/app/routes/auth.kakao.tsx +++ b/src/app/routes/auth.kakao.tsx @@ -1,9 +1,22 @@ import { LoaderFunction, redirect } from '@remix-run/node'; import { loginKakao } from 'src/types'; import { commitSession, getAuthSession } from 'src/app/server/sessions'; -import { getRefreshTokenFromHeader } from 'src/app/server/getRefreshToken'; +import dayjs from 'dayjs'; export const loader: LoaderFunction = async ({ request }) => { + if (import.meta.env.DEV) { + const session = await getAuthSession(request); + session.set('accessToken', process.env.VITE_DEV_JWT_TOKEN ?? ''); + session.set('refreshToken', process.env.VITE_DEV_JWT_TOKEN ?? ''); + session.set('expiredAt', dayjs().add(1, 'day').valueOf().toString()); + + return redirect('/', { + headers: { + 'Set-Cookie': await commitSession(session), + }, + }); + } + const searchParams = new URL(request.url).searchParams; const code = searchParams.get('code'); @@ -15,25 +28,18 @@ export const loader: LoaderFunction = async ({ request }) => { } try { - const { data, headers } = await loginKakao({ code }); - - const refreshToken = getRefreshTokenFromHeader(headers); - if (!refreshToken) return redirect('/login'); + const { data } = await loginKakao({ code }); const session = await getAuthSession(request); session.set('accessToken', data.accessToken); - session.set('refreshToken', refreshToken); - - const token = await getAuthSession(request); - if (token) { - return redirect('/', { - headers: { - 'Set-Cookie': await commitSession(session), - }, - }); - } + session.set('refreshToken', data.refreshToken); + session.set('expiredAt', dayjs().add(1, 'day').valueOf().toString()); - return redirect('/login'); + return redirect('/', { + headers: { + 'Set-Cookie': await commitSession(session), + }, + }); } catch (e) { console.error(e); return redirect('/login'); diff --git a/src/app/routes/mypage.tsx b/src/app/routes/mypage.tsx index d0f4046..8a1d790 100644 --- a/src/app/routes/mypage.tsx +++ b/src/app/routes/mypage.tsx @@ -1,9 +1,12 @@ import { MyPage as _MyPage } from 'src/pages/mypage/MyPage'; -import { withAuthenticated } from 'src/app/server/withAuthenticated'; +import { authenticate } from 'src/app/server/authenticate'; import { info } from 'src/types'; import { useLoaderData } from '@remix-run/react'; +import { LoaderFunction } from '@remix-run/node'; + +export const loader: LoaderFunction = async ({ request }) => { + const accessToken = await authenticate(request); -export const loader = withAuthenticated(async (_, accessToken) => { const { data: userInfo } = await info({ headers: { Authorization: `Bearer ${accessToken}`, @@ -11,7 +14,7 @@ export const loader = withAuthenticated(async (_, accessToken) => { }); return { userInfo }; -}); +}; export default function MyPage() { const { userInfo } = useLoaderData(); diff --git a/src/app/routes/profile.$key.tsx b/src/app/routes/profile.$key.tsx index aa058b5..79dafaa 100644 --- a/src/app/routes/profile.$key.tsx +++ b/src/app/routes/profile.$key.tsx @@ -1,14 +1,17 @@ import { ProfilePage } from 'src/pages/profile/ProfilePage'; import { getInfo } from 'src/types'; -import { withAuthenticated } from 'src/app/server/withAuthenticated'; +import { authenticate } from 'src/app/server/authenticate'; import { useLoaderData } from '@remix-run/react'; import { MyProfileProvider } from 'src/entities/profile/model/myProfileStore'; import { IdealPartnerProvider } from 'src/entities/ideal_partner/model/idealPartnerStore'; import { useMemo } from 'react'; import { convertDtoToProfile } from 'src/entities/profile/model/convertProfileToDto'; import { convertDtoToIdealPartner } from 'src/entities/ideal_partner/model/convertIdealPartnerToDto'; +import { LoaderFunction } from '@remix-run/node'; + +export const loader: LoaderFunction = async ({ request, params }) => { + const accessToken = await authenticate(request); -export const loader = withAuthenticated(async ({ params }, accessToken) => { const { key } = params; if (!key) { @@ -25,7 +28,7 @@ export const loader = withAuthenticated(async ({ params }, accessToken) => { }); return { profile: data }; -}); +}; export default function Page() { const { profile } = useLoaderData(); diff --git a/src/app/server/authenticate.ts b/src/app/server/authenticate.ts new file mode 100644 index 0000000..b164f5c --- /dev/null +++ b/src/app/server/authenticate.ts @@ -0,0 +1,55 @@ +import { redirect } from '@remix-run/node'; +import { commitSession, getAuthSession } from 'src/app/server/sessions'; +import dayjs from 'dayjs'; +import { AuthorizationError } from 'src/shared/lib/custom_instance'; +import { AxiosError } from 'axios'; +import { refreshToken } from 'src/types'; + +class UnauthorizedError extends Error { + message = 'unauthorized'; +} + +export const authenticate = async (request: Request) => { + const session = await getAuthSession(request); + const expiredAt = session.get('expiredAt'); + const accessToken = session.get('accessToken'); + + try { + if (!accessToken) { + throw redirect('/login'); + } + + if (!expiredAt || dayjs(expiredAt).diff(dayjs(), 'day') > 1) { + throw new UnauthorizedError(); + } + + return accessToken; + } catch (e) { + if ( + e instanceof AuthorizationError || + (((e: unknown): e is AxiosError => e instanceof AxiosError)(e) && e.status === 401) + ) { + await requestRefreshToken(request); + } + } +}; + +export const requestRefreshToken = async (request: Request) => { + const session = await getAuthSession(request); + const { data } = await refreshToken({ + accessToken: session.get('accessToken')!, + refreshToken: session.get('refreshToken')!, + }); + + session.set('accessToken', data.accessToken); + session.set('refreshToken', data.refreshToken); + + if (request.method === 'GET') + throw redirect(request.url, { + headers: { + 'Set-Cookie': await commitSession(session), + }, + }); + + return data.accessToken; +}; diff --git a/src/app/server/sessions.ts b/src/app/server/sessions.ts index 18c1a03..df48354 100644 --- a/src/app/server/sessions.ts +++ b/src/app/server/sessions.ts @@ -3,6 +3,7 @@ import { createCookieSessionStorage, Session } from '@remix-run/node'; type AuthSessionData = { accessToken: string; refreshToken: string; + expiredAt: string; }; type SessionFlashData = { @@ -15,7 +16,7 @@ const { getSession, commitSession, destroySession } = createCookieSessionStorage cookie: { name: '__session', httpOnly: true, - maxAge: 1000 * 60 * 60 * 24 * 10, + maxAge: 60 * 60 * 24, path: '/', sameSite: 'lax', secrets: ['s3cret1'], @@ -24,11 +25,7 @@ const { getSession, commitSession, destroySession } = createCookieSessionStorage }); const getAuthSession = async (request: Request) => { - const foundSession = await getSession(request.headers.get('Cookie')); - if (process.env.NODE_ENV === 'development') { - foundSession.set('accessToken', process.env.VITE_DEV_JWT_TOKEN ?? ''); - } - return foundSession; + return await getSession(request.headers.get('Cookie')); }; export { getAuthSession, commitSession, destroySession }; diff --git a/src/app/server/withAuthenticated.ts b/src/app/server/withAuthenticated.ts deleted file mode 100644 index b3b8323..0000000 --- a/src/app/server/withAuthenticated.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { ActionFunctionArgs, LoaderFunctionArgs, redirect } from '@remix-run/node'; -import { refreshToken } from 'src/types'; -import { commitSession, getAuthSession } from 'src/app/server/sessions'; -import { getRefreshTokenFromHeader } from 'src/app/server/getRefreshToken'; -import { AuthorizationError } from 'src/shared/lib/custom_instance'; - -type Handler = (args: LoaderFunctionArgs | ActionFunctionArgs) => Promise; - -type WithAuthenticated = ( - callback: (args: LoaderFunctionArgs | ActionFunctionArgs, accessToken: string) => Promise, -) => Handler; -export const withAuthenticated: WithAuthenticated = ( - callback: (args: LoaderFunctionArgs | ActionFunctionArgs, accessToken: string) => Promise, -) => { - return async (args: LoaderFunctionArgs | ActionFunctionArgs) => { - const { request } = args; - const session = await getAuthSession(request); - const accessToken = session.get('accessToken'); - - if (!accessToken) { - throw redirect('/login'); - } - - try { - return await callback(args, accessToken); - } catch (e) { - if (e instanceof AuthorizationError) { - const { data, headers: resHeaders } = await refreshToken({ accessToken: session.get('accessToken')! }); - const accessToken = data.accessToken; - const newRefreshToken = getRefreshTokenFromHeader(resHeaders); - - if (!newRefreshToken) throw redirect('/login'); - - session.set('accessToken', accessToken); - session.set('refreshToken', newRefreshToken); - - if (request.method === 'GET') - throw redirect(request.url, { - headers: { - 'Set-Cookie': await commitSession(session), - }, - }); - throw e; - } - - throw e; - } - }; -}; diff --git a/src/pages/main/login/LoginPage.tsx b/src/pages/main/login/LoginPage.tsx index cb77901..a7111bb 100644 --- a/src/pages/main/login/LoginPage.tsx +++ b/src/pages/main/login/LoginPage.tsx @@ -8,7 +8,7 @@ export const LoginPage = () => {

내 사랑 구해줄래? 구해줄게!

- +