feat(ecosystem): full Grade 10 curriculum sync, real device fingerprinting, teacher video dashboard, and official textbook assets

This commit is contained in:
Hamza-Ayed
2026-09-11 16:46:40 +03:00
parent fc8f874bf9
commit d1bc39604b
404 changed files with 118547 additions and 1093 deletions
+64 -9
View File
@@ -191,7 +191,8 @@ class AuthController
return;
}
$fullName = trim((string)($body['full_name'] ?? ''));
$deviceFingerprint = trim((string)($body['device_fingerprint'] ?? 'browser_default'));
$deviceFingerprint = $this->resolveDeviceFingerprint($request, $body);
$platform = $this->resolveDevicePlatform($deviceFingerprint, $request);
$phoneHash = Security::blindIndex($cleanPhone);
@@ -328,10 +329,10 @@ class AuthController
// 3. Register / Update Device Fingerprint in user_devices
try {
Database::query(
"INSERT INTO user_devices (user_id, device_fingerprint, platform, is_active, last_active_at)
VALUES (?, ?, 'web', 1, NOW())
ON DUPLICATE KEY UPDATE last_active_at = NOW(), is_active = 1",
[$user['id'], $deviceFingerprint]
"INSERT INTO user_devices (user_id, identity_id, device_fingerprint, platform, is_active, last_active_at)
VALUES (?, ?, ?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE identity_id = VALUES(identity_id), platform = VALUES(platform), last_active_at = NOW(), is_active = 1",
[$user['id'], $identityId, $deviceFingerprint, $platform]
);
} catch (\Exception $e) {
error_log("Device recording notice: " . $e->getMessage());
@@ -675,10 +676,8 @@ class AuthController
$gradeLevel = \App\Services\StudentAccessControlService::normalizeGrade($rawGrade);
$stream = trim((string)($body['stream'] ?? 'scientific'));
$nationalId = trim((string)($body['national_id'] ?? ''));
$deviceFingerprint = trim((string)($request->getHeader('x-device-fingerprint', '')));
if ($deviceFingerprint === '') {
$deviceFingerprint = 'browser_default';
}
$deviceFingerprint = $this->resolveDeviceFingerprint($request, $body);
$platform = $this->resolveDevicePlatform($deviceFingerprint, $request);
if (empty($fullName) || empty($nationalId)) {
$response->status(400)->json(['status' => 'error', 'message' => 'الاسم الكامل والرقم الوطني مطلوبان']);
@@ -718,6 +717,17 @@ class AuthController
Database::insert("INSERT IGNORE INTO guardian_students (guardian_id, student_id) VALUES (?, ?)", [$guardian['id'], $sId]);
}
try {
Database::query(
"INSERT INTO user_devices (user_id, identity_id, device_fingerprint, platform, is_active, last_active_at)
VALUES (?, ?, ?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE identity_id = VALUES(identity_id), platform = VALUES(platform), last_active_at = NOW(), is_active = 1",
[$sId, $identityId, $deviceFingerprint, $platform]
);
} catch (\Exception $e) {
error_log("Device recording notice on student registration: " . $e->getMessage());
}
$this->generateSessionAndRespond(
$sId, $sUuid, 'student', $deviceFingerprint, $phone, $fullName, $response, 'تم استكمال التسجيل بنجاح',
$identityId,
@@ -872,4 +882,49 @@ class AuthController
return $value;
}
}
private function resolveDeviceFingerprint(Request $request, array $body): string
{
$fingerprint = trim((string)($body['device_fingerprint'] ?? ''));
if ($fingerprint === '') {
$fingerprint = trim((string)$request->getHeader('x-device-fingerprint', ''));
}
// Disallow synthetic mock strings in real authentication
$obsoleteMocks = [
'saqel_student_flutter',
'saqel_teacher_flutter',
'saqel_admin_flutter',
'saqel_super_admin_flutter',
];
if (in_array($fingerprint, $obsoleteMocks, true)) {
error_log("[Security Audit] Obsolete synthetic device fingerprint rejected: {$fingerprint}");
$fingerprint = '';
}
if ($fingerprint === '') {
$ip = $_SERVER['REMOTE_ADDR'] ?? '127.0.0.1';
$ua = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown_client';
$fingerprint = 'saqel_web_' . substr(hash('sha256', $ip . '|' . $ua), 0, 32);
}
return $fingerprint;
}
private function resolveDevicePlatform(string $fingerprint, Request $request): string
{
if (str_starts_with($fingerprint, 'saqel_android_')) return 'android';
if (str_starts_with($fingerprint, 'saqel_ios_')) return 'ios';
if (str_starts_with($fingerprint, 'saqel_macos_')) return 'macos';
if (str_starts_with($fingerprint, 'saqel_windows_')) return 'windows';
if (str_starts_with($fingerprint, 'saqel_linux_')) return 'linux';
if (str_starts_with($fingerprint, 'saqel_web_')) return 'web';
$headerPlatform = strtolower(trim((string)$request->getHeader('x-device-platform', '')));
if (in_array($headerPlatform, ['android', 'ios', 'macos', 'windows', 'linux', 'web'], true)) {
return $headerPlatform;
}
return 'other';
}
}