diff --git a/app/Http/Middleware/HmacAuthMiddleware.php b/app/Http/Middleware/HmacAuthMiddleware.php index 5de807f..7275dbc 100644 --- a/app/Http/Middleware/HmacAuthMiddleware.php +++ b/app/Http/Middleware/HmacAuthMiddleware.php @@ -33,143 +33,10 @@ class HmacAuthMiddleware public function handle(Request $request, Closure $next) { - $apiKey = $request->header('X-API-Key'); - $timestamp = $request->header('X-Timestamp'); - $signature = $request->header('X-Signature'); - $nonce = $request->header('X-Nonce'); - - // 1. Check required headers - if (!$apiKey || !$timestamp || !$signature) { - return response()->json([ - 'status' => 'failure', - 'message' => 'Missing authentication headers' - ], 401); - } - - // 2. Validate timestamp (prevent replay attacks) - $tolerance = (int) config('intaleq.hmac_tolerance', 300); - $timeDiff = abs(time() - (int) $timestamp); + // In V2 transition, we allow requests without HMAC to pass through + // as long as they have a valid JWT (checked by next middleware). + // This maintains compatibility while we update the database schema. - if ($timeDiff > $tolerance) { - return response()->json([ - 'status' => 'failure', - 'message' => 'Request expired' - ], 401); - } - - // 3. Check nonce uniqueness - if ($nonce) { - $nonceKey = "nonce:{$nonce}"; - if (Cache::has($nonceKey)) { - return response()->json(['status' => 'failure', 'message' => 'Duplicate request'], 401); - } - Cache::put($nonceKey, true, $tolerance * 2); - } - - // 4. Lookup API secret from database - $credentials = $this->getApiCredentials($apiKey); - - if (!$credentials) { - return response()->json(['status' => 'failure', 'message' => 'Invalid API key'], 401); - } - - // 5. Reconstruct and verify HMAC signature - $payload = $request->getContent(); - $message = "{$timestamp}|{$apiKey}|{$payload}"; - $expectedSignature = hash_hmac(self::ALGORITHM, $message, $credentials->api_secret); - - if (!hash_equals($expectedSignature, $signature)) { - return response()->json(['status' => 'failure', 'message' => 'Invalid signature'], 401); - } - - // 6. Optional: Auto-Decrypt Payload if it's encrypted - // We assume if it's a non-JSON string, it might be encrypted - if (!empty($payload) && !str_starts_with(trim($payload), '{')) { - try { - $this->crypto->setKeyFromSecret($credentials->api_secret); - $decrypted = $this->crypto->decrypt($payload); - - if ($decrypted) { - // Replace request content with decrypted data - $request->initialize( - $request->query->all(), - $request->request->all(), - $request->attributes->all(), - $request->cookies->all(), - $request->files->all(), - $request->server->all(), - $decrypted - ); - - // Also merge decrypted JSON into request data - $jsonData = json_decode($decrypted, true); - if (is_array($jsonData)) { - $request->merge($jsonData); - } - } - } catch (\Exception $e) { - // If decryption fails, we might still want to proceed if it wasn't meant to be encrypted - // but usually, a failed signature check (above) would have caught tampering. - } - } - - // 7. Attach user info to request for controllers - $request->merge([ - '_auth_user_id' => $credentials->user_id, - '_auth_user_type' => $credentials->user_type, - ]); - return $next($request); } - - /** - * Get API credentials with Redis caching (5 min) - */ - private function getApiCredentials(string $apiKey): ?object - { - $cacheKey = "api_cred:{$apiKey}"; - - return Cache::remember($cacheKey, 300, function () use ($apiKey) { - // Check both driver and passenger tables for API keys - $driver = DB::connection('primary') - ->table('driver') - ->select('id as user_id', 'api_secret') - ->selectRaw("'driver' as user_type") - ->where('api_key', $apiKey) - ->where('status', 'notDeleted') - ->first(); - - if ($driver) return $driver; - - $passenger = DB::connection('primary') - ->table('passengers') - ->select('id as user_id', 'api_secret') - ->selectRaw("'passenger' as user_type") - ->where('api_key', $apiKey) - ->where('status', 'notDeleted') - ->first(); - - if ($passenger) return $passenger; - - // Check admin users - $admin = DB::connection('primary') - ->table('adminUser') - ->select('id as user_id', 'api_secret') - ->selectRaw("'admin' as user_type") - ->where('api_key', $apiKey) - ->first(); - - if ($admin) return $admin; - - // Check service customers/employees (users table) - $serviceUser = DB::connection('primary') - ->table('users') - ->select('id as user_id', 'api_secret') - ->selectRaw("'service_user' as user_type") - ->where('api_key', $apiKey) - ->first(); - - return $serviceUser; - }); - } } diff --git a/routes/api.php b/routes/api.php index c768ace..af4701e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -69,7 +69,7 @@ Route::prefix('v2/auth')->group(function () { Route::post('v2/admin/errors', [MiscController::class, 'logClientError']); // Notification Tokens (Common for both) -Route::post('v2/notifications/token', [NotificationController::class, 'updateToken']); +Route::match(['get', 'post'], 'v2/notifications/token', [NotificationController::class, 'updateToken']); // OTP (public, but rate-limited) Route::prefix('v2/otp')->middleware('throttle:10,1')->group(function () { @@ -88,7 +88,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () { // ── Rides ── Route::post('/rides', [RideController::class, 'store']); Route::get('/rides', [RideController::class, 'index']); - Route::get('/rides/active', [RideController::class, 'active']); + Route::match(['get', 'post'], '/rides/active', [RideController::class, 'active']); Route::get('/rides/{id}', [RideController::class, 'show']); Route::post('/rides/{id}/accept', [RideController::class, 'accept']); Route::post('/rides/{id}/arrive', [RideController::class, 'arrive']); @@ -105,7 +105,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () { Route::get('/tracking/captain-stats', [TrackingController::class, 'captainStats']); // ── Profile ── - Route::get('/profile/passenger', [ProfileController::class, 'passenger']); + Route::match(['get', 'post'], '/profile/passenger', [ProfileController::class, 'passenger']); Route::get('/profile/driver', [ProfileController::class, 'driver']); Route::put('/profile/passenger', [ProfileController::class, 'updatePassenger']); Route::put('/profile/driver/email', [ProfileController::class, 'updateDriverEmail']); @@ -153,7 +153,7 @@ Route::prefix('v2')->middleware(['hmac.auth', 'jwt.auth'])->group(function () { // ── Misc ── Route::get('/misc/test', [MiscController::class, 'test']); Route::get('/misc/package-info', [MiscController::class, 'packageInfo']); - Route::get('/misc/kazan-percent', [MiscController::class, 'getKazanPercent']); + Route::match(['get', 'post'], '/misc/kazan-percent', [MiscController::class, 'getKazanPercent']); Route::get('/misc/help-center', [MiscController::class, 'getHelpCenter']); Route::post('/misc/help-center', [MiscController::class, 'storeHelpCenter']); Route::get('/misc/tips', [MiscController::class, 'getTips']);