apiClient.post → apiClient.delete 변경 후 빌드 재배포.REFRESH_TOKEN_REVOKED (JTI 무효화 확인)
user.service.js에 listUsersAdmin / getAdminUser / updateUserAdmin / deleteUserAdmin 구현.user.controller.js에 Zod 검증 포함 핸들러 추가, users.routes.js에 requireRole('ADMIN','SUPERADMIN') 가드 적용.GET /reports/open 누락 (기존 /reports/admin만 존재), 어드민 웹은 /open 호출.GET /disputes/open은 이미 구현되어 있었으나 total 필드 미포함으로 대시보드 카운터 미작동.GET /open 라우트 추가 (requireRole ADMIN/SUPERADMIN 가드).listForAdmin 반환값을 { items, total } 형태로 변경 (Prisma $transaction 배치).listOpen 반환값도 { items, total } 통일.GET /reports/open → 200 { items:[], total:0 } · GET /disputes/open → 200 { items:[], total:0 } · 브라우저: /admin/reports, /admin/disputes 페이지 에러 없이 렌더링 ✓
httpd-ssl.conf, httpd-vhosts.conf)에서 Header set Access-Control-Allow-Origin "*"가 Express 응답 헤더를 덮어쓰고 있었음.Header set Access-Control-Allow-Origin "*" 제거 (SameSite 쿠키 헤더는 유지).callback(new Error(...)) → callback(null, false) 변경 (500 → CORS 헤더 미포함 응답).Access-Control-Allow-Origin: https://match.habyapps.com + credentials: true ✓ · evil.com → 200 (CORS 헤더 없음, 브라우저 차단) · 500 미발생 ✓
ecosystem.config.js 활용: match-api (cluster, 16 instances, DISABLE_JOBS=true) + match-scheduler (fork, 1 instance) 구성./var/log/match/ 로그 디렉터리 생성 후 pm2 start ecosystem.config.js 실행.pm2 save → /root/.pm2/dump.pm2 저장 · pm2 startup → systemctl enable pm2-root 완료 · API 응답 정상 확인
PrismaRateLimitStore (MariaDB rate_limits 테이블) 구현 → 모든 워커가 단일 DB 카운터 공유.skipSuccessfulRequests: true) ✓
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-001 | HIGH | 정상 회원가입 | N/A | Rate limit 초과로 테스트 불가 (신규 계정 생성 필요) |
| TC-002 | MEDIUM | 중복 이메일 가입 차단 | N/A | 신규 계정 생성 필요 |
| TC-003 | MEDIUM | 비밀번호 8자 미만 차단 | PASS | 422 VALIDATION_ERROR · "비밀번호는 최소 8자 이상이어야 합니다." |
| TC-004 | MEDIUM | 닉네임 2자 미만 차단 | PASS | 422 VALIDATION_ERROR · "닉네임은 2자 이상" |
| TC-005 | MEDIUM | 닉네임 50자 초과 차단 | PASS | 422 VALIDATION_ERROR · "닉네임은 50자 이내" |
| TC-006 | MEDIUM | 이메일 형식 불일치 차단 | PASS | 422 VALIDATION_ERROR · "올바른 이메일 형식이 아닙니다." |
| TC-007 | HIGH | 카카오 소셜 로그인 | N/A | KAKAO_CLIENT_ID 미설정 |
| TC-008 | HIGH | 구글 소셜 로그인 | N/A | GOOGLE_CLIENT_ID 미설정 |
| TC-009 | HIGH | 동일 소셜 계정 재로그인 | N/A | 소셜 로그인 키 미설정 |
| TC-010 | HIGH | Access Token → Refresh 갱신 | PASS | POST /auth/refresh → 200 · 신규 accessToken/refreshToken 발급 확인 |
| TC-011 | HIGH | 무효 Refresh Token → 차단 | PASS | 401 INVALID_REFRESH_TOKEN · "유효하지 않은 Refresh Token입니다." |
| TC-012 | MEDIUM | 로그아웃 — JTI 무효화 | PASS | DELETE /api/v1/auth/logout → 204 · ISS-001 수정 완료 (2026-05-20) |
| TC-013 | HIGH | 만 19세 미만 가입 차단 | N/A | 별도 age 검증 테스트 필요 |
| TC-014 | HIGH | 만 19세 이상 가입 허용 | N/A | 별도 age 검증 테스트 필요 |
| TC-015 | MEDIUM | 회원 탈퇴 soft delete | N/A | 탈퇴 전용 계정 필요 |
| TC-016 | LOW | 탈퇴 후 재가입 정책 | N/A | PRD 미결 항목 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-017~029 | - | 매칭 등록·신청·필터·페이지네이션 | N/A | Flutter 앱 기능 — 테스트 계정 및 HOST 서브몰 등록 필요. API GET /matchings → 200 (데이터 없음) 확인 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-030~040 | - | Socket.IO 연결·메시지·확정·이미지 | N/A | Socket.IO 전용 클라이언트 및 매칭 플로우 선행 필요 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-041~046 | - | Sandbox 결제·Webhook·에스크로 | N/A | TOSS_PAYMENTS_TEST_SECRET 미설정. 키 발급 후 재테스트 필요 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-047~055 | - | 노쇼·취소·분쟁·정산 | N/A | 결제 완료 데이터 및 에스크로 HELD 상태 선행 필요 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-056 | MEDIUM | 일일 신고 10건 초과 차단 | N/A | 테스트 신고 데이터 필요 |
| TC-057 | MEDIUM | 동일 대상 중복 신고 차단 | N/A | 테스트 신고 데이터 필요 |
| TC-058 | HIGH | 관리자 행위 audit_logs 자동 기록 | N/A | 관리자 액션 실행 데이터 필요 |
| TC-059 | HIGH | SUPERADMIN 전용 audit-logs 조회 | PASS | GET /api/v1/audit-logs + SUPERADMIN 토큰 → 200 · 빈 목록 정상 반환 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-060~065 | - | COMPLETED 매칭 리뷰 전체 | N/A | COMPLETED 상태 매칭 데이터 선행 필요 |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-066 | HIGH | 관리자 로그인 → 대시보드 | PASS | superadmin@match.local 로그인 성공 · /admin/dashboard 렌더링 · 4개 통계 카드 표시 · 기간 필터 버튼 동작 확인 |
| TC-067 | HIGH | 미로그인 URL 직접 접근 → 로그인 리다이렉트 | PASS | /admin/members 직접 접근 → /admin/login 리다이렉트 확인 (Vue Router 가드 정상 동작) |
| TC-068 | HIGH | 회원 정지 → Flutter 앱 401 | N/A | Flutter 앱 테스트 환경 필요 (API 구현 완료 — ISS-002 FIXED) |
| TC-069 | HIGH | 매칭 강제 취소 | N/A | 테스트 매칭 데이터 없음 |
| TC-070 | MEDIUM | 장소 승인/반려 | N/A | PENDING 장소 데이터 없음 |
| TC-071 | HIGH | 분쟁 처리 필수 항목 검증 | N/A | API 블로커 해소 (ISS-003 FIXED) · /admin/disputes 페이지 렌더링 ✓ · 실제 분쟁 해결 플로우는 결제 데이터 필요 |
| TC-072 | HIGH | SUPERADMIN 전용 메뉴 (ADMIN 미표시) | N/A | ADMIN 역할 테스트 계정 필요 |
| TC-073 | HIGH | SUPERADMIN 감사 로그·관리자 계정 접근 | PASS | 감사 로그(/admin/audit-logs) + 관리자 계정(/admin/admins) 페이지 모두 렌더링 정상 · API 200 |
| TC-074 | LOW | URL 쿼리 파라미터 ↔ 필터 동기화 | N/A | 테스트 데이터 없어 필터 동작 미검증 |
| TC-075 | MEDIUM | TypeScript strict 빌드 통과 | PASS | npm run type-check 에러 0건 · npm run build ✓ built in 1.46s |
| TC | 우선순위 | 항목 | 결과 | 비고 |
|---|---|---|---|---|
| TC-076 | CRITICAL | [P0-1] Webhook HMAC 타이밍 공격 방어 | N/A | Toss Secret 미설정 · 코드 구현 확인 필요 |
| TC-077 | CRITICAL | [P0-2] POST_SETTLEMENT 분쟁 상태 범위 | N/A | 결제 플로우 선행 필요 · 코드 리뷰로 확인 권장 |
| TC-078 | CRITICAL | [P0-3] CORS allowlist 강화 | PASS | Apache wildcard 제거 + Express callback(null,false) 적용 · match.habyapps.com → specific origin header ✓ · evil.com → no CORS headers (브라우저 차단) · 500 없음 ✓ (ISS-004 FIXED) |
| TC-079 | CRITICAL | [P0-4] 분쟁+에스크로 원자적 처리 | N/A | 분쟁 API 404 (ISS-003) · 코드 리뷰로 확인 |
| TC-080 | HIGH | [P1-1] 로그인 Rate Limit 5회/15분 | PASS | PrismaRateLimitStore 도입으로 클러스터 공유 카운터 구현 · 시도 1–5 → 401 · 시도 6 → 429 ✓ (ISS-006 FIXED) |
| TC-081 | HIGH | [P1-2] JTI 기반 토큰 무효화 | PASS | 로그아웃 후 동일 refreshToken 재사용 → 401 REFRESH_TOKEN_REVOKED · ISS-001 수정 완료 |
| TC-082 | HIGH | [P1-3] 이미지 URL 도메인 화이트리스트 | N/A | 채팅방 데이터 필요 |
| TC-083 | CRITICAL | [P1-4] 부분 환불 PG 선행 호출 | N/A | Toss 결제 데이터 필요 |
| TC-084 | HIGH | [P2-1] WebView XSS 방어 | N/A | Flutter WebView 환경 필요 |
| TC-085 | HIGH | [P2-2] PM2 스케줄러 분리 | PASS | match-api (cluster×16, DISABLE_JOBS=true) + match-scheduler (fork×1) · pm2 save + pm2 startup 등록 완료 (ISS-005 FIXED) |
apiClient.post → apiClient.delete 변경 후 빌드 재배포 완료