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: add iota-core library #601

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 16 additions & 0 deletions libs/dart/iota_core/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
API_GATEWAY_URL = "https://apse1.api.affinidi.io"
TOKEN_ENDPOINT = "https://apse1.auth.developer.affinidi.io/auth/oauth2/token"

# Personal Access Token (PAT) config
KEY_ID = "" # optional, unless unique value used on for the PAT
PASSPHRASE = "" # optional, unless private key is encrypted
TOKEN_ID = ""
PROJECT_ID = ""
PRIVATE_KEY = ""

# Configuration for Affinidi Iota Framework (websocket)
IOTA_CONFIG_ID=""
QUERY_ID=""

# User DID
DID=""
7 changes: 7 additions & 0 deletions libs/dart/iota_core/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/

# Avoid committing pubspec.lock for library packages; see
# https://dart.dev/guides/libraries/private-files#pubspeclock.
pubspec.lock
3 changes: 3 additions & 0 deletions libs/dart/iota_core/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 1.0.0

- Initial version.
55 changes: 55 additions & 0 deletions libs/dart/iota_core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Affinidi TDK - Iota core library

The `affinidi_tdk_iota_core` library provides a method for generating the Iota credentials required to initiate the WebSocket data-sharing mode of the Affinidi Iota Framework.


## Table of Contents

