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 35 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
234 changes: 208 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,44 +1,226 @@
# Open Source Project Template
# requests_signature_dart

This repository contains a template to seed a repository for an Open Source
project.
Signs and validates HTTP requests through Dio interceptors.
thorvalld marked this conversation as resolved.
Show resolved Hide resolved

## How to use this template
This projects can help you implements [HMAC](https://en.wikipedia.org/wiki/HMAC) signature to HTTP and HTTPS requests in Dart and/or Flutter.

1. Check out this repository
2. Delete the `.git` folder
3. Git init this repository and start working on your project!
4. Prior to submitting your request for publication, make sure to review the
[Open Source guidelines for publications](https://nventive.visualstudio.com/Internal/_wiki/wikis/Internal_wiki?wikiVersion=GBwikiMaster&pagePath=%2FOpen%20Source%2FPublishing&pageId=7120).
requests_signature_dart depends on
- [cryptography](https://pub.dev/packages/cryptography)
- [dio](https://pub.dev/packages/dio)
- [uuid](https://pub.dev/packages/uuid)

## Features (to keep as-is, configure or remove)
- [Mergify](https://mergify.io/) is configured. You can edit or remove [.mergify.yml](/.mergify.yml).
and is [Dart 3](https://medium.com/dartlang/announcing-dart-3-53f065a10635) compatible.

The following is the template for the final README.md file:
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)

---
## Getting Started

# Project Title
This project provides functionality to implement HMAC signature to HTTP and HTTPS requests in Dart and/or Flutter applications. It utilizes Dio interceptors to seamlessly integrate signature generation and validation into your network requests.

{Project tag line}
The recipe for implementing request signature validation starts with configuring the necessary options for signature generation, including client ID, client secret, header name, signature pattern, clock skew, and auto retry behavior on clock skew detection. These options ensure secure and reliable signature generation and validation.

For client-side implementation in Dart/Flutter, you can follow the provided example code and integrate the RequestsSignatureInterceptor into your Dio client. This interceptor automatically adds the signature header to outgoing requests and validates incoming responses.

{Small description of the purpose of the project}
However, it's important to note that this Dart package focuses on the client-side part of the recipe. For the server-side implementation, you can refer to the corresponding [.NET repository](https://github.com/nventive/RequestsSignature/tree/master), which provides the necessary components for validating incoming requests and generating appropriate responses.

### Implementing Requests Signature Validation in Dart/Flutter app project:

Install the package via command line:

```
flutter pub add requests_signature_dart
```

This will add a line like this to your package's `pubspec.yaml` (and run an implicit `flutter pub get`):

```yaml
dependencies:
request_signature_dart: ^0.0.1
```
Alternatively, your editor might support `flutter pub get`. Check the docs for your editor to learn more.

##### Import it

in your dart code:

```dart
import 'package:requests_signature_dart/requests_signature_dart.dart';
```

### Using

#### Implement HMAC signed HTTP requests

```dart
import 'package:dio/dio.dart'; // For making HTTP requests
import 'package:requests_signature_dart/requests_signature_dart.dart'; // For request signature functionality

void main() {
// Instantiate Dio client
final dio = Dio();

// Define signature options with clockSkew and disableAutoRetryOnClockSkew
final signatureOptions = RequestsSignatureOptions(
clientId: 'your_client_id', // Your unique client ID
clientSecret: 'your_client_secret', // Your client secret key
headerName: 'X-Request-Signature', // Name of the custom header for the signature
signaturePattern: '{ClientId}:{Nonce}:{Timestamp}:{SignatureBody}', // Pattern for the signature header value
clockSkew: Duration(seconds: 30), // Clock skew duration (30 seconds in this example)
disableAutoRetryOnClockSkew: false, // Disable auto retry on clock skew if set to true
);

// Instantiate interceptor
final interceptor = RequestsSignatureInterceptor(
signatureOptions,
dio,
);

// Add interceptor to Dio client
dio.interceptors.add(interceptor);

// Make sample HTTP requests
makePostRequest(dio);
makeGetRequest(dio);
makePutRequest(dio);
makeDeleteRequest(dio);
}

// Function to make a sample POST request
void makePostRequest(Dio dio) async {
try {
// Define POST request data
final requestData = {'key': 'value'};

// Make POST request
final response = await dio.post(
'https://api.example.com/endpoint', // Replace with your actual POST endpoint URL
data: requestData, // Request data to be sent
);

// Print POST response data
print('POST Response: ${response.data}');
} catch (e) {
// Handle POST request error
print('POST Error: $e');
}
}

// Function to make a sample GET request
void makeGetRequest(Dio dio) async {
try {
// Make GET request
final response = await dio.get(
'https://api.example.com/endpoint', // Replace with your actual GET endpoint URL
);

// Print GET response data
print('GET Response: ${response.data}');
} catch (e) {
// Handle GET request error
print('GET Error: $e');
}
}

// Function to make a sample PUT request
void makePutRequest(Dio dio) async {
try {
// Define PUT request data
final requestData = {'updated_key': 'updated_value'};

// Make PUT request
final response = await dio.put(
'https://api.example.com/endpoint', // Replace with your actual PUT endpoint URL
data: requestData, // Request data to be sent
);

// Print PUT response data
print('PUT Response: ${response.data}');
} catch (e) {
// Handle PUT request error
print('PUT Error: $e');
}
}

// Function to make a sample DELETE request
void makeDeleteRequest(Dio dio) async {
try {
// Make DELETE request
final response = await dio.delete(
'https://api.example.com/endpoint', // Replace with your actual DELETE endpoint URL
);

// Print DELETE response data
print('DELETE Response: ${response.data}');
} catch (e) {
// Handle DELETE request error
print('DELETE Error: $e');
}
}
```

[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
## Features

## Getting Started
### Default Header signature and algorithm

{Instructions to quickly get started using the project: pre-requisites, packages
to install, sample code, etc.}
By default, here is how the header is constructed:

## Features
The final header has the following specification: `{ClientId}:{Nonce}:{Timestamp}:{SignatureBody}` where:
- `{ClientId}`: is the client id as specified by the configuration
- `{Nonce}`: is a random value unique to each request (a UUID/GUID is perfectly suitable)
- `{Timestamp}`: is the current time when the request is sent, in Unix Epoch time (in seconds)
- `{SignatureBody}`: Is the Base-64 encoded value of the HMAC SHA256 Signature of the signature components

Signature components (the source for the SignatureBody HMAC value) is a binary value composed of the following values sequentially:
- Nonce: UTF-8 encoded binary values of the Nonce
- Timestamp: UTF-8 encoded binary values of the Timestamp (as a string value)
- Request method: UTF-8 encoded binary values of the **uppercase** Request method
- Request scheme: UTF-8 encoded binary values of the Request Uri scheme (e.g. `https`)
- Request host: UTF-8 encoded binary values of the Request Uri host (e.g. `example.org`)
- Request local path: UTF-8 encoded binary values of the Request Uri local path (e.g. `/api/v1/users`)
- Request query string: UTF-8 encoded binary values of the Request Query string, including the leading `?` (e.g. `?q=search`)
- Request body: Raw bytes of the request body

### Auto retry on clock skew detection (client)

The `RequestsSignatureDelegatingHandler` has a specific features that tries to detect
when a client's clock is not properly synchronized with the server and compensate
for the delta. This is useful when dealing with clients that are not under your control.

The way this work is when the client receives either a 401 or 403 status code and the
response includes a Date header, it compares the date received from the server and the
client current time. If the difference is more than the configured `clockSkew`, it
computes the delta, adjust the time based on the computation and automatically re-tries
the request. All subsequent invocation will also apply the same time delta, until another
potential clock skew is detected.

This behavior can be de-activated using the `disableAutoRetryOnClockSkew` client option.

#### Client-side

- `clockSkew`: The duration of time that a timestamp will still be considered valid when
comparing with the current time (+/-). Defaults to 5 minutes.
- `headerName`: The name of the header that contains the signature. Defaults to `X-RequestSignature`.
- `signaturePattern`: The pattern that is used to create the final header value.
Defaults to `{ClientId}:{Nonce}:{Timestamp}:{SignatureBody}`.
- `disableAutoRetryOnClockSkew`: When set to true, the handler will not attempt to
detect clock skew and auto-retry.

### Further customization

It is possible to further customize the behavior of the component by providing
custom implementation of the following interfaces:

- `ISignatureBodySourceBuilder`: Builds the source data for the signature computation
- `ISignatureBodySigner`: Creates the signature body value (from the signature body source)
- `IRequestsSignatureValidationService`: Performs the signature validation

{More details/listing of features of the project}
Additionally, the Hash algorithm used can be customized by constructing the
`HashAlgorithmSignatureBodySigner` using a custom `hashAlgorithmBuilder`.

## Breaking Changes
## Changelog

Please consult [BREAKING_CHANGES.md](BREAKING_CHANGES.md) for more information about version
history and compatibility.
Please consult the [CHANGELOG](CHANGELOG.md) for more information about version
history.

## License

Expand All @@ -50,4 +232,4 @@ This project is licensed under the Apache 2.0 license - see the
Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on the process for
contributing to this project.

Be mindful of our [Code of Conduct](CODE_OF_CONDUCT.md).
Be mindful of our [Code of Conduct](CODE_OF_CONDUCT.md).
12 changes: 8 additions & 4 deletions lib/requests_signature_dart.dart
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
library requests_signature_dart;

// Export exceptions
/// Export exceptions
export 'src/core/exception/requests_signature_exception.dart';

// Export core implementation
/// Export core implementation
export 'src/core/implementation/hash_algorithm_signature_body_signer.dart';
export 'src/core/implementation/signature_body_parameters.dart';
export 'src/core/implementation/signature_body_source_builder.dart';
export 'src/core/implementation/signature_body_source_components.dart';
export 'src/core/implementation/signature_body_source_parameters.dart';

// Export interfaces
/// Export interfaces
export 'src/core/interface/signature_body_signer.dart';
export 'src/core/interface/signature_body_source_builder.dart';

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

/// Export client implementation
/// Client: Dio
export 'src/client/dio_interceptor.dart';
28 changes: 28 additions & 0 deletions lib/src/client/dio_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import 'package:dio/dio.dart';
import 'package:requests_signature_dart/src/client/dio_interceptor.dart';
import 'package:requests_signature_dart/src/client/requests_signature_options.dart';
import 'package:requests_signature_dart/src/core/interface/signature_body_signer.dart';
import 'package:requests_signature_dart/src/core/interface/signature_body_source_builder.dart';

/// Extension methods for Dio.
extension DioExtension on Dio {
/// Adds [RequestsSignatureInterceptor] as an interceptor in the Dio client.
///
/// [options] - The [RequestsSignatureOptions] to use. If not provided, will retrieve from the container.
///
/// [signatureBodySourceBuilder] - The [ISignatureBodySourceBuilder]. If not provided, will try to retrieve from the container.
///
/// [signatureBodySigner] - The [ISignatureBodySigner]. If not provided, will try to retrieve from the container.
void addRequestsSignatureInterceptor(
{RequestsSignatureOptions? options,
required Dio dio,
ISignatureBodySourceBuilder? signatureBodySourceBuilder,
ISignatureBodySigner? signatureBodySigner}) {
interceptors.add(RequestsSignatureInterceptor(
options!,
dio,
signatureBodySourceBuilder: signatureBodySourceBuilder!,
signatureBodySigner: signatureBodySigner!,
));
}
}
Loading