kintex/mobile/lib/biometric.ts
zio 214b6df1ef feat(public,security,data): visitor public site + AI guide, security hardening, V42-V45 migrations
- frontend: visitor public home (AiAssistant, AiPlanningBriefing, VisitorTrackPage),
  3-track public routing (PublicShell/App), i18n ko/en/zh/ja, favicon
- backend: public AI visitor-assistant + event calendar API (publicsite/*),
  profile avatar API (auth/profile/*), SecurityConfig CORS whitelist,
  SecretStartupValidator (B12 fail-fast, prod only), application-prod.yml,
  AppIntegrity, /api/auth/me expansion (MeResponse)
- db: V42 bulk demo seed, V43 visitor_guide/transport + event calendar view,
  V44 performance indexes, V45 app_user profile photo columns (all idempotent)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 19:05:00 +09:00

93 lines
3.1 KiB
TypeScript

/*
* 생체인식(지문·Face) 로컬 잠금 레이어 — WISE 모바일 패턴 이식.
* 레퍼런스: guardia-messenger/app/uiws/_lib/constants/biometric.ts (문자 그대로 차용).
*
* 성격: 2FA 로그인을 대체하지 않는다 — 이미 로그인된 세션에 대한 "로컬 재인증/잠금" 레이어.
* 방어적 동적 require: expo-local-authentication 미설치 기기에서도 graceful(미지원 취급).
* OS만 생체 데이터를 다루며, 앱은 성공 불린(boolean)만 받는다(생체 원본 미보관).
* pref 키는 kintex 전용 접두어로 분리(WISE `wise_*`와 격리 — CLAUDE.md 토큰키 분리 원칙).
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let LocalAuth: any = null;
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
LocalAuth = require('expo-local-authentication');
} catch {
LocalAuth = null;
}
const PREF_KEY = 'kintex.biometric'; // '1' | '0'
/** 기기가 생체인증 하드웨어 + OS 등록(지문/Face)을 모두 지원하는지. */
export async function isBiometricSupported(): Promise<boolean> {
if (!LocalAuth) return false;
try {
const hasHw = await LocalAuth.hasHardwareAsync();
const enrolled = await LocalAuth.isEnrolledAsync();
return !!(hasHw && enrolled);
} catch {
return false;
}
}
/** 하드웨어만 보유(OS 미등록 포함) — "생체 등록 유도" 안내 노출 판정용. */
export async function isBiometricHardwareAvailable(): Promise<boolean> {
if (!LocalAuth) return false;
try {
return !!(await LocalAuth.hasHardwareAsync());
} catch {
return false;
}
}
/*
* 세션(콜드 스타트) 단위 잠금 통과 플래그 — 모듈 전역.
* 콜드 스타트(JS 번들 재로드) 시 자연 초기화 → 재실행마다 잠금 1회.
* 포커스마다 반복 프롬프트를 막고, 인증 직후 무한 재프롬프트를 방지한다.
*/
let sessionUnlocked = false;
export function isSessionUnlocked(): boolean {
return sessionUnlocked;
}
export function markSessionUnlocked(): void {
sessionUnlocked = true;
}
export function resetSessionUnlocked(): void {
sessionUnlocked = false;
}
/** 사용자 토글(pref) 조회 — 기본 비활성('0'/미설정). */
export async function isBiometricEnabled(): Promise<boolean> {
try {
return (await AsyncStorage.getItem(PREF_KEY)) === '1';
} catch {
return false;
}
}
/** 사용자 토글(pref) 설정. */
export async function setBiometricEnabled(on: boolean): Promise<void> {
try {
await AsyncStorage.setItem(PREF_KEY, on ? '1' : '0');
} catch {
/* graceful */
}
}
/** 생체 인증 프롬프트. 성공 시 true. 미지원/취소/실패 시 false(생체 원본 미노출). */
export async function authenticateBiometric(): Promise<boolean> {
if (!LocalAuth) return false;
try {
const res = await LocalAuth.authenticateAsync({
promptMessage: 'KINTEX 생체인식 잠금 해제',
cancelLabel: '취소',
disableDeviceFallback: false,
});
return !!res?.success;
} catch {
return false;
}
}