[FE] [FEAT] 소셜 로그인 - 리다이렉트 URL을 위한 OAuthSuccess 페이지 구현#107
Conversation
WalkthroughOAuth 리다이렉트 처리 페이지( Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant OAuthSuccess
participant localStorage
participant Router
Note over OAuthSuccess: 신규 페이지 흐름
User->>Browser: OAuth 공급자에서 리다이렉트(`/oauth/success?...`)
Browser->>OAuthSuccess: 페이지 로드 (query params)
OAuthSuccess->>OAuthSuccess: 파라미터 파싱 및 검증
alt error 없음
OAuthSuccess->>localStorage: accessToken/refreshToken/유저정보 저장
OAuthSuccess->>OAuthSuccess: 성공 상태 설정
OAuthSuccess->>Router: 300ms 후 replace(next)
else error 발생
OAuthSuccess->>OAuthSuccess: 에러 상태 설정
OAuthSuccess->>User: 로그인 화면으로 유도 버튼 표시
end
sequenceDiagram
participant User
participant AttendanceManage
participant SessionManagementCard
participant RoundSettingModal
Note over RoundSettingModal: 회차 편집 모달 흐름
User->>SessionManagementCard: 메뉴 버튼 클릭
SessionManagementCard->>AttendanceManage: onClick() 호출\nsetEditingRound(round)
AttendanceManage->>AttendanceManage: isModalOpen = true
AttendanceManage->>RoundSettingModal: 렌더(round, onSave, onClose)
User->>RoundSettingModal: 회차 정보 수정 후 저장
RoundSettingModal->>AttendanceManage: onSave(updatedRound)
AttendanceManage->>AttendanceManage: handleRoundChange로 세션 데이터 업데이트
RoundSettingModal->>AttendanceManage: onClose()
AttendanceManage->>AttendanceManage: isModalOpen = false
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
frontend/src/pages/OAuthSuccess.jsx (1)
50-88: UX 개선 제안현재 구현은 동작하지만 다음 개선사항을 고려하세요:
- 인라인 스타일: 프로젝트의 다른 컴포넌트처럼 CSS 모듈을 사용하는 것이 일관성 있습니다.
- 로딩 인디케이터: "처리 중..." 텍스트 대신 스피너를 표시하면 더 나은 사용자 경험을 제공합니다.
- 접근성: 버튼에
aria-label추가, 로딩 상태에role="status"추가를 고려하세요.CSS 모듈 파일을 생성하고 적용하는 예시:
// OAuthSuccess.module.css 생성 import styles from './OAuthSuccess.module.css'; return ( <div className={styles.container}> <div className={styles.content}> <h2>소셜 로그인</h2> <p role="status">{status}</p> {!params.error && ( <button className={styles.button} onClick={() => nav(safePath, { replace: true })} aria-label="다음 페이지로 이동" > 계속하기 </button> )} {params.error && ( <button className={styles.button} onClick={() => nav('/login', { replace: true })} > 로그인 화면으로 </button> )} </div> </div> );frontend/src/components/attendancemanage/RoundSettingModal.jsx (2)
20-38: 입력 검증 로직 개선 제안현재
isFormValid는 동작하지만, 다음 개선사항을 고려하세요:
- Alert 사용:
alert()는 구식 UX입니다. 인라인 에러 메시지를 표시하는 것이 더 좋습니다.- 실시간 검증: 입력 시 실시간으로 검증하면 사용자 경험이 향상됩니다.
인라인 에러 상태를 추가하는 예시:
const [errors, setErrors] = useState({}); const validateField = (name, value) => { const num = parseInt(value, 10); switch (name) { case 'hour': return isNaN(num) || num < 0 || num > 23 ? '시간은 0-23 사이의 숫자로 입력해주세요.' : ''; case 'minute': case 'second': return isNaN(num) || num < 0 || num > 59 ? '0-59 사이의 숫자로 입력해주세요.' : ''; default: return ''; } }; const handleHourChange = (e) => { const value = e.target.value; setHh(value); setErrors(prev => ({ ...prev, hour: validateField('hour', value) })); }; // 렌더링 시 {errors.hour && <span className={styles.error}>{errors.hour}</span>}
80-101: 입력 필드 개선 제안
type="text"를 사용하면 사용자가 숫자가 아닌 값을 입력할 수 있습니다. 다음 개선을 고려하세요:<input - type="text" + type="number" id="sessionStartTime" value={hh} - maxLength="2" + min="0" + max="23" onChange={(e) => setHh(e.target.value)} placeholder="시(HH)" /> <input - type="text" + type="number" value={mm} - maxLength="2" + min="0" + max="59" onChange={(e) => setMm(e.target.value)} placeholder="분(MM)" /> <input - type="text" + type="number" value={ss} - maxLength="2" + min="0" + max="59" onChange={(e) => setSs(e.target.value)} placeholder="초(SS)" />또는
type="text"를 유지하면서 입력 시 숫자만 허용:const handleNumberInput = (setter, max) => (e) => { const value = e.target.value.replace(/\D/g, '').slice(0, 2); if (value === '' || parseInt(value) <= max) { setter(value); } }; // 사용: onChange={handleNumberInput(setHh, 23)}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
frontend/src/App.jsx(1 hunks)frontend/src/components/VerificationModal.module.css(1 hunks)frontend/src/components/attendancemanage/RoundSettingModal.jsx(1 hunks)frontend/src/components/attendancemanage/SessionManagementCard.jsx(2 hunks)frontend/src/components/login/FindEmailResultModal.jsx(1 hunks)frontend/src/pages/AttendanceManage.jsx(4 hunks)frontend/src/pages/OAuthSuccess.jsx(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/App.jsx (1)
frontend/src/pages/OAuthSuccess.jsx (1)
OAuthSuccess(16-89)
frontend/src/pages/AttendanceManage.jsx (4)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
sessionTitle(5-5)SessionSettingCard(4-126)frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
SessionManagementCard(3-85)frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
AttendanceManagementCard(3-63)frontend/src/components/attendancemanage/RoundSettingModal.jsx (1)
RoundSettingModal(4-127)
🔇 Additional comments (4)
frontend/src/App.jsx (1)
15-15: LGTM! 라우트 구성이 올바릅니다.
OAuthSuccess페이지가/login및/signup과 함께Layout외부에 올바르게 배치되었습니다. OAuth 콜백 페이지는 메인 레이아웃이 필요하지 않으므로 적절한 구조입니다.Also applies to: 23-23
frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
10-11: 변경사항 확인됨 - 그러나 PR 범위 혼합새로운
setEditingRound와onClickprops가 올바르게 추가되고 사용되었습니다. 메뉴 버튼 클릭 시 모달을 여는 흐름이 적절합니다.그러나 이 변경사항은 출석 관리 기능과 관련이 있으며, PR의 주요 목적인 "소셜 로그인을 위한 OAuthSuccess 페이지 생성"과는 관련이 없습니다. 관련 없는 변경사항을 별도의 PR로 분리하는 것을 권장합니다.
이 PR에 출석 관리 관련 변경사항이 포함된 이유를 확인하고, 가능하면 별도 PR로 분리하는 것을 고려해주세요.
Also applies to: 68-71
frontend/src/components/login/FindEmailResultModal.jsx (1)
7-16: LGTM! 모달 UX 개선모달 헤더에 닫기 버튼을 추가한 것은 좋은 UX 개선입니다. 사용자가 헤더의 X 버튼이나 하단의 닫기 버튼 중 편한 방법으로 모달을 닫을 수 있습니다. 사용되지 않는
handleSubmit함수도 깔끔하게 제거되었습니다.참고: 이 변경사항은 OAuth 기능과 관련이 없으므로 별도 PR로 분리하는 것을 고려해주세요.
frontend/src/pages/AttendanceManage.jsx (1)
10-10: RoundSettingModal 통합 확인 - PR 범위 문제
RoundSettingModal통합이 올바르게 구현되었습니다:
- 모달 상태 관리가 적절합니다
handleRoundChange가 immer를 사용하여 상태를 올바르게 업데이트합니다- 모달 open/close 핸들러가 명확합니다
- 조건부 렌더링이 적절합니다
그러나 이 변경사항은 PR의 주요 목적인 "소셜 로그인을 위한 OAuthSuccess 페이지 생성"과 전혀 관련이 없습니다.
출석 관리 기능 개선과 OAuth 로그인 기능을 별도의 PR로 분리하는 것을 강력히 권장합니다. 관련 없는 변경사항을 하나의 PR에 포함하면 다음 문제가 발생합니다:
- 코드 리뷰가 어려워짐
- 버그 추적이 복잡해짐
- 롤백 시 불필요한 변경사항도 함께 롤백됨
- Git 히스토리가 불명확해짐
이 PR을 두 개로 분리하는 것을 고려해주세요:
- PR 1: OAuthSuccess 페이지 (OAuthSuccess.jsx, App.jsx 라우트)
- PR 2: 출석 관리 회차 수정 기능 (RoundSettingModal, SessionManagementCard, AttendanceManage, 관련 CSS)
Also applies to: 60-61, 94-116, 184-191
| /* .availableTimeInputGroup > input { | ||
| width: 200px; | ||
| } */ |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
주석 처리된 코드 제거
사용하지 않는 주석 처리된 CSS를 제거하세요.
.availableTimeInputGroup {
display: grid;
grid-template-columns: 116px auto;
column-gap: 12px;
align-items: center;
justify-content: space-between;
}
-/* .availableTimeInputGroup > input {
- width: 200px;
-} */
.availableTimeInputGroup button {🤖 Prompt for AI Agents
In frontend/src/components/VerificationModal.module.css around lines 160 to 162,
remove the unused commented-out CSS block ("/* .availableTimeInputGroup > input
{ width: 200px; } */") so the file contains no dead/commented styling; delete
the three lines and run a quick lint/format to ensure no leftover whitespace.
frontend/src/pages/OAuthSuccess.jsx
Outdated
| useEffect(() => { | ||
| // 실패 케이스 | ||
| if (params.error) { | ||
| setStatus(`로그인 실패: ${params.error}`); | ||
| return; | ||
| } | ||
|
|
||
| // 토큰 저장 | ||
| if (params.accessToken) { | ||
| localStorage.setItem('accessToken', params.accessToken); | ||
| } | ||
| if (params.refreshToken) { | ||
| localStorage.setItem('refreshToken', params.refreshToken); | ||
| } | ||
|
|
||
| // (선택) 사용자 정보도 함께 보냈다면 저장해 두기 | ||
| if (params.userId) localStorage.setItem('userId', params.userId); | ||
| if (params.name) localStorage.setItem('userName', params.name); | ||
|
|
||
| setStatus('로그인 완료! 이동 중...'); | ||
| const to = decodeURIComponent(params.next || '/'); | ||
|
|
||
| // 살짝 지연 후 이동 | ||
| const t = setTimeout(() => nav(to, { replace: true }), 300); | ||
| return () => clearTimeout(t); | ||
| }, [params, nav]); |
There was a problem hiding this comment.
🧩 Analysis chain
보안: localStorage를 통한 토큰 저장의 위험성
localStorage에 인증 토큰을 저장하는 것은 XSS 공격에 취약합니다. 다음 개선사항을 고려하세요:
- Refresh Token:
refreshToken은 httpOnly 쿠키로 저장되어야 하며, 백엔드에서 직접 설정해야 합니다. - Access Token: 메모리 또는 httpOnly가 아닌 secure 쿠키에 저장하되, 짧은 만료 시간을 설정하세요.
- 토큰 검증: 저장하기 전에 JWT 형식 검증을 수행하세요.
- 에러 처리: localStorage 저장 실패 시 에러 처리가 필요합니다.
백엔드에서 httpOnly 쿠키를 통해 토큰을 설정할 수 있는지 확인하세요. 다음 스크립트로 프로젝트의 인증 패턴을 확인할 수 있습니다:
🏁 Script executed:
#!/bin/bash
# Description: 프로젝트에서 localStorage를 사용한 토큰 저장 패턴 확인
# localStorage.setItem 사용 검색
rg -n "localStorage\.setItem.*[Tt]oken" --type=js --type=jsx -C 3
# 쿠키 관련 코드 검색
rg -n "document\.cookie|httpOnly|secure.*cookie" --type=js --type=jsx -C 3Length of output: 55
🏁 Script executed:
#!/bin/bash
# Description: 프로젝트에서 localStorage를 사용한 토큰 저장 패턴 확인 (수정된 버전)
# localStorage.setItem 사용 검색 - 파일 확장자 글롭 패턴 사용
echo "=== localStorage 토큰 저장 패턴 ==="
rg -n "localStorage\.setItem.*[Tt]oken" -g "*.js" -g "*.jsx" -C 3
echo ""
echo "=== 쿠키 관련 코드 ==="
# 쿠키 관련 코드 검색
rg -n "document\.cookie|httpOnly|secure.*cookie" -g "*.js" -g "*.jsx" -C 3
echo ""
echo "=== localStorage 전체 사용 패턴 ==="
# localStorage 전체 패턴 확인
rg -n "localStorage\.setItem|localStorage\.getItem" -g "*.js" -g "*.jsx" -C 2Length of output: 3952
🏁 Script executed:
#!/bin/bash
# Description: JWT 검증, 토큰 만료 처리, 백엔드 통신 패턴 확인
echo "=== JWT 검증 로직 ==="
rg -n "jwt|JWT|jwtDecode|decode.*token" -g "*.js" -g "*.jsx" -C 2
echo ""
echo "=== 토큰 만료/갱신 로직 ==="
rg -n "expir|expiresIn|expiresAt|TTL|refresh" -g "*.js" -g "*.jsx" -C 2
echo ""
echo "=== axios 설정 및 백엔드 통신 ==="
cat frontend/src/utils/axios.js
echo ""
echo "=== 인증 유틸 전체 ==="
cat frontend/src/utils/auth.jsLength of output: 5806
보안: OAuthSuccess에서의 URL 쿼리 파라미터를 통한 토큰 노출
OAuthSuccess.jsx는 URL 쿼리 파라미터에서 토큰을 직접 수신하고 있는데, 이는 다음과 같은 심각한 보안 위험을 야기합니다:
- 토큰 히스토리 노출: 토큰이 브라우저 히스토리에 평문으로 저장됨
- 네트워크 로그: Referer 헤더를 통해 프록시/로드밸런서 로그에 토큰 노출 가능
- localStorage XSS 취약점: 저장된 토큰이 XSS 공격으로 탈취 가능
필수 개선사항:
- 백엔드에서
Set-Cookie헤더로 httpOnly, Secure 쿠키에 토큰 설정 (URL 파라미터 제거) - OAuth 콜백 시 짧은 만료 시간의 임시 코드만 전달하고, 백엔드에서 토큰 발급
- 토큰 저장 전 JWT 형식 검증 필요 (현재 로직 미흡)
현재 frontend/src/utils/axios.js, frontend/src/utils/auth.js에서도 localStorage 기반 토큰 저장이 사용 중이므로, 전체 인증 흐름 재설계를 권장합니다.
🤖 Prompt for AI Agents
In frontend/src/pages/OAuthSuccess.jsx around lines 23 to 48, the component
currently reads accessToken/refreshToken from URL params and writes them to
localStorage which exposes tokens in browser history, referer logs, and to XSS;
change the flow so the page does NOT persist tokens from URL: have the OAuth
callback deliver only a short-lived authorization code or state (not tokens) and
immediately POST that code to your backend endpoint to exchange for tokens; the
backend must set tokens as httpOnly, Secure (and SameSite) cookies via
Set-Cookie and return only non-sensitive redirect data to the client; update
this component to: 1) reject any accessToken/refreshToken params (ignore them),
2) if a code param exists POST it to /auth/callback (or configured endpoint) and
wait for success, 3) on success redirect to the next URL without storing tokens
in localStorage, and 4) remove any localStorage token writes and add a JWT
format check if you must parse token payload client-side (only for non-sensitive
display), and then coordinate corresponding changes in
frontend/src/utils/axios.js and frontend/src/utils/auth.js to rely on
cookie-based auth rather than localStorage.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
frontend/src/pages/AttendanceManage.jsx (2)
63-75: 사용하지 않는 주석 처리된 코드 제거
useMemo로 최적화된currentSession과currentParticipants계산 로직이 주석 처리되어 있습니다. 이 코드가 더 이상 필요하지 않다면 제거하고, 향후 성능 최적화가 필요하다면 주석을 해제하여 사용하세요.
119-120: UUID 생성 시 불필요한 Math.random() 제거UUID v4는 이미 전역적으로 고유하므로
Math.random()을 추가로 결합할 필요가 없습니다.다음과 같이 단순화하세요:
- const sessionId = `session-${uuid()}-${Math.random().toString(36)}`; - const roundId = `round-${uuid()}-${Math.random().toString(5)}`; + const sessionId = `session-${uuid()}`; + const roundId = `round-${uuid()}`;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/components/attendancemanage/RoundSettingModal.jsx(1 hunks)frontend/src/pages/AttendanceManage.jsx(4 hunks)frontend/src/pages/OAuthSuccess.jsx(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/pages/OAuthSuccess.jsx
- frontend/src/components/attendancemanage/RoundSettingModal.jsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/AttendanceManage.jsx (4)
frontend/src/components/attendancemanage/SessionSettingCard.jsx (2)
sessionTitle(5-5)SessionSettingCard(4-126)frontend/src/components/attendancemanage/SessionManagementCard.jsx (1)
SessionManagementCard(3-85)frontend/src/components/attendancemanage/AttendanceManagementCard.jsx (1)
AttendanceManagementCard(3-63)frontend/src/components/attendancemanage/RoundSettingModal.jsx (1)
RoundSettingModal(4-135)
🔇 Additional comments (1)
frontend/src/pages/AttendanceManage.jsx (1)
94-116: 회차 수정 및 모달 핸들러 로직 확인 완료
handleRoundChange함수는 useImmer의 draft 패턴을 올바르게 사용하여 회차 정보를 업데이트하고 있으며, 모달 open/close 핸들러도 적절히 구현되었습니다.
| // 세션의 기본 위치 정보 | ||
| location: { | ||
| lat: 37.5499, | ||
| lng: 127.0751, | ||
| }, | ||
| defaultStartTime: '18:30', // 세션의 기본 시간 설정 | ||
| defaultAvailableMinutes: 30, // 출석 인정 시간 (분 단위) | ||
| rewardPoints: 100, // 세션의 리워드 | ||
| isVisible: true, // 세션 공개 여부 | ||
| // 세션 회차들 |
There was a problem hiding this comment.
새 세션 생성 시 location 필드 누락 확인 필요
sessionData에 location 필드가 추가되었지만, handleAddSession 함수(lines 118-142)에서 새 세션을 생성할 때 이 필드가 설정되지 않습니다. 데이터 일관성을 위해 새 세션에도 기본 location 값을 설정하거나, location이 선택 사항이라면 코드 전반에서 null 체크가 필요합니다.
🤖 Prompt for AI Agents
In frontend/src/pages/AttendanceManage.jsx around lines 16-25 and in
handleAddSession at lines 118-142, the default sessionData includes a location
object but handleAddSession does not set location when creating a new session;
update handleAddSession to include a location property (use the existing
sessionData.location defaults or a shared DEFAULT_LOCATION constant) when
constructing the new session object, and if location is optional, add
null/undefined guards wherever session.location is read (or validate before use)
to avoid runtime errors.
| {isModalOpen && ( | ||
| <RoundSettingModal | ||
| styles={styles} | ||
| selectedRound={selectedRound} | ||
| onAttendanceChange={handleAttendanceChange} | ||
| participants={participants} | ||
| onClose={closeModal} | ||
| round={editingRound} | ||
| onSave={handleRoundChange} | ||
| /> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
모달 렌더링 시 null 안전성 검증 추가 권장
isModalOpen이 true이지만 editingRound가 null인 경우, RoundSettingModal의 parseTime 함수에서 오류가 발생할 수 있습니다. React 18+의 상태 배칭으로 인해 현재 코드가 작동하더라도, 명시적인 null 체크를 추가하면 더욱 안전합니다.
다음과 같이 수정하세요:
- {isModalOpen && (
+ {isModalOpen && editingRound && (
<RoundSettingModal
styles={styles}
onClose={closeModal}
round={editingRound}
onSave={handleRoundChange}
/>
)}또한 closeModal 함수에서 editingRound를 null로 리셋하는 것을 고려하세요:
const closeModal = () => {
setModalOpen(false);
+ setEditingRound(null);
};📝 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.
| {isModalOpen && ( | |
| <RoundSettingModal | |
| styles={styles} | |
| selectedRound={selectedRound} | |
| onAttendanceChange={handleAttendanceChange} | |
| participants={participants} | |
| onClose={closeModal} | |
| round={editingRound} | |
| onSave={handleRoundChange} | |
| /> | |
| {isModalOpen && editingRound && ( | |
| <RoundSettingModal | |
| styles={styles} | |
| onClose={closeModal} | |
| round={editingRound} | |
| onSave={handleRoundChange} | |
| /> | |
| )} |
🤖 Prompt for AI Agents
In frontend/src/pages/AttendanceManage.jsx around lines 183 to 189, the modal is
rendered when isModalOpen is true but editingRound may be null causing parseTime
errors; update the conditional to require editingRound (e.g., isModalOpen &&
editingRound) before rendering RoundSettingModal, and ensure
handleRoundChange/onSave still work with a non-null round; also modify
closeModal to explicitly set editingRound to null when closing to avoid stale
state.
작업한 이슈번호
변경 요약 (What & Why)
무엇을 변경했는지:
왜 변경했는지(문제/목표):
스크린샷/동영상 (UI 변경 시)
전/후 비교, 반응형(모바일/데스크톱) 캡쳐
Before:
After:
상세 변경사항 (전부 다)
라우팅/페이지:
컴포넌트: OAuthSuccess.jsx
상태관리:
API 호출:
스타일:
기타:
참고사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
개선 사항