feat(auth, admin-ui): switch frontend auth to cookie-based requests and use exam aggregate counts#21
Merged
Hidden character warning
The head ref may contain hidden characters: "feat/#15/vercel-\ubc30\ud3ec-\ubc0f-cicd-\uc218\uc815"
Merged
Conversation
There was a problem hiding this comment.
Pull request overview
This PR migrates frontend HTTP authentication to cookie-based requests (credentials: 'include') and updates the admin UI to consume aggregate participant/completion counts from the exam list API, while also consolidating some admin API calls into lib/api/admin.ts.
Changes:
- Removed frontend-managed
Authorizationheaders / token cookie storage for admin & user HTTP requests and standardized oncredentials: 'include'. - Refactored admin UI components to use the
lib/api/admin.tsAPI layer (problems, results, test sessions). - Extended the
Examtype withparticipantCount/completedCountand updated UI mappings to display these counts.
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| next-env.d.ts | Updates Next route type import path. |
| lib/stores/exam-session-store.ts | Adds in-memory accessToken for STOMP and related setters. |
| lib/auth/utils.ts | Stops JS-side storage/reading/removal of admin_access_token and documents HttpOnly behavior. |
| lib/api/submissions.ts | Removes cookie-token header construction and relies on cookies via credentials: 'include'; updates SSE fetch headers. |
| lib/api/exams.ts | Removes user token header injection; relies on cookie auth. |
| lib/api/chat.ts | Removes user token header injection; relies on cookie auth for HTTP APIs. |
| lib/api/admin.ts | Removes admin token header injection; switches calls to credentials: 'include'; adds getProblems(); extends Exam with aggregate counts; removes old SSE implementation. |
| hooks/use-exam-socket.tsx | Switches WS auth token source from cookie to Zustand store. |
| hooks/use-chat-socket.tsx | Switches WS auth token source from cookie to Zustand store; stabilizes callbacks with refs. |
| components/test-sessions-content.tsx | Moves direct fetch calls to admin API layer and uses participantCount. |
| components/results-content.tsx | Moves direct fetch to admin API layer and uses participantCount/completedCount. |
| components/problems-content.tsx | Moves direct fetch to admin API layer and maps AdminProblem to UI model. |
| components/logs-content.tsx | Removes commented direct-fetch example and documents API-layer convention. |
| components/login-card.tsx | Stops writing user_access_token cookie; stores STOMP token in Zustand; keeps admin cookie-based flow. |
Comments suppressed due to low confidence (1)
hooks/use-exam-socket.tsx:51
useExamSessionStore.getState().accessTokenis read once when the effect runs and isn’t reactive. BecauseexamIdis set beforeaccessTokeninLoginCard, this effect can connect with a null token and never reconnect (deps are only[examId]). Consider selectingaccessTokenvia the hook and adding it to the deps (or batching store updates) so the socket always connects with the JWT when available.
useEffect(() => {
if (!examId) return;
const token = useExamSessionStore.getState().accessToken;
const socket = new SockJS(`${API_BASE_URL}/ws`);
const client = new Client({
webSocketFactory: () => socket,
connectHeaders: token ? { Authorization: `Bearer ${token}` } : {},
debug: (str) => {
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
49
to
60
| useEffect(() => { | ||
| // STOMP CONNECT 시 JWT를 헤더로 전달해 서버 Principal(userId) 설정을 가능하게 함 | ||
| // BE의 StompPrincipalInterceptor가 이 토큰을 파싱해 participantId를 Principal로 등록 | ||
| // → convertAndSendToUser(participantId, "/queue/chat", response) 라우팅이 정상 동작 | ||
| const token = getCookie('user_access_token'); | ||
| const token = useExamSessionStore.getState().accessToken; | ||
|
|
||
| // 만료된 토큰으로 연결 시 Principal이 설정되지 않아 convertAndSendToUser가 무음 실패함 | ||
| if (!token || isTokenExpired(token)) { | ||
| console.warn('[STOMP Chat] JWT 토큰이 없거나 만료됨 - 재로그인 필요'); | ||
| onError?.('세션이 만료되었습니다. 다시 로그인해주세요.'); | ||
| onErrorRef.current?.('세션이 만료되었습니다. 다시 로그인해주세요.'); | ||
| return; | ||
| } |
Comment on lines
58
to
69
| if (response.accessToken) { | ||
| setCookie('user_access_token', response.accessToken); | ||
| setAccessToken(response.accessToken); | ||
| } |
| @@ -20,6 +19,7 @@ import { ScrollArea } from "@/components/ui/scroll-area" | |||
| import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" | |||
| import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet" | |||
| import { useRouter } from "next/navigation"; | |||
Comment on lines
+67
to
+74
| const mapped: TestSession[] = exams.map((exam: Exam) => ({ | ||
| id: exam.id, | ||
| sessionId: exam.title, | ||
| createdBy: "Admin", | ||
| createdAt: exam.startsAt ? exam.startsAt.split("T")[0] : "-", | ||
| status: exam.state === "RUNNING" ? "Active" : "Completed", | ||
| participants: exam.participantCount, | ||
| })) |
Comment on lines
1
to
5
| // Submission API 호출 함수들 | ||
| import { getCookie } from '../auth/cookie-utils'; | ||
|
|
||
| function getAdminAuthHeaders(): HeadersInit { | ||
| const token = getCookie('admin_access_token'); | ||
| return { | ||
| 'Content-Type': 'application/json', | ||
| ...(token ? { Authorization: `Bearer ${token}` } : {}), | ||
| }; | ||
| return { 'Content-Type': 'application/json' }; | ||
| } |
chanwook01
approved these changes
Apr 13, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
변경 사항
쿠키 기반 인증 전환
Authorization헤더를 제거했습니다.credentials: 'include'기반으로 정리했습니다.admin_access_token의 JS 저장/삭제 로직을 제거했습니다.saveAuthInfo()에서 관리자 access token 저장을 제거했습니다.소켓 인증 정리
useExamSocket,useChatSocket에서 cookie 대신 store의accessToken을 읽도록 수정했습니다.관리자 화면 API 레이어 정리
fetch하던 관리자 API 호출을lib/api/admin.ts로 통합했습니다.fetch예시도 제거했습니다.시험 집계 필드 반영
Exam타입에participantCount,completedCount를 반영했습니다.results-contentparticipantCountcompletedCounttest-sessions-contentparticipantCount확인 내용
npm run build통과참고
COOKIE_SECURE=false또는 HTTPS 환경이 필요합니다.