mirror of
https://github.com/th30d4y/OpenLearnX.git
synced 2026-05-26 11:25:49 +00:00
446 lines
15 KiB
TypeScript
446 lines
15 KiB
TypeScript
'use client'
|
|
import React, { useState, useEffect } from 'react'
|
|
import { useRouter, useParams } from 'next/navigation'
|
|
import {
|
|
Shield,
|
|
Monitor,
|
|
Copy,
|
|
AlertTriangle,
|
|
CheckCircle,
|
|
ArrowRight,
|
|
Eye,
|
|
Lock,
|
|
Maximize
|
|
} from 'lucide-react'
|
|
|
|
export default function SecurityCheckPage() {
|
|
const router = useRouter()
|
|
const params = useParams()
|
|
const examCode = params.examCode as string
|
|
|
|
const [securityChecks, setSecurityChecks] = useState({
|
|
fullScreen: false,
|
|
noVirtualBox: false,
|
|
copyPasteBlocked: false,
|
|
focusDetection: false
|
|
})
|
|
|
|
const [isFullScreen, setIsFullScreen] = useState(false)
|
|
const [securityPassed, setSecurityPassed] = useState(false)
|
|
const [agreementAccepted, setAgreementAccepted] = useState(false)
|
|
const [focusLostCount, setFocusLostCount] = useState(0)
|
|
const [warningMessage, setWarningMessage] = useState('')
|
|
|
|
useEffect(() => {
|
|
// Check for virtual machine detection
|
|
detectVirtualMachine()
|
|
|
|
// Block copy/paste
|
|
blockCopyPaste()
|
|
|
|
// Setup fullscreen detection
|
|
setupFullScreenDetection()
|
|
|
|
// Setup focus detection
|
|
setupFocusDetection()
|
|
|
|
// Block right-click
|
|
blockRightClick()
|
|
|
|
// Block developer tools
|
|
blockDevTools()
|
|
|
|
return () => {
|
|
// Cleanup event listeners
|
|
document.removeEventListener('contextmenu', handleRightClick)
|
|
document.removeEventListener('keydown', handleKeyDown)
|
|
window.removeEventListener('blur', handleWindowBlur)
|
|
window.removeEventListener('focus', handleWindowFocus)
|
|
}
|
|
}, [])
|
|
|
|
// ✅ VIRTUAL MACHINE DETECTION
|
|
const detectVirtualMachine = () => {
|
|
try {
|
|
const canvas = document.createElement('canvas')
|
|
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')
|
|
|
|
if (gl) {
|
|
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info')
|
|
if (debugInfo) {
|
|
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL).toLowerCase()
|
|
const vendor = gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL).toLowerCase()
|
|
|
|
const vmIndicators = [
|
|
'virtualbox', 'vmware', 'parallels', 'qemu',
|
|
'virtual', 'vm', 'hyper-v', 'kvm'
|
|
]
|
|
|
|
const isVM = vmIndicators.some(indicator =>
|
|
renderer.includes(indicator) || vendor.includes(indicator)
|
|
)
|
|
|
|
if (isVM) {
|
|
setWarningMessage('❌ Virtual machines are not allowed for this exam')
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// Additional VM detection checks
|
|
if (
|
|
navigator.hardwareConcurrency < 2 ||
|
|
screen.width < 1024 ||
|
|
screen.height < 768 ||
|
|
navigator.deviceMemory && navigator.deviceMemory < 2
|
|
) {
|
|
setWarningMessage('⚠️ Your system may not meet the minimum requirements')
|
|
}
|
|
|
|
setSecurityChecks(prev => ({ ...prev, noVirtualBox: true }))
|
|
} catch (error) {
|
|
console.error('VM detection failed:', error)
|
|
setSecurityChecks(prev => ({ ...prev, noVirtualBox: true }))
|
|
}
|
|
}
|
|
|
|
// ✅ BLOCK COPY/PASTE
|
|
const blockCopyPaste = () => {
|
|
const preventCopyPaste = (e: Event) => {
|
|
e.preventDefault()
|
|
setWarningMessage('⚠️ Copy/Paste is disabled during the exam')
|
|
setTimeout(() => setWarningMessage(''), 3000)
|
|
}
|
|
|
|
document.addEventListener('copy', preventCopyPaste)
|
|
document.addEventListener('paste', preventCopyPaste)
|
|
document.addEventListener('cut', preventCopyPaste)
|
|
document.addEventListener('drag', preventCopyPaste)
|
|
document.addEventListener('drop', preventCopyPaste)
|
|
document.addEventListener('selectstart', preventCopyPaste)
|
|
|
|
// Disable text selection
|
|
document.body.style.userSelect = 'none'
|
|
document.body.style.webkitUserSelect = 'none'
|
|
|
|
setSecurityChecks(prev => ({ ...prev, copyPasteBlocked: true }))
|
|
}
|
|
|
|
// ✅ FULLSCREEN DETECTION
|
|
const setupFullScreenDetection = () => {
|
|
const checkFullScreen = () => {
|
|
const isFS = !!(
|
|
document.fullscreenElement ||
|
|
(document as any).webkitFullscreenElement ||
|
|
(document as any).mozFullScreenElement ||
|
|
(document as any).msFullscreenElement
|
|
)
|
|
|
|
setIsFullScreen(isFS)
|
|
setSecurityChecks(prev => ({ ...prev, fullScreen: isFS }))
|
|
|
|
if (!isFS && securityPassed) {
|
|
setWarningMessage('⚠️ You must stay in fullscreen mode during the exam')
|
|
setTimeout(() => {
|
|
router.push('/coding')
|
|
}, 3000)
|
|
}
|
|
}
|
|
|
|
document.addEventListener('fullscreenchange', checkFullScreen)
|
|
document.addEventListener('webkitfullscreenchange', checkFullScreen)
|
|
document.addEventListener('mozfullscreenchange', checkFullScreen)
|
|
document.addEventListener('MSFullscreenChange', checkFullScreen)
|
|
}
|
|
|
|
// ✅ FOCUS DETECTION
|
|
const setupFocusDetection = () => {
|
|
let focusLost = false
|
|
|
|
const handleWindowBlur = () => {
|
|
if (securityPassed) {
|
|
focusLost = true
|
|
setFocusLostCount(prev => prev + 1)
|
|
setWarningMessage('⚠️ You switched tabs/windows. This is being monitored.')
|
|
|
|
if (focusLostCount >= 2) {
|
|
alert('Multiple focus violations detected. Exam will be terminated.')
|
|
router.push('/coding')
|
|
}
|
|
}
|
|
}
|
|
|
|
const handleWindowFocus = () => {
|
|
if (focusLost) {
|
|
focusLost = false
|
|
setTimeout(() => setWarningMessage(''), 3000)
|
|
}
|
|
}
|
|
|
|
window.addEventListener('blur', handleWindowBlur)
|
|
window.addEventListener('focus', handleWindowFocus)
|
|
|
|
setSecurityChecks(prev => ({ ...prev, focusDetection: true }))
|
|
}
|
|
|
|
// ✅ BLOCK RIGHT-CLICK
|
|
const blockRightClick = () => {
|
|
const handleRightClick = (e: MouseEvent) => {
|
|
e.preventDefault()
|
|
setWarningMessage('⚠️ Right-click is disabled during the exam')
|
|
setTimeout(() => setWarningMessage(''), 2000)
|
|
}
|
|
|
|
document.addEventListener('contextmenu', handleRightClick)
|
|
}
|
|
|
|
// ✅ BLOCK DEVELOPER TOOLS
|
|
const blockDevTools = () => {
|
|
const handleKeyDown = (e: KeyboardEvent) => {
|
|
// Block F12, Ctrl+Shift+I, Ctrl+Shift+C, Ctrl+U
|
|
if (
|
|
e.key === 'F12' ||
|
|
(e.ctrlKey && e.shiftKey && (e.key === 'I' || e.key === 'C')) ||
|
|
(e.ctrlKey && e.key === 'U') ||
|
|
(e.ctrlKey && e.shiftKey && e.key === 'J')
|
|
) {
|
|
e.preventDefault()
|
|
setWarningMessage('⚠️ Developer tools are not allowed during the exam')
|
|
setTimeout(() => setWarningMessage(''), 3000)
|
|
}
|
|
}
|
|
|
|
document.addEventListener('keydown', handleKeyDown)
|
|
|
|
// Detect if dev tools are open
|
|
let devtools = { open: false, orientation: null }
|
|
const threshold = 160
|
|
|
|
setInterval(() => {
|
|
if (
|
|
window.outerHeight - window.innerHeight > threshold ||
|
|
window.outerWidth - window.innerWidth > threshold
|
|
) {
|
|
if (!devtools.open) {
|
|
devtools.open = true
|
|
setWarningMessage('⚠️ Developer tools detected. Please close them.')
|
|
}
|
|
} else {
|
|
devtools.open = false
|
|
}
|
|
}, 500)
|
|
}
|
|
|
|
// ✅ ENTER FULLSCREEN
|
|
const enterFullScreen = async () => {
|
|
try {
|
|
const element = document.documentElement
|
|
|
|
if (element.requestFullscreen) {
|
|
await element.requestFullscreen()
|
|
} else if ((element as any).webkitRequestFullscreen) {
|
|
await (element as any).webkitRequestFullscreen()
|
|
} else if ((element as any).mozRequestFullScreen) {
|
|
await (element as any).mozRequestFullScreen()
|
|
} else if ((element as any).msRequestFullscreen) {
|
|
await (element as any).msRequestFullscreen()
|
|
}
|
|
} catch (error) {
|
|
setWarningMessage('❌ Failed to enter fullscreen. Please try again.')
|
|
}
|
|
}
|
|
|
|
// ✅ CHECK ALL SECURITY MEASURES
|
|
useEffect(() => {
|
|
const allChecksPassed = Object.values(securityChecks).every(check => check === true)
|
|
setSecurityPassed(allChecksPassed && agreementAccepted)
|
|
}, [securityChecks, agreementAccepted])
|
|
|
|
// ✅ PROCEED TO EXAM
|
|
const proceedToExam = () => {
|
|
if (securityPassed && isFullScreen) {
|
|
// Store security session
|
|
sessionStorage.setItem('exam_security_passed', 'true')
|
|
sessionStorage.setItem('exam_start_time', new Date().toISOString())
|
|
|
|
router.push(`/coding/exam/${examCode}`)
|
|
} else {
|
|
setWarningMessage('❌ Please complete all security requirements')
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-gray-900 text-white">
|
|
{/* Header */}
|
|
<div className="bg-gray-800 border-b border-gray-700 p-4">
|
|
<div className="max-w-4xl mx-auto">
|
|
<h1 className="text-2xl font-bold flex items-center space-x-2">
|
|
<Shield className="h-6 w-6 text-red-400" />
|
|
<span>Exam Security Check</span>
|
|
</h1>
|
|
<p className="text-gray-400">Exam Code: {examCode}</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Warning Message */}
|
|
{warningMessage && (
|
|
<div className="bg-red-900 border-b border-red-600 p-3 text-center">
|
|
<p className="text-red-300">{warningMessage}</p>
|
|
</div>
|
|
)}
|
|
|
|
<div className="max-w-4xl mx-auto p-8">
|
|
{/* Security Requirements */}
|
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
<h2 className="text-xl font-bold mb-6">Security Requirements</h2>
|
|
|
|
<div className="space-y-4">
|
|
{/* Fullscreen Check */}
|
|
<div className="flex items-center justify-between p-4 bg-gray-700 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<Maximize className="h-5 w-5 text-blue-400" />
|
|
<div>
|
|
<h3 className="font-medium">Fullscreen Mode</h3>
|
|
<p className="text-sm text-gray-400">Required for exam security</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center space-x-2">
|
|
{securityChecks.fullScreen ? (
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<button
|
|
onClick={enterFullScreen}
|
|
className="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded text-sm"
|
|
>
|
|
Enter Fullscreen
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* VM Detection */}
|
|
<div className="flex items-center justify-between p-4 bg-gray-700 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<Monitor className="h-5 w-5 text-purple-400" />
|
|
<div>
|
|
<h3 className="font-medium">System Verification</h3>
|
|
<p className="text-sm text-gray-400">Checking for virtual machines</p>
|
|
</div>
|
|
</div>
|
|
{securityChecks.noVirtualBox ? (
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<AlertTriangle className="h-5 w-5 text-yellow-400" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Copy/Paste Block */}
|
|
<div className="flex items-center justify-between p-4 bg-gray-700 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<Copy className="h-5 w-5 text-red-400" />
|
|
<div>
|
|
<h3 className="font-medium">Copy/Paste Protection</h3>
|
|
<p className="text-sm text-gray-400">Disabled for exam integrity</p>
|
|
</div>
|
|
</div>
|
|
{securityChecks.copyPasteBlocked ? (
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<AlertTriangle className="h-5 w-5 text-yellow-400" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Focus Detection */}
|
|
<div className="flex items-center justify-between p-4 bg-gray-700 rounded-lg">
|
|
<div className="flex items-center space-x-3">
|
|
<Eye className="h-5 w-5 text-green-400" />
|
|
<div>
|
|
<h3 className="font-medium">Focus Monitoring</h3>
|
|
<p className="text-sm text-gray-400">Tab switching will be tracked</p>
|
|
</div>
|
|
</div>
|
|
{securityChecks.focusDetection ? (
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
) : (
|
|
<AlertTriangle className="h-5 w-5 text-yellow-400" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Agreement */}
|
|
<div className="bg-gray-800 rounded-lg p-6 mb-8">
|
|
<h2 className="text-xl font-bold mb-4">Exam Agreement</h2>
|
|
|
|
<div className="bg-gray-900 p-4 rounded mb-4 max-h-40 overflow-y-auto">
|
|
<div className="text-sm text-gray-300 space-y-2">
|
|
<p>By proceeding with this exam, I agree to:</p>
|
|
<ul className="list-disc list-inside space-y-1 ml-4">
|
|
<li>Stay in fullscreen mode throughout the exam</li>
|
|
<li>Not switch tabs, windows, or applications</li>
|
|
<li>Not use copy/paste or external resources</li>
|
|
<li>Not use virtual machines or emulators</li>
|
|
<li>Not open developer tools or inspect elements</li>
|
|
<li>Accept monitoring of my focus and activity</li>
|
|
<li>Understand that violations may result in exam termination</li>
|
|
</ul>
|
|
<p className="text-yellow-400 font-medium mt-4">
|
|
Violations: {focusLostCount}/2 (3rd violation = automatic termination)
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<label className="flex items-center space-x-3">
|
|
<input
|
|
type="checkbox"
|
|
checked={agreementAccepted}
|
|
onChange={(e) => setAgreementAccepted(e.target.checked)}
|
|
className="rounded"
|
|
/>
|
|
<span>I have read and agree to all terms above</span>
|
|
</label>
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<div className="flex space-x-4">
|
|
<button
|
|
onClick={proceedToExam}
|
|
disabled={!securityPassed || !isFullScreen}
|
|
className="bg-green-600 hover:bg-green-700 disabled:bg-gray-600 disabled:cursor-not-allowed px-8 py-3 rounded-lg flex items-center space-x-2 font-medium"
|
|
>
|
|
<Lock className="h-5 w-5" />
|
|
<span>Start Secure Exam</span>
|
|
<ArrowRight className="h-5 w-5" />
|
|
</button>
|
|
|
|
<button
|
|
onClick={() => router.push('/coding')}
|
|
className="bg-gray-600 hover:bg-gray-700 px-8 py-3 rounded-lg"
|
|
>
|
|
Cancel
|
|
</button>
|
|
</div>
|
|
|
|
{/* Status */}
|
|
<div className="mt-6 p-4 bg-gray-800 rounded-lg">
|
|
<div className="flex items-center space-x-2">
|
|
{securityPassed ? (
|
|
<>
|
|
<CheckCircle className="h-5 w-5 text-green-400" />
|
|
<span className="text-green-400 font-medium">All security checks passed</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<AlertTriangle className="h-5 w-5 text-yellow-400" />
|
|
<span className="text-yellow-400 font-medium">
|
|
Complete all requirements to proceed
|
|
</span>
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|