diff --git a/config/polyfills.js b/config/polyfills.js index 31ba0c86..c705ed32 100644 --- a/config/polyfills.js +++ b/config/polyfills.js @@ -1 +1,2 @@ global.regeneratorRuntime = require("regenerator-runtime"); +global.fetch = require("jest-fetch-mock"); diff --git a/jest.config.js b/jest.config.js index 5d31ac2a..ded9d381 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,6 +4,7 @@ module.exports = { // Automatically clear mock calls and instances between every test clearMocks: true, + automock: false, setupFiles: ["/config/polyfills.js"], diff --git a/package.json b/package.json index b7f2ed4f..895edddb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@stellar/wallet-sdk", - "version": "0.0.4-rc.4", + "version": "0.0.5-rc.1", "description": "Libraries to help you write Stellar-enabled wallets in Javascript", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -53,6 +53,7 @@ "concurrently": "^4.1.1", "husky": "^1.3.1", "jest": "^24.7.1", + "jest-fetch-mock": "^2.1.2", "lint-staged": "^8.1.5", "prettier": "^1.17.0", "regenerator-runtime": "^0.13.2", diff --git a/playground/src/App.js b/playground/src/App.js index 166cb92e..681791c2 100644 --- a/playground/src/App.js +++ b/playground/src/App.js @@ -11,8 +11,6 @@ import Offers from "components/Offers"; import Trades from "components/Trades"; import Transfers from "components/Transfers"; -StellarSdk.Network.usePublicNetwork(); - const El = styled.div` display: flex; font-size: 0.8em; diff --git a/src/KeyManager.test.ts b/src/KeyManager.test.ts index 8577eb32..9eec453a 100644 --- a/src/KeyManager.test.ts +++ b/src/KeyManager.test.ts @@ -61,12 +61,11 @@ describe("KeyManager", function() { const testKeyManager = new KeyManager({ keyStore: testStore, }); + const network = StellarBase.Networks.TESTNET; testKeyManager.registerEncrypter(IdentityEncrypter); - StellarBase.Network.useTestNetwork(); - - const keypair = StellarBase.Keypair.master(); + const keypair = StellarBase.Keypair.master(network); // save this key const keyMetadata = await testKeyManager.storeKey({ @@ -74,6 +73,7 @@ describe("KeyManager", function() { type: KeyType.plaintextKey, publicKey: keypair.publicKey(), privateKey: keypair.secret(), + network, }, password: "test", encrypterName: "IdentityEncrypter", @@ -84,7 +84,10 @@ describe("KeyManager", function() { "0", ); - const transaction = new StellarBase.TransactionBuilder(source, { fee: 100 }) + const transaction = new StellarBase.TransactionBuilder(source, { + fee: 100, + networkPassphrase: network, + }) .addOperation(StellarBase.Operation.inflation({})) .setTimeout(StellarBase.TimeoutInfinite) .build(); @@ -102,4 +105,123 @@ describe("KeyManager", function() { ), ).toEqual(true); }); + + describe("getAuthToken", () => { + beforeEach(() => { + // @ts-ignore + fetch.resetMocks(); + }); + + test("Throws errors for missing params", async () => { + // set up the manager + const testStore = new MemoryKeyStore(); + const testKeyManager = new KeyManager({ + keyStore: testStore, + }); + try { + // @ts-ignore + await testKeyManager.getAuthToken({}); + expect("This test failed").toBe(null); + } catch (e) { + expect(e).toBeTruthy(); + } + }); + + test("Rejects challenges that return errors", async () => { + const authServer = "https://www.stellar.org/auth"; + const error = "Test error"; + const password = "very secure password"; + + // @ts-ignore + fetch.mockResponseOnce( + JSON.stringify({ + error, + }), + ); + + // set up the manager + const testStore = new MemoryKeyStore(); + const testKeyManager = new KeyManager({ + keyStore: testStore, + }); + const network = StellarBase.Networks.TESTNET; + + testKeyManager.registerEncrypter(IdentityEncrypter); + + const keypair = StellarBase.Keypair.master(network); + + // save this key + const keyMetadata = await testKeyManager.storeKey({ + key: { + type: KeyType.plaintextKey, + publicKey: keypair.publicKey(), + privateKey: keypair.secret(), + network, + }, + password, + encrypterName: "IdentityEncrypter", + }); + + try { + await testKeyManager.getAuthToken({ + id: keyMetadata.id, + password, + authServer, + }); + + expect("This test failed").toBe(null); + } catch (e) { + expect(e.toString()).toBe(`Error: ${error}`); + } + }); + + test("Rejects challenges with network mismatches", async () => { + const authServer = "https://www.stellar.org/auth"; + const password = "very secure password"; + + const keyNetwork = StellarBase.Networks.TESTNET; + const challengeNetwork = StellarBase.Networks.PUBLIC; + + // @ts-ignore + fetch.mockResponseOnce( + JSON.stringify({ + network_passphrase: challengeNetwork, + }), + ); + + // set up the manager + const testStore = new MemoryKeyStore(); + const testKeyManager = new KeyManager({ + keyStore: testStore, + }); + + testKeyManager.registerEncrypter(IdentityEncrypter); + + const keypair = StellarBase.Keypair.master(keyNetwork); + + // save this key + const keyMetadata = await testKeyManager.storeKey({ + key: { + type: KeyType.plaintextKey, + publicKey: keypair.publicKey(), + privateKey: keypair.secret(), + network: keyNetwork, + }, + password, + encrypterName: "IdentityEncrypter", + }); + + try { + await testKeyManager.getAuthToken({ + id: keyMetadata.id, + password, + authServer, + }); + + expect("This test failed").toBe(null); + } catch (e) { + expect(e.toString()).toMatch(`Network mismatch`); + } + }); + }); }); diff --git a/src/KeyManager.ts b/src/KeyManager.ts index 26c74158..22d122e1 100644 --- a/src/KeyManager.ts +++ b/src/KeyManager.ts @@ -1,5 +1,5 @@ import sha1 from "js-sha1"; -import { Network, Networks, Transaction } from "stellar-sdk"; +import { Networks, Transaction } from "stellar-sdk"; import { ledgerHandler } from "./keyTypeHandlers/ledger"; import { plaintextKeyHandler } from "./keyTypeHandlers/plaintextKey"; @@ -20,6 +20,7 @@ import { export interface KeyManagerParams { keyStore: KeyStore; + defaultNetworkPassphrase?: string; shouldCache?: boolean; } @@ -79,6 +80,7 @@ export class KeyManager { private keyHandlerMap: { [key: string]: KeyTypeHandler }; private keyCache: { [id: string]: Key }; private shouldCache: boolean; + private defaultNetworkPassphrase: string; constructor(params: KeyManagerParams) { this.encrypterMap = {}; @@ -91,12 +93,23 @@ export class KeyManager { this.keyStore = params.keyStore; this.shouldCache = params.shouldCache || false; + + this.defaultNetworkPassphrase = + params.defaultNetworkPassphrase || Networks.PUBLIC; } + /** + * Register a KeyTypeHandler for a given key type. + * @param {KeyTypeHandler} keyHandler + */ public registerKeyHandler(keyHandler: KeyTypeHandler) { this.keyHandlerMap[keyHandler.keyType] = keyHandler; } + /** + * Register a new encrypter. + * @param {Encrypter} encrypter + */ public registerEncrypter(encrypter: Encrypter) { this.encrypterMap[encrypter.name] = encrypter; } @@ -263,6 +276,8 @@ export class KeyManager { `${authServer}?account=${encodeURIComponent(key.publicKey)}`, ); + const keyNetwork = key.network || this.defaultNetworkPassphrase; + const { transaction, network_passphrase, @@ -273,12 +288,20 @@ export class KeyManager { throw new Error(error); } - Network.use(new Network(network_passphrase || Networks.PUBLIC)); + // Throw error when network_passphrase is returned, and doesn't match + if (network_passphrase !== undefined && keyNetwork !== network_passphrase) { + throw new Error( + ` + Network mismatch: the transfer server expects "${network_passphrase}", + but you're using "${keyNetwork}" + `, + ); + } const keyHandler = this.keyHandlerMap[key.type]; const signedTransaction = await keyHandler.signTransaction({ - transaction: new Transaction(transaction), + transaction: new Transaction(transaction, keyNetwork), key, }); diff --git a/src/types/keys.ts b/src/types/keys.ts index a721820f..f5bfc475 100644 --- a/src/types/keys.ts +++ b/src/types/keys.ts @@ -5,6 +5,8 @@ export interface BaseKey { extra?: any; path?: string; publicKey: string; + // if the network is not set, the key is assumed to be on Networks.PUBLIC + network?: string; type: KeyType | string; } diff --git a/yarn.lock b/yarn.lock index e4a2eca5..a0b39645 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1777,6 +1777,14 @@ crc@^3.5.0: dependencies: buffer "^5.1.0" +cross-fetch@^2.2.2: + version "2.2.3" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e" + integrity sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw== + dependencies: + node-fetch "2.1.2" + whatwg-fetch "2.0.4" + cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -3120,6 +3128,14 @@ jest-environment-node@^24.7.1: jest-mock "^24.7.0" jest-util "^24.7.1" +jest-fetch-mock@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-2.1.2.tgz#1260b347918e3931c4ec743ceaf60433da661bd0" + integrity sha512-tcSR4Lh2bWLe1+0w/IwvNxeDocMI/6yIA2bijZ0fyWxC4kQ18lckQ1n7Yd40NKuisGmcGBRFPandRXrW/ti/Bw== + dependencies: + cross-fetch "^2.2.2" + promise-polyfill "^7.1.1" + jest-get-type@^24.3.0: version "24.3.0" resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da" @@ -3973,6 +3989,11 @@ node-cleanup@^2.1.2: resolved "https://registry.yarnpkg.com/node-cleanup/-/node-cleanup-2.1.2.tgz#7ac19abd297e09a7f72a71545d951b517e4dde2c" integrity sha1-esGavSl+Caf3KnFUXZUbUX5N3iw= +node-fetch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= + node-gyp-build@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.0.tgz#3bc3dd7dd4aafecaf64a2e3729e785bc3cdea565" @@ -4452,6 +4473,11 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise-polyfill@^7.1.1: + version "7.1.2" + resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b" + integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ== + prompts@^2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.0.4.tgz#179f9d4db3128b9933aa35f93a800d8fce76a682" @@ -5728,6 +5754,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3: dependencies: iconv-lite "0.4.24" +whatwg-fetch@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" + integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== + whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"