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

Added webAuthDomain parameter and validation to SEP-10 util functions #607

Merged
merged 7 commits into from
Jan 18, 2021
Merged
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
34 changes: 32 additions & 2 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ export namespace Utils {
* @param {string} homeDomain The fully qualified domain name of the service requiring authentication
* @param {number} [timeout=300] Challenge duration (default to 5 minutes).
* @param {string} networkPassphrase The network passphrase. If you pass this argument then timeout is required.
* @param {string} webAuthDomain The fully qualified domain name of the service issuing the challenge.
* @example
* import { Utils, Keypair, Networks } from 'stellar-sdk'
*
* let serverKeyPair = Keypair.fromSecret("server-secret")
* let challenge = Utils.buildChallengeTx(serverKeyPair, "client-stellar-account-id", "SDF", 300, Networks.TESTNET)
* let challenge = Utils.buildChallengeTx(serverKeyPair, "client-stellar-account-id", "stellar.org", 300, Networks.TESTNET)
* @returns {string} A base64 encoded string of the raw TransactionEnvelope xdr struct for the transaction.
*/
export function buildChallengeTx(
Expand All @@ -42,6 +43,7 @@ export namespace Utils {
homeDomain: string,
timeout: number = 300,
networkPassphrase: string,
webAuthDomain: string,
): string {
if (clientAccountID.startsWith("M")) {
throw Error(
Expand Down Expand Up @@ -74,6 +76,13 @@ export namespace Utils {
source: clientAccountID,
}),
)
.addOperation(
Operation.manageData({
name: "web_auth_domain",
value: webAuthDomain,
source: account.accountId(),
}),
)
.build();

transaction.sign(serverKeypair);
Expand Down Expand Up @@ -103,13 +112,15 @@ export namespace Utils {
* @param {string} serverAccountID The server's stellar account (public key).
* @param {string} networkPassphrase The network passphrase, e.g.: 'Test SDF Network ; September 2015'.
* @param {string|string[]} [homeDomains] The home domain that is expected to be included in the first Manage Data operation's string key. If an array is provided, one of the domain names in the array must match.
* @param {string} webAuthDomain The home domain that is expected to be included as the value of the Manage Data operation with the 'web_auth_domain' key. If no such operation is included, this parameter is not used.
* @returns {Transaction|string|string} The actual transaction and the stellar public key (master key) used to sign the Manage Data operation, and matched home domain.
*/
export function readChallengeTx(
challengeTx: string,
serverAccountID: string,
networkPassphrase: string,
homeDomains: string | string[],
webAuthDomain: string,
): { tx: Transaction; clientAccountID: string; matchedHomeDomain: string } {
if (serverAccountID.startsWith("M")) {
throw Error(
Expand Down Expand Up @@ -182,7 +193,7 @@ export namespace Utils {

if (operation.value === undefined) {
throw new InvalidSep10ChallengeError(
"The transaction's operation value should not be null",
"The transaction's operation values should not be null",
);
}

Expand Down Expand Up @@ -234,6 +245,19 @@ export namespace Utils {
"The transaction has operations that are unrecognized",
);
}
if (op.value === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @leighmcculloch @JakeUrban, I have some confusion about this, does the SEP-10 protocol specify that the values in other ManageData Operation cannot be null?

Copy link
Member

@leighmcculloch leighmcculloch Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, it does not specify this and shouldn't reject a challenge transaction because of this. This is a bug.

Copy link
Contributor Author

@JakeUrban JakeUrban Jan 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. I'll fix it today, before release.

throw new InvalidSep10ChallengeError(
"The transaction's operation values should not be null",
);
}
if (
op.name === "web_auth_domain" &&
op.value.compare(Buffer.from(webAuthDomain))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love it. 🚀

Initially I wasn't sure if this would work because compare returns a -1, 0, or 1 depending how the two buffers sort, but TIL it does work because:

> !!-1
true
> !!1
true
> !!0
false

) {
throw new InvalidSep10ChallengeError(
`'web_auth_domain' operation value does not match ${webAuthDomain}`,
);
}
}

return { tx: transaction, clientAccountID, matchedHomeDomain };
Expand Down Expand Up @@ -266,6 +290,7 @@ export namespace Utils {
* @param {number} threshold The required signatures threshold for verifying this transaction.
* @param {ServerApi.AccountRecordSigners[]} signerSummary a map of all authorized signers to their weights. It's used to validate if the transaction signatures have met the given threshold.
* @param {string|string[]} [homeDomains] The home domain(s) that should be included in the first Manage Data operation's string key. Required in verifyChallengeTxSigners() => readChallengeTx().
* @param {string} webAuthDomain The home domain that is expected to be included as the value of the Manage Data operation with the 'web_auth_domain' key, if present. Used in verifyChallengeTxSigners() => readChallengeTx().
* @returns {string[]} The list of signers public keys that have signed the transaction, excluding the server account ID, given that the threshold was met.
* @example
*
Expand Down Expand Up @@ -317,6 +342,7 @@ export namespace Utils {
threshold: number,
signerSummary: ServerApi.AccountRecordSigners[],
homeDomains: string | string[],
webAuthDomain: string,
): string[] {
const signers = signerSummary.map((signer) => signer.key);

Expand All @@ -326,6 +352,7 @@ export namespace Utils {
networkPassphrase,
signers,
homeDomains,
webAuthDomain,
);

let weight = 0;
Expand Down Expand Up @@ -369,6 +396,7 @@ export namespace Utils {
* @param {string} networkPassphrase The network passphrase, e.g.: 'Test SDF Network ; September 2015'.
* @param {string[]} signers The signers public keys. This list should contain the public keys for all signers that have signed the transaction.
* @param {string|string[]} [homeDomains] The home domain(s) that should be included in the first Manage Data operation's string key. Required in readChallengeTx().
* @param {string} webAuthDomain The home domain that is expected to be included as the value of the Manage Data operation with the 'web_auth_domain' key, if present. Used in readChallengeTx().
* @returns {string[]} The list of signers public keys that have signed the transaction, excluding the server account ID.
* @example
*
Expand Down Expand Up @@ -406,13 +434,15 @@ export namespace Utils {
networkPassphrase: string,
signers: string[],
homeDomains: string | string[],
webAuthDomain: string,
): string[] {
// Read the transaction which validates its structure.
const { tx } = readChallengeTx(
challengeTx,
serverAccountID,
networkPassphrase,
homeDomains,
webAuthDomain,
);

// Ensure the server account ID is an address and not a seed.
Expand Down
Loading