From 82d7d28f3f3440c7fade5284ee5724f895c3f36e Mon Sep 17 00:00:00 2001 From: Adolfo Builes Date: Fri, 19 Jul 2019 15:06:05 -0700 Subject: [PATCH] Implement SEP0010 transaction challenge builder. --- src/index.ts | 1 + src/utils.ts | 51 +++++++++++++++++++++++++++++++++++++++++ test/unit/utils_test.js | 45 ++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/utils.ts create mode 100644 test/unit/utils_test.js diff --git a/src/index.ts b/src/index.ts index ad07af319..ddd2f8688 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ export { SERVER_TIME_MAP, getCurrentServerTime, } from "./horizon_axios_client"; +export * from "./utils"; // expose classes and functions from stellar-base export * from "stellar-base"; diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..5132da980 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,51 @@ +import { randomBytes } from "crypto"; +import { Account, Keypair, Operation, TransactionBuilder } from "stellar-base"; + +/** + * Collection on util functions + * + * @namespace Utils + */ +export namespace Utils { + /** + * Returns a valid SEP0010 challenge which you can use for Stellar Web Authentication. + * + * @see [SEP0010: Stellar Web Authentication](https://github.com/stellar/stellar-protocol/blob/master/ecosystem/sep-0010.md) + * @param {Keypair} serverKeypair Server's keypair. + * @param {string} clientAccountID Client's Stellar account. + * @param {string} anchorName. + * @param {number} [timeout=300] Challenge duration (default to 5 minutes). + */ + export function buildChallengeTx( + serverKeypair: Keypair, + clientAccountID: string, + anchorName: string, + timeout: number = 300, + ): string { + const account = new Account(serverKeypair.publicKey(), "-1"); + const now = Math.floor(Date.now() / 1000); + + const transaction = new TransactionBuilder(account, { + fee: 100, + timebounds: { + minTime: now, + maxTime: now + timeout, + }, + }) + .addOperation( + Operation.manageData({ + name: `${anchorName} auth`, + value: randomBytes(64), + source: clientAccountID, + }), + ) + .build(); + + transaction.sign(serverKeypair); + + return transaction + .toEnvelope() + .toXDR("base64") + .toString(); + } +} diff --git a/test/unit/utils_test.js b/test/unit/utils_test.js new file mode 100644 index 000000000..ef7357649 --- /dev/null +++ b/test/unit/utils_test.js @@ -0,0 +1,45 @@ +describe('Utils', function() { + describe('Utils.buildChallengeTx', function() { + it('returns challenge which follows SEP0010 spec', function() { + let keypair = StellarSdk.Keypair.random(); + + const challenge = StellarSdk.Utils.buildChallengeTx( + keypair, + "GBDIT5GUJ7R5BXO3GJHFXJ6AZ5UQK6MNOIDMPQUSMXLIHTUNR2Q5CFNF", + "SDF" + ); + + const transaction = new StellarSdk.Transaction(challenge); + + expect(transaction.sequence).to.eql("0"); + expect(transaction.source).to.eql(keypair.publicKey()); + expect(transaction.operations.length).to.eql(1); + + const { maxTime, minTime } = transaction.timeBounds; + + expect(parseInt(maxTime) - parseInt(minTime)).to.eql(300); + + const [ operation ] = transaction.operations; + + expect(operation.name).to.eql("SDF auth"); + expect(operation.source).to.eql("GBDIT5GUJ7R5BXO3GJHFXJ6AZ5UQK6MNOIDMPQUSMXLIHTUNR2Q5CFNF"); + expect(operation.type).to.eql("manageData"); + expect(operation.value.length).to.eql(64); + }); + + it('uses the passed-in timeout', function() { + let keypair = StellarSdk.Keypair.random(); + + const challenge = StellarSdk.Utils.buildChallengeTx( + keypair, + "GBDIT5GUJ7R5BXO3GJHFXJ6AZ5UQK6MNOIDMPQUSMXLIHTUNR2Q5CFNF", + "SDF", + 600 + ); + + const transaction = new StellarSdk.Transaction(challenge); + const { maxTime, minTime } = transaction.timeBounds; + expect(parseInt(maxTime) - parseInt(minTime)).to.eql(600); + }); + }); +});