93 lines
3.1 KiB
TypeScript
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;
|
|
}
|
|
}
|