header('X-Signature'); $timestamp = $this->header('X-Timestamp'); $apiKey = $this->header('X-Api-Key'); // Validasi keberadaan header yang diperlukan if (!$signature || !$timestamp || !$apiKey) { Log::warning('HMAC validation failed - missing required headers', [ 'signature' => $signature, 'timestamp' => $timestamp, 'apiKey' => $apiKey, 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); return false; } // Validasi API key dari config $expectedApiKey = config('webstatement.api_key'); if ($apiKey !== $expectedApiKey) { Log::warning('HMAC validation failed - invalid API key', [ 'provided_api_key' => $apiKey, 'expected_api_key' => $expectedApiKey, 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); return false; } // Ambil secret key dari config $secretKey = config('webstatement.secret_key'); // Ambil parameter untuk validasi HMAC $httpMethod = $this->method(); $relativeUrl = $this->path(); $requestBody = $this->getContent(); // Validasi HMAC signature $isValid = validateHmac512( $httpMethod, $relativeUrl, $apiKey, $requestBody, $timestamp, $secretKey, $signature ); if (!$isValid) { Log::warning('HMAC validation failed - invalid signature', [ 'http_method' => $httpMethod, 'relative_url' => $relativeUrl, 'api_key' => $apiKey, 'timestamp' => $timestamp, 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); } return $isValid; } catch (\Exception $e) { Log::error('HMAC validation error', [ 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); return false; } } /** * Get the validation rules that apply to the request. * * @return array */ public function rules(): array { return [ 'account_number' => [ 'required', 'string', 'max:10', 'min:10', 'exists:account_balances,account_number', 'regex:/^[0-9]+$/' // Numeric only ], 'start_date' => [ 'required', 'date_format:Y-m-d', 'before_or_equal:end_date', 'after_or_equal:1900-01-01', 'before_or_equal:today' ], 'end_date' => [ 'required', 'date_format:Y-m-d', 'after_or_equal:start_date', 'after_or_equal:1900-01-01', 'before_or_equal:today' ], ]; } /** * Get custom messages for validator errors. * * @return array */ public function messages(): array { return [ 'account_number.exists' => 'Nomor rekening tidak ditemukan.', 'account_number.required' => 'Nomor rekening wajib diisi.', 'account_number.string' => 'Nomor rekening harus berupa teks.', 'account_number.max' => 'Nomor rekening maksimal :max karakter.', 'account_number.min' => 'Nomor rekening minimal :min karakter.', 'account_number.regex' => 'Nomor rekening hanya boleh mengandung angka.', 'start_date.required' => 'Tanggal awal wajib diisi.', 'start_date.date_format' => 'Format tanggal awal harus YYYY-MM-DD.', 'start_date.before_or_equal' => 'Tanggal awal harus sebelum atau sama dengan tanggal akhir.', 'end_date.required' => 'Tanggal akhir wajib diisi.', 'end_date.date_format' => 'Format tanggal akhir harus YYYY-MM-DD.', 'end_date.after_or_equal' => 'Tanggal akhir harus sesudah atau sama dengan tanggal awal.', 'end_date.before_or_equal' => 'Tanggal akhir harus sebelum atau sama dengan hari ini.', ]; } /** * Handle a failed validation attempt. * * @param \Illuminate\Contracts\Validation\Validator $validator * @return void * * @throws \Illuminate\Validation\ValidationException */ protected function failedValidation($validator) { $errors = $validator->errors(); if($errors->has('account_number') && $errors->first('account_number') === 'Nomor rekening tidak ditemukan.') { throw new HttpResponseException( response()->json( ResponseCode::ACCOUNT_NOT_FOUND->toResponse( null, 'Nomor rekening tidak ditemukan' ), ResponseCode::ACCOUNT_NOT_FOUND->getHttpStatus() ) ); } throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => $errors->all()], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } /** * Handle failed authorization. * * @return void * * @throws \Illuminate\Auth\Access\AuthorizationException */ protected function failedAuthorization() { $xApiKey = $this->header('X-Api-Key'); $xSignature = $this->header('X-Signature'); $xTimestamp = $this->header('X-Timestamp'); $expectedApiKey = config('webstatement.api_key'); if(!$xApiKey){ throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Api-Key' => 'API Key wajib diisi']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } if(!$xSignature){ throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Signature' => 'Signature wajib diisi']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } if(!$xTimestamp){ throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Timestamp' => 'Timestamp wajib diisi']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } // Validasi format timestamp ISO 8601 if (!preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/', $xTimestamp)) { throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Timestamp' => 'Format timestamp tidak valid. Gunakan format ISO 8601 (YYYY-MM-DDTHH:MM:SS.sssZ)']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } // Validasi timestamp tidak lebih dari 5 menit dari waktu sekarang try { $timestamp = new \DateTime($xTimestamp); $now = new \DateTime(); $diff = $now->getTimestamp() - $timestamp->getTimestamp(); if (abs($diff) > 300) { // 5 menit = 300 detik throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Timestamp' => 'Timestamp expired. Maksimal selisih 5 menit dari waktu sekarang']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } } catch (\Exception $e) { throw new HttpResponseException( response()->json( ResponseCode::INVALID_FIELD->toResponse( ['errors' => ['X-Timestamp' => 'Timestamp tidak dapat diproses']], 'Validasi gagal' ), ResponseCode::INVALID_FIELD->getHttpStatus() ) ); } // Cek apakah ini karena invalid API key if ($xApiKey !== $expectedApiKey) { throw new HttpResponseException( response()->json( ResponseCode::INVALID_TOKEN->toResponse( null, 'API Key tidak valid' ), ResponseCode::INVALID_TOKEN->getHttpStatus() ) ); } // Untuk kasus HMAC signature tidak valid throw new HttpResponseException( response()->json( ResponseCode::UNAUTHORIZED->toResponse( null, 'Signature tidak valid' ), ResponseCode::UNAUTHORIZED->getHttpStatus() ) ); } /** * Prepare the data for validation. * * @return void */ protected function prepareForValidation(): void { Log::info('Balance summary request received', [ 'input' => $this->all(), 'ip' => $this->ip(), 'user_agent' => $this->userAgent() ]); } }