# IdentityGuardInterceptor — 코어 본인인증 인터셉터 레퍼런스

코어 `resources/js/core/identity/IdentityGuardInterceptor.ts` 정적 클래스의 공개 API와 동작 계약을 다룹니다. 템플릿 / 플러그인이 IDV 흐름을 wiring 할 때 참조하세요.

## TL;DR (5초 요약)

```text
1. ActionDispatcher.handleApiCall 응답 후처리에서 isIdentityRequired/handle 호출 (engine-v1.44.0+)
2. setLauncher(launcher) 로 모달 launcher 등록 — 템플릿 부트스트랩 의무
3. launcher 반환 타입 = Promise<VerificationResult> (engine-v1.46.0+)
4. handle() 가 verified 시 return_request.url 에 ?verification_token=... query 자동 부착 후 fetch
5. defaultLauncher — launcher 미등록 시 토스트 + /identity/challenge?return=... navigate 폴백
6. 인증 대상(email/phone)은 흐름이 apiCall `identity_target` 으로 선언 → handle() 이 payload.target 에 병합 → launcher 가 사용 (engine-v1.51.0+)
```

## 정적 API 전체

```typescript
class IdentityGuardInterceptor {
  // 등록 / 진단
  static setLauncher(launcher: ModalLauncher): void
  static hasLauncher(): boolean
  static reset(): void                       // 테스트 / 진단용

  // 응답 감지
  static isIdentityRequired(status: number, body: unknown): body is IdentityResponse428
  static async handle(
    response: IdentityResponse428,
    originalRequest?: Pick<RequestInit, 'body' | 'headers' | 'credentials'>,
    target?: IdentityVerificationTarget,   // engine-v1.51.0+ — 흐름이 선언한 인증 대상
  ): Promise<Response | null>

  // Deferred resolver — 모달 ↔ launcher Promise 통신
  static createDeferred(): Promise<VerificationResult>
  static resolveDeferred(result: VerificationResult): void

  // external_redirect helper
  static redirectExternally(payload: VerificationPayload): Promise<VerificationResult>
}
```

## launcher 반환 타입 — VerificationResult

engine-v1.46.0+ — 4-상태 객체로 확장 (이전: `Promise<boolean>`).

```typescript
type VerificationResult =
  | { status: 'verified'; token: string; providerData?: Record<string, unknown> }
  | { status: 'pending'; pollUrl: string; pollIntervalMs?: number; expiresAt: string }
  | { status: 'cancelled' }
  | { status: 'failed'; failureCode: string; reason?: string };
```

| 상태 | handle() 동작 |
| --- | --- |
| `verified` | `return_request.url` 에 `?verification_token=` 자동 부착 → `fetch` 재실행 → Response 반환 |
| `pending` | 1차 구현은 `failed` 로 강등 (caller 에 null 반환). 향후 폴링 루프 도입 시 활용 |
| `cancelled` | null 반환 — 원 요청 폐기 |
| `failed` | null 반환 — `failureCode` 는 caller 가 onError 분기에서 처리 |

## verification_token 자동 query 부착

`handle()` 이 verify 성공 시 `return_request.url` 에 token 을 query 로 자동 부착:

```text
원 요청: POST /api/auth/register   (body: { email, password, ... })
재실행:  POST /api/auth/register?verification_token=tok-abc-123   (body: 원본 그대로)
```

회원가입 폼 등 기존 레이아웃이 `query.verification_token` 패턴을 사용 중이라면 그대로 호환. 백엔드 `IdvTokenRule` 은 query/body 어디에 있든 검증.

## resolveIdentityChallenge 핸들러 spec

코어 `ActionDispatcher` 가 등록한 표준 핸들러 — 레이아웃 JSON 에서 직접 호출.

