Skip to content
43 changes: 43 additions & 0 deletions src/api/core/auth/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios from 'axios';

import { getAccessToken } from '@/lib/auth/token';
import { CommonErrorResponse } from '@/types/service/common';

import { createApiHelper } from '../lib/apiHelper';

const authInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 20000,
withCredentials: true,
});
Comment on lines +1 to +12
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Next.js 16.0.10에 적용되는 보안 취약점 — 즉시 업그레이드 필요

프로젝트가 사용 중인 Next.js 16.0.x에는 다음 CVE가 존재합니다:

  • CVE-2025-55184 (고위험 DoS) 및 CVE-2025-55183 (중위험 소스코드 노출): React Server Components에서 발견된 취약점으로, 모든 16.x 사용자에게 즉시 업그레이드가 권고됩니다.
  • CVE-2025-66478 (CVSS 10.0, 원격 코드 실행): React Server Components 프로토콜에서 발견된 치명적 취약점으로, 모든 15.x 및 16.x 사용자에게 즉시 업그레이드가 권고됩니다.

현재 최신 안정 버전은 Next.js 16.1이며, Turbopack 파일 시스템 캐싱(안정화) 등의 개선이 포함되어 있습니다.

npm install next@latest
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/core/auth/index.ts` around lines 1 - 12, Project uses Next.js 16.0.x
which contains multiple critical CVEs; update Next to the latest stable (e.g.,
16.1 or next@latest) by bumping the dependency in package.json and reinstalling
(npm install or yarn), regenerate lockfile, run build/tests, and redeploy;
verify that related runtime code such as server-side auth flows (refer to
authInstance / axios.create in src/api/core/auth/index.ts) still behave
correctly and fix any breaking changes introduced by the Next upgrade.


authInstance.interceptors.request.use(async (config) => {
const isServer = typeof window === 'undefined';

if (isServer) {
const { cookies } = await import('next/headers');
const cookieStore = await cookies();
const refreshToken = cookieStore.get('refreshToken')?.value;
if (refreshToken) {
config.headers.Cookie = `refreshToken=${refreshToken}`;
}
}

const accessToken = await getAccessToken();
if (accessToken && config.headers) {
config.headers.Authorization = `Bearer ${accessToken}`;
}

return config;
});

authInstance.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
return new CommonErrorResponse(error.response?.data);
},
);
Comment on lines +34 to +41
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

[심각] 에러 인터셉터에서 throw 대신 return으로 인해 모든 authAPI 에러 호출이 TypeError를 발생시킵니다.

async (error) => { return new CommonErrorResponse(...) }를 사용하면 Axios는 해당 promise를 resolve합니다. 그 결과 createApiHelper 내부에서는:

const response = await axios.post<CommonSuccessResponse<T>>(url, data, config);
// response = CommonErrorResponse 객체 (AxiosResponse가 아님)
return response.data.data;
// response.data = undefined → TypeError: Cannot read properties of undefined

즉, authAPI를 통한 모든 요청(login, logout, refresh 등)에서 에러가 발생하면 CommonErrorResponse가 아닌 TypeError가 throw되어 호출부의 에러 핸들링이 완전히 깨집니다.

🐛 수정 제안
  authInstance.interceptors.response.use(
    (response) => {
      return response;
    },
    async (error) => {
-     return new CommonErrorResponse(error.response?.data);
+     return Promise.reject(new CommonErrorResponse(error.response?.data));
    },
  );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/core/auth/index.ts` around lines 34 - 41, The response interceptor
currently returns a CommonErrorResponse which resolves the axios promise and
breaks callers; in authInstance.interceptors.response.use's error handler
replace the `return new CommonErrorResponse(...)` with rejecting the promise
(e.g., throw or return Promise.reject(...)) so axios keeps the rejection flow;
ensure you propagate the error payload (use error.response?.data when
constructing the CommonErrorResponse) so callers of createApiHelper still
receive a rejected promise with the proper error object instead of a resolved
value causing a TypeError.


