# task-1647.1 완료 보고서

## SCQA

**S**: InsuWiki 위키 앱에 검토 시스템 + 신뢰도 UX를 도입하는 Phase 1 Week 1 작업이다. 현재 4역할 체계(admin/reviewer/member/guest)와 Custom Claims 기반 인증이 없으며, AI API 3개 엔드포인트에 인증이 부재하다.

**C**: SEC-02 CRITICAL — search-wiki(인증 없음), index-wiki(인증 없음), invalidate-cache(Bearer 존재만 확인, verifyIdToken 미호출)로 검색 인덱스 오염, 무인증 크롤링, 캐시 DoS 공격 벡터가 존재한다. 역할 체계도 firestore.ts와 AuthContext.tsx에 중복 정의되어 reviewer 역할 확장이 불가하다.

**Q**: SEC-02 보안 취약점을 패치하고, 4역할 + Custom Claims 기반 인증 인프라를 구축할 수 있는가?

**A**: 13개 파일(수정 8 + 생성 5) 변경으로 전체 구현 완료. SEC-02 3건 즉시 패치, UserRole 단일 소스화(reviewer 추가), syncCustomClaims CF(Firestore 트리거), backfillCustomClaims CF(Callable), Security Rules 불변식(민감 필드 보호 + role 필드 보호 + reviews/auditLogs 서브컬렉션). 테스트 17건 전체 통과.

## 작업 내용

### SEC-02 즉시 패치 (CRITICAL)
- `/api/ai/search-wiki`: `verifyMember` 미들웨어 추가
- `/api/ai/index-wiki`: `verifyAdmin` 미들웨어 추가
- `/api/ai/invalidate-cache`: Bearer 존재 확인 → `verifyAuth()`(verifyIdToken) 교체
- `auth-middleware.ts`에 `verifyAdmin()` 함수 신규 추가

### UserRole 확장 + 타입 단일 소스화
- `shared/types/roles.ts` 생성: `UserRole = 'admin' | 'reviewer' | 'member' | 'guest'`
- `ROLE_HIERARCHY` (admin=3, reviewer=2, member=1, guest=0)
- `hasMinRole()` 함수, `ROLE_PERMISSIONS` 상수
- `firestore.ts`: 기존 UserRole 정의 제거 → shared import + re-export
- `AuthContext.tsx`: 기존 UserRole 정의 제거 → shared import + re-export

### Custom Claims
- `syncCustomClaims`: users/{uid} 문서 onWrite 트리거, role 변경 시 자동 Claims 동기화
- `backfillCustomClaims`: admin 전용 Callable Function, 전체 유저 Claims 백필 (배치 10건)
- `AuthContext`: onSnapshot에서 role 변경 감지 시 `getIdToken(true)` 강제 갱신

### Security Rules
- Claims 기반 helper: `hasClaimRole()`, `isReviewerOrAbove()`, `isMemberOrAbove()`
- `isMemberOrAdmin()`에 'reviewer' 역할 추가 (기존 함수 호환)
- documents update: `status`, `reliabilityScores`, `verificationStatus` 클라이언트 쓰기 차단
- users: `role`, `permissions` 필드 일반 사용자 수정 차단 (admin만 허용)
- `reviews` 서브컬렉션: member 이상 읽기, reviewer 이상 생성
- `auditLogs` 서브컬렉션: reviewer 이상 읽기, 쓰기는 CF only

## 산출물 파일

### 생성
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/shared/types/roles.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/shared/types/__tests__/roles.test.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/setCustomClaims.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/backfillClaims.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/__tests__/customClaims.test.ts`

### 수정
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/firestore.rules`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/index.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/index-wiki/route.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/invalidate-cache/route.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/search-wiki/route.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/contexts/AuthContext.tsx`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/lib/auth-middleware.ts`
- `/home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/types/firestore.ts`

## 테스트 결과
- `roles.test.ts`: 10/10 통과 (ROLE_HIERARCHY 2건, hasMinRole 4건, ROLE_PERMISSIONS 4건)
- `customClaims.test.ts`: 7/7 통과 (syncCustomClaims 4건, backfillCustomClaims 3건)
- Cloud Functions TypeScript 컴파일: 에러 0건

## 발견 이슈 및 해결

