Conversation
- CommonSuccessResponse: 일반 interface로 수정 - CommonErrorResponse: contructor 구문 수정
- baseApi: 인증 로직을 제외한 일반 API용 instance - authApi: 인증 API용 instance(공통 에러 처리 미포함)
- baseAPI 직접 호출 구조로 수정 - v1, v2 경로 url에 직접 포함하도록 수정
전체 요약새로운 중앙화된 인증 API 래퍼(authAPI)와 기본 API 래퍼(baseAPI)를 도입하고, 모든 서비스 모듈을 업데이트하여 새로운 API 클라이언트를 사용하도록 변경했습니다. 토큰 관리, 에러 처리, SSR/CSR 동작을 포함한 API 헬퍼 팩토리를 추가했습니다. 변경 사항
예상 코드 리뷰 노력🎯 4 (Complex) | ⏱️ ~75분 관련된 가능성 있는 PR
추천 레이블
추천 리뷰어
시 🐰
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🎭 Playwright Report✨ E2E Test가 성공적으로 완료되었습니다. Test 요약 내용을 확인해주세요.
📊 Test Summary
📜 Test Details✅ Passed Tests (3)
|
📊 Coverage Report
📈 #386을 main에 병합하면 coverage가 Coverage 요약@@ Coverage Diff @@
## main #386 +/- ##
===========================================
+ Coverage 35.38% 35.69% +0.31%
===========================================
Files 260 263 +3
Lines 12123 12141 +18
Branches 472 477 +5
===========================================
+ Hits 4290 4334 +44
- Misses 7833 7807 -26 영향받은 파일
|
🎨 Storybook Report✅ 변경 사항이 없습니다 모든 Story가 이전 빌드와 동일합니다.
|
🚀 PR Preview Report✨ Build가 성공적으로 완료되었습니다. Preview에서 변경사항을 확인하세요.
|
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/api/service/follower-service/index.ts (1)
7-28:⚠️ Potential issue | 🟠 Major
getFollowers와getFolloweeList가 동일한 엔드포인트를 호출하며,getFollowers의 주석이 부정확합니다.
getFollowers(주석: 팔로워 목록 조회)와getFolloweeList모두/api/v1/users/${userId}/follow엔드포인트를 호출합니다. 두 함수 모두 활발히 사용되고 있지만,getFollowers의 주석은 부정확합니다. 이 함수는 사용자가 팔로우한 사람들의 목록(팔로위)을 반환하므로, 주석은 "팔로위 목록 조회" 또는 "팔로잉 목록 조회"로 수정되어야 합니다. 팔로워는/api/v1/users/${userId}/follower엔드포인트를 사용하는getFollowerList에서 조회됩니다.해결 방안:
getFollowers를getFolloweeList로 통일하여 하나의 함수로 관리하거나getFollowers의 주석을 "팔로잉 목록 조회"로 수정하고, 필요시getFolloweeList를 deprecated 처리할 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/service/follower-service/index.ts` around lines 7 - 28, getFollowers currently calls `/api/v1/users/${userId}/follow` same as getFolloweeList, so update to remove ambiguity: either consolidate by removing getFolloweeList and use getFollowers (or vice versa) so only one function calls the follow (followee) endpoint, or keep both but change getFollowers' comment/name to indicate it returns "팔로잉(팔로위) 목록" and mark getFolloweeList as deprecated; ensure getFollowerList remains the only function calling `/api/v1/users/${userId}/follower`. Update docs/comments for getFollowers, getFolloweeList, and getFollowerList accordingly.
🧹 Nitpick comments (4)
src/lib/auth/token.ts (1)
41-41: 정적 정규식으로 교체하여 ast-grep 경고 제거
ACCESS_TOKEN_KEY가 모듈 내 상수('accessToken')이므로 실제 ReDoS 위험은 없지만, 정적 분석 도구의 경고를 없애고 가독성을 높이기 위해 리터럴 정규식을 사용하는 것을 권장합니다.♻️ 제안 리팩토링
- const match = document.cookie.match(new RegExp(`(^| )${ACCESS_TOKEN_KEY}=([^;]+)`)); + const match = document.cookie.match(/(^| )accessToken=([^;]+)/);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/auth/token.ts` at line 41, 문서 쿠키에서 토큰을 찾는 문장에서 동적 RegExp 생성으로 인해 ast-grep 경고가 발생하므로, 모듈 상수 ACCESS_TOKEN_KEY('accessToken')가 고정값임을 이용해 document.cookie.match(new RegExp(`...${ACCESS_TOKEN_KEY}...`))을 리터럴 정규식(/(^| )accessToken=([^;]+)/)으로 교체하세요; 대상 식별자는 ACCESS_TOKEN_KEY와 해당 호출이 있는 document.cookie.match(...)이며, 리터럴로 바꾼 뒤 match 변수 사용 로직은 그대로 유지하면 됩니다.src/api/core/auth/index.ts (1)
14-32:authAPI요청 인터셉터의 모든 요청에서 서버 사이드 동적 임포트 발생서버 환경에서
getAccessToken()은await import('next/headers')를 매번 호출합니다. Node.js 모듈 캐싱으로 인해 첫 번째 이후의 임포트는 빠르지만,authAPI인터셉터(Line 18)에서도 별도로await import('next/headers')를 호출하여refreshToken을 읽습니다. 두 번 중복 호출되는 구조이므로 단일 호출로 통합할 수 있습니다.♻️ 서버 사이드 중복 임포트 통합 제안
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 = cookieStore.get('accessToken')?.value; + if (accessToken && config.headers) { + config.headers.Authorization = `Bearer ${decodeURIComponent(accessToken)}`; + } + return config; } - const accessToken = await getAccessToken(); - if (accessToken && config.headers) { - config.headers.Authorization = `Bearer ${accessToken}`; - } + // 클라이언트: document.cookie에서 토큰 읽기 + const match = document.cookie.match(/(^| )accessToken=([^;]+)/); + const accessToken = match ? decodeURIComponent(match[2]) : undefined; + if (accessToken && config.headers) { + config.headers.Authorization = `Bearer ${accessToken}`; + } return config; });🤖 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 14 - 32, The interceptor authInstance.interceptors.request currently does two server-side dynamic imports of next/headers (once directly and again inside getAccessToken), causing duplicate work; fix this by importing next/headers once inside the interceptor (await import('next/headers')), read cookies() and extract refreshToken, then pass that refreshToken into getAccessToken (or refactor getAccessToken to accept an injected refreshToken) so getAccessToken no longer performs its own dynamic import; ensure you still set config.headers.Cookie and Authorization from the single refreshToken/accessToken source and remove the redundant import calls.src/api/service/group-service/index.ts (1)
102-108: 단일 파라미터에URLSearchParams대신 axiosparamsconfig 사용 권장
getJoinRequests는status하나만 쿼리 파라미터로 전달하므로 다른 메서드들(getGroupDetails,getGroupDetails등)과 일관되게 axiosparamsconfig 옵션을 사용하는 편이 더 간결합니다. (getGroups/getMyGroups는 배열 파라미터 직렬화를 위해URLSearchParams가 필요하므로 예외입니다.)♻️ 제안 리팩토링
getJoinRequests: (params: GroupIdParams, status: string = 'PENDING') => { - const queryParams = new URLSearchParams(); - queryParams.append('status', status); - return baseAPI.get<GetJoinRequestsResponse>( - `/api/v2/groups/${params.groupId}/attendance?${queryParams.toString()}`, - ); + return baseAPI.get<GetJoinRequestsResponse>( + `/api/v2/groups/${params.groupId}/attendance`, + { params: { status } }, + ); },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/service/group-service/index.ts` around lines 102 - 108, getJoinRequests currently builds the query with URLSearchParams for a single "status" param; change it to use axios' params config for consistency with other methods: in the getJoinRequests function, remove URLSearchParams usage and call baseAPI.get<GetJoinRequestsResponse>(`/api/v2/groups/${params.groupId}/attendance`, { params: { status } }) while preserving the default status = 'PENDING' and the existing generic type.src/api/core/lib/apiHelper.ts (1)
5-5: 매개변수 이름axios가 모듈명과 충돌
axios매개변수명이 상위 스코프의axios모듈 import와 동일합니다. 현재는 타입만 import하므로 런타임 문제는 없지만, 향후 혼동을 줄이기 위해instance등으로 변경하는 것을 권장합니다.제안된 수정
-export const createApiHelper = (axios: AxiosInstance) => ({ - get: async <T>(url: string, config?: AxiosRequestConfig): Promise<T> => { - const response = await axios.get<CommonSuccessResponse<T>>(url, config); +export const createApiHelper = (instance: AxiosInstance) => ({ + get: async <T>(url: string, config?: AxiosRequestConfig): Promise<T> => { + const response = await instance.get<CommonSuccessResponse<T>>(url, config);(나머지 메서드도 동일하게
axios→instance로 변경)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/api/core/lib/apiHelper.ts` at line 5, 매개변수 이름 충돌 문제: createApiHelper 함수의 매개변수명 axios가 상위 스코프의 axios 모듈명과 동일해 혼동을 줄 수 있으니 함수 시그니처와 내부 모든 참조를 axios → instance(또는 client)로 변경하세요; 구체적으로 createApiHelper(axios: AxiosInstance)와 내부에서 axios로 호출하는 모든 메서드(예: get/post 호출 등)를 instance로 바꿔 일관되게 사용하도록 수정하세요.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/api/core/auth/index.ts`:
- Around line 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.
- Around line 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.
In `@src/api/core/base/index.ts`:
- Around line 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.
- Around line 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.
- 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).
In `@src/api/service/auth-service/index.ts`:
- Around line 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.
In `@src/api/service/user-service/index.ts`:
- Around line 62-65: getMeSkipRedirect currently calls the same endpoint with
baseAPI.get as getMe so the intended "skipAuthRedirect" behavior is not applied;
fix by wiring getMeSkipRedirect to use a dedicated request configuration or
client that disables the auth-redirect interceptor (e.g., create/consume a
separate axios instance or pass a request config flag like { skipAuthRedirect:
true } to baseAPI.get) and ensure only getMeSkipRedirect (not getMe) uses that
instance/flag; update the function getMeSkipRedirect to call the alternate
client or include the skip flag while leaving getMe and baseAPI unchanged.
In `@src/lib/auth/token.ts`:
- Around line 34-43: getAccessToken returns the raw cookie value on the server
branch which can mismatch the client where decodeURIComponent is used; update
the server path inside getAccessToken to decode the cookie value from
cookieStore.get(ACCESS_TOKEN_KEY)?.value with decodeURIComponent (or return
undefined if no value) so server and client return consistently-decoded tokens;
reference getAccessToken, ACCESS_TOKEN_KEY and cookieStore.get to locate the
change.
---
Outside diff comments:
In `@src/api/service/follower-service/index.ts`:
- Around line 7-28: getFollowers currently calls
`/api/v1/users/${userId}/follow` same as getFolloweeList, so update to remove
ambiguity: either consolidate by removing getFolloweeList and use getFollowers
(or vice versa) so only one function calls the follow (followee) endpoint, or
keep both but change getFollowers' comment/name to indicate it returns "팔로잉(팔로위)
목록" and mark getFolloweeList as deprecated; ensure getFollowerList remains the
only function calling `/api/v1/users/${userId}/follower`. Update docs/comments
for getFollowers, getFolloweeList, and getFollowerList accordingly.
---
Nitpick comments:
In `@src/api/core/auth/index.ts`:
- Around line 14-32: The interceptor authInstance.interceptors.request currently
does two server-side dynamic imports of next/headers (once directly and again
inside getAccessToken), causing duplicate work; fix this by importing
next/headers once inside the interceptor (await import('next/headers')), read
cookies() and extract refreshToken, then pass that refreshToken into
getAccessToken (or refactor getAccessToken to accept an injected refreshToken)
so getAccessToken no longer performs its own dynamic import; ensure you still
set config.headers.Cookie and Authorization from the single
refreshToken/accessToken source and remove the redundant import calls.
In `@src/api/core/lib/apiHelper.ts`:
- Line 5: 매개변수 이름 충돌 문제: createApiHelper 함수의 매개변수명 axios가 상위 스코프의 axios 모듈명과 동일해
혼동을 줄 수 있으니 함수 시그니처와 내부 모든 참조를 axios → instance(또는 client)로 변경하세요; 구체적으로
createApiHelper(axios: AxiosInstance)와 내부에서 axios로 호출하는 모든 메서드(예: get/post 호출
등)를 instance로 바꿔 일관되게 사용하도록 수정하세요.
In `@src/api/service/group-service/index.ts`:
- Around line 102-108: getJoinRequests currently builds the query with
URLSearchParams for a single "status" param; change it to use axios' params
config for consistency with other methods: in the getJoinRequests function,
remove URLSearchParams usage and call
baseAPI.get<GetJoinRequestsResponse>(`/api/v2/groups/${params.groupId}/attendance`,
{ params: { status } }) while preserving the default status = 'PENDING' and the
existing generic type.
In `@src/lib/auth/token.ts`:
- Line 41: 문서 쿠키에서 토큰을 찾는 문장에서 동적 RegExp 생성으로 인해 ast-grep 경고가 발생하므로, 모듈 상수
ACCESS_TOKEN_KEY('accessToken')가 고정값임을 이용해 document.cookie.match(new
RegExp(`...${ACCESS_TOKEN_KEY}...`))을 리터럴 정규식(/(^| )accessToken=([^;]+)/)으로
교체하세요; 대상 식별자는 ACCESS_TOKEN_KEY와 해당 호출이 있는 document.cookie.match(...)이며, 리터럴로 바꾼
뒤 match 변수 사용 로직은 그대로 유지하면 됩니다.
| 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, | ||
| }); |
There was a problem hiding this comment.
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.response.use( | ||
| (response) => { | ||
| return response; | ||
| }, | ||
| async (error) => { | ||
| return new CommonErrorResponse(error.response?.data); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
[심각] 에러 인터셉터에서 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.
| import { getAccessToken } from '@/lib/auth/token'; | ||
| import { CommonErrorResponse } from '@/types/service/common'; | ||
|
|
||
| import { API } from '../..'; |
There was a problem hiding this comment.
🧩 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.tsRepository: 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.tsRepository: WeGo-Together/WeGo_FrontEnd
Length of output: 3264
순환 의존성 제거 필요
API를 ../.. (src/api/)에서 가져오면, 모듈 초기화 시 순환 참조가 발생합니다:
src/api/core/base/index.ts → src/api/index.ts → src/api/service/auth-service → src/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).
| if (status === 401 && !originalRequest._retry) { | ||
| originalRequest._retry = true; |
There was a problem hiding this comment.
🧩 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 -20Repository: 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
fiRepository: 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
fiRepository: 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.tsRepository: 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.
There was a problem hiding this comment.
originalRequest.m이 어디있나요? 저는 originalRequest._retry 밖에 사용하지 않았습니다.
There was a problem hiding this comment.
@Chiman2937 죄송합니다. 제가 실수했습니다.
코드를 다시 확인해보니 originalRequest._retry를 올바르게 사용하고 계시네요. 제 이전 코멘트에서 originalRequest.n이라고 잘못 언급한 것은 오류였습니다. (그리고 .m에 대해 질문하셨는데, 제가 언급한 것은 .n이었습니다 - 혼란을 드려 죄송합니다)
현재 코드는 이미 _retry 속성을 사용하고 있으며, 이는 좋은 네이밍입니다. 다만 TypeScript strict 모드에서는 여전히 _retry가 Axios의 InternalAxiosRequestConfig 타입에 정의되어 있지 않아 타입 에러가 발생할 수 있습니다.
만약 TypeScript 컴파일 시 에러가 발생하지 않는다면 현재 설정으로도 충분합니다. 에러가 발생한다면 앞서 제안드린 타입 확장 방법을 적용하시면 됩니다.
혼란을 드려 죄송합니다! 🙏
| } else { | ||
| const currentPath = window.location.pathname + window.location.search; | ||
| window.location.href = `/login?error=unauthorized&path=${encodeURIComponent(currentPath)}`; | ||
| } | ||
| } |
There was a problem hiding this comment.
클라이언트 측 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.
| } 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.
| 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(); | ||
| }, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate the auth service files
find . -type f -name "index.ts" | grep -E "(auth|service)" | head -20Repository: 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_modulesRepository: WeGo-Together/WeGo_FrontEnd
Length of output: 190
🏁 Script executed:
# Read the authAPI error interceptor implementation
cat -n src/api/core/auth/index.tsRepository: 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.tsRepository: 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.
| // 8-1. 본인 프로필 조회(redirect skip) | ||
| getMeSkipRedirect: async () => { | ||
| return apiV1.get<User>(`/users/me`, { skipAuthRedirect: false }); | ||
| return baseAPI.get<User>(`/api/v1/users/me`); | ||
| }, |
There was a problem hiding this comment.
getMeSkipRedirect가 getMe와 완전히 동일하게 동작합니다.
두 메서드가 같은 엔드포인트(/api/v1/users/me)를 차이 없이 호출하므로, skipAuthRedirect 의도가 전혀 반영되지 않습니다. PR 설명에서 "추후 수정 예정"으로 명시되어 있으나, 이 상태로 병합되면 getMeSkipRedirect를 사용하는 모든 호출부도 동일하게 리다이렉트 처리를 받게 됩니다.
getMeSkipRedirect 전용 로직(예: 별도 에러 인터셉터 없이 동작하는 인스턴스 활용)을 구현하는 것을 도와드릴까요? 이슈를 새로 생성하길 원하시면 말씀해 주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/api/service/user-service/index.ts` around lines 62 - 65,
getMeSkipRedirect currently calls the same endpoint with baseAPI.get as getMe so
the intended "skipAuthRedirect" behavior is not applied; fix by wiring
getMeSkipRedirect to use a dedicated request configuration or client that
disables the auth-redirect interceptor (e.g., create/consume a separate axios
instance or pass a request config flag like { skipAuthRedirect: true } to
baseAPI.get) and ensure only getMeSkipRedirect (not getMe) uses that
instance/flag; update the function getMeSkipRedirect to call the alternate
client or include the skip flag while leaving getMe and baseAPI unchanged.
| export const getAccessToken = async () => { | ||
| const isServer = typeof window === 'undefined'; | ||
| if (isServer) { | ||
| const { cookies } = await import('next/headers'); | ||
| const cookieStore = await cookies(); | ||
| return cookieStore.get(ACCESS_TOKEN_KEY)?.value; | ||
| } else { | ||
| const match = document.cookie.match(new RegExp(`(^| )${ACCESS_TOKEN_KEY}=([^;]+)`)); | ||
| return match ? decodeURIComponent(match[2]) : undefined; | ||
| } |
There was a problem hiding this comment.
서버 경로에서 URL 디코딩 누락으로 인한 인코딩 불일치 가능성
setAccessToken이 encodeURIComponent로 토큰을 인코딩하여 쿠키에 저장하는데, 클라이언트 경로(Line 42)는 decodeURIComponent로 올바르게 디코딩하지만 서버 경로(Line 39)는 cookieStore.get(ACCESS_TOKEN_KEY)?.value의 raw 값을 그대로 반환합니다. JWT는 base64url 인코딩이라 =, + 등 특수문자가 거의 없어 실제 영향이 제한적일 수 있으나, 일관성 보장을 위해 서버 경로도 디코딩하는 것이 안전합니다.
🛡️ 수정 제안
if (isServer) {
const { cookies } = await import('next/headers');
const cookieStore = await cookies();
- return cookieStore.get(ACCESS_TOKEN_KEY)?.value;
+ const raw = cookieStore.get(ACCESS_TOKEN_KEY)?.value;
+ return raw ? decodeURIComponent(raw) : undefined;
} else {📝 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.
| export const getAccessToken = async () => { | |
| const isServer = typeof window === 'undefined'; | |
| if (isServer) { | |
| const { cookies } = await import('next/headers'); | |
| const cookieStore = await cookies(); | |
| return cookieStore.get(ACCESS_TOKEN_KEY)?.value; | |
| } else { | |
| const match = document.cookie.match(new RegExp(`(^| )${ACCESS_TOKEN_KEY}=([^;]+)`)); | |
| return match ? decodeURIComponent(match[2]) : undefined; | |
| } | |
| export const getAccessToken = async () => { | |
| const isServer = typeof window === 'undefined'; | |
| if (isServer) { | |
| const { cookies } = await import('next/headers'); | |
| const cookieStore = await cookies(); | |
| const raw = cookieStore.get(ACCESS_TOKEN_KEY)?.value; | |
| return raw ? decodeURIComponent(raw) : undefined; | |
| } else { | |
| const match = document.cookie.match(new RegExp(`(^| )${ACCESS_TOKEN_KEY}=([^;]+)`)); | |
| return match ? decodeURIComponent(match[2]) : undefined; | |
| } | |
| } |
🧰 Tools
🪛 ast-grep (0.40.5)
[warning] 40-40: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp((^| )${ACCESS_TOKEN_KEY}=([^;]+))
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/auth/token.ts` around lines 34 - 43, getAccessToken returns the raw
cookie value on the server branch which can mismatch the client where
decodeURIComponent is used; update the server path inside getAccessToken to
decode the cookie value from cookieStore.get(ACCESS_TOKEN_KEY)?.value with
decodeURIComponent (or return undefined if no value) so server and client return
consistently-decoded tokens; reference getAccessToken, ACCESS_TOKEN_KEY and
cookieStore.get to locate the change.
There was a problem hiding this comment.
@coderabbitai Next.js의 cookies()는 자동으로 디코딩 해주지 않나요?
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Next.js cookies() API automatic URL decoding behavior
💡 Result:
In Next.js, cookies() (from next/headers) returns URL-decoded cookie values.
- When parsing the incoming
Cookieheader, Next’s underlying cookie parser callsdecodeURIComponent(value)for each cookie value, socookieStore.get('x')?.valueis the decoded string (e.g.%7B%22a%22%3A1%7D→{"a":1}), not the raw header bytes. If decoding throws (malformed%sequences), that cookie value is ignored by the parser. [2] - When you set cookies via the same stack, it URL-encodes values using
encodeURIComponentwhen constructingCookie/Set-Cookiestrings. So a round-trip throughcookies().set()thencookies().get()yields your original string. [2][3] - The public Next.js docs for
cookies()describe the API surface but don’t call out the encode/decode behavior explicitly. [1]
If you need the raw (non-decoded) value, you’ll have to read the raw Cookie header directly instead of relying on cookies().get(). [2]
Sources: Next.js cookies() docs [1]; @edge-runtime/cookies implementation (parseCookie uses decodeURIComponent, stringifyCookie uses encodeURIComponent) [2][3].
@Chiman2937 맞습니다! 제 리뷰 코멘트가 잘못되었습니다.
Next.js의 cookies() API는 내부적으로 decodeURIComponent를 호출하여 쿠키 값을 자동으로 디코딩합니다. 따라서 cookieStore.get(ACCESS_TOKEN_KEY)?.value는 이미 디코딩된 값을 반환하므로, 서버 경로에서 추가로 decodeURIComponent를 호출할 필요가 없습니다.
현재 코드가 올바르며, 서버와 클라이언트 경로 모두 일관되게 디코딩된 토큰을 반환합니다. 지적해주셔서 감사합니다! 🙏
📝 변경 사항
커밋 히스토리
efe96dcb13b9f9119af60480a24470c396e77c3ea91620d7b1. Axios 인스턴스 분리
기존 단일
baseAPI인스턴스를 용도별 2개 인스턴스로 분리.Before
After
baseAPIcore/base/index.tsauthAPIcore/auth/index.tswithCredentials: true, 서버사이드 refreshToken 쿠키 주입2. apiHelper 유틸함수 분리
src/api/core/lib/apiHelper.ts에createApiHelper팩토리 함수 신규 생성.주요 개선점
data파라미터any→unknown,config파라미터any→AxiosRequestConfig/api/v1,/api/v2프리픽스를 붙이던 방식 제거 → 각 서비스에서 URL에 직접 버전 경로 포함createApiHelper(axiosInstance)형태로 어떤 axios 인스턴스든 받아서 헬퍼 생성 가능3. authAPI 인스턴스 - 서버사이드 refreshToken 쿠키 주입
src/api/core/auth/index.ts의 request interceptor에서 서버 환경 감지 후next/headers의 cookies를 통해 refreshToken을 수동으로 헤더에 설정.4. getAccessToken 유틸함수 추가
src/lib/auth/token.ts에getAccessToken함수 추가.next/headers의cookies()사용document.cookie파싱5. API Service 참조 경로 수정
모든 서비스 파일에서 import 및 API 호출 방식 변경.
Import 변경
import { api } from '@/api/core'import { authAPI } from '@/api/core'import { apiV1 } from '@/api/core'import { baseAPI } from '@/api/core'import { api } from '@/api/core'import { baseAPI } from '@/api/core'import { apiV2 } from '@/api/core'import { baseAPI } from '@/api/core'import { apiV1 } from '@/api/core'import { baseAPI } from '@/api/core'import { apiV1 } from '@/api/core'import { baseAPI } from '@/api/core'URL 패턴 변경
6. auth-service 간소화
src/api/service/auth-service/index.ts에서authAPI사용으로 인한 변경:withCredentials: true옵션 제거 →authAPI인스턴스 레벨에서 설정refresh()메서드에서redirect,_retry,skipAuthRedirect파라미터 제거 → 인증 관련 리다이렉트 로직은baseAPIinterceptor에서 처리추가 발견사항 (놓친 부분)
1.
skipAuthRedirect로직 제거기존
baseAPI에 있던skipAuthRedirect플래그 기반의 조건부 리다이렉트 로직이 완전히 제거됨. 이에 따라user-service의getMeSkipRedirect메서드도 현재는getMe와 동일한 동작을 함:3. baseAPI 401 에러 처리 - 서버사이드 리다이렉트 로직 변경
기존에는 서버사이드에서 401 에러 시
redirect('/login')처리했으나, 변경 후에는throw refreshError로 에러를 던지는 방식으로 변경됨. 서버사이드에서의 인증 실패 처리 방식이 달라진 점 참고.4. 공통 응답 타입 (
CommonErrorResponse) 변경이번 커밋 범위에 직접 포함되지는 않지만, 현재 코드 기준으로
CommonErrorResponse가class로 관리되고 있으며, interceptor에서new CommonErrorResponse(error.response?.data)형태로 인스턴스를 생성하여 반환하는 구조. 기존 인라인 객체 리터럴 방식에서 class 기반으로 변경되어 에러 응답의 기본값 처리가 constructor에서 일원화됨.변경된 파일 목록
src/api/core/index.tssrc/api/core/auth/index.tssrc/api/core/base/index.tssrc/api/core/lib/apiHelper.tssrc/lib/auth/token.tssrc/api/service/auth-service/index.tssrc/api/service/chat-service/index.tssrc/api/service/follower-service/index.tssrc/api/service/group-service/index.tssrc/api/service/notification-service/index.tssrc/api/service/user-service/index.ts🔗 관련 이슈
Closes #
🧪 테스트 방법
📸 스크린샷 (선택)
📋 체크리스트
💬 추가 코멘트
CodeRabbit Review는 자동으로 실행되지 않습니다.
Review를 실행하려면 comment에 아래와 같이 작성해주세요
Summary by CodeRabbit
릴리즈 노트
New Features
Refactor