export const authAPI = createApiHelper(authInstance);
56 changes: 56 additions & 0 deletions src/api/core/base/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import axios from 'axios';

import { getAccessToken } from '@/lib/auth/token';
import { CommonErrorResponse } from '@/types/service/common';

import { API } from '../..';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the structure of src/api directory
echo "=== Directory structure ==="
find src/api -type f -name "*.ts" | head -20

echo ""
echo "=== Content of src/api/index.ts ==="
cat src/api/index.ts 2>/dev/null || echo "File not found"

echo ""
echo "=== Content of src/api/core/index.ts ==="
cat src/api/core/index.ts 2>/dev/null || echo "File not found"

echo ""
echo "=== All imports in src/api/core/base/index.ts ==="
head -20 src/api/core/base/index.ts

echo ""
echo "=== Check if authAPI exists ==="
fd -t f 'auth' src/api --type f | grep -E "\.(ts|js)$"

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 1923


🏁 Script executed:

#!/bin/bash
echo "=== Content of src/api/service/auth-service/index.ts ==="
cat src/api/service/auth-service/index.ts

echo ""
echo "=== Content of src/api/service/index.ts ==="
cat src/api/service/index.ts

echo ""
echo "=== Check imports in auth-service ==="
head -15 src/api/service/auth-service/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 2233


🏁 Script executed:

#!/bin/bash
echo "=== Content of src/api/core/auth/index.ts ==="
cat src/api/core/auth/index.ts

echo ""
echo "=== Full content of src/api/core/base/index.ts to see line 34 context ==="
cat -n src/api/core/base/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 3264


순환 의존성 제거 필요

API../.. (src/api/)에서 가져오면, 모듈 초기화 시 순환 참조가 발생합니다:

src/api/core/base/index.tssrc/api/index.tssrc/api/service/auth-servicesrc/api/core → (다시 base로)

src/api/core/index.ts가 아직 로딩 중인 ./base를 내보내려 할 때 baseAPI가 정의되지 않을 수 있습니다.

authAPI 직접 임포트 권장:

  • import { authAPI } from './auth'로 변경 (6번 라인)
  • 34번 라인: API.authService.refresh() 대신 authAPI.post('/api/v1/auth/refresh', {}) + 토큰 저장 로직 필요

추가 이슈: 40-42번 라인