### 자체 해결 (3건)
1. **firestore.ts UserRole 로컬 사용 불가** — `export type { UserRole }` re-export만으로는 같은 파일 내 사용 불가. `import type + export type` 패턴으로 수정.
2. **Document 인터페이스 중복 필드 선언** — question, answer, subcategory, sourceType 등 8개 필드가 라인 122-132와 163-171에 중복 선언. 후자 블록 삭제.
3. **isMemberOrAdmin()에 reviewer 역할 누락** — Security Rules에서 reviewer 사용자가 문서 접근 차단되는 치명적 버그. `['member', 'admin']` → `['member', 'reviewer', 'admin']` 수정.

### 마아트 독립 검증 후 추가 수정 (2건)
4. **invalidate-cache 과소 인증** — verifyAuth → verifyAdmin으로 변경. 캐시 무효화는 관리 작업이므로 admin 전용이 적절.
5. **AuthContext stale closure** — onSnapshot 콜백 내 `userRole` 참조가 항상 초기값 null을 캡처하여 매 스냅샷마다 getIdToken(true) 호출. useRef로 현재 역할 추적하여 실제 변경 시에만 토큰 갱신.

### 범위 외 미해결 (2건)
1. **Claims 기반 함수와 DB 조회 기반 함수 혼용** — reviews/auditLogs 서브컬렉션이 Claims 기반 함수를 사용하므로, backfillCustomClaims 실행 전에는 기존 유저가 접근 불가. 단, reviews는 Week 2에서 생성되므로 현재 데이터 없어 실제 영향 없음. 배포 순서: CF 배포 → backfill 실행 → Rules 배포.
2. **admin/users/page.tsx reviewer UI 미반영** — 범위 외 (Phase 2b 검토 UI 작업에서 처리). reviewer 배지 색상 및 셀렉트박스 옵션 추가 필요.

## 머지 판단
- **머지 필요**: Yes (이미 자동 머지됨)
- **브랜치**: task/task-1647.1-dev1
- **워크트리 경로**: 머지 완료 후 정리됨
- **머지 의견**: SEC-02 보안 패치 + 마아트 검증 이슈 수정 완료. 테스트 17건 통과. 배포 시 backfillCustomClaims 실행 필수.

## 배포 순서 가이드
1. Cloud Functions 배포 (syncCustomClaims + backfillCustomClaims)
2. admin이 backfillCustomClaims 호출하여 기존 유저 Claims 백필
3. Firestore Security Rules 배포
4. Next.js 앱 배포

## 모델 사용 기록
- 불칸-A: SEC-02 API 패치 + verifyAdmin / sonnet
- 불칸-B: Custom Claims CF + 백필 스크립트 / sonnet
- 불칸-C: Security Rules 업데이트 / sonnet
- 이리스: UserRole 타입 단일 소스화 + AuthContext 토큰 갱신 / sonnet
- 아르고스: 테스트 작성 / sonnet
- 마아트: 독립 QC 검증 / sonnet

## 세션 통계
- 총 도구 호출: 36회

### 수정 파일 목록
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/firestore.rules: 5회 (Edit)
- bash_cmd: 4회 (Bash)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/types/firestore.ts: 3회 (Edit)
- /home/jay/projects/insuwiki/nextapp/src/contexts/AuthContext.tsx: 3회 (Edit)
- /home/jay/workspace/memory/reports/task-1647.1.md: 3회 (Edit, Write)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/__tests__/customClaims.test.ts: 2회 (Edit, Write)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/index-wiki/route.ts: 2회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/search-wiki/route.ts: 2회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/contexts/AuthContext.tsx: 2회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/backfillClaims.ts: 1회 (Write)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/index.ts: 1회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/functions/src/setCustomClaims.ts: 1회 (Write)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/app/api/ai/invalidate-cache/route.ts: 1회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/lib/auth-middleware.ts: 1회 (Edit)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/shared/types/__tests__/roles.test.ts: 1회 (Write)
- /home/jay/projects/insuwiki/.worktrees/task-1647.1-dev1/nextapp/src/shared/types/roles.ts: 1회 (Write)
- /home/jay/projects/insuwiki/firestore.rules: 1회 (Edit)
- /home/jay/projects/insuwiki/nextapp/src/app/api/ai/invalidate-cache/route.ts: 1회 (Edit)
- /home/jay/workspace/memory/tasks/task-1647.1.md: 1회 (dispatch)

### 도구 사용 현황
- Edit: 25회
- Write: 6회
- Bash: 4회
- dispatch: 1회