- [Affinidi TDK - Iota core](#affinidi-tdk---iota-core)
- [Table of Contents](#table-of-contents)
- [Requirements](#requirements)
- [Getting Started](#getting-started)
- [Usage](#usage)

## Requirements

- Dart SDK version ^3.6.0

## Getting Started

Add the following to your `pubspec.yaml` file:

```yaml
dependencies:
affinidi_tdk_iota_core: ^<version_number>
```

Then run:

```bash
dart pub get
```

## Usage

Once you've installed the package, import it into your Dart code:

```dart
import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart';
import 'package:affinidi_tdk_iota_core/affinidi_tdk_iota_core.dart';

final provider = AuthProvider(
projectId: env['PROJECT_ID']!,
tokenId: env['TOKEN_ID']!,
privateKey: env['PRIVATE_KEY']!.replaceAll('\\n', '\n'),
// Optional parameters
keyId: env['KEY_ID'],
passphrase: env['PASSPHRASE'],
);

// Create Iota Token from AuthProvider Package.
final iotaToken = provider.createIotaToken(iotaConfigId: env['IOTA_CONFIG_ID']!, did: env['DID']!);

// A static method that fetches the Iota Credentials by passing an Iota JWT Token derived from the AuthProvider.
final iotaCredentials = await IotaCore.limitedTokenToIotaCredentials(iotaToken.iotaJwt);
```
35 changes: 35 additions & 0 deletions libs/dart/iota_core/example/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart';
import 'package:affinidi_tdk_iota_core/affinidi_tdk_iota_core.dart';
import 'package:affinidi_tdk_common/affinidi_tdk_common.dart';
import 'package:dotenv/dotenv.dart';

void main() async {
var env = DotEnv()..load();
// DID is usually obtained at runtime from the registered user
if (!env.isEveryDefined(['PROJECT_ID', 'TOKEN_ID', 'PRIVATE_KEY', 'IOTA_CONFIG_ID', 'DID'])) {
print('Missing variables. Please provide PROJECT_ID, TOKEN_ID, PRIVATE_KEY, IOTA_CONFIG_ID and DID');
return;
}
// Workaround for dotenv multiline limitations
final privateKey = env['PRIVATE_KEY']!.replaceAll('\\n', '\n');

final provider = AuthProvider(
projectId: env['PROJECT_ID']!,
tokenId: env['TOKEN_ID']!,
privateKey: privateKey,
// Optional parameters
keyId: env['KEY_ID'],
passphrase: env['PASSPHRASE'],
);

// Fetch iota token (websocket).
try {
final iotaToken = provider.createIotaToken(iotaConfigId: env['IOTA_CONFIG_ID']!, did: env['DID']!);
final iotaCredentials = await IotaCore.limitedTokenToIotaCredentials(iotaToken.iotaJwt);

print(iotaCredentials.credentials.accessKeyId);
print(iotaCredentials.connectionClientId);
} catch (e) {
print('Error obtaining token: $e');
}
}
12 changes: 12 additions & 0 deletions libs/dart/iota_core/lib/affinidi_tdk_iota_core.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
library;

import 'src/iota_auth_provider.dart';

class IotaCore {
static Future<IotaCredentials> limitedTokenToIotaCredentials(
String token,
) async {
final iotaAuthProvider = IotaAuthProvider();
return iotaAuthProvider.limitedTokenToIotaCredentials(token);
}
}
133 changes: 133 additions & 0 deletions libs/dart/iota_core/lib/src/iota_auth_provider.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import 'package:affinidi_tdk_common/affinidi_tdk_common.dart';
import 'package:aws_signature_v4/aws_signature_v4.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class AuthProviderParams {
final String region;
final String apiGW;

AuthProviderParams({
required this.region,
required this.apiGW,
});
}

class IotaCredentials {
final Credentials credentials;
final String connectionClientId;

IotaCredentials({
required this.credentials,
required this.connectionClientId,
});
}

class Credentials {
final String? accessKeyId;
final String? secretKey;
final String? sessionToken;
final DateTime? expiration;

Credentials({
this.accessKeyId,
this.secretKey,
this.sessionToken,
this.expiration,
});
}

class IdentityCredentials {
final String identityId;
final String token;

IdentityCredentials({
required this.identityId,
required this.token,
});

factory IdentityCredentials.fromJson(Map<String, dynamic> json) {
return IdentityCredentials(
identityId: json['identityId'],
token: json['token'],
);
}
}

class IotaAuthProvider {
late final String region;
late final String apiGW;

IotaAuthProvider({ Map<String, dynamic>? params }) {
region = params?['region'] ?? 'ap-southeast-1';
apiGW = params?['apiGW'] ?? Environment.fetchApiGwUrl();
}

Future<IotaCredentials> limitedTokenToIotaCredentials(String limitedToken) async {
final response = await http.post(
Uri.parse('$apiGW/ais/v1/aws-exchange-credentials'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'assertion': limitedToken}),
);

if (response.statusCode != 200) {
throw Exception('Failed to exchange credentials');
}

final Map<String, dynamic> data = jsonDecode(response.body);
final connectionClientId = data['connectionClientId'] as String;

final identityCredentials = IdentityCredentials.fromJson(data['credentials']);

final credentials = await exchangeIdentityCredentials(identityCredentials);

return IotaCredentials(
credentials: credentials,
connectionClientId: connectionClientId,
);
}

Future<Credentials> exchangeIdentityCredentials(IdentityCredentials identityCredentials) async {
final cognitoResponse = await fetchCognitoCredentials(identityCredentials);
final creds = cognitoResponse['Credentials'];
if (creds == null) {
throw Exception('Error fetching credentials');
}
final expirationSeconds = creds['Expiration'] as double;
final expirationDate = DateTime.fromMillisecondsSinceEpoch((expirationSeconds * 1000).toInt());

return Credentials(
accessKeyId: creds['AccessKeyId'],
secretKey: creds['SecretKey'],
sessionToken: creds['SessionToken'],
expiration: creds['Expiration'] != null
? expirationDate
: null,
);
}

Future<Map<String, dynamic>> fetchCognitoCredentials(IdentityCredentials identityCredentials) async {
final url = 'https://cognito-identity.$region.amazonaws.com/';
final payload = jsonEncode({
'IdentityId': identityCredentials.identityId,
'Logins': {
'cognito-identity.amazonaws.com': identityCredentials.token,
},
});

final response = await http.post(
Uri.parse(url),
headers: {
'Content-Type': 'application/x-amz-json-1.1',
'X-Amz-Target': 'AWSCognitoIdentityService.GetCredentialsForIdentity',
},
body: payload,
);

if (response.statusCode != 200) {
throw Exception('Failed to fetch Cognito credentials');
}

return jsonDecode(response.body);
}
}
19 changes: 19 additions & 0 deletions libs/dart/iota_core/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: affinidi_tdk_iota_core
description: Iota core library
version: 1.0.0
repository: https://github.com/affinidi/affinidi-tdk

environment:
sdk: ^3.6.0

resolution: workspace

dependencies:
affinidi_tdk_auth_provider: ^1.0.0
affinidi_tdk_common: ^1.0.0
http: ^1.1.0
aws_signature_v4: ^0.5.1

dev_dependencies:
lints: ^5.0.0
test: ^1.24.0
41 changes: 41 additions & 0 deletions libs/dart/iota_core/test/iota_core_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import 'package:affinidi_tdk_auth_provider/affinidi_tdk_auth_provider.dart';
import 'package:affinidi_tdk_iota_core/affinidi_tdk_iota_core.dart';
import 'package:test/test.dart';
import 'package:dotenv/dotenv.dart';

void main() {
group('Iota core', () {
late AuthProvider authProvider;
final env = DotEnv()..load();

setUp(() {
// Workaround for dotenv multiline limitations
final privateKey = env['PRIVATE_KEY']!.replaceAll('\\n', '\n');

authProvider = AuthProvider(
projectId: env['PROJECT_ID']!,
tokenId: env['TOKEN_ID']!,
privateKey: privateKey,
// Optional parameters
keyId: env['KEY_ID'],
passphrase: env['PASSPHRASE'],
);
});

test('Get Iota credentials', () async {
final iotaToken = await authProvider.createIotaToken(
iotaConfigId: env['IOTA_CONFIG_ID']!, did: env['DID']!);

expect(iotaToken.iotaJwt, isNotEmpty);
expect(iotaToken.iotaSessionId, isNotEmpty);

final iotaCredentials = await IotaCore.limitedTokenToIotaCredentials(iotaToken.iotaJwt);

expect(iotaCredentials.credentials.accessKeyId, isNotEmpty);
expect(iotaCredentials.credentials.secretKey, isNotEmpty);
expect(iotaCredentials.credentials.sessionToken, isNotEmpty);
expect(iotaCredentials.credentials.expiration, isNotNull);
expect(iotaCredentials.connectionClientId, isNotEmpty);
});
});
}
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ workspace:
- packages/dart/auth_provider
- packages/dart/consumer_auth_provider
- libs/dart/seed_cryptography
- libs/dart/iota_core
- tests/integration/dart
4 changes: 4 additions & 0 deletions tests/integration/dart/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dev_dependencies:
path: ../../../clients/dart/credential_issuance_client
affinidi_tdk_vault_data_manager_client:
path: ../../../clients/dart/vault_data_manager_client
affinidi_tdk_iota_client:
path: ../../../clients/dart/iota_client
dotenv: ^4.2.0
lints: ^5.0.0
test: ^1.24.0
Expand All @@ -25,3 +27,5 @@ dev_dependencies:
dio: '^5.2.0'
one_of: ^1.5.0
convert: ^3.1.2
uuid: ^4.5.1
jwt_decode: ^0.3.1
Loading