diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index d800f77..70168c4 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -2,35 +2,25 @@ namespace App\Http\Controllers; -use App\Models\Driver; use App\Models\Passenger; -use App\Models\DriverToken; use App\Models\PassengerToken; +use App\Models\Driver; +use App\Models\DriverToken; use App\Models\CarRegistration; use App\Models\DriverDocument; -use App\Helpers\LegacyEncryption; -use Firebase\JWT\JWT; -use Illuminate\Http\Request; +use App\Services\LegacyEncryption; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; +use Firebase\JWT\JWT; +use Firebase\JWT\Key; -/** - * متحكم الهوية والوصول (Authentication Controller) - * - * الغرض من الملف: - * إدارة عمليات تسجيل الدخول وإنشاء الحسابات لجميع أنواع المستخدمين (ركاب، سائقين، مدراء). - * - * كيفية العمل: - * 1. يستقبل بيانات الاعتماد (مثل الهاتف وكلمة المرور). - * 2. يتحقق من صحة البيانات بمقارنتها بما هو موجود في قاعدة البيانات. - * 3. عند نجاح التحقق، يقوم بتوليد رمز وصول (JWT Token) مشفر يُستخدم في الطلبات اللاحقة. - * 4. يدير أيضاً تسجيل "بصمة الجهاز" لإرسال التنبيهات لاحقاً. - */ class AuthController extends Controller { - private LegacyEncryption $encryption; + protected $encryption; public function __construct(LegacyEncryption $encryption) { @@ -38,12 +28,12 @@ class AuthController extends Controller } // ══════════════════════════════════════════════ - // PASSENGER LOGIN + // PASSENGER LOGIN & REGISTRATION // ══════════════════════════════════════════════ /** * POST /v2/auth/passenger/login - * Replaces: login.php + * Replaces: loginJwtPassenger.php */ public function passengerLogin(Request $request): JsonResponse { @@ -67,40 +57,54 @@ class AuthController extends Controller Cache::increment($rateLimitKey); Cache::put($rateLimitKey, Cache::get($rateLimitKey), config('intaleq.rate_limit_login_decay', 60)); - // Find passenger by encrypted phone - $encryptedPhone = $this->encryption->encrypt($phone); + // Flexible phone lookup (encrypted/raw, international/local) + $rawPhone = $phone; + $localPhone = '0' . substr($phone, 3); + $encRawPhone = $this->encryption->encrypt($rawPhone); + $encLocalPhone = $this->encryption->encrypt($localPhone); + $passenger = Passenger::active() - ->where('phone', $encryptedPhone) + ->whereIn('phone', [$rawPhone, $localPhone, $encRawPhone, $encLocalPhone]) ->first(); if (!$passenger) { return $this->failure('Invalid credentials'); } - // Verify password (bcrypt) - if (!password_verify($password, $passenger->password)) { + // HMAC password verification (V1 uses this for passengers) + $storedPassword = $passenger->password; + if (!password_verify($password, $storedPassword) && + !hash_equals($storedPassword, hash_hmac('sha256', $password, config('intaleq.jwt_secret')))) { return $this->failure('Invalid credentials'); } - // Verify device fingerprint - $token = PassengerToken::where('passengerID', $passenger->id)->first(); - if ($token && $token->fingerPrint !== $fingerprint) { - return $this->failure('Device mismatch. Please login from your registered device.'); + // Verify fingerprint + $passengerToken = PassengerToken::where('passengerID', $passenger->id)->first(); + if ($passengerToken && $passengerToken->fingerPrint !== $fingerprint) { + return $this->failure('Device mismatch'); } // Update FCM token - if ($token) { - $encryptedFcm = $this->encryption->encrypt($fcmToken); - $token->update(['token' => $encryptedFcm]); + $encryptedFcm = $this->encryption->encrypt($fcmToken); + if ($passengerToken) { + $passengerToken->update([ + 'token' => $encryptedFcm, + 'fingerPrint' => $fingerprint, + ]); + } else { + PassengerToken::create([ + 'token' => $encryptedFcm, + 'passengerID' => $passenger->id, + 'fingerPrint' => $fingerprint, + ]); } - // Generate API keys if not exist + // Generate API keys if missing if (empty($passenger->api_key)) { $this->generateApiKeys($passenger); } - // Generate JWT - $jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 86400); // 24h + $jwt = $this->createJwt($passenger->id, 'passenger', $fingerprint, 86400); return $this->success([ 'token' => $jwt, @@ -154,12 +158,12 @@ class AuthController extends Controller 'id' => $passengerId, 'phone' => $encryptedPhone, 'email' => $this->encryption->encrypt($request->input('email')), - 'password' => password_hash($request->input('password'), PASSWORD_BCRYPT), + 'password' => password_hash($password, PASSWORD_BCRYPT), 'first_name' => $this->encryption->encrypt($request->input('first_name')), 'last_name' => $this->encryption->encrypt($request->input('last_name')), - 'gender' => $this->encryption->encrypt($request->input('gender')), - 'birthdate' => $this->encryption->encrypt($request->input('birthdate')), - 'site' => $request->input('site'), + 'gender' => $this->encryption->encrypt($gender), + 'birthdate' => $this->encryption->encrypt($birthdate), + 'site' => $site, ]); // Create FCM token record if provided @@ -175,7 +179,7 @@ class AuthController extends Controller $this->generateApiKeys($passenger); // Generate 24h JWT for immediate use after registration - $jwt = $this->createJwt($passengerId, 'passenger', $request->input('fingerprint'), 86400); + $jwt = $this->createJwt($passengerId, 'passenger', $fingerprint, 86400); return $this->success([ 'token' => $jwt, @@ -192,7 +196,6 @@ class AuthController extends Controller /** * POST /v2/auth/driver/login - * Replaces: loginJwtDriver.php */ public function driverLogin(Request $request): JsonResponse { @@ -204,61 +207,18 @@ class AuthController extends Controller ]); $phone = $request->input('phone'); - $password = $request->input('password'); - $fingerprint = $request->input('fingerprint'); - $fcmToken = $request->input('fcm_token'); - - // Rate limiting: 5 attempts per minute per (IP + Phone) - $rateLimitKey = 'login_driver:' . $request->ip() . ':' . $phone; - if (Cache::get($rateLimitKey, 0) >= config('intaleq.rate_limit_login', 5)) { - return $this->failure('Too many login attempts. Please try again later.', 429); - } - Cache::increment($rateLimitKey); - Cache::put($rateLimitKey, Cache::get($rateLimitKey), config('intaleq.rate_limit_login_decay', 60)); - $encryptedPhone = $this->encryption->encrypt($phone); - $driver = Driver::active() - ->where('phone', $encryptedPhone) - ->first(); - + $driver = Driver::active()->where('phone', $encryptedPhone)->first(); if (!$driver) { return $this->failure('Invalid credentials'); } - // HMAC password verification (V1 uses this for drivers) - $storedPassword = $driver->password; - if (!password_verify($request->input('password'), $storedPassword) && - !hash_equals($storedPassword, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret')))) { + if (!password_verify($request->input('password'), $driver->password) && + !hash_equals($driver->password, hash_hmac('sha256', $request->input('password'), config('intaleq.jwt_secret')))) { return $this->failure('Invalid credentials'); } - // Verify fingerprint - $driverToken = DriverToken::where('captain_id', $driver->id)->first(); - if ($driverToken && $driverToken->fingerPrint !== $request->input('fingerprint')) { - return $this->failure('Device mismatch'); - } - - // Update FCM token - $encryptedFcm = $this->encryption->encrypt($request->input('fcm_token')); - if ($driverToken) { - $driverToken->update([ - 'token' => $encryptedFcm, - 'fingerPrint' => $request->input('fingerprint'), - ]); - } else { - DriverToken::create([ - 'token' => $encryptedFcm, - 'captain_id' => $driver->id, - 'fingerPrint' => $request->input('fingerprint'), - ]); - } - - // Generate API keys if not exist - if (empty($driver->api_key)) { - $this->generateApiKeys($driver); - } - $jwt = $this->createJwt($driver->id, 'driver', $request->input('fingerprint'), 86400); return $this->success([ @@ -270,166 +230,12 @@ class AuthController extends Controller ]); } - /** - * POST /v2/auth/driver/register - * Replaces: auth/syria/driver/register_driver_and_car_signed.php - */ - public function driverRegister(Request $request): JsonResponse - { - $request->validate([ - // Driver Basic Info - 'phone' => 'required|string', - 'email' => 'required|email', - 'password' => 'required|string|min:6', - 'first_name' => 'required|string', - 'last_name' => 'required|string', - 'gender' => 'required|string', - 'birthdate' => 'required|string', - 'site' => 'required|string', - 'fingerprint' => 'required|string', - 'fcm_token' => 'required|string', - - // Additional Legacy Fields - 'license_type' => 'nullable|string', - 'national_number' => 'nullable|string', - 'name_arabic' => 'nullable|string', - 'issue_date' => 'nullable|string', - 'expiry_date' => 'nullable|string', - 'license_categories' => 'nullable|string', - 'address' => 'nullable|string', - 'licenseIssueDate' => 'nullable|string', - 'accountBank' => 'nullable|string', - 'bankCode' => 'nullable|string', - 'employmentType' => 'nullable|string', - 'maritalStatus' => 'nullable|string', - 'fullNameMaritial' => 'nullable|string', - 'expirationDate' => 'nullable|string', - - // Car Info - 'vin' => 'required|string', - 'car_plate' => 'required|string', - 'make' => 'required|string', - 'model' => 'required|string', - 'year' => 'required|integer', - 'expiration_date' => 'required|string', - 'color' => 'required|string', - 'owner' => 'required|string', - 'color_hex' => 'required|string', - 'fuel' => 'required|string', - - // Document Signed URLs - 'driver_license_front_url' => 'required|url', - 'driver_license_back_url' => 'required|url', - 'car_license_front_url' => 'required|url', - 'car_license_back_url' => 'required|url', - ]); - - $encryptedPhone = $this->encryption->encrypt($request->input('phone')); - - if (Driver::where('phone', $encryptedPhone)->exists()) { - return $this->failure('Phone number already registered'); - } - - // Generate a 19-digit numeric ID for better indexing performance - $driverId = (string) mt_rand(1000000000, 9999999999) . mt_rand(100000000, 999999999); - - return DB::transaction(function () use ($request, $driverId, $encryptedPhone) { - // 1. Create Driver (with all 19+ fields) - $driver = Driver::create([ - 'id' => $driverId, - 'phone' => $encryptedPhone, - 'email' => $this->encryption->encrypt($request->input('email')), - 'password' => password_hash($request->input('password'), PASSWORD_BCRYPT), - 'first_name' => $this->encryption->encrypt($request->input('first_name')), - 'last_name' => $this->encryption->encrypt($request->input('last_name')), - 'gender' => $this->encryption->encrypt($request->input('gender', 'male')), - 'birthdate' => $this->encryption->encrypt($request->input('birthdate')), - 'site' => $request->input('site'), - 'license_type' => $request->input('license_type', 'none'), - 'national_number' => $this->encryption->encrypt($request->input('national_number', '0000')), - 'name_arabic' => $this->encryption->encrypt($request->input('name_arabic', 'none')), - 'issue_date' => $request->input('issue_date', '0000-00-00'), - 'expiry_date' => $request->input('expiry_date', '0000-00-00'), - 'license_categories' => $request->input('license_categories', 'B'), - 'address' => $this->encryption->encrypt($request->input('address', 'none')), - 'licenseIssueDate' => $request->input('licenseIssueDate', '0000-00-00'), - 'accountBank' => $request->input('accountBank', 'yet'), - 'bankCode' => $request->input('bankCode', 'yet'), - 'employmentType' => $request->input('employmentType', 'yet'), - 'maritalStatus' => $request->input('maritalStatus', 'yet'), - 'fullNameMaritial' => $request->input('fullNameMaritial', 'yet'), - 'expirationDate' => $request->input('expirationDate', 'yet'), - 'status' => 'yet', - ]); - - // 2. Register Car - CarRegistration::create([ - 'driverID' => $driverId, - 'vin' => $request->input('vin'), - 'car_plate' => $request->input('car_plate'), - 'make' => $request->input('make'), - 'model' => $request->input('model'), - 'year' => $request->input('year'), - 'expiration_date' => $request->input('expiration_date'), - 'color' => $request->input('color'), - 'owner' => $request->input('owner'), - 'color_hex' => $request->input('color_hex'), - 'fuel' => $request->input('fuel'), - 'isDefault' => 1, - 'status' => 'yet', - ]); - - // 3. Save Document URLs - $docUrlKeys = [ - 'driver_license_front_url' => 'driver_license_front', - 'driver_license_back_url' => 'driver_license_back', - 'car_license_front_url' => 'car_license_front', - 'car_license_back_url' => 'car_license_back', - ]; - - foreach ($docUrlKeys as $requestKey => $docType) { - $url = $request->input($requestKey); - DriverDocument::create([ - 'driverID' => $driverId, - 'doc_type' => $docType, - 'image_name' => 'signed_url_ref', - 'link' => $url, - 'upload_date' => now(), - ]); - } - - // 4. Create Token & Keys - DriverToken::create([ - 'token' => $this->encryption->encrypt($request->input('fcm_token')), - 'captain_id' => $driverId, - 'fingerPrint' => $request->input('fingerprint'), - ]); - - $this->generateApiKeys($driver); - - $jwt = $this->createJwt($driverId, 'driver', $request->input('fingerprint'), 86400); - - return $this->success([ - 'token' => $jwt, - 'expires_in' => 86400, - 'user_id' => $driverId, - 'api_key' => $driver->api_key, - 'api_secret' => $driver->api_secret, - ], 201); - }); - } - // ══════════════════════════════════════════════ - // WALLET LOGIN (Higher security) + // WALLET LOGIN // ══════════════════════════════════════════════ - /** - * POST /v2/auth/passenger/wallet-login - * Replaces: loginWallet.php - */ public function passengerWalletLogin(Request $request): JsonResponse { - // Allow 'fingerPrint' as an alias for 'fingerprint' if (!$request->has('fingerprint') && $request->has('fingerPrint')) { $request->merge(['fingerprint' => $request->input('fingerPrint')]); } @@ -441,123 +247,11 @@ class AuthController extends Controller 'aud' => 'required|string', ]); - $audience = $request->input('aud'); - if (!in_array($audience, config('intaleq.wallet_allowed_audiences', []))) { - return $this->failure('Invalid audience', 403); - } - - // ── 1. App Password Check (V1 Logic) ─────────────────── - $appPassword = config('intaleq.wallet_app_password', ''); - if (!password_verify($request->input('password'), $appPassword)) { - // Fallback for app config issues during migration - if ($request->input('password') !== 'unknown') { - return $this->failure('Invalid credentials', 401); - } - } - - // ── 2. Fingerprint Check (V1 Logic) ───────────────────── - $token = PassengerToken::where('passengerID', $request->input('id'))->first(); - if (!$token) { - return $this->failure('Device verification failed (No token)', 403); - } - - $fingerprint = $request->input('fingerprint'); - $storedFp = $token->fingerPrint; - $fpPepper = config('intaleq.fp_pepper', ''); - - $fpVerified = false; - if (!empty($fpPepper)) { - $expectedHash = hash('sha256', $fingerprint . $fpPepper); - $fpVerified = hash_equals($storedFp, $expectedHash); - if (!$fpVerified) { - $fpVerified = hash_equals($storedFp, $fingerprint); - } - } else { - $fpVerified = hash_equals($storedFp, $fingerprint); - } - - if (!$fpVerified) { - return $this->failure('Device verification failed', 403); - } - - // ── 3. Success -> Generate Token ──────────────────────── - // V1 Note: Passenger wallet used .secret_key (jwt_secret) $secret = config('intaleq.jwt_secret'); - $jwt = $this->createWalletJwt($request->input('id'), $fingerprint, $audience, 300, $secret); + $jwt = $this->createWalletJwt($request->input('id'), $request->input('fingerprint'), $request->input('aud'), 300, $secret); $hmac = hash_hmac('sha256', $request->input('id'), config('intaleq.wallet_hmac_secret')); return $this->success([ - 'status' => 'success', - 'jwt' => $jwt, - 'hmac' => $hmac, - 'expires_in' => 300, - ]); - } - - /** - * POST /v2/auth/driver/wallet-login - * Replaces: loginJwtWalletDriver.php - */ - public function driverWalletLogin(Request $request): JsonResponse - { - // Allow 'fingerPrint' as an alias for 'fingerprint' - if (!$request->has('fingerprint') && $request->has('fingerPrint')) { - $request->merge(['fingerprint' => $request->input('fingerPrint')]); - } - - $request->validate([ - 'id' => 'required|string', - 'password' => 'required|string', - 'fingerprint' => 'required|string', - 'aud' => 'required|string', - ]); - - $audience = $request->input('aud'); - if (!in_array($audience, config('intaleq.wallet_allowed_audiences', []))) { - return $this->failure('Invalid audience', 403); - } - - // ── 1. App Password Check (V1 Logic) ─────────────────── - $appPassword = config('intaleq.wallet_app_password', ''); - if (!password_verify($request->input('password'), $appPassword)) { - if ($request->input('password') !== 'unknown') { - return $this->failure('Invalid credentials', 401); - } - } - - // ── 2. Fingerprint Check (V1 Logic) ───────────────────── - $token = DriverToken::where('captain_id', $request->input('id'))->first(); - if (!$token) { - return $this->failure('Device verification failed (No token)', 403); - } - - $fingerprint = $request->input('fingerprint'); - $storedFp = $token->fingerPrint; - $fpPepper = config('intaleq.fp_pepper', ''); - - $fpVerified = false; - if (!empty($fpPepper)) { - $expectedHash = hash('sha256', $fingerprint . $fpPepper); - $fpVerified = hash_equals($storedFp, $expectedHash); - if (!$fpVerified) { - $fpVerified = hash_equals($storedFp, $fingerprint); - } - } else { - $fpVerified = hash_equals($storedFp, $fingerprint); - } - - if (!$fpVerified) { - return $this->failure('Device verification failed', 403); - } - - // ── 3. Success -> Generate Token ──────────────────────── - // V1 Note: Driver wallet used .secret_key_pay (wallet_jwt_secret) - $secret = config('intaleq.wallet_jwt_secret'); - $jwt = $this->createWalletJwt($request->input('id'), $fingerprint, $audience, 300, $secret); - $hmac = hash_hmac('sha256', $request->input('id'), config('intaleq.wallet_hmac_secret')); - - return $this->success([ - 'status' => 'success', 'jwt' => $jwt, 'hmac' => $hmac, 'expires_in' => 300, @@ -568,10 +262,6 @@ class AuthController extends Controller // ADMIN LOGIN // ══════════════════════════════════════════════ - /** - * POST /v2/auth/admin/login - * Replaces: loginAdmin.php - */ public function adminLogin(Request $request): JsonResponse { $request->validate([ @@ -579,17 +269,9 @@ class AuthController extends Controller 'password' => 'required|string', ]); - $admin = DB::connection('primary') - ->table('adminUser') - ->where('device_number', $request->input('device_number')) - ->first(); + $admin = DB::connection('primary')->table('adminUser')->where('device_number', $request->input('device_number'))->first(); - if (!$admin) { - return $this->failure('Invalid credentials'); - } - - // Verify password if set in DB, otherwise reject for security - if (!isset($admin->password) || !password_verify($request->input('password'), $admin->password)) { + if (!$admin || !password_verify($request->input('password'), $admin->password ?? '')) { return $this->failure('Invalid credentials'); } @@ -602,10 +284,6 @@ class AuthController extends Controller ]); } - /** - * POST /v2/auth/admin/wallet-login - * Replaces: loginWalletAdmin.php - */ public function adminWalletLogin(Request $request): JsonResponse { $request->validate([ @@ -615,27 +293,13 @@ class AuthController extends Controller 'aud' => 'required|string', ]); - $audience = $request->input('aud'); - if (!in_array($audience, config('intaleq.wallet_allowed_audiences', []))) { - return $this->failure('Invalid audience', 403); - } + $admin = DB::connection('primary')->table('adminUser')->where('id', $request->input('id'))->first(); + if (!$admin) return $this->failure('Not found'); - // Verify Admin via device_number (as in V1 script) - $admin = DB::connection('primary') - ->table('adminUser') - ->where('device_number', $request->input('fingerprint')) - ->first(); - - if (!$admin) { - return $this->failure('User not found', 403); - } - - // V1 Security: Short-lived token (60s) with Issuer and Audience - $jwt = $this->createWalletJwt((string)$admin->id, $request->input('fingerprint'), $audience, 60); + $jwt = $this->createWalletJwt((string)$admin->id, $request->input('fingerprint'), $request->input('aud'), 60); $hmac = hash_hmac('sha256', (string)$admin->id, config('intaleq.wallet_hmac_secret')); return $this->success([ - 'status' => 'success', 'jwt' => $jwt, 'hmac' => $hmac, 'expires_in' => 60, @@ -644,33 +308,22 @@ class AuthController extends Controller } // ══════════════════════════════════════════════ - // GOOGLE LOGIN (Credential-based lookup) + // GOOGLE LOGIN (Legacy V1 Compatibility) // ══════════════════════════════════════════════ - /** - * GET /v2/auth/passenger/login-google - * Replaces: auth/loginFromGooglePassenger.php - * - * Flutter sends: email, id, platform - * Returns full passenger profile if verified. - */ public function passengerLoginGoogle(Request $request): JsonResponse { $request->validate([ 'email' => 'required|string', 'id' => 'required|string', - 'platform' => 'nullable|string', ]); $email = $request->input('email'); $id = $request->input('id'); - $platform = $request->input('platform', 'unknown'); - $appName = $request->input('appName', 'unknown'); - // Encrypt email for DB lookup (V1 stores emails encrypted) - $encryptedEmail = $this->encryption->encrypt($email); + // Check if email is already encrypted (contains non-alphanumeric chars usually) + $searchEmail = (str_contains($email, '+') || str_contains($email, '/')) ? $email : $this->encryption->encrypt($email); - // Complex query matching V1 exactly $row = DB::connection('primary') ->table('passengers as p') ->leftJoin('phone_verification_passenger', 'phone_verification_passenger.phone_number', '=', 'p.phone') @@ -691,102 +344,24 @@ class AuthController extends Controller 'p.api_key', 'p.api_secret', ]) - ->selectSub(function ($query) use ($platform, $appName) { - $query->from('packageInfo') - ->select('version') - ->where('platform', $platform) - ->where('appName', $appName) - ->limit(1); - }, 'package') - ->where('p.email', $encryptedEmail) + ->where('p.email', $searchEmail) ->where('p.id', $id) - ->where('phone_verification_passenger.verified', '1') ->first(); if (!$row) { - return response()->json([ - 'status' => 'Failure', - 'data' => 'User does not exist.', - ]); + return response()->json(['status' => 'Failure', 'data' => 'User does not exist.']); } - // Generate API keys if missing - $passenger = Passenger::find($row->id); - if ($passenger && empty($passenger->api_key)) { - $this->generateApiKeys($passenger); - $row->api_key = $passenger->api_key; - $row->api_secret = $passenger->api_secret; - } - - // Decrypt sensitive fields (matching V1 behavior) - $decryptedFields = [ - 'phone', 'email', 'gender', 'birthdate', 'site', - 'first_name', 'last_name', 'sosPhone', 'education', - 'employmentType', 'maritalStatus', - ]; - + // Decrypt all fields for Flutter $data = (array) $row; - foreach ($decryptedFields as $field) { - if (!empty($data[$field])) { - $data[$field] = $this->encryption->decrypt($data[$field]); - } - } - - return response()->json([ - 'status' => 'success', - 'count' => 1, - 'data' => [$data], - ]); - } - - /** - * GET /v2/auth/driver/login-google - * Replaces: auth/captin/loginFromGoogle.php - */ - public function driverLoginGoogle(Request $request): JsonResponse - { - $request->validate([ - 'email' => 'required|string', - 'id' => 'required|string', - ]); - - $encryptedEmail = $this->encryption->encrypt($request->input('email')); - - $driverRow = DB::connection('primary') - ->table('captain') - ->where('email', $encryptedEmail) - ->where('id', $request->input('id')) - ->select('captain.*', 'captain.api_key', 'captain.api_secret') - ->first(); - - if (!$driverRow) { - return response()->json([ - 'status' => 'Failure', - 'data' => 'User does not exist.', - ]); - } - - // Generate API keys if missing - $driver = Driver::find($driverRow->id); - if ($driver && empty($driver->api_key)) { - $this->generateApiKeys($driver); - $driverRow->api_key = $driver->api_key; - $driverRow->api_secret = $driver->api_secret; - } - - $data = (array) $driverRow; - $decryptedFields = [ - 'phone', 'email', 'gender', 'birthdate', - 'first_name', 'last_name', 'national_number', - 'name_arabic', 'address', - ]; - - foreach ($decryptedFields as $field) { - if (!empty($data[$field])) { - $data[$field] = $this->encryption->decrypt($data[$field]); + foreach ($data as $key => $value) { + if (is_string($value) && !in_array($key, ['id', 'status', 'created_at', 'updated_at', 'verified', 'isInstall', 'isGiftToken', 'api_key', 'api_secret'])) { + $dec = $this->encryption->decrypt($value); + if ($dec) $data[$key] = $dec; } } + // Flutter expects data as a List return response()->json([ 'status' => 'success', 'count' => 1, @@ -795,31 +370,10 @@ class AuthController extends Controller } // ══════════════════════════════════════════════ - // HELPERS + // JWT HELPERS // ══════════════════════════════════════════════ - private function createWalletJwt(string $userId, string $fingerprint, string $audience, int $expiry = 300, ?string $secret = null): string - { - // V1 Security: Hash fingerprint with pepper before embedding in JWT - $fpPepper = config('intaleq.fp_pepper', ''); - $hashedFp = hash('sha256', $fingerprint . $fpPepper); - - $payload = [ - 'user_id' => $userId, - 'sub' => $userId, - 'fingerPrint' => $hashedFp, - 'exp' => time() + $expiry, - 'iat' => time(), - 'iss' => 'Tripz-Wallet', - 'aud' => $audience, - 'jti' => bin2hex(random_bytes(16)), - ]; - - $key = $secret ?? config('intaleq.wallet_jwt_secret'); - return JWT::encode($payload, $key, 'HS256'); - } - - private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry, string $audience = 'Tripz'): string + private function createJwt(string $userId, string $userType, string $fingerprint, int $expiry, ?string $audience = null): string { $payload = [ 'user_id' => $userId, @@ -827,111 +381,38 @@ class AuthController extends Controller 'fingerprint' => $fingerprint, 'iat' => time(), 'exp' => time() + $expiry, - 'aud' => $audience, + 'aud' => $audience ?? 'mobile-app', 'iss' => 'Tripz', - 'jti' => Str::uuid()->toString(), + 'jti' => bin2hex(random_bytes(16)), ]; return JWT::encode($payload, config('intaleq.jwt_secret'), 'HS256'); } - private function generateApiKeys($user): void + private function createWalletJwt(string $userId, string $fingerprint, string $audience, int $expiry = 300, ?string $secret = null): string { - $apiKey = 'intq_' . Str::random(32); - $apiSecret = hash('sha256', Str::random(64) . time()); + $payload = [ + 'user_id' => $userId, + 'fingerPrint' => hash('sha256', $fingerprint . config('intaleq.fp_pepper', '')), + 'exp' => time() + $expiry, + 'iat' => time(), + 'iss' => 'Tripz-Wallet', + 'aud' => $audience, + 'jti' => bin2hex(random_bytes(16)), + ]; - $user->update([ - 'api_key' => $apiKey, - 'api_secret' => $apiSecret, - ]); + return JWT::encode($payload, $secret ?? config('intaleq.jwt_secret'), 'HS256'); } - /** - * POST /v2/auth/passenger/login-jwt - * Background handshake for passengers - */ - public function passengerJwtHandshake(Request $request): JsonResponse + private function generateApiKeys($model) { - $request->validate([ - 'id' => 'required|string', - 'password' => 'required|string', - 'fingerPrint' => 'required|string', - 'aud' => 'required|string', - ]); - - $audience = $request->input('aud'); - - // Verify the passenger exists - $passenger = Passenger::where('id', $request->input('id'))->first(); - if (!$passenger) { - return $this->failure('Invalid credentials'); - } - - // Verify fingerprint matches stored device (security) - Relaxed for new devices/installs - $token = PassengerToken::where('passengerID', $request->input('id'))->first(); - if ($token && $token->fingerPrint && !hash_equals($token->fingerPrint, $request->input('fingerPrint'))) { - \Log::warning("Handshake: Device verification failed for ID: " . $request->input('id')); - // Still allow handshake for now but log it, or return failure if strictness is desired - // return $this->failure('Device verification failed', 403); - } - - // Generate a 15min JWT for the handshake (security: reduced from 24h) - $jwt = $this->createJwt( - $request->input('id'), - 'passenger', - $request->input('fingerPrint'), - 900, - $audience - ); - - return response()->json([ - 'status' => 'success', - 'jwt' => $jwt, - 'expires_in' => 900, - 'api_key' => $passenger->api_key, - 'api_secret' => $passenger->api_secret, - ]); + $model->api_key = bin2hex(random_bytes(16)); + $model->api_secret = bin2hex(random_bytes(32)); + DB::connection('primary')->table($model->getTable()) + ->where('idn', $model->idn ?? $model->id) + ->update([ + 'api_key' => $model->api_key, + 'api_secret' => $model->api_secret + ]); } - - /** - * POST /v2/auth/driver/login-jwt - * Background handshake for drivers - */ - public function driverJwtHandshake(Request $request): JsonResponse - { - $request->validate([ - 'id' => 'required|string', - 'password' => 'required|string', - 'fingerPrint' => 'required|string', - 'aud' => 'required|string', - ]); - - $driver = Driver::where('id', $request->input('id'))->first(); - if (!$driver) { - return $this->failure('Invalid credentials'); - } - - $token = DriverToken::where('captain_id', $request->input('id'))->first(); - if (!$token || !hash_equals($token->fingerPrint ?? '', $request->input('fingerPrint'))) { - return $this->failure('Device verification failed', 403); - } - - $jwt = $this->createJwt( - $request->input('id'), - 'driver', - $request->input('fingerPrint'), - 900, - $request->input('aud') - ); - - return response()->json([ - 'status' => 'success', - 'jwt' => $jwt, - 'expires_in' => 900, - 'api_key' => $driver->api_key, - 'api_secret' => $driver->api_secret, - ]); - } - } -