@stellar/typescript-wallet-sdk 4.0.0 → 4.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.MD +13 -0
- package/lib/bundle.js +59 -15
- package/lib/bundle.js.map +1 -1
- package/lib/bundle_browser.js +59 -15
- package/lib/bundle_browser.js.map +1 -1
- package/lib/walletSdk/Exceptions/index.d.ts +9 -0
- package/lib/walletSdk/Server/index.d.ts +17 -1
- package/lib/walletSdk/Types/auth.d.ts +5 -0
- package/package.json +1 -1
- package/src/walletSdk/Exceptions/index.ts +19 -0
- package/src/walletSdk/Server/index.ts +54 -11
- package/src/walletSdk/Types/auth.ts +5 -0
- package/test/server.test.ts +219 -1
|
@@ -115,9 +115,18 @@ export declare class NetworkPassphraseMismatchError extends Error {
|
|
|
115
115
|
export declare class ChallengeTxnIncorrectSequenceError extends Error {
|
|
116
116
|
constructor();
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* @deprecated No longer thrown. `Server.signChallengeTransaction` now validates
|
|
120
|
+
* challenges with `WebAuth.readChallengeTx` and reports every failure, including
|
|
121
|
+
* an invalid server signature, as `ChallengeValidationFailedError`. This class
|
|
122
|
+
* will be removed in the next major release.
|
|
123
|
+
*/
|
|
118
124
|
export declare class ChallengeTxnInvalidSignatureError extends Error {
|
|
119
125
|
constructor();
|
|
120
126
|
}
|
|
127
|
+
export declare class ChallengeTxnClientAccountMismatchError extends Error {
|
|
128
|
+
constructor(expected: string, received: string);
|
|
129
|
+
}
|
|
121
130
|
export declare class AllowHttpOnNonTestnetError extends Error {
|
|
122
131
|
constructor();
|
|
123
132
|
}
|
|
@@ -5,14 +5,30 @@
|
|
|
5
5
|
import { SignChallengeTxnParams, SignChallengeTxnResponse, AnchorTransaction } from "../Types";
|
|
6
6
|
/**
|
|
7
7
|
* Helper method for signing a SEP-10 challenge transaction if valid.
|
|
8
|
+
*
|
|
9
|
+
* The challenge is validated with `WebAuth.readChallengeTx` from
|
|
10
|
+
* `@stellar/stellar-sdk` — the same validator used by `Sep10.sign()` — which
|
|
11
|
+
* checks the full SEP-10 structure and binds the challenge to the expected
|
|
12
|
+
* anchor: the transaction source must be the anchor's `SIGNING_KEY`, the
|
|
13
|
+
* `<home domain> auth` operation must match the expected home domain, and any
|
|
14
|
+
* `web_auth_domain` operation must match the host of the anchor's
|
|
15
|
+
* `WEB_AUTH_ENDPOINT` (falling back to `anchorDomain` when the TOML omits it).
|
|
16
|
+
* The client account the challenge was issued for is then checked against
|
|
17
|
+
* `accountKp`, so this helper only signs challenges meant for it. Muxed client
|
|
18
|
+
* accounts are supported: the comparison is made on the underlying `G...`
|
|
19
|
+
* address, since that is the key that signs. The muxed id itself is not pinned
|
|
20
|
+
* — a caller that needs to bind a specific subaccount should check the
|
|
21
|
+
* challenge's client account itself.
|
|
22
|
+
*
|
|
8
23
|
* @param {SignChallengeTxnParams} params - The Authentication params.
|
|
9
24
|
* @param {AccountKeypair} params.accountKp - Keypair for the Stellar account signing the transaction.
|
|
10
25
|
* @param {string} [params.challengeTx] - The challenge transaction given by an anchor for authentication.
|
|
11
26
|
* @param {string} [params.networkPassphrase] - The network passphrase for the network authenticating on.
|
|
12
27
|
* @param {string} [params.anchorDomain] - Domain hosting stellar.toml file containing `SIGNING_KEY`.
|
|
28
|
+
* @param {string|string[]} [params.homeDomain] - Expected home domain(s) of the challenge. Defaults to `anchorDomain`.
|
|
13
29
|
* @returns {Promise<SignChallengeTxnResponse>} The signed transaction.
|
|
14
30
|
*/
|
|
15
|
-
export declare const signChallengeTransaction: ({ accountKp, challengeTx, networkPassphrase, anchorDomain, }: SignChallengeTxnParams) => Promise<SignChallengeTxnResponse>;
|
|
31
|
+
export declare const signChallengeTransaction: ({ accountKp, challengeTx, networkPassphrase, anchorDomain, homeDomain, }: SignChallengeTxnParams) => Promise<SignChallengeTxnResponse>;
|
|
16
32
|
/**
|
|
17
33
|
* Helper method for parsing a JSON string into an AnchorTransaction.
|
|
18
34
|
* @param {string} transaction - The json string of an anchor transaction.
|
|
@@ -54,6 +54,11 @@ export type SignChallengeTxnParams = {
|
|
|
54
54
|
challengeTx: string;
|
|
55
55
|
networkPassphrase: string;
|
|
56
56
|
anchorDomain: string;
|
|
57
|
+
/**
|
|
58
|
+
* The home domain(s) the challenge is expected to be scoped to. Defaults to
|
|
59
|
+
* `anchorDomain`. Pass an array for anchors that serve multiple home domains.
|
|
60
|
+
*/
|
|
61
|
+
homeDomain?: string | string[];
|
|
57
62
|
};
|
|
58
63
|
export type SignChallengeTxnResponse = {
|
|
59
64
|
transaction: XdrEncodedTransaction;
|
package/package.json
CHANGED
|
@@ -312,6 +312,12 @@ export class ChallengeTxnIncorrectSequenceError extends Error {
|
|
|
312
312
|
}
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* @deprecated No longer thrown. `Server.signChallengeTransaction` now validates
|
|
317
|
+
* challenges with `WebAuth.readChallengeTx` and reports every failure, including
|
|
318
|
+
* an invalid server signature, as `ChallengeValidationFailedError`. This class
|
|
319
|
+
* will be removed in the next major release.
|
|
320
|
+
*/
|
|
315
321
|
export class ChallengeTxnInvalidSignatureError extends Error {
|
|
316
322
|
constructor() {
|
|
317
323
|
super("Invalid signature for challenge transaction");
|
|
@@ -319,6 +325,19 @@ export class ChallengeTxnInvalidSignatureError extends Error {
|
|
|
319
325
|
}
|
|
320
326
|
}
|
|
321
327
|
|
|
328
|
+
export class ChallengeTxnClientAccountMismatchError extends Error {
|
|
329
|
+
constructor(expected: string, received: string) {
|
|
330
|
+
super(
|
|
331
|
+
`Challenge transaction client account mismatch: expected "${expected}" ` +
|
|
332
|
+
`but challenge was issued for "${received}"`,
|
|
333
|
+
);
|
|
334
|
+
Object.setPrototypeOf(
|
|
335
|
+
this,
|
|
336
|
+
ChallengeTxnClientAccountMismatchError.prototype,
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
322
341
|
export class AllowHttpOnNonTestnetError extends Error {
|
|
323
342
|
constructor() {
|
|
324
343
|
super("Can only allow Http on Testnet");
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
import {
|
|
7
7
|
Transaction,
|
|
8
8
|
TransactionBuilder,
|
|
9
|
-
Keypair,
|
|
10
9
|
StellarToml,
|
|
10
|
+
WebAuth,
|
|
11
|
+
extractBaseAddress,
|
|
11
12
|
} from "@stellar/stellar-sdk";
|
|
12
13
|
|
|
13
14
|
import { parseToml } from "../Utils";
|
|
@@ -21,18 +22,35 @@ import {
|
|
|
21
22
|
} from "../Types";
|
|
22
23
|
import {
|
|
23
24
|
ChallengeTxnIncorrectSequenceError,
|
|
24
|
-
|
|
25
|
+
ChallengeTxnClientAccountMismatchError,
|
|
26
|
+
ChallengeValidationFailedError,
|
|
25
27
|
UnknownAnchorTransactionError,
|
|
26
28
|
InvalidJsonError,
|
|
27
29
|
} from "../Exceptions";
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Helper method for signing a SEP-10 challenge transaction if valid.
|
|
33
|
+
*
|
|
34
|
+
* The challenge is validated with `WebAuth.readChallengeTx` from
|
|
35
|
+
* `@stellar/stellar-sdk` — the same validator used by `Sep10.sign()` — which
|
|
36
|
+
* checks the full SEP-10 structure and binds the challenge to the expected
|
|
37
|
+
* anchor: the transaction source must be the anchor's `SIGNING_KEY`, the
|
|
38
|
+
* `<home domain> auth` operation must match the expected home domain, and any
|
|
39
|
+
* `web_auth_domain` operation must match the host of the anchor's
|
|
40
|
+
* `WEB_AUTH_ENDPOINT` (falling back to `anchorDomain` when the TOML omits it).
|
|
41
|
+
* The client account the challenge was issued for is then checked against
|
|
42
|
+
* `accountKp`, so this helper only signs challenges meant for it. Muxed client
|
|
43
|
+
* accounts are supported: the comparison is made on the underlying `G...`
|
|
44
|
+
* address, since that is the key that signs. The muxed id itself is not pinned
|
|
45
|
+
* — a caller that needs to bind a specific subaccount should check the
|
|
46
|
+
* challenge's client account itself.
|
|
47
|
+
*
|
|
31
48
|
* @param {SignChallengeTxnParams} params - The Authentication params.
|
|
32
49
|
* @param {AccountKeypair} params.accountKp - Keypair for the Stellar account signing the transaction.
|
|
33
50
|
* @param {string} [params.challengeTx] - The challenge transaction given by an anchor for authentication.
|
|
34
51
|
* @param {string} [params.networkPassphrase] - The network passphrase for the network authenticating on.
|
|
35
52
|
* @param {string} [params.anchorDomain] - Domain hosting stellar.toml file containing `SIGNING_KEY`.
|
|
53
|
+
* @param {string|string[]} [params.homeDomain] - Expected home domain(s) of the challenge. Defaults to `anchorDomain`.
|
|
36
54
|
* @returns {Promise<SignChallengeTxnResponse>} The signed transaction.
|
|
37
55
|
*/
|
|
38
56
|
export const signChallengeTransaction = async ({
|
|
@@ -40,25 +58,50 @@ export const signChallengeTransaction = async ({
|
|
|
40
58
|
challengeTx,
|
|
41
59
|
networkPassphrase,
|
|
42
60
|
anchorDomain,
|
|
61
|
+
homeDomain,
|
|
43
62
|
}: SignChallengeTxnParams): Promise<SignChallengeTxnResponse> => {
|
|
44
|
-
const
|
|
63
|
+
const parsedTx = TransactionBuilder.fromXDR(
|
|
45
64
|
challengeTx,
|
|
46
65
|
networkPassphrase,
|
|
47
66
|
) as Transaction;
|
|
48
67
|
|
|
49
|
-
if (parseInt(
|
|
68
|
+
if (parseInt(parsedTx.sequence) !== 0) {
|
|
50
69
|
throw new ChallengeTxnIncorrectSequenceError();
|
|
51
70
|
}
|
|
52
71
|
|
|
53
72
|
const tomlResp = await StellarToml.Resolver.resolve(anchorDomain);
|
|
54
|
-
const
|
|
55
|
-
const anchorKp = Keypair.fromPublicKey(parsedToml.signingKey);
|
|
73
|
+
const { signingKey, webAuthEndpoint } = parseToml(tomlResp);
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
let tx: Transaction;
|
|
76
|
+
let clientAccountID: string;
|
|
77
|
+
try {
|
|
78
|
+
// Kept inside the try so a malformed WEB_AUTH_ENDPOINT surfaces as a
|
|
79
|
+
// ChallengeValidationFailedError rather than a raw TypeError.
|
|
80
|
+
const webAuthDomain = webAuthEndpoint
|
|
81
|
+
? new URL(webAuthEndpoint).hostname
|
|
82
|
+
: anchorDomain;
|
|
83
|
+
|
|
84
|
+
({ tx, clientAccountID } = WebAuth.readChallengeTx(
|
|
85
|
+
challengeTx,
|
|
86
|
+
signingKey,
|
|
87
|
+
networkPassphrase,
|
|
88
|
+
homeDomain ?? anchorDomain,
|
|
89
|
+
webAuthDomain,
|
|
90
|
+
));
|
|
91
|
+
} catch (e) {
|
|
92
|
+
throw new ChallengeValidationFailedError(
|
|
93
|
+
e instanceof Error ? e : new Error(String(e)),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// readChallengeTx returns the operation source verbatim, which is an `M...`
|
|
98
|
+
// address for a muxed client account. accountKp.publicKey is always the
|
|
99
|
+
// underlying `G...` key, so compare base addresses.
|
|
100
|
+
if (extractBaseAddress(clientAccountID) !== accountKp.publicKey) {
|
|
101
|
+
throw new ChallengeTxnClientAccountMismatchError(
|
|
102
|
+
accountKp.publicKey,
|
|
103
|
+
clientAccountID,
|
|
104
|
+
);
|
|
62
105
|
}
|
|
63
106
|
|
|
64
107
|
accountKp.sign(tx);
|
|
@@ -94,6 +94,11 @@ export type SignChallengeTxnParams = {
|
|
|
94
94
|
challengeTx: string;
|
|
95
95
|
networkPassphrase: string;
|
|
96
96
|
anchorDomain: string;
|
|
97
|
+
/**
|
|
98
|
+
* The home domain(s) the challenge is expected to be scoped to. Defaults to
|
|
99
|
+
* `anchorDomain`. Pass an array for anchors that serve multiple home domains.
|
|
100
|
+
*/
|
|
101
|
+
homeDomain?: string | string[];
|
|
97
102
|
};
|
|
98
103
|
|
|
99
104
|
export type SignChallengeTxnResponse = {
|
package/test/server.test.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import
|
|
2
|
+
import sinon from "sinon";
|
|
3
|
+
import {
|
|
4
|
+
Account,
|
|
5
|
+
Asset,
|
|
6
|
+
Keypair,
|
|
7
|
+
MuxedAccount,
|
|
8
|
+
Operation,
|
|
9
|
+
StellarToml,
|
|
10
|
+
Transaction,
|
|
11
|
+
TransactionBuilder,
|
|
12
|
+
WebAuth,
|
|
13
|
+
} from "@stellar/stellar-sdk";
|
|
3
14
|
import { Wallet, Server } from "../src";
|
|
15
|
+
import {
|
|
16
|
+
ChallengeTxnClientAccountMismatchError,
|
|
17
|
+
ChallengeValidationFailedError,
|
|
18
|
+
} from "../src/walletSdk/Exceptions";
|
|
4
19
|
|
|
5
20
|
let wallet;
|
|
6
21
|
let account;
|
|
@@ -78,3 +93,206 @@ describe("Server helpers", () => {
|
|
|
78
93
|
expect(parsed.kind).toBe("withdrawal");
|
|
79
94
|
});
|
|
80
95
|
});
|
|
96
|
+
|
|
97
|
+
describe("signChallengeTransaction validation", () => {
|
|
98
|
+
const anchorA = "anchor-a.example.com";
|
|
99
|
+
const anchorB = "anchor-b.example.com";
|
|
100
|
+
|
|
101
|
+
let anchorAKp;
|
|
102
|
+
let anchorBKp;
|
|
103
|
+
let accountKp;
|
|
104
|
+
|
|
105
|
+
beforeEach(() => {
|
|
106
|
+
anchorAKp = Keypair.random();
|
|
107
|
+
anchorBKp = Keypair.random();
|
|
108
|
+
accountKp = Wallet.TestNet().stellar().account().createKeypair();
|
|
109
|
+
|
|
110
|
+
// anchorDomain always resolves to anchor A's stellar.toml
|
|
111
|
+
sinon.stub(StellarToml.Resolver, "resolve").resolves({
|
|
112
|
+
SIGNING_KEY: anchorAKp.publicKey(),
|
|
113
|
+
WEB_AUTH_ENDPOINT: `https://${anchorA}/auth`,
|
|
114
|
+
DOCUMENTATION: {},
|
|
115
|
+
} as StellarToml.Api.StellarToml);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
afterEach(() => {
|
|
119
|
+
sinon.restore();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// webAuthDomain defaults to anchor A, which is what the stubbed
|
|
123
|
+
// WEB_AUTH_ENDPOINT resolves to for anchorDomain.
|
|
124
|
+
const buildChallenge = (
|
|
125
|
+
serverKp,
|
|
126
|
+
homeDomain,
|
|
127
|
+
clientAccountID,
|
|
128
|
+
webAuthDomain = anchorA,
|
|
129
|
+
) =>
|
|
130
|
+
WebAuth.buildChallengeTx(
|
|
131
|
+
serverKp,
|
|
132
|
+
clientAccountID,
|
|
133
|
+
homeDomain,
|
|
134
|
+
300,
|
|
135
|
+
networkPassphrase,
|
|
136
|
+
webAuthDomain,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
it("should sign a challenge issued by the expected anchor", async () => {
|
|
140
|
+
const challengeTx = buildChallenge(anchorAKp, anchorA, accountKp.publicKey);
|
|
141
|
+
|
|
142
|
+
const signedResp = await Server.signChallengeTransaction({
|
|
143
|
+
accountKp,
|
|
144
|
+
challengeTx,
|
|
145
|
+
networkPassphrase,
|
|
146
|
+
anchorDomain: anchorA,
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const signedTxn = TransactionBuilder.fromXDR(
|
|
150
|
+
signedResp.transaction,
|
|
151
|
+
networkPassphrase,
|
|
152
|
+
);
|
|
153
|
+
expect(signedTxn.signatures.length).toBe(2);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should reject a challenge issued by a different anchor", async () => {
|
|
157
|
+
// A relays a challenge that anchor B issued for the same account, ordering
|
|
158
|
+
// its own signature first so that signatures[0] matches the SIGNING_KEY
|
|
159
|
+
// published at anchorDomain. Only the home domain, web auth domain and
|
|
160
|
+
// transaction source bind the challenge to the anchor that issued it.
|
|
161
|
+
const relayed = TransactionBuilder.fromXDR(
|
|
162
|
+
buildChallenge(anchorBKp, anchorB, accountKp.publicKey, anchorB),
|
|
163
|
+
networkPassphrase,
|
|
164
|
+
) as Transaction;
|
|
165
|
+
|
|
166
|
+
const anchorBSignature = relayed.signatures[0];
|
|
167
|
+
relayed.signatures.length = 0;
|
|
168
|
+
relayed.sign(anchorAKp);
|
|
169
|
+
relayed.signatures.push(anchorBSignature);
|
|
170
|
+
|
|
171
|
+
await expect(
|
|
172
|
+
Server.signChallengeTransaction({
|
|
173
|
+
accountKp,
|
|
174
|
+
challengeTx: relayed.toXDR(),
|
|
175
|
+
networkPassphrase,
|
|
176
|
+
anchorDomain: anchorA,
|
|
177
|
+
}),
|
|
178
|
+
).rejects.toThrow(ChallengeValidationFailedError);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should sign a challenge issued for a muxed account of the signing key", async () => {
|
|
182
|
+
// readChallengeTx returns the M... source verbatim; the underlying G... key
|
|
183
|
+
// is the correct signer, so this must be accepted.
|
|
184
|
+
const muxed = new MuxedAccount(
|
|
185
|
+
new Account(accountKp.publicKey, "0"),
|
|
186
|
+
"1234",
|
|
187
|
+
).accountId();
|
|
188
|
+
expect(muxed.startsWith("M")).toBe(true);
|
|
189
|
+
|
|
190
|
+
const challengeTx = buildChallenge(anchorAKp, anchorA, muxed);
|
|
191
|
+
|
|
192
|
+
const signedResp = await Server.signChallengeTransaction({
|
|
193
|
+
accountKp,
|
|
194
|
+
challengeTx,
|
|
195
|
+
networkPassphrase,
|
|
196
|
+
anchorDomain: anchorA,
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const signedTxn = TransactionBuilder.fromXDR(
|
|
200
|
+
signedResp.transaction,
|
|
201
|
+
networkPassphrase,
|
|
202
|
+
);
|
|
203
|
+
expect(signedTxn.signatures.length).toBe(2);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should reject a muxed challenge whose underlying key is not the signer", async () => {
|
|
207
|
+
const otherMuxed = new MuxedAccount(
|
|
208
|
+
new Account(Keypair.random().publicKey(), "0"),
|
|
209
|
+
"1234",
|
|
210
|
+
).accountId();
|
|
211
|
+
|
|
212
|
+
await expect(
|
|
213
|
+
Server.signChallengeTransaction({
|
|
214
|
+
accountKp,
|
|
215
|
+
challengeTx: buildChallenge(anchorAKp, anchorA, otherMuxed),
|
|
216
|
+
networkPassphrase,
|
|
217
|
+
anchorDomain: anchorA,
|
|
218
|
+
}),
|
|
219
|
+
).rejects.toThrow(ChallengeTxnClientAccountMismatchError);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("should reject a challenge issued for a different account", async () => {
|
|
223
|
+
const otherAccount = Keypair.random().publicKey();
|
|
224
|
+
const challengeTx = buildChallenge(anchorAKp, anchorA, otherAccount);
|
|
225
|
+
|
|
226
|
+
await expect(
|
|
227
|
+
Server.signChallengeTransaction({
|
|
228
|
+
accountKp,
|
|
229
|
+
challengeTx,
|
|
230
|
+
networkPassphrase,
|
|
231
|
+
anchorDomain: anchorA,
|
|
232
|
+
}),
|
|
233
|
+
).rejects.toThrow(ChallengeTxnClientAccountMismatchError);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("should reject a transaction that is not a SEP-10 challenge", async () => {
|
|
237
|
+
const notAChallenge = new TransactionBuilder(
|
|
238
|
+
new Account(anchorAKp.publicKey(), "-1"),
|
|
239
|
+
{ fee: "100", networkPassphrase },
|
|
240
|
+
)
|
|
241
|
+
.addOperation(
|
|
242
|
+
Operation.payment({
|
|
243
|
+
destination: anchorAKp.publicKey(),
|
|
244
|
+
asset: Asset.native(),
|
|
245
|
+
amount: "1000",
|
|
246
|
+
}),
|
|
247
|
+
)
|
|
248
|
+
.setTimeout(300)
|
|
249
|
+
.build();
|
|
250
|
+
notAChallenge.sign(anchorAKp);
|
|
251
|
+
|
|
252
|
+
await expect(
|
|
253
|
+
Server.signChallengeTransaction({
|
|
254
|
+
accountKp,
|
|
255
|
+
challengeTx: notAChallenge.toXDR(),
|
|
256
|
+
networkPassphrase,
|
|
257
|
+
anchorDomain: anchorA,
|
|
258
|
+
}),
|
|
259
|
+
).rejects.toThrow(ChallengeValidationFailedError);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("should wrap a malformed WEB_AUTH_ENDPOINT as a validation failure", async () => {
|
|
263
|
+
sinon.restore();
|
|
264
|
+
sinon.stub(StellarToml.Resolver, "resolve").resolves({
|
|
265
|
+
SIGNING_KEY: anchorAKp.publicKey(),
|
|
266
|
+
WEB_AUTH_ENDPOINT: "not a url",
|
|
267
|
+
DOCUMENTATION: {},
|
|
268
|
+
} as StellarToml.Api.StellarToml);
|
|
269
|
+
|
|
270
|
+
await expect(
|
|
271
|
+
Server.signChallengeTransaction({
|
|
272
|
+
accountKp,
|
|
273
|
+
challengeTx: buildChallenge(anchorAKp, anchorA, accountKp.publicKey),
|
|
274
|
+
networkPassphrase,
|
|
275
|
+
anchorDomain: anchorA,
|
|
276
|
+
}),
|
|
277
|
+
).rejects.toThrow(ChallengeValidationFailedError);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should accept an explicit homeDomain that differs from anchorDomain", async () => {
|
|
281
|
+
const homeDomain = "wallet-home.example.com";
|
|
282
|
+
const challengeTx = buildChallenge(
|
|
283
|
+
anchorAKp,
|
|
284
|
+
homeDomain,
|
|
285
|
+
accountKp.publicKey,
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
const signedResp = await Server.signChallengeTransaction({
|
|
289
|
+
accountKp,
|
|
290
|
+
challengeTx,
|
|
291
|
+
networkPassphrase,
|
|
292
|
+
anchorDomain: anchorA,
|
|
293
|
+
homeDomain,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
expect(signedResp.networkPassphrase).toBe(networkPassphrase);
|
|
297
|
+
});
|
|
298
|
+
});
|