Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dio client interceptor #5

Closed
wants to merge 38 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2c83cc7
feat: dio client interceptor
thorvalld Mar 15, 2024
cb8cfeb
refactor: added exports
thorvalld Mar 15, 2024
c4339e7
test: WIP draft test case
thorvalld Mar 15, 2024
f0e346e
test: refactored dio interceptor test and dropped mockito
thorvalld Mar 19, 2024
e865d7a
test: documentation added
thorvalld Mar 19, 2024
1e2d511
refactor: minor comments formatting
thorvalld Mar 19, 2024
3833f87
refactor: dio interception logic
thorvalld Mar 19, 2024
e6c0142
fix: dio interception implementation
thorvalld Mar 21, 2024
4c59426
feat: added uuid generation pckg
thorvalld Mar 21, 2024
d26f23f
refactor: adjusted test scenarios
thorvalld Mar 21, 2024
49975b0
refactor: added try catch block
thorvalld Mar 21, 2024
0d5db05
refactor: dropped malforomed data test
thorvalld Mar 21, 2024
d594b4e
fix: clockskew adjusted
thorvalld Mar 21, 2024
cdce087
test: added test case for clockskew
thorvalld Mar 21, 2024
fdd1ae1
refactor: WIP interception onRequest Method
thorvalld Mar 22, 2024
66056b1
refactor: interceptor can pass a dio as arg and added injectable time…
thorvalld Mar 26, 2024
9525cd3
refactor: dio interceptor
thorvalld Mar 27, 2024
8d81410
refactor: injectable getTime()
thorvalld Apr 2, 2024
950643a
refactor: updated getTime()
thorvalld Apr 2, 2024
db49ac1
refactor: now variable using getTime() insyteam of _getTime()
thorvalld Apr 2, 2024
bc057aa
chore: Support
Soap-141 Apr 3, 2024
188a0bb
Merge pull request #6 from nventive/dev/thla/support
thorvalld Apr 4, 2024
ee107b0
refactor: adjusted test case and bug fix
thorvalld Apr 5, 2024
0c73a49
refactor: dropped unused third party libraries
thorvalld Apr 5, 2024
c97c9ae
chore: package readme
thorvalld Apr 5, 2024
eb81213
refactor: readme adjusted
thorvalld Apr 8, 2024
a0e5d4e
refactor: dropped dart_tool files
thorvalld Apr 8, 2024
9c9bad2
refactor: added assertion to test case #2
thorvalld Apr 8, 2024
5dd6fa7
refactor: test case # 2
thorvalld Apr 8, 2024
0e7ae55
refactor: added test cases
thorvalld Apr 9, 2024
0ea224c
refactor: dropped unnecessary recheck for dates
thorvalld Apr 9, 2024
9ca1fba
fix: typo
thorvalld Apr 9, 2024
63980e8
refactor: dropped unnecessary clockskew validation
thorvalld Apr 9, 2024
defa2f4
refactor: improvements and refactored for reusability
thorvalld Apr 12, 2024
f939086
refactor: minor variables refactoring
thorvalld Apr 12, 2024
64192fc
refactor: ropped use of getDateHeader in interceptor
thorvalld Apr 15, 2024
dfec5c5
refactor: refactored test cases
thorvalld Apr 16, 2024
351fd06
fix: test case when auto-retry disabled
thorvalld Apr 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/requests_signature_dart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ export 'src/core/interface/signature_body_source_builder.dart';

// Export default constants
export 'src/core/default_constants.dart';

// Export client implementation
///Client: Dio
thorvalld marked this conversation as resolved.
Show resolved Hide resolved
export 'src/client/dio_interceptor.dart';
89 changes: 89 additions & 0 deletions lib/src/client/dio_interceptor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import 'dart:math';

import 'package:cryptography/dart.dart';
import 'package:dio/dio.dart';
import 'package:requests_signature_dart/requests_signature_dart.dart';

