Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] 세션 방식의 인증인가로 수정 #165

Merged
merged 8 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 1 addition & 5 deletions src/apis/queryFunctions/Auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import createApiClient from '@/apis/queryFunctions/apiClient';
import {
LoginParams,
RegisterParams,
CheckEmailParams,
PostRegisterResponseData,
Expand All @@ -14,10 +13,7 @@ const apiClient = createApiClient();
export const postRegister = (param: RegisterParams) =>
apiClient.post<Response<PostRegisterResponseData>>('/v2/auth/register', param);

export const postLogin = (param: LoginParams) =>
apiClient.post<Response<null>>('/v2/auth/login', param);

export const postLogout = () => apiClient.post<Response<null>>('v2/auth/logout');
export const postLogout = () => apiClient.post<Response<null>>('v2/auth/logout'); // 없어져야할 것

export const getEmailValidation = async (params: CheckEmailParams) => {
const response = await apiClient.get<Response<boolean>>('v2/auth/validate-email', {
Expand Down
45 changes: 23 additions & 22 deletions src/apis/queryFunctions/apiClient.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
import axios from 'axios';

function createApiClient(cookie?: string) {
const isServer = typeof window === 'undefined';

function createApiClient() {
const client = axios.create({
baseURL: `${process.env.NEXT_PUBLIC_SERVER_API_URL}/api`,
timeout: 100000,
withCredentials: true,
headers: {
...(cookie ? { Cookie: cookie } : {}),
'Content-Type': 'application/json',
},
});

client.interceptors.request.use(
config => {
// 성공한 요청
client.interceptors.request.use(async config => {
if (isServer) {
// 서버에서 실행되는 경우에는 쿠키에서 access_token 가져옵니다.
const { cookies } = await import('next/headers');
Comment on lines +15 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분 깔끔하게 eslint diabled 추가해서 require로 선언하는 거 어떨까요??

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음...다른 곳에서도 모두 Import 문법을 쓰고 있어서 통일 하는게 더 낫다고 느껴지기는 하네요ㅠㅠ
import 하면 상단에 정의되어 비동기문으로 처리해야하는 것이 조금 불편할 수 있지만 저렇게 처리 하는 경우도 있다고 들어서요..!
혹시 성능에 큰 문제가 없다면 import문으로 통일하시는건 어떤가요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 확인했습니다~

const accessToken = cookies().get('accessToken')?.value;

return config;
},
error => {
// 실패한 요청
return Promise.reject(error);
},
);
if (accessToken && !config.headers.Authorization) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = accessToken;
}
} else {
// 클라이언트에서 실행되는 경우에는 스토리지에서 access_token 가져옵니다.
const accessToken = sessionStorage.getItem('accessToken');

client.interceptors.response.use(
response => {
// 성공한 응답
return response;
},
error => {
// 실패한 응답
return Promise.reject(error);
},
);
if (accessToken && !config.headers.Authorization) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = accessToken;
}
}
return config;
});

return client;
}
Expand Down
14 changes: 14 additions & 0 deletions src/apis/queryFunctions/clientLogin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { LoginParams } from '../types/Auth';

import login from './login';
// 클라이언트 측의 로그인 함수

export async function clientLogin(params: LoginParams) {
const { accessToken, refreshToken } = await login(params);

// 세션 스토리지에 저장
if (typeof window !== 'undefined') {
sessionStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
}
}
27 changes: 27 additions & 0 deletions src/apis/queryFunctions/login.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use server';

import { setCookie } from '@/utils/cookies';

import { LoginParams } from '../types/Auth';
// 서버 측의 로그인 함수

async function login(params: LoginParams) {
const loginData = await fetch(`${process.env.NEXT_PUBLIC_SERVER_API_URL}/api/v2/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
})
.then(res => res.json())
.then(jsonRes => jsonRes.result_data);

const accessToken = loginData.access_token;
const refreshToken = loginData.refresh_token;

setCookie('accessToken', accessToken);

return { accessToken, refreshToken };
}

export default login;
43 changes: 0 additions & 43 deletions src/apis/queryHooks/Auth/usePostLogin.ts

This file was deleted.

1 change: 1 addition & 0 deletions src/apis/queryHooks/User/useGetUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useAuth } from '@/provider/authProvider';

function useGetUser() {
const { isLoggedIn } = useAuth();

const { data, isLoading } = useQuery({
queryKey: ['userInfo'],
queryFn: () => getUser(),
Expand Down
5 changes: 5 additions & 0 deletions src/apis/types/Auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface LoginParams {
password: string;
}

export interface PostLoginResponseData {
access_token: string;
refresh_token: string;
}

export interface CheckEmailParams {
email: string;
}
Expand Down
1 change: 1 addition & 0 deletions src/app/basicauction/[id]/HaveToLoginNotiModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as S from './HaveToLoginNotiModal.css';

function HaveToLoginNotiModal() {
const { isLoggedIn } = useAuth();

const router = useRouter();
const pathname = usePathname();

Expand Down
5 changes: 3 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
const cookie = cookies();
const isLoggedIn = cookie.has('access');
const isLoggedIn = !!cookies().get('accessToken')?.value;

const queryClient = await usePrefetchQueriesWithCookie([
// TODO 로그인이 필요한 호출을 따로 빼던가 불리해야함
// 현재 useInfo의 경우 로그인을 해야만 호출 가능한데 프리패치로 미로그인 상태에서도 호출 되는 중
{ queryKey: ['userInfo'], api: '/v2/member' },
{ queryKey: ['category'], api: '/v2/categories' },
]);
Expand Down
30 changes: 24 additions & 6 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
import { useForm, SubmitHandler } from 'react-hook-form';

import Link from 'next/link';
import { useRouter, useSearchParams } from 'next/navigation';

import usePostLogin from '@/apis/queryHooks/Auth/usePostLogin';
import { clientLogin } from '@/apis/queryFunctions/clientLogin';
import GoogleIcon from '@/assets/svg/google.svg';
import NaverIcon from '@/assets/svg/naver.svg';
import CommonButton from '@/components/CommonButton';
import CommonInput from '@/components/CommonInput';
import MaxLayout from '@/components/MaxLayout';
import { useAuth } from '@/provider/authProvider';
import sha256 from '@/utils/sha256';

import * as S from './Login.css';
Expand All @@ -26,15 +28,31 @@ function Home() {
formState: { errors },
} = useForm<Inputs>();

const { mutate: login } = usePostLogin();
const { setIsLoggedIn } = useAuth();
const router = useRouter();
const searchParams = useSearchParams();

const prevUrl = searchParams.get('prevUrl');

const onSubmit: SubmitHandler<Inputs> = async data => {
const newPassword = await sha256(data.passwordRequired);

login({
email: data.emailRequired,
password: newPassword,
});
try {
await clientLogin({
email: data.emailRequired,
password: newPassword,
});
setIsLoggedIn(true);
if (prevUrl?.startsWith('/join') || prevUrl?.startsWith('/login')) {
router.push('/');
} else {
router.push(prevUrl || '/');
}

router.refresh();
} catch (error) {
console.error('Login failed:', error);
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import { useMutation } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { useRouter, useSearchParams } from 'next/navigation';

import { useAuth } from '@/provider/authProvider';
import { useToast } from '@/provider/toastProvider';

import { LoginParams } from '../types/Auth';
import { Response } from '../types/common';

import login from './login';

export function useClientLogin() {
  const { showToast } = useToast();
  const router = useRouter();
  const searchParams = useSearchParams();
  const prevUrl = searchParams.get('prevUrl');
  const { setIsLoggedIn } = useAuth();

  const { mutate } = useMutation({
    mutationFn: (loginParams: LoginParams) => login(loginParams),
    onSuccess: data => {
      sessionStorage.setItem('accessToken', data.accessToken);
      localStorage.setItem('refreshToken', data.refreshToken);
      setIsLoggedIn(true);
      if (prevUrl?.startsWith('/join') || prevUrl?.startsWith('/login')) {
        router.push('/');
      } else {
        router.push(prevUrl || '/');
      }
    },
    onError: (e: AxiosError<Response<string>>) => {
      if (e.response) {
        showToast('error', `${e.response.data.result_msg}`);
      } else {
        // 네트워크 에러나 기타 처리되지 않은 에러 처리
        showToast('error', '알 수 없는 오류가 발생했습니다. 새로고침을 진행해 주세요.');
      }
    },
  });

  return { mutate };

  // 세션 스토리지에 저장
  // if (typeof window !== 'undefined') {
  //   sessionStorage.setItem('accessToken', accessToken);
  //   localStorage.setItem('refreshToken', refreshToken);
  // }
}

이전 usePostLogin이랑 동일해요. mutationFn이 단지 fetch로 동작하는 것 뿐입니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  const { mutate } = useMutation({
    mutationFn: (loginParams: LoginParams) => setTokenCookies(loginParams),
    onSuccess: data => {
      sessionStorage.setItem('accessToken', data.accessToken);
      localStorage.setItem('refreshToken', data.refreshToken);
      setIsLoggedIn(true);
      showToast('success', '로그인 되었습니다.');
      if (prevUrl?.startsWith('/join') || prevUrl?.startsWith('/login')) {
        router.push('/');
      } else {
        router.push(prevUrl || '/');
      }
    },
    onError: (e: AxiosError<Response<string>>) => {
      if (e.response) {
        showToast('error', `${e.response.data.result_msg}`);
      } else {
        // 네트워크 에러나 기타 처리되지 않은 에러 처리
        showToast('error', '알 수 없는 오류가 발생했습니다. 새로고침을 진행해 주세요.');
      }
    },
  });

  return { mutate };

이런식으로 변경해보았어요~
해당 파일은 queryHook => Auth 쪽으로 옮겨두었어요

};

return (
Expand Down
1 change: 1 addition & 0 deletions src/components/AuctionInfo/AuctionBidListModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface AuctionBidListModalProps {

function AuctionBidListModal({ id }: AuctionBidListModalProps) {
const { isLoggedIn } = useAuth();

const router = useRouter();
const pathname = usePathname();

Expand Down
1 change: 1 addition & 0 deletions src/components/AuctionInfo/hooks/usePermissionBidPrice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useAuth } from '@/provider/authProvider';

export function usePermissionBidPrice(auctionId: number, sellerId: number) {
const { isLoggedIn } = useAuth();

const { data: user } = useGetUser();
const [expired, setExpired] = useState('');
const { data: auctionBidList } = useGetBasicAuctionBidList(auctionId);
Expand Down
1 change: 1 addition & 0 deletions src/components/Chatting/ChattingIconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as S from './ChattingIconButton.css';

function ChattingIconButton() {
const { isLoggedIn } = useAuth();

const [isOpen, setIsOpen] = useState(false);

if (!isLoggedIn) return null;
Expand Down
1 change: 1 addition & 0 deletions src/components/Chatting/ChattingMessageSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface ChattingMessageSectionProps {

function ChattingMessageSection({ lastChat, roomId }: ChattingMessageSectionProps) {
const { data: user } = useGetUser();

const { refetch } = useGetChatroomList({
pageable: 0,
});
Expand Down
6 changes: 6 additions & 0 deletions src/components/Chatting/hooks/useChatSocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ function useChatSocket({
onMessage,
checkBottom,
}: UseChatSocketParams) {
const accessToken = sessionStorage.getItem('accessToken');

console.log('accessToken: useChatSocket.ts', accessToken);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 지워도 괜찮을 것 가탕요!


const [newMessage, setNewMessage] = useState<Message | null>(null);
const user = useGetUser();
const timerRef = useRef<NodeJS.Timeout | null>(null); // 타이머 ID 저장
Expand Down Expand Up @@ -78,6 +82,7 @@ function useChatSocket({
};

const { client } = useSocket({
access: accessToken,
url: `${process.env.NEXT_PUBLIC_SOCKET_SERVER_URL}`,
config: {
// https://stomp-js.github.io/api-docs/latest/classes/Client.html
Expand All @@ -86,6 +91,7 @@ function useChatSocket({
// 연결을 시도합니다.
setMessages(lastChat);
},

onWebSocketError: error => {
console.log('WebSocket Error :', error);
},
Expand Down
4 changes: 4 additions & 0 deletions src/components/HeaderSection/components/UserHeader/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';
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
Expand All @@ -11,7 +13,9 @@ import * as S from './UserHeader.css';

function UserHeader() {
const router = useRouter();

const { isLoggedIn } = useAuth();

const { mutate: logout } = usePostLogout();
const pathname = usePathname();
const searchParams = useSearchParams();
Expand Down
4 changes: 1 addition & 3 deletions src/hooks/usePrefetchQueriesWithCookie.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { QueryClient, QueryKey } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import { cookies } from 'next/headers';

import createApiClient from '@/apis/queryFunctions/apiClient';
import { Response } from '@/apis/types/common';
Expand All @@ -14,8 +13,7 @@ interface UsePrefetchQueriesWithCookieProps<T, TQueryKey extends QueryKey> {
async function usePrefetchQueriesWithCookie<T, TQueryKey extends QueryKey>(
queries: UsePrefetchQueriesWithCookieProps<T, TQueryKey>[],
) {
const cookie = cookies();
const apiClient = createApiClient(cookie.toString());
const apiClient = createApiClient();
const queryClient = new QueryClient();

await Promise.all(
Expand Down
4 changes: 1 addition & 3 deletions src/hooks/usePrefetchQueryWithCookie.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { QueryClient, QueryKey } from '@tanstack/react-query';
import { cookies } from 'next/headers';

import createApiClient from '@/apis/queryFunctions/apiClient';
import { Response } from '@/apis/types/common';
Expand All @@ -14,8 +13,7 @@ async function usePrefetchQueryWithCookie<T, TQueryKey extends QueryKey>({
queryKey,
api,
}: UsePrefetchQueryWithCookieProps<T, TQueryKey>) {
const cookie = cookies();
const apiClient = createApiClient(cookie.toString());
const apiClient = createApiClient();
const queryClient = new QueryClient();

await queryClient.prefetchQuery({
Expand Down
Loading
Loading