window.location.href 할당 후 명시적 return을 추가하세요. 현재는 52번 라인의 throw가 여전히 실행됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/core/base/index.ts` at line 6, The import of API from '../..' causes
a circular dependency; replace that import with a direct import of authAPI from
'./auth' and update usages: in the refresh flow (where API.authService.refresh()
is called) call authAPI.post('/api/v1/auth/refresh', {}) and store the returned
token into the same token storage logic instead of delegating to
API.authService; additionally, after assigning window.location.href in the
logout/redirect branch add an explicit return to prevent the subsequent throw
from executing (fix the control flow around the redirect in the function
handling auth/refresh).

import { createApiHelper } from '../lib/apiHelper';

const baseInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 20000,
});

baseInstance.interceptors.request.use(async (config) => {
const token = await getAccessToken();
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});

baseInstance.interceptors.response.use(
(response) => response,
async (error) => {
const isServer = typeof window === 'undefined';

const errorResponse = new CommonErrorResponse(error.response?.data);
const status = errorResponse.status;
const originalRequest = error.config;

if (status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
Comment on lines +31 to +32
Copy link
Contributor

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for _retry usage in the codebase
echo "=== Searching for _retry usage ==="
rg -rn '_retry' --type=ts

echo -e "\n=== Checking for axios type augmentations ==="
rg -rn 'declare module.*axios' --type=ts

echo -e "\n=== Checking for .d.ts files in types directory ==="
fd -t f '\.d\.ts$' | head -20

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 375


🏁 Script executed:

#!/bin/bash
# Read the actual file to see context around lines 31-32
echo "=== Checking src/api/core/base/index.ts ==="
if [ -f "src/api/core/base/index.ts" ]; then
  cat -n "src/api/core/base/index.ts" | sed -n '25,50p'
else
  echo "File not found at src/api/core/base/index.ts"
  fd -t f 'index.ts' | grep -E '(api|core|base)' | head -5
fi

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 1148


🏁 Script executed:

#!/bin/bash
# Check if tsconfig.json has strict mode enabled
echo "=== Checking TypeScript configuration ==="
if [ -f "tsconfig.json" ]; then
  cat tsconfig.json | jq '.compilerOptions | {strict, noImplicitAny, strictNullChecks}' 2>/dev/null || cat tsconfig.json
fi

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 186


🏁 Script executed:

#!/bin/bash
# Check if the code has any type errors related to the n property
echo "=== Checking if there's an `@ts-ignore` or similar suppression ==="
rg -B2 -A2 'originalRequest\.n' src/api/core/base/index.ts

echo -e "\n=== Checking if InternalAxiosRequestConfig is imported ==="
rg 'InternalAxiosRequestConfig|AxiosRequestConfig' src/api/core/base/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 195


현재 코드의 커스텀 속성 n에 대한 타입 선언 부재

originalRequest.n은 표준 Axios 타입에 존재하지 않아 TypeScript strict 모드에서 타입 에러가 발생합니다. 또한 n이라는 속성명은 명확성이 떨어지므로, _retry로 변경하고 모듈 선언을 통해 타입을 확장하는 것을 권장합니다.

타입 확장 예시
// src/types/axios.d.ts
import 'axios';
declare module 'axios' {
  export interface InternalAxiosRequestConfig {
    _retry?: boolean;
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/core/base/index.ts` around lines 31 - 32, The code uses a custom
property originalRequest.n which isn't on Axios types; rename all uses of
originalRequest.n to originalRequest._retry and update the code in the retry
branch (the block that sets originalRequest._retry = true) accordingly, then add
an Axios module augmentation declaring InternalAxiosRequestConfig { _retry?:
boolean } so TypeScript strict mode knows about the property; ensure
imports/compilation include that declaration file and rebuild to confirm the
type error is resolved.

Copy link
Member Author

Choose a reason for hiding this comment

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

originalRequest.m이 어디있나요? 저는 originalRequest._retry 밖에 사용하지 않았습니다.

Copy link
Contributor

Choose a reason for hiding this comment

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

@Chiman2937 죄송합니다. 제가 실수했습니다.

코드를 다시 확인해보니 originalRequest._retry를 올바르게 사용하고 계시네요. 제 이전 코멘트에서 originalRequest.n이라고 잘못 언급한 것은 오류였습니다. (그리고 .m에 대해 질문하셨는데, 제가 언급한 것은 .n이었습니다 - 혼란을 드려 죄송합니다)

현재 코드는 이미 _retry 속성을 사용하고 있으며, 이는 좋은 네이밍입니다. 다만 TypeScript strict 모드에서는 여전히 _retry가 Axios의 InternalAxiosRequestConfig 타입에 정의되어 있지 않아 타입 에러가 발생할 수 있습니다.

만약 TypeScript 컴파일 시 에러가 발생하지 않는다면 현재 설정으로도 충분합니다. 에러가 발생한다면 앞서 제안드린 타입 확장 방법을 적용하시면 됩니다.

혼란을 드려 죄송합니다! 🙏

try {
await API.authService.refresh();
return baseInstance(originalRequest);
} catch (refreshError) {
if (isServer) {
throw refreshError;
} else {
const currentPath = window.location.pathname + window.location.search;
window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
}
}
Comment on lines +39 to +43
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

클라이언트 측 refresh 실패 시 return 누락

window.location.href 할당 후 함수가 즉시 종료되지 않아, Line 52의 throw errorResponse가 페이지 이동 전에 실행됩니다. 이로 인해 호출자에게 에러가 전파되어 의도치 않은 에러 UI가 깜빡일 수 있습니다.

제안된 수정
          window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
