From 95bc4b473d8d8e9e637ed2c72a0b99ef2e056dcf Mon Sep 17 00:00:00 2001 From: sisou Date: Wed, 15 Apr 2020 11:14:55 +0200 Subject: [PATCH 1/4] Small cleanup, condition alignment --- src/lib/RequestParser.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index 814a299b0..c1309e7d4 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -144,15 +144,11 @@ export class RequestParser { } if (!checkoutRequest.callbackUrl || typeof checkoutRequest.callbackUrl !== 'string') { - if (checkoutRequest.paymentOptions.some( - (option) => option.currency !== Currency.NIM, - )) { + if (checkoutRequest.paymentOptions.some((option) => option.currency !== Currency.NIM)) { throw new Error('A callbackUrl: string is required for currencies other than NIM to ' + 'monitor payments.'); } - if (!checkoutRequest.paymentOptions.every( - (option) => !!option.protocolSpecific.recipient, - )) { + if (!checkoutRequest.paymentOptions.every((option) => !!option.protocolSpecific.recipient)) { throw new Error('A callbackUrl: string or all recipients must be provided'); } } else { @@ -206,13 +202,12 @@ export class RequestParser { case Currency.NIM: // Once extraData from MultiCurrencyCheckoutRequest is removed // the next few lines become obsolete. - if (!option.protocolSpecific.extraData) { + if (!option.protocolSpecific.extraData && checkoutRequest.extraData) { + console.warn('Usage of MultiCurrencyCheckoutRequest.extraData is' + + ' deprecated. Use NimiqDirectPaymentOptions.protocolSpecific' + + '.extraData instead'); + option.protocolSpecific.extraData = checkoutRequest.extraData; - if (option.protocolSpecific.extraData) { - console.warn('Usage of MultiCurrencyCheckoutRequest.extraData is' - + ' deprecated. Use NimiqDirectPaymentOptions.protocolSpecific' - + '.extraData instead'); - } } return new ParsedNimiqDirectPaymentOptions(option); case Currency.ETH: @@ -228,6 +223,8 @@ export class RequestParser { }), } as ParsedCheckoutRequest; } + + throw new Error('Invalid version: must be 1 or 2'); case RequestType.ONBOARD: const onboardRequest = request as OnboardRequest; return { From 19e0f68f65fe7cce4448e73a183b7c251b80b15c Mon Sep 17 00:00:00 2001 From: sisou Date: Wed, 15 Apr 2020 11:54:44 +0200 Subject: [PATCH 2/4] Allow Checkout without NIM option for CPL --- src/config/config.local.ts | 1 + src/config/config.mainnet.ts | 3 +++ src/config/config.testnet.ts | 3 +++ src/lib/RequestParser.ts | 8 ++++++-- src/lib/RpcApi.ts | 2 +- src/views/Checkout.vue | 9 ++++++++- src/views/SignTransactionLedger.vue | 8 ++++++-- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/config/config.local.ts b/src/config/config.local.ts index 5ab725aeb..fd8e5fd95 100644 --- a/src/config/config.local.ts +++ b/src/config/config.local.ts @@ -7,4 +7,5 @@ export default { privilegedOrigins: [ '*' ], redirectTarget: window.location.protocol + '//' + window.location.hostname + ':8080/demos.html', reportToSentry: false, + allowsCheckoutWithoutNim: (origin: string): boolean => true, }; diff --git a/src/config/config.mainnet.ts b/src/config/config.mainnet.ts index 282b0a601..d8c2cab54 100644 --- a/src/config/config.mainnet.ts +++ b/src/config/config.mainnet.ts @@ -13,4 +13,7 @@ export default { ], redirectTarget: 'https://safe.nimiq.com', reportToSentry: true, + allowsCheckoutWithoutNim: (origin: string): boolean => [ + 'https://vendor.cryptopayment.link', + ].includes(origin), }; diff --git a/src/config/config.testnet.ts b/src/config/config.testnet.ts index 857cc110b..6b095f177 100644 --- a/src/config/config.testnet.ts +++ b/src/config/config.testnet.ts @@ -13,4 +13,7 @@ export default { ], redirectTarget: 'https://safe.nimiq-testnet.com', reportToSentry: true, + allowsCheckoutWithoutNim: (origin: string): boolean => [ + 'https://checkout-service-staging-0.web.app', + ].includes(origin), }; diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index c1309e7d4..a7308cd95 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -36,6 +36,7 @@ import { ParsedNimiqDirectPaymentOptions } from './paymentOptions/NimiqPaymentOp import { ParsedEtherDirectPaymentOptions } from './paymentOptions/EtherPaymentOptions'; import { ParsedBitcoinDirectPaymentOptions } from './paymentOptions/BitcoinPaymentOptions'; import { Utf8Tools } from '@nimiq/utils'; +import config from 'config'; export class RequestParser { public static parse( @@ -118,9 +119,12 @@ export class RequestParser { } if (checkoutRequest.version === 2) { - if (!checkoutRequest.paymentOptions.some((option) => option.currency === Currency.NIM)) { - throw new Error('CheckoutRequest must provide a NIM paymentOption.'); + if (!config.allowsCheckoutWithoutNim(state.origin)) { + if (!checkoutRequest.paymentOptions.some((option) => option.currency === Currency.NIM)) { + throw new Error('CheckoutRequest must provide a NIM paymentOption.'); + } } + if (!checkoutRequest.shopLogoUrl) { throw new Error('shopLogoUrl: string is required'); // shop logo non optional in version 2 } diff --git a/src/lib/RpcApi.ts b/src/lib/RpcApi.ts index 1b10fd278..d08a1035c 100644 --- a/src/lib/RpcApi.ts +++ b/src/lib/RpcApi.ts @@ -292,7 +292,7 @@ export default class RpcApi { } } else if (requestType === RequestType.CHECKOUT) { const checkoutRequest = request as ParsedCheckoutRequest; - // forceSender only applies to non-multi-currency checkouts. + // forceSender only applies to NIM-only checkouts. if (checkoutRequest.paymentOptions.length === 1 && checkoutRequest.paymentOptions[0].currency === Currency.NIM) { diff --git a/src/views/Checkout.vue b/src/views/Checkout.vue index e08509847..274a888b2 100644 --- a/src/views/Checkout.vue +++ b/src/views/Checkout.vue @@ -97,7 +97,7 @@ class Checkout extends Vue { @Static private rpcState!: RpcState; @Static private request!: ParsedCheckoutRequest; private choosenCurrency: PublicCurrency | null = null; - private selectedCurrency: PublicCurrency = PublicCurrency.NIM; + private selectedCurrency: PublicCurrency | null = null; private leftCard: PublicCurrency | null = null; private rightCard: PublicCurrency | null = null; private initialCurrencies: PublicCurrency[] = []; @@ -109,6 +109,8 @@ class Checkout extends Vue { @Watch('selectedCurrency', { immediate: true }) private updateUnselected() { + if (!this.selectedCurrency) return; + const entries = this.request.paymentOptions.map((paymentOptions) => paymentOptions.currency); if (entries.length === 1) return; @@ -132,6 +134,11 @@ class Checkout extends Vue { || history.state[HISTORY_KEY_SELECTED_CURRENCY] === currency, ); this.availableCurrencies = [ ...this.initialCurrencies ]; + if (this.availableCurrencies.includes(PublicCurrency.NIM)) { + this.selectedCurrency = PublicCurrency.NIM; + } else { + this.selectedCurrency = this.availableCurrencies[0]; + } document.title = 'Nimiq Checkout'; const lastDisclaimerClose = parseInt(window.localStorage[Checkout.DISCLAIMER_CLOSED_STORAGE_KEY], 10); this.disclaimerRecentlyClosed = !Number.isNaN(lastDisclaimerClose) diff --git a/src/views/SignTransactionLedger.vue b/src/views/SignTransactionLedger.vue index fa1afa423..86691a786 100644 --- a/src/views/SignTransactionLedger.vue +++ b/src/views/SignTransactionLedger.vue @@ -219,7 +219,11 @@ export default class SignTransactionLedger extends Vue { if (checkoutRequest.callbackUrl && checkoutRequest.csrf) { try { const fetchedPaymentOptions = await CheckoutServerApi.fetchPaymentOption( - checkoutRequest.callbackUrl, Currency.NIM, checkoutPaymentOptions.type, checkoutRequest.csrf); + checkoutRequest.callbackUrl, + checkoutPaymentOptions.currency, + checkoutPaymentOptions.type, + checkoutRequest.csrf, + ); checkoutPaymentOptions.update(fetchedPaymentOptions); } catch (e) { this.$rpc.reject(e); @@ -375,7 +379,7 @@ export default class SignTransactionLedger extends Vue { if (this.request.kind !== RequestType.CHECKOUT) return null; const checkoutRequest = this.request as ParsedCheckoutRequest; return checkoutRequest.paymentOptions.find( - (option) => option.currency === Currency.NIM, + (option) => option.currency === Currency.NIM, // TODO: Also handle BTC payments ) as ParsedNimiqDirectPaymentOptions; } From 03a2cc782c901ea039858e0e3b3aef60c789cd00 Mon Sep 17 00:00:00 2001 From: sisou Date: Fri, 17 Apr 2020 10:28:22 +0200 Subject: [PATCH 3/4] Refactor origin checking helper function --- src/config/config.local.ts | 2 +- src/config/config.mainnet.ts | 4 ++-- src/config/config.testnet.ts | 4 ++-- src/lib/Helpers.ts | 4 ++-- src/lib/RequestParser.ts | 15 ++++++++------- src/lib/RpcApi.ts | 8 +++++--- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/config/config.local.ts b/src/config/config.local.ts index fd8e5fd95..c83514cc6 100644 --- a/src/config/config.local.ts +++ b/src/config/config.local.ts @@ -7,5 +7,5 @@ export default { privilegedOrigins: [ '*' ], redirectTarget: window.location.protocol + '//' + window.location.hostname + ':8080/demos.html', reportToSentry: false, - allowsCheckoutWithoutNim: (origin: string): boolean => true, + checkoutWithoutNimOrigins: [ '*' ], }; diff --git a/src/config/config.mainnet.ts b/src/config/config.mainnet.ts index d8c2cab54..804443f41 100644 --- a/src/config/config.mainnet.ts +++ b/src/config/config.mainnet.ts @@ -13,7 +13,7 @@ export default { ], redirectTarget: 'https://safe.nimiq.com', reportToSentry: true, - allowsCheckoutWithoutNim: (origin: string): boolean => [ + checkoutWithoutNimOrigins: [ 'https://vendor.cryptopayment.link', - ].includes(origin), + ], }; diff --git a/src/config/config.testnet.ts b/src/config/config.testnet.ts index 6b095f177..b806c21b9 100644 --- a/src/config/config.testnet.ts +++ b/src/config/config.testnet.ts @@ -13,7 +13,7 @@ export default { ], redirectTarget: 'https://safe.nimiq-testnet.com', reportToSentry: true, - allowsCheckoutWithoutNim: (origin: string): boolean => [ + checkoutWithoutNimOrigins: [ 'https://checkout-service-staging-0.web.app', - ].includes(origin), + ], }; diff --git a/src/lib/Helpers.ts b/src/lib/Helpers.ts index dbeddfb33..b9a766ec0 100644 --- a/src/lib/Helpers.ts +++ b/src/lib/Helpers.ts @@ -45,8 +45,8 @@ export const loadNimiq = async () => { } }; -export function isPriviledgedOrigin(origin: string) { - return Config.privilegedOrigins.includes(origin) || Config.privilegedOrigins.includes('*'); +export function includesOrigin(list: string[], origin: string) { + return list.includes(origin) || list.includes('*'); } export function isDesktop() { diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index a7308cd95..2ad421f8f 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -1,4 +1,4 @@ -import { isMilliseconds, isPriviledgedOrigin } from './Helpers'; +import { isMilliseconds, includesOrigin } from './Helpers'; import { State } from '@nimiq/rpc'; import { BasicRequest, @@ -36,7 +36,7 @@ import { ParsedNimiqDirectPaymentOptions } from './paymentOptions/NimiqPaymentOp import { ParsedEtherDirectPaymentOptions } from './paymentOptions/EtherPaymentOptions'; import { ParsedBitcoinDirectPaymentOptions } from './paymentOptions/BitcoinPaymentOptions'; import { Utf8Tools } from '@nimiq/utils'; -import config from 'config'; +import Config from 'config'; export class RequestParser { public static parse( @@ -119,10 +119,11 @@ export class RequestParser { } if (checkoutRequest.version === 2) { - if (!config.allowsCheckoutWithoutNim(state.origin)) { - if (!checkoutRequest.paymentOptions.some((option) => option.currency === Currency.NIM)) { - throw new Error('CheckoutRequest must provide a NIM paymentOption.'); - } + if ( // Check if the origin is allowed to make requests without a NIM payment option + !includesOrigin(Config.checkoutWithoutNimOrigins, state.origin) + && !checkoutRequest.paymentOptions.some((option) => option.currency === Currency.NIM) + ) { + throw new Error('CheckoutRequest must provide a NIM paymentOption.'); } if (!checkoutRequest.shopLogoUrl) { @@ -333,7 +334,7 @@ export class RequestParser { } const returnLink = !!createCashlinkRequest.returnLink; - if (returnLink && !isPriviledgedOrigin(state.origin)) { + if (returnLink && !includesOrigin(Config.privilegedOrigins, state.origin)) { throw new Error(`Origin ${state.origin} is not authorized to request returnLink.`); } const skipSharing = !!createCashlinkRequest.returnLink && !!createCashlinkRequest.skipSharing; diff --git a/src/lib/RpcApi.ts b/src/lib/RpcApi.ts index d08a1035c..ceab12779 100644 --- a/src/lib/RpcApi.ts +++ b/src/lib/RpcApi.ts @@ -27,7 +27,7 @@ import Cashlink from '@/lib/Cashlink'; import CookieJar from '@/lib/CookieJar'; import { captureException } from '@sentry/browser'; import { ERROR_CANCELED } from './Constants'; -import { isPriviledgedOrigin } from '@/lib/Helpers'; +import { includesOrigin } from '@/lib/Helpers'; import Config from 'config'; import { setHistoryStorage, getHistoryStorage } from '@/lib/Helpers'; @@ -241,8 +241,10 @@ export default class RpcApi { private async _hubApiHandler(requestType: RequestType, state: RpcState, arg: RpcRequest) { let request; - // Check that a non-whitelisted request comes from a privileged origin - if (!this._3rdPartyRequestWhitelist.includes(requestType) && !isPriviledgedOrigin(state.origin)) { + if ( // Check that a non-whitelisted request comes from a privileged origin + !this._3rdPartyRequestWhitelist.includes(requestType) + && !includesOrigin(Config.privilegedOrigins, state.origin) + ) { state.reply(ResponseStatus.ERROR, new Error(`${state.origin} is unauthorized to call ${requestType}`)); return; } From 8cd3db4ffc503935eca4bd21cb940fc904f5dcbc Mon Sep 17 00:00:00 2001 From: sisou Date: Fri, 17 Apr 2020 10:28:36 +0200 Subject: [PATCH 4/4] Indentation, non-immediate watcher --- src/lib/RequestParser.ts | 4 ++-- src/views/Checkout.vue | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/RequestParser.ts b/src/lib/RequestParser.ts index 2ad421f8f..70747d2d0 100644 --- a/src/lib/RequestParser.ts +++ b/src/lib/RequestParser.ts @@ -209,8 +209,8 @@ export class RequestParser { // the next few lines become obsolete. if (!option.protocolSpecific.extraData && checkoutRequest.extraData) { console.warn('Usage of MultiCurrencyCheckoutRequest.extraData is' - + ' deprecated. Use NimiqDirectPaymentOptions.protocolSpecific' - + '.extraData instead'); + + ' deprecated. Use NimiqDirectPaymentOptions.protocolSpecific' + + '.extraData instead'); option.protocolSpecific.extraData = checkoutRequest.extraData; } diff --git a/src/views/Checkout.vue b/src/views/Checkout.vue index 274a888b2..7a62c0f48 100644 --- a/src/views/Checkout.vue +++ b/src/views/Checkout.vue @@ -107,14 +107,12 @@ class Checkout extends Vue { private screenFitsDisclaimer: boolean = true; private dimensionsUpdateTimeout: number = -1; - @Watch('selectedCurrency', { immediate: true }) + @Watch('selectedCurrency') private updateUnselected() { - if (!this.selectedCurrency) return; - const entries = this.request.paymentOptions.map((paymentOptions) => paymentOptions.currency); if (entries.length === 1) return; - const indexSelected = entries.indexOf(this.selectedCurrency); + const indexSelected = entries.indexOf(this.selectedCurrency!); if (entries.length === 2) { // We have two cards. Determine whether the non selected is to the left or to the right. this.leftCard = indexSelected === 1 ? entries[0] : null;