@sanctum-key/react-native-sdk 1.0.61 → 1.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -0
- package/android/build/.transforms/ac63f648699cdd4b91e6ee06476f2a70/results.bin +1 -0
- package/android/build/.transforms/ac63f648699cdd4b91e6ee06476f2a70/transformed/classes/classes_dex/classes.dex +0 -0
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +9 -0
- package/android/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +45 -25
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +9 -0
- package/android/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/inputs/source-to-output.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-attributes.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/class-fq-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/internal-name-to-source.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/proto.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/source-to-classes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/subtypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/jvm/kotlin/supertypes.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/counters.tab +1 -1
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/file-to-id.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/id-to-file.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.keystream.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.len +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab.values.at +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/caches-jvm/lookups/lookups.tab_i +0 -0
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/classpath-snapshot/shrunk-classpath-snapshot.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +34 -12
- package/android/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$BarcodeRegion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$Companion.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$6$1.class → SanctumKeySdkModule$definition$1$7$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$1$6$2.class → SanctumKeySdkModule$definition$1$7$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$5.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$3.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$3.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$4.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$2.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$5.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$6.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$4.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$7.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$8.class +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunction$6.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunction$9.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$2.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$3.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$3.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$4.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$4.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$5.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$5.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$6.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$6.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$AsyncFunctionWithPromise$7.class → SanctumKeySdkModule$definition$lambda$24$$inlined$AsyncFunctionWithPromise$7.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$$inlined$View$1.class → SanctumKeySdkModule$definition$lambda$24$$inlined$View$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$1.class → SanctumKeySdkModule$definition$lambda$24$lambda$23$$inlined$Prop$1.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/{SanctumKeySdkModule$definition$lambda$16$lambda$15$$inlined$Prop$2.class → SanctumKeySdkModule$definition$lambda$24$lambda$23$$inlined$Prop$2.class} +0 -0
- package/android/build/tmp/kotlin-classes/debug/kyc/sanctumkey/com/SanctumKeySdkModule.class +0 -0
- package/android/src/main/AndroidManifest.xml +9 -0
- package/android/src/main/java/kyc/sanctumkey/com/SanctumKeySdkModule.kt +147 -0
- package/build/package.json +1 -1
- package/build/src/SanctumKeySdkModule.d.ts +2 -0
- package/build/src/SanctumKeySdkModule.d.ts.map +1 -1
- package/build/src/SanctumKeySdkModule.js.map +1 -1
- package/build/src/SanctumKeySdkModule.web.d.ts +2 -0
- package/build/src/SanctumKeySdkModule.web.d.ts.map +1 -1
- package/build/src/SanctumKeySdkModule.web.js +14 -0
- package/build/src/SanctumKeySdkModule.web.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelection.js +1 -1
- package/build/src/components/KYCElements/CountrySelection.js.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.d.ts.map +1 -1
- package/build/src/components/KYCElements/CountrySelectionTemplate.js +27 -17
- package/build/src/components/KYCElements/CountrySelectionTemplate.js.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/src/components/KYCElements/IDCardCapture.js +46 -28
- package/build/src/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/src/components/OverLay/IdCard.d.ts +1 -1
- package/build/src/components/OverLay/IdCard.d.ts.map +1 -1
- package/build/src/components/OverLay/IdCard.js +47 -14
- package/build/src/components/OverLay/IdCard.js.map +1 -1
- package/build/src/components/OverLay/type.d.ts +2 -0
- package/build/src/components/OverLay/type.d.ts.map +1 -1
- package/build/src/components/OverLay/type.js.map +1 -1
- package/build/src/components/Svgs/scanningLine.d.ts +4 -1
- package/build/src/components/Svgs/scanningLine.d.ts.map +1 -1
- package/build/src/components/Svgs/scanningLine.js +15 -11
- package/build/src/components/Svgs/scanningLine.js.map +1 -1
- package/build/src/components/TemplateKYCExample.d.ts +4 -1
- package/build/src/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/src/components/TemplateKYCExample.js +2 -2
- package/build/src/components/TemplateKYCExample.js.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.d.ts +4 -2
- package/build/src/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/src/components/TemplateKYCFlowRefactored.js +157 -8
- package/build/src/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/src/config/region_mapping.d.ts.map +1 -1
- package/build/src/config/region_mapping.js +12 -0
- package/build/src/config/region_mapping.js.map +1 -1
- package/build/src/config/region_mapping.json +12 -0
- package/build/src/index.d.ts +2 -0
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +2 -0
- package/build/src/index.js.map +1 -1
- package/build/src/security/deviceSecurity.d.ts +30 -0
- package/build/src/security/deviceSecurity.d.ts.map +1 -0
- package/build/src/security/deviceSecurity.js +55 -0
- package/build/src/security/deviceSecurity.js.map +1 -0
- package/build/src/utils/logger.d.ts.map +1 -1
- package/build/src/utils/logger.js +5 -0
- package/build/src/utils/logger.js.map +1 -1
- package/ios/SanctumKeySdkModule.swift +93 -0
- package/package.json +1 -1
- package/src/SanctumKeySdkModule.ts +2 -0
- package/src/SanctumKeySdkModule.web.ts +16 -0
- package/src/components/KYCElements/CountrySelection.tsx +3 -1
- package/src/components/KYCElements/CountrySelectionTemplate.tsx +73 -24
- package/src/components/KYCElements/IDCardCapture.tsx +53 -30
- package/src/components/OverLay/IdCard.tsx +72 -15
- package/src/components/OverLay/type.ts +4 -2
- package/src/components/Svgs/scanningLine.tsx +19 -12
- package/src/components/TemplateKYCExample.tsx +8 -1
- package/src/components/TemplateKYCFlowRefactored.tsx +202 -16
- package/src/config/region_mapping.json +12 -0
- package/src/config/region_mapping.ts +12 -0
- package/src/index.ts +9 -1
- package/src/security/deviceSecurity.ts +106 -0
- package/src/utils/logger.ts +5 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react';
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { View, Text, StyleSheet, SafeAreaView, TouchableOpacity, ActivityIndicator, Dimensions, ScrollView, Platform } from 'react-native';
|
|
3
3
|
import { KYCTemplate, VerificationState } from '../types/KYC.types';
|
|
4
4
|
import { TemplateKYCFlowProvider, useTemplateKYCFlowContext } from '../hooks/useTemplateKYCFlow';
|
|
@@ -21,8 +21,15 @@ import { AdditionalDocumentsTemplate } from './KYCElements/AdditionalDocumentsTe
|
|
|
21
21
|
import { KycEnvironment, BackendEnvironment } from '../types/env.types';
|
|
22
22
|
import KYCConfig from '../config/KYCConfig';
|
|
23
23
|
import { logger, sdkLogger } from '../utils/logger';
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
import {
|
|
25
|
+
checkDeviceSecurity,
|
|
26
|
+
getDeviceSecurityViolations,
|
|
27
|
+
type DeviceSecurityCheckResult,
|
|
28
|
+
type DeviceSecurityPolicy,
|
|
29
|
+
type DeviceSecurityViolation,
|
|
30
|
+
} from '../security/deviceSecurity';
|
|
31
|
+
|
|
32
|
+
export interface TemplateKYCFlowProps {
|
|
26
33
|
template?: KYCTemplate;
|
|
27
34
|
templateId?: string;
|
|
28
35
|
onComplete?: (data: VerificationState) => void;
|
|
@@ -40,6 +47,33 @@ interface TemplateKYCFlowProps {
|
|
|
40
47
|
onSessionSave?: (sessionId: string) => void;
|
|
41
48
|
kyc_session_id?: string;
|
|
42
49
|
templateConfig?: Record<string, string>;
|
|
50
|
+
deviceSecurityPolicy?: DeviceSecurityPolicy;
|
|
51
|
+
onDeviceSecurityCheck?: (result: DeviceSecurityCheckResult) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function deviceSecurityErrorMessage(
|
|
55
|
+
violations: DeviceSecurityViolation[],
|
|
56
|
+
language: string,
|
|
57
|
+
): string {
|
|
58
|
+
const french = language.toLowerCase().startsWith('fr');
|
|
59
|
+
const labels: Record<DeviceSecurityViolation, string> = french
|
|
60
|
+
? {
|
|
61
|
+
compromised_device: 'appareil rooté ou jailbreaké',
|
|
62
|
+
developer_mode: 'mode développeur ou débogueur activé',
|
|
63
|
+
emulator: 'émulateur ou simulateur détecté',
|
|
64
|
+
check_unavailable: 'vérification de sécurité indisponible',
|
|
65
|
+
}
|
|
66
|
+
: {
|
|
67
|
+
compromised_device: 'rooted or jailbroken device',
|
|
68
|
+
developer_mode: 'developer mode or debugger enabled',
|
|
69
|
+
emulator: 'emulator or simulator detected',
|
|
70
|
+
check_unavailable: 'device security check unavailable',
|
|
71
|
+
};
|
|
72
|
+
const details = violations.map(violation => labels[violation]).join(', ');
|
|
73
|
+
|
|
74
|
+
return french
|
|
75
|
+
? `La vérification d'identité ne peut pas continuer : ${details}.`
|
|
76
|
+
: `Identity verification cannot continue: ${details}.`;
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
@@ -60,15 +94,118 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
60
94
|
onSessionSave,
|
|
61
95
|
kyc_session_id,
|
|
62
96
|
templateConfig,
|
|
97
|
+
deviceSecurityPolicy,
|
|
98
|
+
onDeviceSecurityCheck,
|
|
63
99
|
}) => {
|
|
64
100
|
const { t } = useI18n();
|
|
65
101
|
const { template: loadedTemplate, isLoading, error, loadTemplate } = useTemplateLoader();
|
|
66
102
|
const [resolvedTemplateId, setResolvedTemplateId] = useState<string | undefined>(templateId);
|
|
67
103
|
const [isResolving, setIsResolving] = useState<boolean>(!templateId && !!templateConfig);
|
|
104
|
+
const securityCheckEnabled = Platform.OS !== 'web' && deviceSecurityPolicy?.enabled !== false;
|
|
105
|
+
const enforceSecurity = env === 'PRODUCTION';
|
|
106
|
+
const blockCompromisedDevices = deviceSecurityPolicy?.blockCompromisedDevices ?? enforceSecurity;
|
|
107
|
+
const blockDeveloperMode = deviceSecurityPolicy?.blockDeveloperMode ?? enforceSecurity;
|
|
108
|
+
const blockEmulators = deviceSecurityPolicy?.blockEmulators ?? enforceSecurity;
|
|
109
|
+
const failClosed = deviceSecurityPolicy?.failClosed ?? enforceSecurity;
|
|
110
|
+
const [securityStatus, setSecurityStatus] = useState<'checking' | 'allowed' | 'blocked'>(
|
|
111
|
+
securityCheckEnabled ? 'checking' : 'allowed',
|
|
112
|
+
);
|
|
113
|
+
const [securityError, setSecurityError] = useState('');
|
|
114
|
+
const [securityRetryToken, setSecurityRetryToken] = useState(0);
|
|
115
|
+
const onErrorRef = useRef(onError);
|
|
116
|
+
const onDeviceSecurityCheckRef = useRef(onDeviceSecurityCheck);
|
|
117
|
+
|
|
118
|
+
useEffect(() => { onErrorRef.current = onError; }, [onError]);
|
|
119
|
+
useEffect(() => { onDeviceSecurityCheckRef.current = onDeviceSecurityCheck; }, [onDeviceSecurityCheck]);
|
|
68
120
|
|
|
69
121
|
// 🚨 1. Determine the active API key based on the environment
|
|
70
122
|
const activeApiKey = env === 'PRODUCTION' ? API_KEY : (TEST_API_KEY || API_KEY);
|
|
71
123
|
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
let cancelled = false;
|
|
126
|
+
|
|
127
|
+
if (!securityCheckEnabled) {
|
|
128
|
+
setSecurityStatus('allowed');
|
|
129
|
+
setSecurityError('');
|
|
130
|
+
return () => { cancelled = true; };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
setSecurityStatus('checking');
|
|
134
|
+
setSecurityError('');
|
|
135
|
+
|
|
136
|
+
const runSecurityCheck = async () => {
|
|
137
|
+
try {
|
|
138
|
+
const result = await checkDeviceSecurity();
|
|
139
|
+
if (cancelled) return;
|
|
140
|
+
|
|
141
|
+
onDeviceSecurityCheckRef.current?.(result);
|
|
142
|
+
const violations = getDeviceSecurityViolations(result, {
|
|
143
|
+
blockCompromisedDevices,
|
|
144
|
+
blockDeveloperMode,
|
|
145
|
+
blockEmulators,
|
|
146
|
+
failClosed,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
sdkLogger.info('Device security check completed', {
|
|
150
|
+
platform: result.platform,
|
|
151
|
+
supported: result.supported,
|
|
152
|
+
isCompromised: result.isCompromised,
|
|
153
|
+
isDeveloperModeEnabled: result.isDeveloperModeEnabled,
|
|
154
|
+
isEmulator: result.isEmulator,
|
|
155
|
+
riskSignals: result.riskSignals,
|
|
156
|
+
blocked: violations.length > 0,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
if (violations.length > 0) {
|
|
160
|
+
const message = deviceSecurityErrorMessage(violations, language);
|
|
161
|
+
setSecurityError(message);
|
|
162
|
+
setSecurityStatus('blocked');
|
|
163
|
+
onErrorRef.current?.(message);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setSecurityStatus('allowed');
|
|
168
|
+
} catch (error: any) {
|
|
169
|
+
if (cancelled) return;
|
|
170
|
+
const unavailableResult: DeviceSecurityCheckResult = {
|
|
171
|
+
platform: Platform.OS === 'android' || Platform.OS === 'ios' ? Platform.OS : 'unknown',
|
|
172
|
+
supported: false,
|
|
173
|
+
isRooted: false,
|
|
174
|
+
isJailbroken: false,
|
|
175
|
+
isEmulator: false,
|
|
176
|
+
isDeveloperModeEnabled: false,
|
|
177
|
+
developerModeCheckSupported: false,
|
|
178
|
+
isDebuggerAttached: false,
|
|
179
|
+
isCompromised: false,
|
|
180
|
+
riskSignals: ['native_device_security_check_failed'],
|
|
181
|
+
checkedAt: new Date().toISOString(),
|
|
182
|
+
};
|
|
183
|
+
onDeviceSecurityCheckRef.current?.(unavailableResult);
|
|
184
|
+
sdkLogger.error('Device security check failed', { error: error?.message });
|
|
185
|
+
|
|
186
|
+
if (failClosed) {
|
|
187
|
+
const message = deviceSecurityErrorMessage(['check_unavailable'], language);
|
|
188
|
+
setSecurityError(message);
|
|
189
|
+
setSecurityStatus('blocked');
|
|
190
|
+
onErrorRef.current?.(message);
|
|
191
|
+
} else {
|
|
192
|
+
setSecurityStatus('allowed');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
void runSecurityCheck();
|
|
198
|
+
return () => { cancelled = true; };
|
|
199
|
+
}, [
|
|
200
|
+
securityCheckEnabled,
|
|
201
|
+
blockCompromisedDevices,
|
|
202
|
+
blockDeveloperMode,
|
|
203
|
+
blockEmulators,
|
|
204
|
+
failClosed,
|
|
205
|
+
securityRetryToken,
|
|
206
|
+
language,
|
|
207
|
+
]);
|
|
208
|
+
|
|
72
209
|
useEffect(() => {
|
|
73
210
|
if (activeApiKey) {
|
|
74
211
|
if (sdkLogger.setApiKey) sdkLogger.setApiKey(activeApiKey);
|
|
@@ -83,6 +220,8 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
83
220
|
|
|
84
221
|
|
|
85
222
|
useEffect(() => {
|
|
223
|
+
if (securityStatus !== 'allowed') return;
|
|
224
|
+
|
|
86
225
|
// If a strict templateId is provided, use it immediately and skip geolocation
|
|
87
226
|
if (templateId) {
|
|
88
227
|
setResolvedTemplateId(templateId);
|
|
@@ -125,12 +264,12 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
125
264
|
};
|
|
126
265
|
|
|
127
266
|
fetchLocationAndRoute();
|
|
128
|
-
}, [templateId, templateConfig]); // Only re-run if the props change
|
|
267
|
+
}, [templateId, templateConfig, securityStatus]); // Only re-run if the props change
|
|
129
268
|
|
|
130
269
|
|
|
131
270
|
|
|
132
271
|
useEffect(() => {
|
|
133
|
-
if (isResolving) return;
|
|
272
|
+
if (isResolving || securityStatus !== 'allowed') return;
|
|
134
273
|
|
|
135
274
|
// Updated error check to include templateConfig
|
|
136
275
|
if (!providedTemplate && !resolvedTemplateId) {
|
|
@@ -148,7 +287,7 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
148
287
|
logger.log(`Fetching template ${resolvedTemplateId} via API Key`);
|
|
149
288
|
loadTemplate(resolvedTemplateId, activeApiKey);
|
|
150
289
|
}
|
|
151
|
-
}, [resolvedTemplateId, activeApiKey, providedTemplate, loadTemplate, onError, isResolving]);
|
|
290
|
+
}, [resolvedTemplateId, activeApiKey, providedTemplate, loadTemplate, onError, isResolving, securityStatus]);
|
|
152
291
|
|
|
153
292
|
useEffect(() => {
|
|
154
293
|
if (error) {
|
|
@@ -180,7 +319,44 @@ export const TemplateKYCFlow: React.FC<TemplateKYCFlowProps> = ({
|
|
|
180
319
|
|
|
181
320
|
const template = providedTemplate || loadedTemplate;
|
|
182
321
|
|
|
183
|
-
|
|
322
|
+
if (securityStatus === 'checking') {
|
|
323
|
+
return (
|
|
324
|
+
<View style={styles.loadingContainer}>
|
|
325
|
+
<ActivityIndicator size="large" color="#2DBD60" />
|
|
326
|
+
<Text style={styles.loadingText}>
|
|
327
|
+
{language.toLowerCase().startsWith('fr')
|
|
328
|
+
? 'Vérification de la sécurité de l’appareil…'
|
|
329
|
+
: 'Checking device security…'}
|
|
330
|
+
</Text>
|
|
331
|
+
</View>
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (securityStatus === 'blocked') {
|
|
336
|
+
return (
|
|
337
|
+
<View style={styles.errorContainer}>
|
|
338
|
+
<Text style={styles.securityTitle}>
|
|
339
|
+
{language.toLowerCase().startsWith('fr') ? 'Appareil non sécurisé' : 'Device security issue'}
|
|
340
|
+
</Text>
|
|
341
|
+
<Text style={styles.errorText}>{securityError}</Text>
|
|
342
|
+
<TouchableOpacity
|
|
343
|
+
style={styles.retryButton}
|
|
344
|
+
onPress={() => setSecurityRetryToken(value => value + 1)}
|
|
345
|
+
>
|
|
346
|
+
<Text style={styles.retryButtonText}>
|
|
347
|
+
{language.toLowerCase().startsWith('fr') ? 'Réessayer' : 'Retry'}
|
|
348
|
+
</Text>
|
|
349
|
+
</TouchableOpacity>
|
|
350
|
+
{onCancel ? (
|
|
351
|
+
<TouchableOpacity style={styles.cancelSecurityButton} onPress={onCancel}>
|
|
352
|
+
<Text style={styles.cancelSecurityButtonText}>
|
|
353
|
+
{language.toLowerCase().startsWith('fr') ? 'Annuler' : 'Cancel'}
|
|
354
|
+
</Text>
|
|
355
|
+
</TouchableOpacity>
|
|
356
|
+
) : null}
|
|
357
|
+
</View>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
184
360
|
|
|
185
361
|
if (templateId && isLoading && !template) {
|
|
186
362
|
return (
|
|
@@ -352,14 +528,20 @@ const TemplateKYCFlowContent: React.FC<{ onCancel?: () => void }> = ({ onCancel
|
|
|
352
528
|
</View>
|
|
353
529
|
) : null}
|
|
354
530
|
|
|
355
|
-
|
|
356
|
-
style={styles.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
531
|
+
{currentComponent.type === 'country_selection' ? (
|
|
532
|
+
<View style={styles.selfScrollingContent}>
|
|
533
|
+
{renderCurrentComponent()}
|
|
534
|
+
</View>
|
|
535
|
+
) : (
|
|
536
|
+
<ScrollView
|
|
537
|
+
style={styles.scrollView}
|
|
538
|
+
contentContainerStyle={styles.content}
|
|
539
|
+
showsVerticalScrollIndicator={Platform.OS === 'web'}
|
|
540
|
+
bounces={false}
|
|
541
|
+
>
|
|
542
|
+
{renderCurrentComponent()}
|
|
543
|
+
</ScrollView>
|
|
544
|
+
)}
|
|
363
545
|
|
|
364
546
|
{state.isProcessing && (
|
|
365
547
|
<View style={styles.processingContainer}>
|
|
@@ -386,6 +568,7 @@ const styles = StyleSheet.create({
|
|
|
386
568
|
stepDescription: { fontSize: 14, color: '#666', lineHeight: 20, },
|
|
387
569
|
scrollView: { flex: 1, },
|
|
388
570
|
content: { flexGrow: 1, alignItems: 'center', paddingBottom: 20, },
|
|
571
|
+
selfScrollingContent: { flex: 1, minHeight: 0, alignItems: 'center', paddingBottom: 20, },
|
|
389
572
|
navigation: { flexDirection: 'row', justifyContent: 'space-between', padding: 20, backgroundColor: 'white', borderTopWidth: 1, borderTopColor: '#e0e0e0', },
|
|
390
573
|
navButton: { paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, minWidth: 100, alignItems: 'center', },
|
|
391
574
|
primaryButton: { backgroundColor: '#2DBD60', },
|
|
@@ -400,4 +583,7 @@ const styles = StyleSheet.create({
|
|
|
400
583
|
errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f5f5f5', padding: 20, },
|
|
401
584
|
retryButton: { marginTop: 20, paddingHorizontal: 24, paddingVertical: 12, backgroundColor: '#2DBD60', borderRadius: 8, },
|
|
402
585
|
retryButtonText: { color: '#fff', fontSize: 16, fontWeight: '600', },
|
|
403
|
-
}
|
|
586
|
+
securityTitle: { color: '#991b1b', fontSize: 22, fontWeight: '700', textAlign: 'center', },
|
|
587
|
+
cancelSecurityButton: { marginTop: 12, paddingHorizontal: 24, paddingVertical: 12, },
|
|
588
|
+
cancelSecurityButtonText: { color: '#4b5563', fontSize: 16, fontWeight: '600', },
|
|
589
|
+
});
|
|
@@ -594,6 +594,18 @@
|
|
|
594
594
|
}
|
|
595
595
|
},
|
|
596
596
|
"Kenya": {
|
|
597
|
+
"drivers_licence": {
|
|
598
|
+
"root": [
|
|
599
|
+
{
|
|
600
|
+
"py_file": "dl_back.py",
|
|
601
|
+
"keys_found": null
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
"py_file": "dl_front.py",
|
|
605
|
+
"keys_found": null
|
|
606
|
+
}
|
|
607
|
+
]
|
|
608
|
+
},
|
|
597
609
|
"identity_card": {
|
|
598
610
|
"root": [
|
|
599
611
|
{
|
|
@@ -608,6 +608,18 @@ export const countryMapping: Record<string, Record<string, Record<string, ICount
|
|
|
608
608
|
}
|
|
609
609
|
},
|
|
610
610
|
"KE": {
|
|
611
|
+
"drivers_licence": {
|
|
612
|
+
"root": [
|
|
613
|
+
{
|
|
614
|
+
"py_file": "dl_back.py",
|
|
615
|
+
"keys_found": null
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
"py_file": "dl_front.py",
|
|
619
|
+
"keys_found": null
|
|
620
|
+
}
|
|
621
|
+
]
|
|
622
|
+
},
|
|
611
623
|
"identity_card": {
|
|
612
624
|
"root": [
|
|
613
625
|
{
|
package/src/index.ts
CHANGED
|
@@ -25,5 +25,13 @@ export { WelcomeTemplate } from './components/KYCElements/WelcomeTemplate';
|
|
|
25
25
|
export { EnhancedCameraView } from './components/EnhancedCameraView';
|
|
26
26
|
export { UnifiedCameraModule } from './modules/camera';
|
|
27
27
|
|
|
28
|
+
// Export native device-integrity checks and policy types.
|
|
29
|
+
export { checkDeviceSecurity, getDeviceSecurityViolations } from './security/deviceSecurity';
|
|
30
|
+
export type {
|
|
31
|
+
DeviceSecurityCheckResult,
|
|
32
|
+
DeviceSecurityPolicy,
|
|
33
|
+
DeviceSecurityViolation,
|
|
34
|
+
} from './security/deviceSecurity';
|
|
35
|
+
|
|
28
36
|
// Export Web KYC Components
|
|
29
|
-
export { default as WebKYCEntry } from './web/WebKYCEntry';
|
|
37
|
+
export { default as WebKYCEntry } from './web/WebKYCEntry';
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
import SanctumKeySdkModule from '../SanctumKeySdkModule';
|
|
3
|
+
|
|
4
|
+
export type DeviceSecurityPlatform = 'android' | 'ios' | 'web' | 'unknown';
|
|
5
|
+
|
|
6
|
+
export interface DeviceSecurityCheckResult {
|
|
7
|
+
platform: DeviceSecurityPlatform;
|
|
8
|
+
supported: boolean;
|
|
9
|
+
isRooted: boolean;
|
|
10
|
+
isJailbroken: boolean;
|
|
11
|
+
isEmulator: boolean;
|
|
12
|
+
isDeveloperModeEnabled: boolean;
|
|
13
|
+
developerModeCheckSupported: boolean;
|
|
14
|
+
isDebuggerAttached: boolean;
|
|
15
|
+
isCompromised: boolean;
|
|
16
|
+
riskSignals: string[];
|
|
17
|
+
checkedAt: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DeviceSecurityPolicy {
|
|
21
|
+
/** Run the check. Defaults to true on Android and iOS. */
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
/** Block rooted Android or jailbroken iOS devices. Defaults to true in production. */
|
|
24
|
+
blockCompromisedDevices?: boolean;
|
|
25
|
+
/** Block Android developer options or an attached iOS debugger. Defaults to true in production. */
|
|
26
|
+
blockDeveloperMode?: boolean;
|
|
27
|
+
/** Block Android emulators and the iOS Simulator. Defaults to true in production. */
|
|
28
|
+
blockEmulators?: boolean;
|
|
29
|
+
/** Block when the native check is unavailable or fails. Defaults to true in production. */
|
|
30
|
+
failClosed?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type DeviceSecurityViolation =
|
|
34
|
+
| 'compromised_device'
|
|
35
|
+
| 'developer_mode'
|
|
36
|
+
| 'emulator'
|
|
37
|
+
| 'check_unavailable';
|
|
38
|
+
|
|
39
|
+
type NativeDeviceSecurityResult = Partial<Omit<DeviceSecurityCheckResult, 'checkedAt'>>;
|
|
40
|
+
|
|
41
|
+
const booleanValue = (value: unknown): boolean => value === true;
|
|
42
|
+
|
|
43
|
+
const stringArray = (value: unknown): string[] => (
|
|
44
|
+
Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
export async function checkDeviceSecurity(): Promise<DeviceSecurityCheckResult> {
|
|
48
|
+
if (Platform.OS === 'web') {
|
|
49
|
+
return {
|
|
50
|
+
platform: 'web',
|
|
51
|
+
supported: false,
|
|
52
|
+
isRooted: false,
|
|
53
|
+
isJailbroken: false,
|
|
54
|
+
isEmulator: false,
|
|
55
|
+
isDeveloperModeEnabled: false,
|
|
56
|
+
developerModeCheckSupported: false,
|
|
57
|
+
isDebuggerAttached: false,
|
|
58
|
+
isCompromised: false,
|
|
59
|
+
riskSignals: ['native_device_security_check_unavailable_on_web'],
|
|
60
|
+
checkedAt: new Date().toISOString(),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (typeof SanctumKeySdkModule.checkDeviceSecurity !== 'function') {
|
|
65
|
+
throw new Error(
|
|
66
|
+
'The native device-security checker is unavailable. Rebuild the Android or iOS application with the latest SanctumKey SDK.',
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const nativeResult = await SanctumKeySdkModule.checkDeviceSecurity() as NativeDeviceSecurityResult;
|
|
71
|
+
const isRooted = booleanValue(nativeResult.isRooted);
|
|
72
|
+
const isJailbroken = booleanValue(nativeResult.isJailbroken);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
platform: nativeResult.platform === 'android' || nativeResult.platform === 'ios'
|
|
76
|
+
? nativeResult.platform
|
|
77
|
+
: 'unknown',
|
|
78
|
+
supported: nativeResult.supported !== false,
|
|
79
|
+
isRooted,
|
|
80
|
+
isJailbroken,
|
|
81
|
+
isEmulator: booleanValue(nativeResult.isEmulator),
|
|
82
|
+
isDeveloperModeEnabled: booleanValue(nativeResult.isDeveloperModeEnabled),
|
|
83
|
+
developerModeCheckSupported: booleanValue(nativeResult.developerModeCheckSupported),
|
|
84
|
+
isDebuggerAttached: booleanValue(nativeResult.isDebuggerAttached),
|
|
85
|
+
isCompromised: booleanValue(nativeResult.isCompromised) || isRooted || isJailbroken,
|
|
86
|
+
riskSignals: stringArray(nativeResult.riskSignals),
|
|
87
|
+
checkedAt: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function getDeviceSecurityViolations(
|
|
92
|
+
result: DeviceSecurityCheckResult,
|
|
93
|
+
policy: Required<Pick<
|
|
94
|
+
DeviceSecurityPolicy,
|
|
95
|
+
'blockCompromisedDevices' | 'blockDeveloperMode' | 'blockEmulators' | 'failClosed'
|
|
96
|
+
>>,
|
|
97
|
+
): DeviceSecurityViolation[] {
|
|
98
|
+
const violations: DeviceSecurityViolation[] = [];
|
|
99
|
+
|
|
100
|
+
if (!result.supported && policy.failClosed) violations.push('check_unavailable');
|
|
101
|
+
if (result.isCompromised && policy.blockCompromisedDevices) violations.push('compromised_device');
|
|
102
|
+
if (result.isDeveloperModeEnabled && policy.blockDeveloperMode) violations.push('developer_mode');
|
|
103
|
+
if (result.isEmulator && policy.blockEmulators) violations.push('emulator');
|
|
104
|
+
|
|
105
|
+
return violations;
|
|
106
|
+
}
|
package/src/utils/logger.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Platform, AppState, AppStateStatus } from 'react-native';
|
|
2
2
|
import PostHog from 'posthog-react-native';
|
|
3
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
3
4
|
|
|
4
5
|
const posthogClient = new PostHog('phc_qjCHjCgkdVniammXrrfLrmNPoPH5uAmkzoQMhrEhGaYR', {
|
|
5
6
|
host: 'https://eu.i.posthog.com',
|
|
@@ -7,6 +8,10 @@ const posthogClient = new PostHog('phc_qjCHjCgkdVniammXrrfLrmNPoPH5uAmkzoQMhrEhG
|
|
|
7
8
|
captureAppLifecycleEvents: false,
|
|
8
9
|
flushAt: 1,
|
|
9
10
|
flushInterval: 1000, // Backup: flush automatically every 1 second
|
|
11
|
+
customStorage: {
|
|
12
|
+
getItem: async (key) => await AsyncStorage.getItem(key),
|
|
13
|
+
setItem: async (key, value) => await AsyncStorage.setItem(key, value),
|
|
14
|
+
}
|
|
10
15
|
});
|
|
11
16
|
|
|
12
17
|
const isProduction = (): boolean => {
|