feat(ecosystem): full Grade 10 curriculum sync, real device fingerprinting, teacher video dashboard, and official textbook assets
This commit is contained in:
@@ -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';
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user