Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions frontend/app/configs/fingerprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ export const FINGERPRINT_CONFIG = {
confidenceThreshold: parseFloat(
process.env.EXPO_PUBLIC_FINGERPRINT_CONFIDENCE_THRESHOLD || "0.7",
),
// Suspect score threshold for blocking suspicious devices
suspectScoreThreshold: parseInt(
process.env.EXPO_PUBLIC_FINGERPRINT_SUSPECT_SCORE_THRESHOLD || "5",
),
// Additional configuration options
options: {
// Enable extended result for more detailed device information
Expand Down Expand Up @@ -74,6 +78,54 @@ export interface FingerprintValidationResult {
timestamp?: number;
}

// Suspect Score types
export interface SuspectScore {
data: {
result: number;
};
}

// Smart Signals types - all signals follow the pattern: { data: { result: boolean } }
export interface BooleanSmartSignal {
data: {
result: boolean;
};
}

// High Activity signal has additional fields
export interface HighActivitySignal {
data: {
result: boolean;
dailyRequests?: number;
};
}

// Factory Reset signal has timestamp fields
export interface FactoryResetSignal {
data: {
time: string; // ISO 8601 format
timestamp: number; // Unix epoch time
};
}

// Union type for all signal types
export type SmartSignal =
| BooleanSmartSignal
| HighActivitySignal
| FactoryResetSignal;

// Smart Signals collection
export interface SmartSignals {
emulator?: BooleanSmartSignal;
jailbroken?: BooleanSmartSignal;
rootApps?: BooleanSmartSignal;
frida?: BooleanSmartSignal;
clonedApp?: BooleanSmartSignal;
highActivity?: HighActivitySignal;
factoryReset?: FactoryResetSignal;
[key: string]: SmartSignal | undefined;
}

// Utility function to convert visitor ID string to felt252 hex format
export function visitorIdToFelt252(visitorId: string): string {
// Convert string to bytes and encode as hex
Expand Down
6 changes: 6 additions & 0 deletions frontend/app/hooks/useRewardsStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ export function useRewardsStatus() {
}
}

if (visitorId && visitorId == "0x0") {
setIsRewardAvailable(false);
setIsLoading(false);
return;
}

// Check prestige threshold
const params = await getRewardParams();
const threshold = params?.rewardPrestigeThreshold || 1;
Expand Down
206 changes: 203 additions & 3 deletions frontend/app/hooks/useVisitorId.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useMemo } from "react";
import { useVisitorData } from "@fingerprintjs/fingerprintjs-pro-react-native";
import { visitorIdToFelt252, FINGERPRINT_CONFIG } from "../configs/fingerprint";
import {
visitorIdToFelt252,
FINGERPRINT_CONFIG,
type SmartSignals,
} from "../configs/fingerprint";

/**
* Hook to get the visitor ID from fingerprint and convert it to felt252 format.
Expand All @@ -12,12 +16,176 @@ import { visitorIdToFelt252, FINGERPRINT_CONFIG } from "../configs/fingerprint";
* - rawVisitorData: The raw visitor data object from fingerprintjs
* - hasVisitorId: Whether a valid visitor ID was retrieved
* - tagEvent: Function to tag a Fingerprint event with custom metadata and linkedId
* - suspectScore: The suspect score from fingerprint (undefined if not available)
* - isEmulator: Whether emulator signal was detected (undefined if not available)
* - isJailbroken: Whether jailbroken signal was detected (iOS, undefined if not available)
* - isRooted: Whether rootApps signal was detected (Android, undefined if not available)
* - isFrida: Whether frida signal was detected (undefined if not available)
* - isClonedApp: Whether clonedApp signal was detected (Android, undefined if not available)
* - isHighActivity: Whether highActivity signal was detected (undefined if not available)
* - hasRecentFactoryReset: Whether factory reset was detected recently (undefined if not available)
* - isDeviceBlocked: Whether the device should be blocked based on suspect score or any suspicious signals
*/
export function useVisitorId() {
const { data: visitorData, isLoading, error, getData } = useVisitorData();
const [visitorId, setVisitorId] = useState<string>("0x0");
const [hasVisitorId, setHasVisitorId] = useState<boolean>(false);

// Extract suspect score
const suspectScore = useMemo(() => {
// Type assertion needed as FingerprintJS types may not include suspectScore
const data = visitorData as any;
if (
!data?.suspectScore?.data ||
data.suspectScore.data.result === undefined
) {
return undefined;
}
return data.suspectScore.data.result as number;
}, [visitorData]);

// Extract all device integrity and abuse prevention signals
const signals = useMemo(() => {
// Type assertion needed as FingerprintJS types may not include signals
const data = visitorData as any;
return data?.signals as SmartSignals | undefined;
}, [visitorData]);

// Device integrity signals
const isEmulator = useMemo(() => {
return signals?.emulator?.data?.result === true;
}, [signals]);

const isJailbroken = useMemo(() => {
return signals?.jailbroken?.data?.result === true;
}, [signals]);

const isRooted = useMemo(() => {
return signals?.rootApps?.data?.result === true;
}, [signals]);

const isFrida = useMemo(() => {
return signals?.frida?.data?.result === true;
}, [signals]);

const isClonedApp = useMemo(() => {
return signals?.clonedApp?.data?.result === true;
}, [signals]);

// Abuse prevention signals
const isHighActivity = useMemo(() => {
return signals?.highActivity?.data?.result === true;
}, [signals]);

// Factory reset detection - check if reset was recent (not epoch and within last 30 days)
const hasRecentFactoryReset = useMemo(() => {
const factoryReset = signals?.factoryReset;
if (!factoryReset?.data) {
return undefined;
}

const timestamp = factoryReset.data.timestamp;
// Epoch (0) means never reset (or iOS simulator)
if (timestamp === 0) {
return false;
}

// Check if reset was within last 30 days (30 * 24 * 60 * 60 * 1000 ms)
const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
const resetTime = timestamp * 1000; // Convert to milliseconds
return resetTime > thirtyDaysAgo;
}, [signals]);

// Check if device should be blocked
const isDeviceBlocked = useMemo(() => {
if (isLoading || error || !visitorData) {
return false; // Don't block while loading or on error
}

// Check suspect score threshold
if (
suspectScore !== undefined &&
suspectScore >= FINGERPRINT_CONFIG.suspectScoreThreshold
) {
if (__DEV__) {
console.log(
"useVisitorId: Device blocked due to suspect score:",
suspectScore,
);
}
return true;
}

// Check device integrity signals
if (isEmulator === true) {
if (__DEV__) {
console.log("useVisitorId: Device blocked due to emulator detection");
}
return true;
}

if (isJailbroken === true) {
if (__DEV__) {
console.log("useVisitorId: Device blocked due to jailbroken detection");
}
return true;
}

if (isRooted === true) {
if (__DEV__) {
console.log(
"useVisitorId: Device blocked due to rooted device detection",
);
}
return true;
}

if (isFrida === true) {
if (__DEV__) {
console.log("useVisitorId: Device blocked due to Frida detection");
}
return true;
}

if (isClonedApp === true) {
if (__DEV__) {
console.log("useVisitorId: Device blocked due to cloned app detection");
}
return true;
}

// Check abuse prevention signals
if (isHighActivity === true) {
if (__DEV__) {
console.log(
"useVisitorId: Device blocked due to high activity detection",
);
}
return true;
}

if (hasRecentFactoryReset === true) {
if (__DEV__) {
console.log("useVisitorId: Device blocked due to recent factory reset");
}
return true;
}

return false;
}, [
isLoading,
error,
visitorData,
suspectScore,
isEmulator,
isJailbroken,
isRooted,
isFrida,
isClonedApp,
isHighActivity,
hasRecentFactoryReset,
]);

// Debug logging
useEffect(() => {
if (__DEV__) {
Expand All @@ -27,11 +195,34 @@ export function useVisitorId() {
visitorData: visitorData ? "present" : "undefined",
visitorId: visitorData?.visitorId,
confidence: visitorData?.confidence?.score,
suspectScore,
isEmulator,
isJailbroken,
isRooted,
isFrida,
isClonedApp,
isHighActivity,
hasRecentFactoryReset,
isDeviceBlocked,
hasGetData: !!getData,
visitorDataFull: visitorData,
});
}
}, [isLoading, error, visitorData, getData]);
}, [
isLoading,
error,
visitorData,
getData,
suspectScore,
isEmulator,
isJailbroken,
isRooted,
isFrida,
isClonedApp,
isHighActivity,
hasRecentFactoryReset,
isDeviceBlocked,
]);

useEffect(() => {
if (isLoading) {
Expand Down Expand Up @@ -118,5 +309,14 @@ export function useVisitorId() {
rawVisitorData: visitorData,
hasVisitorId,
tagEvent,
suspectScore,
isEmulator,
isJailbroken,
isRooted,
isFrida,
isClonedApp,
isHighActivity,
hasRecentFactoryReset,
isDeviceBlocked,
};
}
Loading