crypto = $crypto; } public function handle(Request $request, Closure $next) { $signature = $request->header('X-Signature'); $timestamp = $request->header('X-Timestamp'); $nonce = $request->header('X-Nonce'); $apiKey = $request->header('X-API-Key'); // All headers required if (!$signature || !$timestamp || !$nonce || !$apiKey) { return response()->json([ 'status' => 'failure', 'message' => 'Missing security headers' ], 401); } // Reject if timestamp older than tolerance (replay protection) $tolerance = config('intaleq.hmac_tolerance', 300); if (abs(time() - (int) $timestamp) > $tolerance) { return response()->json([ 'status' => 'failure', 'message' => 'Request expired' ], 401); } // Nonce replay check (prevent duplicate requests) $nonceKey = 'nonce:' . $nonce; if (Cache::has($nonceKey)) { return response()->json([ 'status' => 'failure', 'message' => 'Duplicate request' ], 401); } Cache::put($nonceKey, true, $tolerance); // Lookup api_secret by api_key $user = DB::connection('primary')->table('passengers') ->where('api_key', $apiKey)->first(['api_secret']); if (!$user) { $user = DB::connection('primary')->table('driver') ->where('api_key', $apiKey)->first(['api_secret']); } if (!$user) { return response()->json([ 'status' => 'failure', 'message' => 'Invalid API key' ], 401); } // Compute expected signature // METHOD:PATH:TIMESTAMP:NONCE:BODY $method = strtoupper($request->method()); $path = $request->path(); // returns path without leading slash (e.g. v2/ride/create) $body = $request->getContent(); $payload = "$method:$path:$timestamp:$nonce:$body"; $expected = hash_hmac(self::ALGORITHM, $payload, $user->api_secret); if (!hash_equals($expected, $signature)) { return response()->json([ 'status' => 'failure', 'message' => 'Invalid signature' ], 401); } return $next($request); } }