Conversation
Walkthrough인증 컨텍스트를 도입하여 전역 로그인 상태를 관리하고, 학번 기반 로그인으로 변경하며, 보호된 경로 컴포넌트를 추가하여 인증되지 않은 사용자를 로그인 페이지로 리디렉션하고, API 프록시 설정을 추가했습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant User as 사용자
participant App as App<br/>(ProtectedRoute)
participant AuthCtx as AuthContext<br/>(AuthProvider)
participant API as API Server
participant Login as LoginForm
User->>App: 앱 접속
App->>AuthCtx: AuthProvider 마운트
AuthCtx->>API: GET /api/user/details<br/>(로그인 확인)
API-->>AuthCtx: 사용자 정보 / 실패
AuthCtx->>AuthCtx: isLoggedIn 상태 설정
AuthCtx-->>App: 로드 완료
alt 로그인됨
App->>App: Outlet으로<br/>보호된 라우트 렌더
App-->>User: 대시보드 표시
else 미로그인
App->>App: /login으로<br/>리디렉션
User->>Login: 로그인 페이지 이동
User->>Login: 학번 + 비밀번호 입력
Login->>AuthCtx: authLogin(studentId, pw)
AuthCtx->>API: POST /api/auth/login<br/>{studentId, password}
API-->>AuthCtx: 인증 토큰 반환
AuthCtx->>AuthCtx: isLoggedIn = true
AuthCtx-->>Login: 로그인 성공
Login->>App: 리디렉션
App->>App: 보호된 라우트 렌더
App-->>User: 대시보드 표시
end
sequenceDiagram
participant User as 사용자
participant Header as Header/<br/>Sidebar
participant AuthCtx as AuthContext
participant API as API Server
User->>Header: 로그아웃 버튼 클릭
Header->>Header: handleLogout() 실행
Header->>AuthCtx: logout()
AuthCtx->>API: POST /api/auth/logout
API-->>AuthCtx: 응답 (에러 무시)
AuthCtx->>AuthCtx: localStorage 'user' 제거
AuthCtx->>AuthCtx: isLoggedIn = false
AuthCtx-->>Header: 로그아웃 완료
Header->>Header: 토스트 표시
Header->>Header: /로 네비게이션
Header-->>User: 로그인 페이지 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 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)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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 |
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)
frontend/src/components/login/LoginForm.jsx (1)
36-49:⚠️ Potential issue | 🔴 Critical중대:
authLogin이 실제 서버 인증을 수행하지 않아 인증 우회 버그 발생
AuthContext의login함수(26줄)는 매개변수를 받지 않고() => setIsLoggedIn(true)로만 정의되어 있습니다. 그러나LoginForm의handleLogin(37-43줄)에서는await authLogin({ studentId, password }, signal)로 자격증명을 전달하고 있습니다.결과적으로:
- 서버에 인증 요청을 보내지 않음
- 전달된 매개변수(학번, 비밀번호, signal) 무시
- 항상 에러를 throw하지 않으므로 catch 블록 실행 불가
- 임의의 8자리 숫자와 어떤 비밀번호로든 "로그인 성공"
AuthContext의login함수에 실제 API 호출 로직을 추가하고 응답을 처리해야 합니다. 현재api모듈이 import되어 있으니, 이를 활용하여/api/auth/login엔드포인트로 인증 요청을 보내고 응답을 처리하는 코드를 작성하세요.
🤖 Fix all issues with AI agents
In `@frontend/src/components/Header.jsx`:
- Around line 14-18: The logout handler calls an undefined symbol navigate
causing a ReferenceError; update the code so the navigation call uses the actual
useNavigate return value (either rename the earlier nav variable to navigate or
change handleLogout to call nav('/')); locate the useNavigate() assignment and
the handleLogout function (symbols: useNavigate, nav, handleLogout, logout,
toast) and make the names consistent so the logout flow navigates without
runtime errors.
In `@frontend/src/components/login/ResetPasswordModal.jsx`:
- Around line 26-35: The catch block for resetPassword should ignore aborts so
canceled requests don't show an error toast: in the catch of the try surrounding
resetPassword({ email }, abortRef.current.signal) check the thrown error (err)
for an abort indicator (e.g., err.name === 'AbortError' or common cancellation
codes like err.code === 'ERR_CANCELED') and simply return/do nothing for those
cases; otherwise continue to console.dir(err) and call toast.error as before.
This change touches the try-catch around resetPassword and uses
abortRef.current.signal and err to determine the early return.
In `@frontend/src/components/Sidebar.jsx`:
- Around line 172-176: There is a typo calling handleLogoutogout() causing a
ReferenceError; replace that call with the correctly defined function
handleLogout so the click handler invokes handleLogout() and then
handleNavLinkClick(); update the onClick handler where handleLogoutogout is
referenced to call handleLogout and ensure the order (preventDefault,
handleLogout, handleNavLinkClick) remains the same.
In `@frontend/src/contexts/AttendanceContext.jsx`:
- Around line 87-88: getAttendanceSessions() already returns the response body
(not an Axios wrapper), so calling setSessions(res.data || []) double-unwraps
and yields undefined; change the assignment in AttendanceContext.jsx where you
call getAttendanceSessions() (the const res = await getAttendanceSessions();
followed by setSessions(...)) to use the returned value directly (e.g.,
setSessions(res || [])) so sessions load correctly; search for
getAttendanceSessions and update the setSessions call in that handler to mirror
the other correct usages (setSessions(updatedSessions || [])).
In `@frontend/src/contexts/AuthContext.jsx`:
- Line 46: The useAuth hook currently returns the raw AuthContext value which
can be null outside AuthProvider; add a guard in useAuth (the exported hook) to
check the result of useContext(AuthContext) and throw a clear error like
"useAuth must be used within AuthProvider" (or return a safe default) to prevent
runtime destructure errors in consumers; mirror the pattern used by
AttendanceContext to locate the fix around the useAuth function and update any
associated tests or callers if you choose to return defaults instead of
throwing.
In `@frontend/src/utils/attendanceManage.js`:
- Around line 21-22: Remove the leftover debug console.log by deleting the call
to console.log(res.data) in frontend/src/utils/attendanceManage.js (where the
API response is handled), or replace it with the app's proper
logging/error-handling mechanism if response inspection is required (e.g., use a
debug logger or process the data in the existing function that handles the API
response).
In `@frontend/src/utils/axios.js`:
- Around line 3-6: The token refresh call currently builds a full URL using
import.meta.env.VITE_API_URL while the exported axios instance api uses baseURL:
'', causing mismatch in production; update the refresh request to use the api
instance (e.g., replace `${import.meta.env.VITE_API_URL}/...` with
api.post('/auth/refresh' or the existing refresh path) so it uses
api.defaults.baseURL and withCredentials, or alternatively set
api.defaults.baseURL from import.meta.env.VITE_API_URL and then use
api.post('/auth/refresh'); locate the exported api in the axios utility (export
const api) and the token refresh call that constructs the VITE_API_URL string
and make them consistent.
In `@frontend/vite.config.js`:
- Around line 37-45: The proxy target for server.proxy['/api'] in vite.config.js
is hardcoded to 'http://54.180.175.139:8080'; change it to read from an
environment variable (e.g. VITE_API_TARGET) instead: load the env (via loadEnv
or process.env) inside the Vite config and set target: env.VITE_API_TARGET ||
'http://localhost:8080' so the proxy value is configurable and falls back
safely; update any docs or .env.example to mention VITE_API_TARGET.
🧹 Nitpick comments (9)
frontend/package.json (1)
17-17:qrcode.react의존성이 이 PR의 범위와 무관해 보입니다.이 PR은 인증 상태 및 로그인 페이지 수정에 관한 것인데,
qrcode.react는 QR 코드 관련 기능으로 보입니다. 별도 PR로 분리하는 것이 변경 이력 추적에 더 적합합니다.frontend/src/utils/auth.js (2)
20-21:paylaod오타가 있습니다.
paylaod→payload로 수정해 주세요. 기존부터 있던 오타이지만 이번에 이 라인이 수정되었으므로 함께 정리하면 좋겠습니다.🔧 수정 제안
- const paylaod = { studentId, password }; - - const res = await api.post('/api/auth/login', paylaod, { signal }); + const payload = { studentId, password }; + + const res = await api.post('/api/auth/login', payload, { signal });
49-56:resetPassword에서 이메일을 쿼리 파라미터로 전송하고 있습니다.이메일(PII)이 URL 쿼리 파라미터에 포함되면 서버 액세스 로그, 브라우저 히스토리, 네트워크 모니터링 등에 노출될 수 있습니다. 가능하다면 request body로 전송하는 것이 더 안전합니다. 백엔드 API 설계에 따라 결정해 주세요.
frontend/src/contexts/AuthContext.jsx (2)
11-24: 로그인 체크 시 사용자 정보를 저장하지 않고 있습니다.
/api/user/details응답에 사용자 정보(역할, 이름 등)가 포함될 텐데 버리고 있습니다. 이후 컴포넌트에서 사용자 정보가 필요하면 중복 API 호출이 발생합니다. 응답 데이터를 상태에 저장하는 것을 고려해 주세요.🔧 수정 제안
export const AuthProvider = ({ children }) => { const [isLoggedIn, setIsLoggedIn] = useState(false); const [loading, setLoading] = useState(true); + const [user, setUser] = useState(null); useEffect(() => { const checkLogin = async () => { try { - await api.get('/api/user/details'); + const res = await api.get('/api/user/details'); setIsLoggedIn(true); + setUser(res.data); } catch (err) { setIsLoggedIn(false); + setUser(null); } finally { setLoading(false); } }; checkLogin(); }, []);
31-32:console.log를 제거하거나console.warn으로 변경하세요.프로덕션 환경에서 로그아웃 실패가 무시된다는 사실이
console.log로 남으면 노이즈가 됩니다.🔧 수정 제안
} catch (e) { - console.log('logout api 실패 무시'); + // 로그아웃 API 실패 시 로컬 상태만 정리 } finally {frontend/src/components/protectedRoute.jsx (1)
1-1: 파일 경로 주석이 실제 경로와 불일치합니다.주석에는
routes/ProtectedRoute.jsx로 표기되어 있지만, 실제 파일 경로는components/protectedRoute.jsx입니다. 혼동을 방지하기 위해 수정하거나 제거해 주세요.frontend/src/components/attendancemanage/SessionManagementCard.jsx (2)
58-72: "출석일자 추가" 버튼 클릭 시 아무 동작도 하지 않습니다.
openAddRoundsModal()이 주석 처리되어 있어, 세션 선택 후 버튼을 클릭해도 아무 반응이 없습니다. 사용자 혼란을 방지하기 위해 라운드 API 연결 전까지 버튼을 비활성화(disabled)하거나 숨기는 것을 고려해 주세요.♻️ 버튼 비활성화 제안
<button className={commonStyles.iconButton} + disabled onClick={() => { if (!currentSession) { toast.error('세션을 먼저 선택해주세요.'); return; } // openAddRoundsModal(); }} >
41-50: 라운드 조회 API 연결이 TODO로 남아 있습니다.
currentDisplayedRounds가 항상 빈 배열이므로 테이블에 항상 "회차 정보가 없습니다."가 표시됩니다. 이 TODO를 추적하기 위한 이슈가 있는지 확인해 주세요.이 TODO 항목을 추적하기 위해 새 이슈를 생성할까요?
frontend/src/components/login/LoginForm.jsx (1)
106-112:<a>태그에href가 없습니다.Line 107의
<a>태그에href속성이 없어 접근성에 영향을 줄 수 있습니다. 클릭 가능한 요소임을 명확히 하기 위해<button>으로 변경하거나href="#"와role="button"을 추가하는 것을 권장합니다.♻️ button으로 변경
- <a - className={styles.text} - onClick={() => setModalStep('resetPassword')} - > - 비밀번호 초기화 - </a> + <button + type="button" + className={styles.text} + onClick={() => setModalStep('resetPassword')} + > + 비밀번호 초기화 + </button>
| ); | ||
| }; | ||
|
|
||
| export const useAuth = () => useContext(AuthContext); |
There was a problem hiding this comment.
useAuth에 null 체크가 없습니다.
AuthProvider 외부에서 useAuth()를 호출하면 null이 반환되어 소비 컴포넌트에서 Cannot destructure property 'isLoggedIn' of null 런타임 에러가 발생합니다. AttendanceContext처럼 가드를 추가하세요.
🔧 수정 제안
-export const useAuth = () => useContext(AuthContext);
+export const useAuth = () => {
+ const context = useContext(AuthContext);
+ if (!context) {
+ throw new Error('useAuth must be used within an AuthProvider');
+ }
+ return context;
+};📝 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 useAuth = () => useContext(AuthContext); | |
| export const useAuth = () => { | |
| const context = useContext(AuthContext); | |
| if (!context) { | |
| throw new Error('useAuth must be used within an AuthProvider'); | |
| } | |
| return context; | |
| }; |
🤖 Prompt for AI Agents
In `@frontend/src/contexts/AuthContext.jsx` at line 46, The useAuth hook currently
returns the raw AuthContext value which can be null outside AuthProvider; add a
guard in useAuth (the exported hook) to check the result of
useContext(AuthContext) and throw a clear error like "useAuth must be used
within AuthProvider" (or return a safe default) to prevent runtime destructure
errors in consumers; mirror the pattern used by AttendanceContext to locate the
fix around the useAuth function and update any associated tests or callers if
you choose to return defaults instead of throwing.
| // console.log('API BASE URL:', import.meta.env.VITE_API_URL); | ||
| console.log(res.data); |
There was a problem hiding this comment.
디버그용 console.log를 제거하세요.
console.log(res.data)는 디버깅용으로 보입니다. 프로덕션 코드에 불필요한 로그가 남지 않도록 머지 전에 제거해 주세요.
🔧 수정 제안
- // console.log('API BASE URL:', import.meta.env.VITE_API_URL);
- console.log(res.data);📝 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.
| // console.log('API BASE URL:', import.meta.env.VITE_API_URL); | |
| console.log(res.data); |
🤖 Prompt for AI Agents
In `@frontend/src/utils/attendanceManage.js` around lines 21 - 22, Remove the
leftover debug console.log by deleting the call to console.log(res.data) in
frontend/src/utils/attendanceManage.js (where the API response is handled), or
replace it with the app's proper logging/error-handling mechanism if response
inspection is required (e.g., use a debug logger or process the data in the
existing function that handles the API response).
| export const api = axios.create({ | ||
| baseURL: import.meta.env.VITE_API_URL, | ||
| baseURL: '', | ||
| withCredentials: true, | ||
| }); |
There was a problem hiding this comment.
baseURL와 토큰 갱신 URL 간 불일치가 있습니다.
api 인스턴스의 baseURL은 ''(상대 경로)로 변경되었지만, Line 20의 토큰 갱신 요청은 여전히 ${import.meta.env.VITE_API_URL}을 사용합니다. 개발 환경에서는 vite proxy 덕분에 api 호출이 작동하지만, 토큰 갱신 요청은 VITE_API_URL 환경 변수에 의존합니다.
프로덕션 빌드 시 vite proxy가 없으므로, baseURL: ''인 api 인스턴스의 요청은 별도의 리버스 프록시 설정 없이는 백엔드에 도달하지 못합니다. 두 경로의 URL 전략을 통일해 주세요.
🔧 수정 제안 (토큰 갱신도 api 인스턴스 사용)
- await axios.post(
- `${import.meta.env.VITE_API_URL}/api/auth/reissue`,
- {}, // body 비움
- { withCredentials: true }
- );
+ await axios.post(
+ '/api/auth/reissue',
+ {},
+ { withCredentials: true }
+ );Also applies to: 19-23
🤖 Prompt for AI Agents
In `@frontend/src/utils/axios.js` around lines 3 - 6, The token refresh call
currently builds a full URL using import.meta.env.VITE_API_URL while the
exported axios instance api uses baseURL: '', causing mismatch in production;
update the refresh request to use the api instance (e.g., replace
`${import.meta.env.VITE_API_URL}/...` with api.post('/auth/refresh' or the
existing refresh path) so it uses api.defaults.baseURL and withCredentials, or
alternatively set api.defaults.baseURL from import.meta.env.VITE_API_URL and
then use api.post('/auth/refresh'); locate the exported api in the axios utility
(export const api) and the token refresh call that constructs the VITE_API_URL
string and make them consistent.
| server: { | ||
| proxy: { | ||
| '/api': { | ||
| target: 'http://54.180.175.139:8080', // 예: http://54.180.175.139 | ||
| changeOrigin: true, | ||
| secure: false, | ||
| }, | ||
| }, | ||
| }, |
There was a problem hiding this comment.
백엔드 서버 IP 주소를 하드코딩하지 마세요.
http://54.180.175.139:8080이 소스 코드에 직접 노출되어 있습니다. 인프라 정보가 Git 히스토리에 남게 되며, 서버 변경 시 코드 수정이 필요합니다. 환경 변수를 사용하세요.
🔧 수정 제안
server: {
proxy: {
'/api': {
- target: 'http://54.180.175.139:8080', // 예: http://54.180.175.139
+ target: process.env.VITE_API_URL || 'http://localhost:8080',
changeOrigin: true,
secure: false,
},
},
},🤖 Prompt for AI Agents
In `@frontend/vite.config.js` around lines 37 - 45, The proxy target for
server.proxy['/api'] in vite.config.js is hardcoded to
'http://54.180.175.139:8080'; change it to read from an environment variable
(e.g. VITE_API_TARGET) instead: load the env (via loadEnv or process.env) inside
the Vite config and set target: env.VITE_API_TARGET || 'http://localhost:8080'
so the proxy value is configurable and falls back safely; update any docs or
.env.example to mention VITE_API_TARGET.
discipline24
left a comment
There was a problem hiding this comment.
고생하셨습니다~ 토끼 리뷰만 resolve 부탁드립니다
1) #217 #218
2) 변경 요약 (What & Why)
로그인페이지
이메일 --> 학번 로그인으로 변경
이메일 찾기 기능 삭제
비밀번호 찾기 --> 비밀번호 초기화로 변경
소셜 로그인 기능 주석처리
로그인/로그아웃 관련 라우터 구조를 정리
인증 흐름에 맞게 접근 제어
---> 인증 상태에 따른 라우팅이 명확하지 않아 발생한 로그아웃 관련 이슈 해결
(원인 자체는 sidebar, header, page들 제각각 라우팅을 하는데서 발생)
3) 스크린샷/동영상 (UI 변경 시)
4) 상세 변경사항 (전부 다)
라우팅/페이지:
컴포넌트:
상태관리:
API 호출:
스타일:
기타:
5) 참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항
버그 수정