Skip to content

Commit

Permalink
SEP-10: add client attribution support
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-rogobete committed Jul 27, 2021
1 parent 43b3978 commit 9008a1d
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 66 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## [1.1.9] - 27.July.2021.
- extend sep-0010 support: add client attribution support
- extend sep-0010 support: accept multiple signers

## [1.1.8] - 23.July.2021.
- add sep-0006 support
- add sep-0009 support
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The Soneso open source Stellar SDK for Flutter is build with Dart and provides A
1. Add the dependency to your pubspec.yaml file:
```
dependencies:
stellar_flutter_sdk: ^1.1.8
stellar_flutter_sdk: ^1.1.9
```
2. Install it (command line or IDE):
```
Expand Down
Binary file modified documentation/sdk_api_doc.zip
Binary file not shown.
34 changes: 13 additions & 21 deletions documentation/sdk_examples/sep-0010-webauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,37 @@ The following examples show how to use the flutter stellar sdk to create authent



**Create a WebAuth instance**

### Create a WebAuth instance by providing the domain hosting the stellar.toml file
by providing the domain hosting the stellar.toml file:

```dart
final webauth = await WebAuth.fromDomain("place.domain.com", Network.TESTNET);
```

**Request the jwtToken**

### Request the jwtToken for the web session by providing the KeyPair of the user to sign the challenge from the web auth server
for the web session by providing the account id of the client/user and the signers of the client account to sign the challenge from the web auth server:

```dart
String jwtToken = await webAuth.jwtToken(KeyPair.fromSecretSeed(clientSecretSeed));
KeyPair clientKeyPair = KeyPair.fromSecretSeed(clientSecretSeed);
String jwtToken = await webAuth.jwtToken(clientKeyPair.accountId,[clientKeyPair]);
```

That is all you need to do. The method ```jwtToken``` will request the challenge from the web auth server, validate it, sign it on behalf of the user and send it back to the web auth server. The web auth server will than respond with the jwt token.



### If multiple accounts need to sign the challenge

If you need multiple accounts to sign the challenge you can do it similar to the ```jwtToken``` method:
### Client Attribution support
The flutter sdk also provides client attribution support. To use it, pass the client domain and the client domain account key pair for signing:

```dart
// get the challenge transaction from the web auth server
String transaction = await webAuth.getChallenge(kp.accountId, homeDomain);
// validate the transaction received from the web auth server.
webAuth.validateChallenge(transaction, kp.accountId); // throws if not valid
// sign the transaction received from the web auth server using the provided user/client keypair by parameter.
String signedTransaction = webAuth.signTransaction(transaction, kp);
// sign again with another account ...
signedTransaction = webAuth.signTransaction(signedTransaction, kp2);
// request the jwt token by sending back the signed challenge transaction to the web auth server.
final String jwtToken = await webAuth.sendSignedChallengeTransaction(signedTransaction);
KeyPair clientKeyPair = KeyPair.fromSecretSeed(clientSecretSeed);
KeyPair clientDomainAccountKeyPair = KeyPair.fromSecretSeed(clientDomainAccountSecretSeed);
String jwtToken = await webAuth.jwtToken(clientKeyPair.accountId,[clientKeyPair],clientDomain:"place.client.com", clientDomainAccountKeyPair: clientDomainAccountKeyPair);
```
### More examples
You can find more examples in the test cases.



78 changes: 58 additions & 20 deletions lib/src/sep/0010/webauth.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,40 @@ class WebAuth {
}

/// Get JWT token for wallet.
/// - Parameter userKeyPair: The keypair of the wallet to get the JWT token for. It must contain the secret seed for signing the challenge
/// - Parameter clientAccountId: The account id of the client/user to get the JWT token for.
/// - Parameter signers: list of signers (keypairs including secret seed) of the client account
/// - Parameter homeDomain: optional, used for requesting the challenge depending on the home domain if needed. The web auth server may serve multiple home domains.
Future<String> jwtToken(KeyPair userKeyPair, [String homeDomain]) async {

KeyPair kp = checkNotNull(userKeyPair, "keyPair can not be null");
/// - Parameter clientDomain: optional, domain of the client hosting it's stellar.toml
/// - Parameter clientDomainAccountKeyPair: optional, KeyPair of the client domain account including the seed (mandatory and used for signing the transaction if client domain is provided)
Future<String> jwtToken(String clientAccountId, List<KeyPair> signers,
{String homeDomain,
String clientDomain,
KeyPair clientDomainAccountKeyPair}) async {
String accountId =
checkNotNull(clientAccountId, "clientAccountId can not be null");
checkNotNull(signers, "signers can not be null");

// get the challenge transaction from the web auth server
String transaction = await getChallenge(kp.accountId, homeDomain);
String transaction =
await getChallenge(accountId, homeDomain, clientDomain);

String clientDomainAccountId = null;
if (clientDomainAccountKeyPair != null) {
clientDomainAccountId = clientDomainAccountKeyPair.accountId;
}
// validate the transaction received from the web auth server.
validateChallenge(transaction, kp.accountId); // throws if not valid
validateChallenge(
transaction, accountId, clientDomainAccountId); // throws if not valid

if (clientDomainAccountKeyPair != null) {
signers.add(clientDomainAccountKeyPair);
}
// sign the transaction received from the web auth server using the provided user/client keypair by parameter.
final signedTransaction = signTransaction(transaction, kp);
final signedTransaction = signTransaction(transaction, signers);

// request the jwt token by sending back the signed challenge transaction to the web auth server.
final String jwtToken = await sendSignedChallengeTransaction(signedTransaction);
final String jwtToken =
await sendSignedChallengeTransaction(signedTransaction);

return jwtToken;
}
Expand All @@ -87,7 +104,7 @@ class WebAuth {
/// - Parameter clientAccountId: The account id of the client/user that requests the challenge.
/// - Parameter homeDomain: optional, used for requesting the challenge depending on the home domain if needed. The web auth server may serve multiple home domains.
Future<String> getChallenge(String clientAccountId,
[String homeDomain]) async {
[String homeDomain, String clientDomain]) async {
ChallengeResponse challengeResponse =
await getChallengeResponse(clientAccountId, homeDomain);

Expand All @@ -99,7 +116,8 @@ class WebAuth {
}

/// Validates the challenge transaction received from the web auth server.
void validateChallenge(String challengeTransaction, String userAccountId) {
void validateChallenge(String challengeTransaction, String userAccountId,
String clientDomainAccountId) {
final String trans =
checkNotNull(challengeTransaction, "transaction can not be null");

Expand Down Expand Up @@ -143,18 +161,27 @@ class WebAuth {
throw ChallengeValidationErrorInvalidSourceAccount(
"invalid source account in operation[$i]");
}
if (i > 0 && opSourceAccountId != _serverSigningKey) {
throw ChallengeValidationErrorInvalidSourceAccount(
"invalid source account in operation[$i]");
}

// all operations must be manage data operations
if (op.body.discriminant != XdrOperationType.MANAGE_DATA ||
op.body.manageDataOp == null) {
throw ChallengeValidationErrorInvalidOperationType("invalid type of operation $i");
throw ChallengeValidationErrorInvalidOperationType(
"invalid type of operation $i");
}

final dataName = op.body.manageDataOp.dataName.string64;
if (i > 0) {
if (dataName == "client_domain") {
if (opSourceAccountId != clientDomainAccountId) {
throw ChallengeValidationErrorInvalidSourceAccount(
"invalid source account in operation[$i]");
}
} else if (opSourceAccountId != _serverSigningKey) {
throw ChallengeValidationErrorInvalidSourceAccount(
"invalid source account in operation[$i]");
}
}

if (i == 0 && dataName != _serverHomeDomain + " auth") {
throw ChallengeValidationErrorInvalidHomeDomain(
"invalid home domain in operation $i");
Expand Down Expand Up @@ -201,10 +228,10 @@ class WebAuth {
}
}

String signTransaction(String challengeTransaction, KeyPair userKeyPair) {
String signTransaction(String challengeTransaction, List<KeyPair> signers) {
final String trans =
checkNotNull(challengeTransaction, "transaction can not be null");
final KeyPair kp = checkNotNull(userKeyPair, "userKeyPair can not be null");
checkNotNull(signers, "signers can not be null");

XdrTransactionEnvelope envelopeXdr =
XdrTransactionEnvelope.fromEnvelopeXdrString(trans);
Expand All @@ -218,14 +245,17 @@ class WebAuth {

List<XdrDecoratedSignature> signatures = List<XdrDecoratedSignature>();
signatures.addAll(envelopeXdr.v1.signatures);
signatures.add(kp.signDecorated(txHash));
for (KeyPair signer in signers) {
signatures.add(signer.signDecorated(txHash));
}
envelopeXdr.v1.signatures = signatures;
return envelopeXdr.toEnvelopeXdrBase64();
}

/// Sends the signed challenge transaction back to the web auth server to obtain the jwt token.
/// In case of success, it returns the jwt token obtained from the web auth server.
Future<String> sendSignedChallengeTransaction(String base64EnvelopeXDR) async {
Future<String> sendSignedChallengeTransaction(
String base64EnvelopeXDR) async {
Uri serverURI = Uri.parse(_authEndpoint);

SubmitCompletedChallengeResponse result = await httpClient
Expand Down Expand Up @@ -258,7 +288,7 @@ class WebAuth {
}

Future<ChallengeResponse> getChallengeResponse(String accountId,
[String homeDomain]) async {
[String homeDomain, String clientDomain]) async {
String id = checkNotNull(accountId, "accountId can not be null");

Uri serverURI = Uri.parse(_authEndpoint);
Expand All @@ -268,6 +298,7 @@ class WebAuth {
ChallengeResponse response = await requestBuilder
.forAccountId(id)
.forHomeDomain(homeDomain)
.forClientDomain(clientDomain)
.execute();
return response;
} catch (e) {
Expand Down Expand Up @@ -309,6 +340,13 @@ class _ChallengeRequestBuilder extends RequestBuilder {
return this;
}

_ChallengeRequestBuilder forClientDomain(String clientDomain) {
if (clientDomain != null) {
queryParameters.addAll({"client_domain": clientDomain});
}
return this;
}

_ChallengeRequestBuilder forQueryParameters(Map<String, String> queryParams) {
queryParameters.addAll(queryParams);
return this;
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: stellar_flutter_sdk
description: A stellar blockchain sdk that query's horizon, build, signs and submits transactions to the stellar network.
version: 1.1.8
version: 1.1.9
homepage: https://github.com/Soneso/stellar_flutter_sdk

environment:
Expand Down
Loading

0 comments on commit 9008a1d

Please sign in to comment.