From 70ea9156f6916f32e40adf7464322476a9acd8ab Mon Sep 17 00:00:00 2001 From: ahsan-javaid Date: Thu, 5 May 2022 17:53:42 +0500 Subject: [PATCH] fix: allow referrer header in request options --- packages/common/src/fetchUtil.ts | 36 ++++++++++++++++--- packages/common/tests/fetch.test.ts | 28 +++++++++++++++ packages/keychain/src/identity.ts | 3 +- packages/keychain/src/profiles.ts | 5 +-- packages/keychain/src/utils/gaia.ts | 27 +++++++------- packages/keychain/src/utils/index.ts | 6 ++-- packages/keychain/src/wallet/index.ts | 4 +-- packages/transactions/src/utils.ts | 14 ++------ .../wallet-sdk/tests/models/wallet.test.ts | 7 ++++ 9 files changed, 94 insertions(+), 36 deletions(-) create mode 100644 packages/common/tests/fetch.test.ts diff --git a/packages/common/src/fetchUtil.ts b/packages/common/src/fetchUtil.ts index 4b36e5fca..a4ed8bfcd 100644 --- a/packages/common/src/fetchUtil.ts +++ b/packages/common/src/fetchUtil.ts @@ -1,12 +1,38 @@ import 'cross-fetch/polyfill'; +// Define a default request options and allow modification using getters, setters +// Reference: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request +const defaultFetchOpts: RequestInit = { + // By default referrer value will be client:origin: above reference link + referrerPolicy: 'origin', // Use origin value for referrer policy + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy +}; +/* + * Get fetch options + * @return fetchOptions + */ +export const getFetchOptions = () => { + return defaultFetchOpts; +}; +/* + * Set fetch options + * Users can change default referrer as well as other options when fetch is used internally by stacks.js libraries or from server side + * @example + * Reference: https://developer.mozilla.org/en-US/docs/Web/API/Request/Request + * setFetchOptions({ referrer: 'no-referrer', referrerPolicy: 'no-referrer', ... other options as per above reference }); + * Now all the subsequent fetchPrivate will use above options + * @return fetchOptions + */ +export const setFetchOptions = (ops: RequestInit) => { + return Object.assign(defaultFetchOpts, ops); +}; + /** @ignore */ export async function fetchPrivate(input: RequestInfo, init?: RequestInit): Promise { - const defaultFetchOpts: RequestInit = { - referrer: 'no-referrer', - referrerPolicy: 'no-referrer', - }; - const fetchOpts = Object.assign(defaultFetchOpts, init); + const fetchOpts = {}; + // Use the provided options in request options along with default or user provided values + Object.assign(fetchOpts, init, defaultFetchOpts); + const fetchResult = await fetch(input, fetchOpts); return fetchResult; } diff --git a/packages/common/tests/fetch.test.ts b/packages/common/tests/fetch.test.ts new file mode 100644 index 000000000..36ea6b3f6 --- /dev/null +++ b/packages/common/tests/fetch.test.ts @@ -0,0 +1,28 @@ +import { fetchPrivate, getFetchOptions, setFetchOptions } from '../src' +import fetchMock from "jest-fetch-mock"; + +test('Verify fetch private options', async () => { + const defaultOptioins = getFetchOptions(); + + expect(defaultOptioins).toEqual({ referrerPolicy: 'origin' }); + + // Override default options when fetchPrivate is called internally by other stacks.js libraries like transactions or from server side + // This is for developers as they cannot directly pass options directly in fetchPrivate + const modifiedOptions: RequestInit= { referrer: 'http://test.com', referrerPolicy: 'same-origin' }; + + // Developers can set fetch options globally one time specifically when fetchPrivate is used internally by stacks.js libraries + setFetchOptions(modifiedOptions); + + expect(getFetchOptions()).toEqual(modifiedOptions); + + // Browser will replace about:client with actual url but it will not be visible in test case + fetchMock.mockOnce(`{ status: 'success'}`, { headers: modifiedOptions as any }); + + const result = await fetchPrivate('https://example.com'); + + // Verify the request options + expect(result.status).toEqual(200); + expect(result.headers.get('referrer')).toEqual(modifiedOptions.referrer); + expect(result.headers.get('referrerPolicy')).toEqual(modifiedOptions.referrerPolicy); +}) + diff --git a/packages/keychain/src/identity.ts b/packages/keychain/src/identity.ts index f006ba791..d1498073c 100644 --- a/packages/keychain/src/identity.ts +++ b/packages/keychain/src/identity.ts @@ -5,6 +5,7 @@ import { Identity as IdentifyInterface, Profile } from './common'; import IdentityAddressOwnerNode from './nodes/identity-address-owner-node'; import { DEFAULT_PROFILE, fetchProfile, signAndUploadProfile } from './profiles'; import { getProfileURLFromZoneFile, IdentityKeyPair } from './utils'; +import { fetchPrivate } from '@stacks/common'; import { connectToGaiaHubWithConfig, DEFAULT_GAIA_HUB, @@ -131,7 +132,7 @@ export class Identity implements IdentifyInterface { async fetchNames() { const getNamesUrl = `https://stacks-node-api.stacks.co/v1/addresses/bitcoin/${this.address}`; - const res = await fetch(getNamesUrl); + const res = await fetchPrivate(getNamesUrl); const data = await res.json(); const { names }: { names: string[] } = data; return names; diff --git a/packages/keychain/src/profiles.ts b/packages/keychain/src/profiles.ts index 57136f6ce..64e112f30 100644 --- a/packages/keychain/src/profiles.ts +++ b/packages/keychain/src/profiles.ts @@ -4,6 +4,7 @@ import { Identity, Profile } from './common'; import { IdentityKeyPair } from './utils'; import { uploadToGaiaHub } from './utils/gaia'; import { GaiaHubConfig } from '@stacks/storage'; +import { fetchPrivate } from '@stacks/common'; export const DEFAULT_PROFILE: Profile = { '@type': 'Person', @@ -81,7 +82,7 @@ const sendUsernameToRegistrar = async ({ 'Content-Type': 'application/json', }; - const response = await fetch(registerUrl, { + const response = await fetchPrivate(registerUrl, { method: 'POST', headers: requestHeaders, body: registrationRequestBody, @@ -156,7 +157,7 @@ export const fetchProfile = async ({ }) => { try { const url = await identity.profileUrl(gaiaUrl); - const res = await fetch(url); + const res = await fetchPrivate(url); if (res.ok) { const json = await res.json(); const { decodedToken } = json[0]; diff --git a/packages/keychain/src/utils/gaia.ts b/packages/keychain/src/utils/gaia.ts index 8c5b912a8..45ec8428c 100644 --- a/packages/keychain/src/utils/gaia.ts +++ b/packages/keychain/src/utils/gaia.ts @@ -1,5 +1,5 @@ // @ts-ignore -import { Buffer } from '@stacks/common'; +import { Buffer, fetchPrivate } from '@stacks/common'; import { TokenSigner, Json } from 'jsontokens'; import { getPublicKeyFromPrivate, publicKeyToAddress } from '@stacks/encryption'; import randomBytes from 'randombytes'; @@ -13,7 +13,7 @@ interface HubInfo { } export const getHubInfo = async (hubUrl: string) => { - const response = await fetch(`${hubUrl}/hub_info`); + const response = await fetchPrivate(`${hubUrl}/hub_info`); const data: HubInfo = await response.json(); return data; }; @@ -115,16 +115,19 @@ export const uploadToGaiaHub = async ( ): Promise => { const contentType = 'application/json'; - const response = await fetch(`${hubConfig.server}/store/${hubConfig.address}/${filename}`, { - method: 'POST', - headers: { - 'Content-Type': contentType, - Authorization: `bearer ${hubConfig.token}`, - }, - body: contents, - referrer: 'no-referrer', - referrerPolicy: 'no-referrer', - }); + const response = await fetchPrivate( + `${hubConfig.server}/store/${hubConfig.address}/${filename}`, + { + method: 'POST', + headers: { + 'Content-Type': contentType, + Authorization: `bearer ${hubConfig.token}`, + }, + body: contents, + referrer: 'no-referrer', + referrerPolicy: 'no-referrer', + } + ); const { publicURL } = await response.json(); return publicURL; }; diff --git a/packages/keychain/src/utils/index.ts b/packages/keychain/src/utils/index.ts index b970facd4..a2be2c059 100644 --- a/packages/keychain/src/utils/index.ts +++ b/packages/keychain/src/utils/index.ts @@ -1,4 +1,4 @@ -import { Buffer } from '@stacks/common'; +import { Buffer, fetchPrivate } from '@stacks/common'; import { BIP32Interface } from 'bitcoinjs-lib'; import IdentityAddressOwnerNode from '../nodes/identity-address-owner-node'; import { createSha2Hash, publicKeyToAddress } from '@stacks/encryption'; @@ -185,7 +185,7 @@ export const validateSubdomainAvailability = async ( subdomain: Subdomains = Subdomains.BLOCKSTACK ) => { const url = `${registrars[subdomain].apiUrl}/${name.toLowerCase()}.${subdomain}`; - const resp = await fetch(url); + const resp = await fetchPrivate(url); const data = await resp.json(); return data; }; @@ -249,7 +249,7 @@ interface NameInfoResponse { export const getProfileURLFromZoneFile = async (name: string) => { const url = `https://stacks-node-api.stacks.co/v1/names/${name}`; - const res = await fetch(url); + const res = await fetchPrivate(url); if (res.ok) { const nameInfo: NameInfoResponse = await res.json(); const zone = parseZoneFile(nameInfo.zonefile); diff --git a/packages/keychain/src/wallet/index.ts b/packages/keychain/src/wallet/index.ts index 255f29879..149ccd006 100644 --- a/packages/keychain/src/wallet/index.ts +++ b/packages/keychain/src/wallet/index.ts @@ -1,4 +1,4 @@ -import { Buffer } from '@stacks/common'; +import { Buffer, fetchPrivate } from '@stacks/common'; import { mnemonicToSeed } from 'bip39'; import { bip32, BIP32Interface } from 'bitcoinjs-lib'; import { ChainID } from '@stacks/transactions'; @@ -227,7 +227,7 @@ export class Wallet { async fetchConfig(gaiaConfig: GaiaHubConfig): Promise { try { - const response = await fetch( + const response = await fetchPrivate( `${gaiaConfig.url_prefix}${gaiaConfig.address}/wallet-config.json` ); const encrypted = await response.text(); diff --git a/packages/transactions/src/utils.ts b/packages/transactions/src/utils.ts index a777a7e55..fa9b91a29 100644 --- a/packages/transactions/src/utils.ts +++ b/packages/transactions/src/utils.ts @@ -1,10 +1,9 @@ -import { Buffer } from '@stacks/common'; +import { Buffer, fetchPrivate } from '@stacks/common'; import { sha256, sha512 } from 'sha.js'; import { ClarityValue, serializeCV } from './clarity'; import RIPEMD160 from 'ripemd160-min'; import { utils } from '@noble/secp256k1'; import { deserializeCV } from './clarity'; -import fetch from 'cross-fetch'; import { c32addressDecode } from 'c32check'; import lodashCloneDeep from 'lodash.clonedeep'; import { with0x } from '@stacks/common'; @@ -199,15 +198,8 @@ export function isClarityName(name: string) { } /** @ignore */ -export async function fetchPrivate(input: RequestInfo, init?: RequestInit): Promise { - const defaultFetchOpts: RequestInit = { - referrer: 'no-referrer', - referrerPolicy: 'no-referrer', - }; - const fetchOpts = Object.assign(defaultFetchOpts, init); - const fetchResult = await fetch(input, fetchOpts); - return fetchResult; -} +export { fetchPrivate }; + /** * Converts a clarity value to a hex encoded string with `0x` prefix * @param {ClarityValue} cv - the clarity value to convert diff --git a/packages/wallet-sdk/tests/models/wallet.test.ts b/packages/wallet-sdk/tests/models/wallet.test.ts index 9d781a73d..152e2910a 100644 --- a/packages/wallet-sdk/tests/models/wallet.test.ts +++ b/packages/wallet-sdk/tests/models/wallet.test.ts @@ -43,6 +43,13 @@ test('restore wallet with username owned by stx private key', async () => { fetchMock .once(mockGaiaHubInfo) .once(JSON.stringify('no found'), { status: 404 }) // TODO mock fetch legacy wallet config + .once(JSON.stringify({ // mock wallet-config.json + iv: "a39461ec18e5dea8ac759d5b8141319d", + ephemeralPK: "03e04d0046a9dd5b7a8fea4de55a8d912909738b26f2c72f8e5962217fa45f00bb", + cipherText: "bf16d2da29b54a153d553ab99597096f3fa11bd964441927355c1d979bf614477c8ceba7098620a37fa98f92f0f79813f771b6e2c2087e6fd2b1675d98a5e14f28cba28134dac2913bcb06f469439a16b47778747c7e93f50169727a7b9053b5441c8645fc729b28f063d2ffd673a01342e2cbc4fbf0e05350a67ec53ee3b7e43ff4e2dddbded5868cc3f5c4ca323b621bd13b5f9f036dc4406c2418e98b1b905974479cc79ab102d9ba1eb7fe858987dd0777ed3b0356d6bd0bc775213ef878bffaa58c40365d831a9e436fbfc388bcff2909659cab38a65ae8512508f6fda247437d4819c98ea15e48a4a00c1594eb58f7bf7eb85aad7cd51e5b43a7ca1fec06385be125d0b8c07bac1ac1094bb4687e620f23a5a14f4b20674ccd198271eb2451f12ad294efa79a9b5a001a3682a5ec833140b78333f57ce9912f60ff94edf99ee0b5e59ddfe7fb4a1472c0d303eeab22471585a5a5689ed9779e4ded10a4feecf5107df4c847522b2d6c95ed1e45cccb8b834b47a79f6671b49ffbdb02e4887465ca521472b7f11a53be0221eaeeffed2c6cf4d17a6fdae4b8f2b963d8a102c5376e6fa01bdaf9dc3d544c9954090b23fc02c8f500319b0cc43d7f73ff5012514d473afc818967eb0d0837a9c6920f9bc39e5f49fefdbc4fa33e6be88820a1abaeacb836bd398e7031d4286121383f53e2873ea1c2f0b649a12aec7db049505c58323fb34aaaf8d59fc0b962df05b8e9ea0dabf7fc9923b25af9ff3bd08a0b2dea7462a9e889485aba8605592308847468e843fca721a70aae9528d4abaae1a539f57c624f06b5b7dfdcf0a9d94b509697a1d0020b5f0b60ab19cc6abaf14928612663a9b6f15e18174a0a31fc506c428df13889fd877b7b639106d72c1b9dc8509758035337776c9d2d489da61f8de92a880b8ab802bd098fea111ab6af59fadd275285c59a98f824d5023990664856f971a6928869d3447f4426cb6855be55e43778f65a77e4d4348da0eda3e45e56a5d8fd11aff6ec62f53eac1cd862", + mac: "623331e6804bf8c0dee7db7894a84ed02c70bf1ec0b6a5b2ccaff7d0638a9d88", + wasString: true + })) .once(JSON.stringify({ names: ['public_profile_for_testing.id.blockstack'] })) .once(JSON.stringify('ok')); // updateWalletConfig