+         return new Promise(() => {}); // 리다이렉트 중 Promise를 pending 상태로 유지
        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else {
const currentPath = window.location.pathname + window.location.search;
window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
}
}
} else {
const currentPath = window.location.pathname + window.location.search;
window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
return new Promise(() => {}); // 리다이렉트 중 Promise를 pending 상태로 유지
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/core/base/index.ts` around lines 39 - 43, The client-side refresh
failure branch sets window.location.href but doesn't stop execution, so the
subsequent throw errorResponse still runs; in the code in
src/api/core/base/index.ts locate the else branch that computes currentPath and
assigns window.location.href (references: currentPath, window.location.href,
errorResponse, throw errorResponse) and add an explicit return immediately after
setting window.location.href to prevent the function from continuing and
throwing the error to the caller.

}
if (status === 404) {
if (isServer) {
const { notFound } = await import('next/navigation');
notFound();
}
}

throw errorResponse;
},
);

export const baseAPI = createApiHelper(baseInstance);
126 changes: 2 additions & 124 deletions src/api/core/index.ts
Original file line number Diff line number Diff line change
@@ -1,124 +1,2 @@
import axios from 'axios';

import { CommonErrorResponse, CommonSuccessResponse } from '@/types/service/common';

import { API } from '..';

export const baseAPI = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 20000,
});

baseAPI.interceptors.request.use(async (config) => {
const isServer = typeof window === 'undefined';

if (isServer) {
// // Server 환경
const { cookies } = await import('next/headers');
const cookieStore = await cookies();
const token = cookieStore.get('accessToken')?.value;
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
} else {
// Client 환경
const match = document.cookie.match(new RegExp('(^| )accessToken=([^;]+)'));
const token = match ? decodeURIComponent(match[2]) : undefined;
if (token && config.headers) {
config.headers.Authorization = `Bearer ${token}`;
}
}

return config;
});

baseAPI.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const errorResponse: CommonErrorResponse = error.response?.data || {
type: 'about:blank',
title: 'Network Error',
status: 0,
detail: '서버와 연결할 수 없습니다.',
instance: error.config?.url || '',
errorCode: 'NETWORK_ERROR',
};

const status = error.response?.status ?? errorResponse.status;
const isServer = typeof window === 'undefined';
const originalRequest = error.config;

// skipAuthRedirect flag가 지정되어있지 않으면 항상 redirect 되도록
if (originalRequest.skipAuthRedirect === undefined) {
originalRequest.skipAuthRedirect = true;
}

if (status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// refresh - set cookie는 클라이언트 요청만 동작함
if (!isServer) {
await API.authService.refresh(originalRequest.skipAuthRedirect);
}
return baseAPI(originalRequest);
} catch (refreshError) {
if (!originalRequest.skipAuthRedirect) throw refreshError;
if (isServer) {
const { redirect } = await import('next/navigation');
redirect('/login');
} else {
if (window.location.pathname === '/login') {
throw errorResponse;
}
const currentPath = window.location.pathname + window.location.search;
window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`;
}
}
}
if (status === 404) {
if (isServer) {
const { notFound } = await import('next/navigation');
notFound();
}
}

throw errorResponse;
},
);

type ApiVersionType = 'v1' | 'v2';

