update
This commit is contained in:
247
Frontend/src/features/auth/contexts/AntibotContext.tsx
Normal file
247
Frontend/src/features/auth/contexts/AntibotContext.tsx
Normal file
@@ -0,0 +1,247 @@
|
||||
import React, { createContext, useContext, useState, useEffect, useRef, useCallback } from 'react';
|
||||
import {
|
||||
FormTiming,
|
||||
MouseMovement,
|
||||
validateAntibotData,
|
||||
checkRateLimit,
|
||||
checkRateLimitStatus,
|
||||
clearRateLimit,
|
||||
getFingerprintHash,
|
||||
type AntibotValidationResult,
|
||||
} from '../../../shared/utils/antibot';
|
||||
|
||||
interface AntibotContextType {
|
||||
timing: FormTiming;
|
||||
fingerprintHash: string;
|
||||
startFormTracking: () => void;
|
||||
stopFormTracking: () => void;
|
||||
validateForm: (
|
||||
honeypotValue: string,
|
||||
recaptchaToken: string | null,
|
||||
options?: {
|
||||
minTimeOnPage?: number;
|
||||
minTimeToFill?: number;
|
||||
requireRecaptcha?: boolean;
|
||||
checkMouseMovements?: boolean;
|
||||
}
|
||||
) => AntibotValidationResult;
|
||||
checkActionRateLimit: (action: string, maxAttempts?: number) => {
|
||||
allowed: boolean;
|
||||
remainingAttempts: number;
|
||||
resetTime: number;
|
||||
};
|
||||
checkActionRateLimitStatus: (action: string, maxAttempts?: number) => {
|
||||
allowed: boolean;
|
||||
remainingAttempts: number;
|
||||
resetTime: number;
|
||||
};
|
||||
clearActionRateLimit: (action: string) => void;
|
||||
recordMouseMovement: (x: number, y: number) => void;
|
||||
recordClick: () => void;
|
||||
recordKeyPress: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
const AntibotContext = createContext<AntibotContextType | undefined>(undefined);
|
||||
|
||||
export const useAntibot = () => {
|
||||
const context = useContext(AntibotContext);
|
||||
if (!context) {
|
||||
throw new Error('useAntibot must be used within an AntibotProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface AntibotProviderProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const AntibotProvider: React.FC<AntibotProviderProps> = ({ children }) => {
|
||||
const [timing, setTiming] = useState<FormTiming>({
|
||||
pageLoadTime: Date.now(),
|
||||
formStartTime: null,
|
||||
formSubmitTime: null,
|
||||
timeOnPage: 0,
|
||||
timeToFill: null,
|
||||
mouseMovements: [],
|
||||
clickCount: 0,
|
||||
keyPressCount: 0,
|
||||
});
|
||||
|
||||
const [fingerprintHash] = useState<string>(() => getFingerprintHash());
|
||||
const pageLoadTimeRef = useRef<number>(Date.now());
|
||||
const formStartTimeRef = useRef<number | null>(null);
|
||||
const mouseMovementsRef = useRef<MouseMovement[]>([]);
|
||||
const clickCountRef = useRef<number>(0);
|
||||
const keyPressCountRef = useRef<number>(0);
|
||||
const updateIntervalRef = useRef<number | null>(null);
|
||||
|
||||
// Update time on page periodically
|
||||
useEffect(() => {
|
||||
updateIntervalRef.current = window.setInterval(() => {
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
timeOnPage: Date.now() - pageLoadTimeRef.current,
|
||||
}));
|
||||
}, 1000);
|
||||
|
||||
return () => {
|
||||
if (updateIntervalRef.current !== null) {
|
||||
clearInterval(updateIntervalRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Track mouse movements
|
||||
useEffect(() => {
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
recordMouseMovement(e.clientX, e.clientY);
|
||||
};
|
||||
|
||||
window.addEventListener('mousemove', handleMouseMove);
|
||||
return () => window.removeEventListener('mousemove', handleMouseMove);
|
||||
}, []);
|
||||
|
||||
// Track clicks
|
||||
useEffect(() => {
|
||||
const handleClick = () => {
|
||||
recordClick();
|
||||
};
|
||||
|
||||
window.addEventListener('click', handleClick);
|
||||
return () => window.removeEventListener('click', handleClick);
|
||||
}, []);
|
||||
|
||||
// Track key presses
|
||||
useEffect(() => {
|
||||
const handleKeyPress = () => {
|
||||
recordKeyPress();
|
||||
};
|
||||
|
||||
window.addEventListener('keypress', handleKeyPress);
|
||||
return () => window.removeEventListener('keypress', handleKeyPress);
|
||||
}, []);
|
||||
|
||||
const recordMouseMovement = useCallback((x: number, y: number) => {
|
||||
const movement: MouseMovement = {
|
||||
x,
|
||||
y,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
mouseMovementsRef.current.push(movement);
|
||||
|
||||
// Keep only last 50 movements to avoid memory issues
|
||||
if (mouseMovementsRef.current.length > 50) {
|
||||
mouseMovementsRef.current = mouseMovementsRef.current.slice(-50);
|
||||
}
|
||||
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
mouseMovements: [...mouseMovementsRef.current],
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const recordClick = useCallback(() => {
|
||||
clickCountRef.current += 1;
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
clickCount: clickCountRef.current,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const recordKeyPress = useCallback(() => {
|
||||
keyPressCountRef.current += 1;
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
keyPressCount: keyPressCountRef.current,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const startFormTracking = useCallback(() => {
|
||||
formStartTimeRef.current = Date.now();
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
formStartTime: formStartTimeRef.current,
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const stopFormTracking = useCallback(() => {
|
||||
if (formStartTimeRef.current !== null) {
|
||||
const formSubmitTime = Date.now();
|
||||
const timeToFill = formSubmitTime - formStartTimeRef.current;
|
||||
|
||||
setTiming((prev) => ({
|
||||
...prev,
|
||||
formSubmitTime,
|
||||
timeToFill,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const validateForm = useCallback((
|
||||
honeypotValue: string,
|
||||
recaptchaToken: string | null,
|
||||
options?: {
|
||||
minTimeOnPage?: number;
|
||||
minTimeToFill?: number;
|
||||
requireRecaptcha?: boolean;
|
||||
checkMouseMovements?: boolean;
|
||||
}
|
||||
): AntibotValidationResult => {
|
||||
return validateAntibotData(timing, honeypotValue, recaptchaToken, options);
|
||||
}, [timing]);
|
||||
|
||||
const checkActionRateLimit = useCallback((action: string, maxAttempts: number = 5) => {
|
||||
return checkRateLimit(action, maxAttempts);
|
||||
}, []);
|
||||
|
||||
const checkActionRateLimitStatus = useCallback((action: string, maxAttempts: number = 5) => {
|
||||
return checkRateLimitStatus(action, maxAttempts);
|
||||
}, []);
|
||||
|
||||
const clearActionRateLimit = useCallback((action: string) => {
|
||||
clearRateLimit(action);
|
||||
}, []);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
pageLoadTimeRef.current = Date.now();
|
||||
formStartTimeRef.current = null;
|
||||
mouseMovementsRef.current = [];
|
||||
clickCountRef.current = 0;
|
||||
keyPressCountRef.current = 0;
|
||||
|
||||
setTiming({
|
||||
pageLoadTime: pageLoadTimeRef.current,
|
||||
formStartTime: null,
|
||||
formSubmitTime: null,
|
||||
timeOnPage: 0,
|
||||
timeToFill: null,
|
||||
mouseMovements: [],
|
||||
clickCount: 0,
|
||||
keyPressCount: 0,
|
||||
});
|
||||
}, []);
|
||||
|
||||
const value: AntibotContextType = {
|
||||
timing,
|
||||
fingerprintHash,
|
||||
startFormTracking,
|
||||
stopFormTracking,
|
||||
validateForm,
|
||||
checkActionRateLimit,
|
||||
checkActionRateLimitStatus,
|
||||
clearActionRateLimit,
|
||||
recordMouseMovement,
|
||||
recordClick,
|
||||
recordKeyPress,
|
||||
reset,
|
||||
};
|
||||
|
||||
return (
|
||||
<AntibotContext.Provider value={value}>
|
||||
{children}
|
||||
</AntibotContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user