/// Interceptor for signing HTTP requests with HMAC authentication.
///
/// This interceptor adds HMAC authentication headers to outgoing HTTP requests
/// based on the provided client ID and client secret. It calculates the signature
/// using the specified hash algorithm.
class HMACDioInterceptor extends Interceptor {
thorvalld marked this conversation as resolved.
Show resolved Hide resolved
/// The client ID used for HMAC authentication.
final String clientId;

/// The client secret used for HMAC authentication.
final String clientSecret;

/// The signer used to calculate the HMAC signature.
final HashAlgorithmSignatureBodySigner signer;

/// Creates a new [HMACInterceptor] instance.
///
/// [clientId] is the client ID used for HMAC authentication.
///
/// [clientSecret] is the client secret used for HMAC authentication.
HMACDioInterceptor(this.clientId, this.clientSecret)
: signer =
HashAlgorithmSignatureBodySigner(hmacAlgorithm: DartHmac.sha256());

@override
Future onRequest(
RequestOptions options, RequestInterceptorHandler handler) async {
// Generate timestamp and nonce
final timestamp = _epochTime();
final nonce = _genGuid();

// Extract request details
final uri = options.uri;
final method = options.method;
final headers = options.headers;

// Prepare parameters for signature calculation
final signatureBodySourceParameters = SignatureBodySourceParameters(
method,
uri,
headers.map((key, value) => MapEntry(key.toString(), value.toString())),
nonce,
timestamp,
clientId,
DefaultConstants.signatureBodySourceComponents,
);

// Build the signature body source
final bodySourceBuilder = SignatureBodySourceBuilder();
final signatureBodySource =
await bodySourceBuilder.build(signatureBodySourceParameters);
final signatureBodyParameters =
SignatureBodyParameters(signatureBodySource, clientSecret);

// Calculate HMAC signature
final signature = await signer.sign(signatureBodyParameters);

// Add authentication headers to the request
options.headers['X-RequestSignature-ClientId'] = clientId;
options.headers['X-RequestSignature-Nonce'] = nonce;
options.headers['X-RequestSignature-Timestamp'] = timestamp.toString();
options.headers['X-RequestSignature-Signature'] = signature;

return super.onRequest(options, handler);
}

/// Generates a random nonce.
///
/// The nonce is a unique string used for each request to prevent replay attacks.
String _genGuid() {
const String uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return uuid.replaceAllMapped(RegExp('[xy]'), (match) {
final int rand = Random().nextInt(16);
final int index = match.group(0) == 'x' ? rand : (rand & 0x3 | 0x8);
return index.toRadixString(16);
});
}

/// Returns the current epoch time in milliseconds.
int _epochTime() {
thorvalld marked this conversation as resolved.
Show resolved Hide resolved
return DateTime.now().millisecondsSinceEpoch;
}
}
51 changes: 51 additions & 0 deletions test/client/dio_hmac_interceptor_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/// This file contains unit tests for the [HMACDioInterceptor] class.
///
/// The [HMACDioInterceptor] class is responsible for adding HMAC authentication headers
/// to outgoing HTTP requests based on the provided client ID and client secret. It calculates
/// the signature using the specified hash algorithm.
///
/// The tests in this file ensure that the interceptor behaves correctly under different scenarios
/// and edge cases, verifying that it signs requests with authentication headers correctly.
/// Suggestions for additional test cases are also provided for further coverage.
///
/// To run the tests, use the `dart test` command with the appropriate arguments.
/// Run test via terminal:
/// ```
/// dart test test/client/dio_hmac_interceptor_test.dart
/// ```
/// Ensure that the appropriate dependencies are included in the `pubspec.yaml` file
/// and imported into the test file for the tests to work properly.
///

import 'package:dio/dio.dart';
import 'package:test/test.dart';
import 'package:requests_signature_dart/requests_signature_dart.dart';

void main() {
group('HMACDioInterceptor', () {
test('Interceptor signs the request with authentication headers', () async {
// Test case to ensure that the interceptor signs requests with authentication headers correctly.
// Arrange
final dio = Dio();
final interceptor =
HMACDioInterceptor("your_client_id", "your_client_secret");
dio.interceptors.add(interceptor);

final options = RequestOptions(
method: 'GET',
path: '/api/data',
baseUrl: 'https://example.com',
headers: {'Content-Type': 'application/json'},
);

// Act
await interceptor.onRequest(options, RequestInterceptorHandler());

// Assert
expect(options.headers['X-RequestSignature-ClientId'], "your_client_id");
expect(options.headers['X-RequestSignature-Nonce'], isNotEmpty);
expect(options.headers['X-RequestSignature-Timestamp'], isNotEmpty);
expect(options.headers['X-RequestSignature-Signature'], isNotEmpty);
});
});
}