Update: 2026-05-13 22:58:30
This commit is contained in:
@@ -39,38 +39,77 @@ if (!$user || !password_verify($password, $user['password_hash'])) {
|
||||
json_error('بيانات الدخول غير صحيحة', 401);
|
||||
}
|
||||
|
||||
// 3. Issue Token
|
||||
// 3. Handle device registration if provided (for mobile app login)
|
||||
$deviceId = $data['device_id'] ?? null;
|
||||
$deviceName = $data['device_name'] ?? 'Web Browser';
|
||||
$deviceSecret = null;
|
||||
|
||||
if ($deviceId) {
|
||||
$deviceSecret = hash('sha256', $user['id'] . $deviceId . bin2hex(random_bytes(16)));
|
||||
$stmt = $db->prepare("
|
||||
INSERT INTO user_devices (id, user_id, device_fingerprint, device_name, platform, app_version, device_secret, is_trusted, last_seen_at)
|
||||
VALUES (UUID(), ?, ?, ?, ?, ?, ?, TRUE, NOW())
|
||||
ON DUPLICATE KEY UPDATE
|
||||
device_name = VALUES(device_name),
|
||||
platform = VALUES(platform),
|
||||
app_version = VALUES(app_version),
|
||||
device_secret = VALUES(device_secret),
|
||||
is_trusted = TRUE,
|
||||
last_seen_at = NOW(),
|
||||
updated_at = NOW()
|
||||
");
|
||||
$stmt->execute([
|
||||
$user['id'],
|
||||
$deviceId,
|
||||
$deviceName,
|
||||
$data['platform'] ?? 'web',
|
||||
$data['app_version'] ?? '1.0.0',
|
||||
password_hash($deviceSecret, PASSWORD_DEFAULT),
|
||||
]);
|
||||
}
|
||||
|
||||
// 4. Issue Token
|
||||
$secret = env('JWT_SECRET');
|
||||
if (!$secret || strlen($secret) < 32) {
|
||||
error_log('FATAL: JWT_SECRET is missing or too short in .env');
|
||||
json_error('Server configuration error', 500);
|
||||
}
|
||||
|
||||
// Longer expiry for mobile (30 days), short for web (15 mins)
|
||||
$expiry = $deviceId ? (30 * 24 * 3600) : (15 * 60);
|
||||
|
||||
$payload = [
|
||||
'user_id' => $user['id'],
|
||||
'tenant_id' => $user['tenant_id'],
|
||||
'role' => $user['role'],
|
||||
'exp' => time() + (15 * 60) // 15 minutes
|
||||
'device_id' => $deviceId,
|
||||
'source' => $deviceId ? 'mobile' : 'web',
|
||||
'exp' => time() + $expiry
|
||||
];
|
||||
|
||||
$token = JWT::encode($payload, $secret);
|
||||
|
||||
// 4. Update Refresh Token (Hashed before storage for security)
|
||||
// 5. Update Refresh Token (Hashed before storage for security)
|
||||
$refreshToken = bin2hex(random_bytes(32));
|
||||
$refreshTokenHash = hash('sha256', $refreshToken);
|
||||
$stmt = $db->prepare("UPDATE users SET refresh_token_hash = ? WHERE id = ?");
|
||||
$stmt = $db->prepare("UPDATE users SET refresh_token_hash = ?, last_login_at = NOW() WHERE id = ?");
|
||||
$stmt->execute([$refreshTokenHash, $user['id']]);
|
||||
|
||||
// 7. Secure Refresh Token delivery via HttpOnly Cookie
|
||||
setcookie('refresh_token', $refreshToken, [
|
||||
'expires' => time() + (7 * 24 * 60 * 60), // 7 days
|
||||
'path' => '/api/v1/auth/refresh',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
// 6. Secure Refresh Token delivery via HttpOnly Cookie (for web)
|
||||
if (!$deviceId) {
|
||||
setcookie('refresh_token', $refreshToken, [
|
||||
'expires' => time() + (7 * 24 * 60 * 60), // 7 days
|
||||
'path' => '/api/v1/auth/refresh',
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Strict',
|
||||
]);
|
||||
}
|
||||
|
||||
json_success([
|
||||
'access_token' => $token,
|
||||
'refresh_token' => $refreshToken,
|
||||
'device_secret' => $deviceSecret,
|
||||
'user' => [
|
||||
'id' => $user['id'],
|
||||
'name' => (App\Core\Encryption::decrypt($user['name']) ?: $user['name']),
|
||||
|
||||
Reference in New Issue
Block a user