// 공통 응답 형식 처리를 위한 api 헬퍼
const apiHelper = (v: ApiVersionType = 'v1') => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
get: async <T>(url: string, config?: any): Promise<T> => {
const response = await baseAPI.get<CommonSuccessResponse<T>>(`/api/${v}${url}`, config);
return response.data.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
post: async <T>(url: string, data?: any, config?: any): Promise<T> => {
const response = await baseAPI.post<CommonSuccessResponse<T>>(`/api/${v}${url}`, data, config);
return response.data.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
put: async <T>(url: string, data?: any, config?: any): Promise<T> => {
const response = await baseAPI.put<CommonSuccessResponse<T>>(`/api/${v}${url}`, data, config);
return response.data.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete: async <T>(url: string, config?: any): Promise<T> => {
const response = await baseAPI.delete<CommonSuccessResponse<T>>(`/api/${v}${url}`, config);
return response.data.data;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
patch: async <T>(url: string, data?: any, config?: any): Promise<T> => {
const response = await baseAPI.patch<CommonSuccessResponse<T>>(`/api/${v}${url}`, data, config);
return response.data.data;
},
});

export const api = apiHelper('v1'); // breaking change 방지용
export const apiV1 = apiHelper('v1');
export const apiV2 = apiHelper('v2');
export { authAPI } from './auth';
export { baseAPI } from './base';
26 changes: 26 additions & 0 deletions src/api/core/lib/apiHelper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AxiosInstance, AxiosRequestConfig } from 'axios';

import { CommonSuccessResponse } from '@/types/service/common';

export const createApiHelper = (axios: AxiosInstance) => ({
get: async <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const response = await axios.get<CommonSuccessResponse<T>>(url, config);
return response.data.data;
},
post: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> => {
const response = await axios.post<CommonSuccessResponse<T>>(url, data, config);
return response.data.data;
},
put: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> => {
const response = await axios.put<CommonSuccessResponse<T>>(url, data, config);
return response.data.data;
},
delete: async <T>(url: string, config?: AxiosRequestConfig): Promise<T> => {
const response = await axios.delete<CommonSuccessResponse<T>>(url, config);
return response.data.data;
},
patch: async <T>(url: string, data?: unknown, config?: AxiosRequestConfig): Promise<T> => {
const response = await axios.patch<CommonSuccessResponse<T>>(url, data, config);
return response.data.data;
},
});
27 changes: 11 additions & 16 deletions src/api/service/auth-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { api } from '@/api/core';
import { authAPI } from '@/api/core';
import { clearAccessToken, setAccessToken } from '@/lib/auth/token';
import {
GoogleOAuthExchangeRequest,
Expand All @@ -13,47 +13,42 @@ import {
export const authServiceRemote = () => ({
// 로그인
login: async (payload: LoginRequest) => {
const data = await api.post<LoginResponse>('/auth/login', payload, { withCredentials: true });
const data = await authAPI.post<LoginResponse>('/api/v1/auth/login', payload);

setAccessToken(data.accessToken, data.expiresIn);
return data;
},

// 회원가입
signup: (payload: SignupRequest) => api.post<SignupResponse>(`/auth/signup`, payload),
signup: async (payload: SignupRequest) => {
return authAPI.post<SignupResponse>(`/api/v1/auth/signup`, payload);
},

// 로그아웃
logout: async () => {
await api.post<void>('/auth/logout', null, { withCredentials: true });
await authAPI.post<void>('/api/v1/auth/logout', null);
clearAccessToken();
},

// 액세스 토큰 재발급
refresh: async (redirect: boolean = true) => {
const data = await api.post<RefreshResponse>(
'/auth/refresh',
{},
{ _retry: true, withCredentials: true, skipAuthRedirect: redirect },
);

refresh: async () => {
//prettier-ignore
const data = await authAPI.post<RefreshResponse>('/api/v1/auth/refresh', {});
setAccessToken(data.accessToken, data.expiresIn);
return data;
},

// 회원 탈퇴
withdraw: async () => {
await api.delete<void>('/auth/withdraw', { withCredentials: true });
await authAPI.delete<void>('/api/v1/auth/withdraw');
clearAccessToken();
},
Comment on lines 28 to 45
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate the auth service files
find . -type f -name "index.ts" | grep -E "(auth|service)" | head -20

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 732


🏁 Script executed:

# Check the structure of the api directory
ls -la src/api/ 2>/dev/null || echo "Directory not found"

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 350


🏁 Script executed:

# Find the auth-related files
find . -path "*api/core/auth*" -o -path "*api/service/auth-service*" | grep -v node_modules

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 190


🏁 Script executed:

# Read the authAPI error interceptor implementation
cat -n src/api/core/auth/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 1481


🏁 Script executed:

# Read the auth-service implementation to see the full context
cat -n src/api/service/auth-service/index.ts

Repository: WeGo-Together/WeGo_FrontEnd

Length of output: 1874


authAPI 에러 인터셉터가 throw 대신 return으로 구현되어 모든 메서드의 에러 처리가 비정상 동작합니다.

src/api/core/auth/index.ts의 에러 인터셉터(34-41줄)에서 에러를 throw하지 않고 CommonErrorResponse 객체를 반환하고 있습니다. 이로 인해 logout, withdraw, refresh 등의 메서드에서 네트워크/서버 에러 발생 시 Promise가 resolve되어 CommonErrorResponse 인스턴스가 반환되고, 호출부가 .accessToken 같은 속성에 접근할 때 TypeError가 발생하거나 undefined를 받게 됩니다. 인터셉터에서 return 대신 throw를 사용하도록 수정이 필수입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/service/auth-service/index.ts` around lines 28 - 45, The authAPI
error interceptor currently returns a CommonErrorResponse object instead of
rejecting, causing logout/refresh/withdraw callers to receive a resolved value
on failure; update the interceptor in the authAPI error handler to rethrow the
error (either throw error or return Promise.reject(error) / throw new
CommonErrorResponse(...)) instead of returning it so that authAPI.post/delete
calls (used by logout, refresh, withdraw) produce rejected Promises and normal
error handling works.


// 구글 OAuth 코드 교환
exchangeGoogleCode: async (payload: GoogleOAuthExchangeRequest) => {
const data = await api.post<GoogleOAuthExchangeResponse>('/auth/google', payload, {
withCredentials: true,
});
const data = await authAPI.post<GoogleOAuthExchangeResponse>('/api/v1/auth/google', payload);

setAccessToken(data.accessToken, data.expiresIn);

return data;
},
});
16 changes: 8 additions & 8 deletions src/api/service/chat-service/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { apiV1 } from '@/api/core';
import { baseAPI } from '@/api/core';
import {
ChattingRoom,
CreateDMPayloads,
Expand All @@ -17,17 +17,17 @@ import {
export const chatServiceRemote = () => ({
// 채팅방 목록 조회
getChatRooms: async () => {
return apiV1.get<GetChatRoomsResponse>('/chat/rooms');
return baseAPI.get<GetChatRoomsResponse>('/api/v1/chat/rooms');
},

// 1:1(DM) 채팅방 생성
createDMChatRoom: async (payloads: CreateDMPayloads) => {
return apiV1.post<ChattingRoom>('/chat/dm', payloads);
return baseAPI.post<ChattingRoom>('/api/v1/chat/dm', payloads);
},

// 메세지 이력 조회
getChatMessages: async ({ roomId, cursor, size }: GetChatMessagesParams) => {
return apiV1.get<GetChatMessagesResponse>(`/chat/rooms/${roomId}/messages`, {
return baseAPI.get<GetChatMessagesResponse>(`/api/v1/chat/rooms/${roomId}/messages`, {
params: {
cursor,
size,
Expand All @@ -37,21 +37,21 @@ export const chatServiceRemote = () => ({

// 메세지 읽음 처리
readMessages: async ({ roomId }: ReadMessagesParams) => {
return apiV1.put<ReadMessagesResponse>(`/chat/rooms/${roomId}/read`);
return baseAPI.put<ReadMessagesResponse>(`/api/v1/chat/rooms/${roomId}/read`);
},

// 채팅방 상세 조회
getChatRoom: async ({ roomId }: GetChatRoomParams) => {
return apiV1.get<getChatRoomResponse>(`/chat/rooms/${roomId}`);
return baseAPI.get<getChatRoomResponse>(`/api/v1/chat/rooms/${roomId}`);
},

// 참여자 목록 조회
getParticipants: async ({ roomId }: GetParticipantsParams) => {
return apiV1.get<GetParticipantsResponse>(`/chat/rooms/${roomId}/participants`);
return baseAPI.get<GetParticipantsResponse>(`/api/v1/chat/rooms/${roomId}/participants`);
},

// 추방하기
kickUser: async (roomId: number, payload: KickUserPayloads) => {
return apiV1.post(`/chat/rooms/${roomId}/kick`, payload);
return baseAPI.post(`/api/v1/chat/rooms/${roomId}/kick`, payload);
},
});
Loading