| params | 타입 | 설명 |
| --- | --- | --- |
| `result` | `'verified'\|'cancelled'\|'failed'\|'pending'` | 결과 종류 (필수) |
| `token` | string | result=verified 일 때 verification_token (필수) |
| `failureCode` | string | result=failed 일 때 — INVALID_CODE / EXPIRED / MAX_ATTEMPTS 등 |
| `reason` | string | result=failed 의 사람-읽기용 메시지 (선택) |
| `providerData` | object | provider 가 돌려준 추가 데이터 (선택) |
| `pollUrl`, `pollIntervalMs`, `expiresAt` | — | result=pending 인터페이스 (예약) |

값 누락 시 안전한 기본값으로 강등:
- result=verified 인데 token 누락 → `failed/MISSING_TOKEN`
- result=pending 인데 pollUrl/expiresAt 누락 → `failed/MALFORMED_PENDING`
- result 미상 / 오타 → `cancelled`

## defaultLauncher — launcher 미등록 폴백

외부 템플릿이 `setLauncher` 미호출 시 코어가 사용하는 폴백:

```text
1. external_redirect 또는 redirect_url 있음 → redirectExternally 위임
2. G7Core.toast: warning("본인 확인이 필요합니다.")
3. sessionStorage stash (return_url + payload)
4. G7Core.dispatch: navigate('/identity/challenge?return=' + encodeURIComponent(currentUrl))
5. Promise<never> — 페이지 unmount
```

G7Core 미초기화 환경: `console.error` 만 출력 + `failed/G7_NOT_READY` 반환.

## external_redirect helper — redirectExternally

```text
1. payload.redirect_url 누락 → failed/MISSING_REDIRECT_URL
2. sessionStorage[g7.identity.redirectStash] = JSON({ return_url, payload, stashed_at })
3. window.location.href = redirect_url
4. Promise<never>
```

콜백 URL(`POST /api/identity/callback/{providerId}` 처리 후 redirect) 에서 stash 복원 책임은 콜백 처리 레이아웃이 담당.

## sessionStorage stash 키

```typescript
import { IDENTITY_REDIRECT_STASH_KEY } from '../identity/types';
// 또는 window.G7Core.identity.redirectStashKey
// 값: 'g7.identity.redirectStash'
```

## window.G7Core.identity 노출 (engine-v1.46.0+)

템플릿 IIFE 번들이 코어 모듈을 중복 포함하면 정적 클래스 상태가 분리되므로, 직접 import 대신 `window.G7Core.identity` 를 사용해야 합니다.

```typescript
// 템플릿 / 플러그인 코드
const identity = (window as any).G7Core?.identity;
if (identity?.setLauncher) {
  identity.setLauncher(myLauncher);
}
identity.redirectExternally(payload);
const deferred = identity.createDeferred();
identity.resolveDeferred({ status: 'verified', token });
```

| 노출 메서드 | 설명 |
| --- | --- |
| `setLauncher(launcher)` | 모달 launcher 등록 |
| `hasLauncher()` | 등록 여부 |
| `redirectExternally(payload)` | external_redirect helper |
| `createDeferred()` | deferred Promise 생성 |
| `resolveDeferred(result)` | resolver 호출 (resolveIdentityChallenge 핸들러가 내부적으로 사용) |
| `reset()` | launcher + deferred 슬롯 초기화 (테스트용) |
| `redirectStashKey` | 'g7.identity.redirectStash' 상수 |

## 동시 launcher 진입 처리

두 번째 `createDeferred()` 호출 시 이전 deferred 는 자동으로 `cancelled` 로 강제 종료:

```typescript
const first = IdentityGuardInterceptor.createDeferred();
const second = IdentityGuardInterceptor.createDeferred();   // first 는 자동 cancelled
IdentityGuardInterceptor.resolveDeferred({ status: 'verified', token: 'tok' });
// first  → resolves to { status: 'cancelled' }
// second → resolves to { status: 'verified', token: 'tok' }
```

## 관련 문서

- [identity-verification-ui.md](identity-verification-ui.md) — 모달 UI / 상태 머신 / Extension Point 슬롯 가이드
- [../extension/template-idv-bootstrap.md](../extension/template-idv-bootstrap.md) — 외부 템플릿 launcher 등록 가이드
- [../backend/identity-policies.md](../backend/identity-policies.md) — 백엔드 정책 시스템
