Update: 2026-07-30 02:27:45

This commit is contained in:
Hamza-Ayed
2026-07-30 02:27:45 +03:00
parent 5f62455113
commit ca4a7c2e70
56 changed files with 3391 additions and 709 deletions
+29 -5
View File
@@ -40,7 +40,12 @@ if (!$user || !password_verify($password, $user['password_hash'])) {
}
$deviceId = $data['device_id'] ?? null;
$isReviewer = (strtolower($email) === 'reviewer@musadaq.jo');
// App-store reviewer account skips the WhatsApp OTP step (reviewers have no
// access to the registered phone). Configured via .env so the exception is not
// baked into the source, and disabled entirely when the var is unset.
$reviewerEmail = strtolower(trim((string)env('REVIEWER_EMAIL', '')));
$isReviewer = $reviewerEmail !== '' && strtolower($email) === $reviewerEmail;
if ($deviceId && !$isReviewer) {
// Generate and send WhatsApp OTP
@@ -127,7 +132,9 @@ if ($deviceId) {
$deviceName,
$data['platform'] ?? 'web',
$data['app_version'] ?? '1.0.0',
password_hash($deviceSecret, PASSWORD_DEFAULT),
// Stored encrypted, NOT bcrypt-hashed: the server must be able to
// recompute the client's HMAC signature from this same secret.
\App\Core\Encryption::encrypt($deviceSecret),
]);
}
@@ -152,11 +159,28 @@ $payload = [
$token = JWT::encode($payload, $secret);
// 5. Update Refresh Token (Hashed before storage for security)
// 5. Issue Refresh Token (hashed before storage).
//
// Mobile tokens are stored PER DEVICE. users.refresh_token_hash is a single
// column, so writing there logged the user out of every other device silently.
$refreshToken = bin2hex(random_bytes(32));
$refreshTokenHash = hash('sha256', $refreshToken);
$stmt = $db->prepare("UPDATE users SET refresh_token_hash = ?, last_login_at = NOW() WHERE id = ?");
$stmt->execute([$refreshTokenHash, $user['id']]);
$refreshTtlDays = $deviceId ? 60 : 7;
$refreshExpiresAt = date('Y-m-d H:i:s', time() + ($refreshTtlDays * 24 * 3600));
if ($deviceId) {
$stmt = $db->prepare("
UPDATE user_devices
SET refresh_token_hash = ?, refresh_expires_at = ?, last_seen_at = NOW()
WHERE user_id = ? AND device_fingerprint = ?
");
$stmt->execute([$refreshTokenHash, $refreshExpiresAt, $user['id'], $deviceId]);
$db->prepare("UPDATE users SET last_login_at = NOW() WHERE id = ?")->execute([$user['id']]);
} else {
$stmt = $db->prepare("UPDATE users SET refresh_token_hash = ?, last_login_at = NOW() WHERE id = ?");
$stmt->execute([$refreshTokenHash, $user['id']]);
}
// 6. Secure Refresh Token delivery via HttpOnly Cookie (for web)
if (!$deviceId) {