Skip to content

feat(auth, admin-ui): switch frontend auth to cookie-based requests and use exam aggregate counts#21

Merged
ydking0911 merged 6 commits into
developfrom
feat/#15/vercel-배포-및-cicd-수정
Apr 13, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/#15/vercel-\ubc30\ud3ec-\ubc0f-cicd-\uc218\uc815"
Merged

feat(auth, admin-ui): switch frontend auth to cookie-based requests and use exam aggregate counts#21
ydking0911 merged 6 commits into
developfrom
feat/#15/vercel-배포-및-cicd-수정

Conversation

@ydking0911
Copy link
Copy Markdown
Member

변경 사항

쿠키 기반 인증 전환

  • 관리자/사용자 HTTP API 요청에서 직접 Authorization 헤더를 제거했습니다.
  • 모든 인증 요청을 credentials: 'include' 기반으로 정리했습니다.
  • admin_access_token의 JS 저장/삭제 로직을 제거했습니다.
  • saveAuthInfo()에서 관리자 access token 저장을 제거했습니다.
  • 관리자 logout 이후 HttpOnly 쿠키 삭제는 백엔드 응답에 맡기도록 정리했습니다.

소켓 인증 정리

  • 사용자 로그인 응답의 access token은 STOMP 연결용으로만 Zustand 메모리에 저장하도록 변경했습니다.
  • useExamSocket, useChatSocket에서 cookie 대신 store의 accessToken을 읽도록 수정했습니다.
  • HttpOnly 쿠키는 JS에서 읽을 수 없기 때문에 실시간 연결용 토큰만 메모리에 유지하도록 분리했습니다.

관리자 화면 API 레이어 정리

  • 컴포넌트에서 직접 fetch하던 관리자 API 호출을 lib/api/admin.ts로 통합했습니다.
  • 적용 화면:
    • 문제 관리
    • 평가 결과
    • 테스트 세션
  • 주석에 남아 있던 직접 fetch 예시도 제거했습니다.

시험 집계 필드 반영

  • Exam 타입에 participantCount, completedCount를 반영했습니다.
  • results-content
    • 총 인원 = participantCount
    • 완료 인원 = completedCount
  • test-sessions-content
    • 참가자 수 = participantCount

확인 내용

  • npm run build 통과
  • 쿠키 기반 HTTP 인증 흐름 기준으로 FE API 호출 정리 완료
  • BE의 시험 목록 집계 필드를 관리자 화면에 반영 완료

참고

  • 사용자 STOMP 인증은 현재 메모리 토큰 기반이라 새로고침 시 다시 로그인 흐름이 필요할 수 있습니다.
  • 로컬 브라우저 테스트 시 COOKIE_SECURE=false 또는 HTTPS 환경이 필요합니다.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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 Authorization headers / token cookie storage for admin & user HTTP requests and standardized on credentials: 'include'.
  • Refactored admin UI components to use the lib/api/admin.ts API layer (problems, results, test sessions).
  • Extended the Exam type with participantCount / completedCount and 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().accessToken is read once when the effect runs and isn’t reactive. Because examId is set before accessToken in LoginCard, this effect can connect with a null token and never reconnect (deps are only [examId]). Consider selecting accessToken via 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 thread hooks/use-chat-socket.tsx
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 thread components/login-card.tsx Outdated
Comment on lines 58 to 69
if (response.accessToken) {
setCookie('user_access_token', response.accessToken);
setAccessToken(response.accessToken);
}
Comment thread components/test-sessions-content.tsx Outdated
@@ -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 thread lib/api/submissions.ts Outdated
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' };
}
@ydking0911 ydking0911 requested a review from chanwook01 April 12, 2026 07:18
@ydking0911 ydking0911 merged commit beb1f30 into develop Apr 13, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants