From b50b69e8b166700bafe636d9ab0304949654aa5f Mon Sep 17 00:00:00 2001 From: koekiebox Date: Thu, 6 Feb 2025 15:36:54 +0100 Subject: [PATCH 1/2] feat(3180): exclude certain wallet addresses from being created. --- packages/backend/src/config/app.ts | 14 ++++++++- .../open_payments/wallet_address/service.ts | 31 +++++++++++++++---- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 26e6dcdc13..718221b364 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -22,6 +22,15 @@ function envInt(name: string, value: number): number { return envValue == null ? value : parseInt(envValue) } +function envRegExPatterns(name: string): RegExp[] { + const envValue = process.env[name] + return envValue == null || envValue.trim().length == 0 + ? [] + : envValue.split(',').map((pattern) => { + return new RegExp(pattern.trim()) + }) +} + function envFloat(name: string, value: number): number { const envValue = process.env[name] return envValue == null ? value : +envValue @@ -192,7 +201,10 @@ export const Config = { 'MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS', 5 ), - localCacheDuration: envInt('LOCAL_CACHE_DURATION_MS', 15_000) + localCacheDuration: envInt('LOCAL_CACHE_DURATION_MS', 15_000), + excludedWalletAddressPatterns: envRegExPatterns( + 'excluded_Wallet_Address_Patterns' + ) } function parseRedisTlsConfig( diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index b2b79bd5c3..87b77b3c53 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -130,23 +130,38 @@ export const FORBIDDEN_PATHS = [ '/quotes' ] -function isValidWalletAddressUrl(walletAddressUrl: string): boolean { +function isValidWalletAddressUrl( + walletAddressUrl: string, + config: IAppConfig +): boolean { try { const url = new URL(walletAddressUrl) if (url.protocol !== 'https:' || url.pathname === '/') { return false } for (const path of FORBIDDEN_PATHS) { - if (url.pathname.includes(path)) { - return false - } + if (url.pathname.includes(path)) return false } - return true + return !isWalletAddressInvalidPattern(walletAddressUrl, config) } catch (_) { return false } } +function isWalletAddressInvalidPattern( + walletAddressUrl: string, + config: IAppConfig +) { + if ( + !config.excludedWalletAddressPatterns || + config.excludedWalletAddressPatterns.length === 0 + ) + return false + return config.excludedWalletAddressPatterns.some((regex) => + regex.test(walletAddressUrl) + ) +} + function cleanAdditionalProperties( additionalProperties: WalletAddressAdditionalPropertyInput[] ): WalletAddressAdditionalPropertyInput[] { @@ -163,7 +178,7 @@ async function createWalletAddress( deps: ServiceDependencies, options: CreateOptions ): Promise { - if (!isValidWalletAddressUrl(options.url)) { + if (!isValidWalletAddressUrl(options.url, deps.config)) { return WalletAddressError.InvalidUrl } @@ -295,6 +310,8 @@ async function getOrPollByUrl( deps: ServiceDependencies, url: string ): Promise { + if (isWalletAddressInvalidPattern(url, deps.config)) return undefined + const existingWalletAddress = await getWalletAddressByUrl(deps, url) if (existingWalletAddress) return existingWalletAddress @@ -325,6 +342,8 @@ async function getWalletAddressByUrl( deps: ServiceDependencies, url: string ): Promise { + if (isWalletAddressInvalidPattern(url, deps.config)) return undefined + const walletAddress = await WalletAddress.query(deps.knex).findOne({ url: url.toLowerCase() }) From 5a3fc553acd8e4d3dcedf80c025365192873080a Mon Sep 17 00:00:00 2001 From: koekiebox Date: Mon, 10 Feb 2025 13:20:25 +0100 Subject: [PATCH 2/2] feat(3180): ignore wallet address where invalid url. --- packages/backend/src/config/app.ts | 2 +- .../wallet_address/service.test.ts | 38 +++++++++++++++---- .../open_payments/wallet_address/service.ts | 2 +- 3 files changed, 33 insertions(+), 9 deletions(-) diff --git a/packages/backend/src/config/app.ts b/packages/backend/src/config/app.ts index 718221b364..0ad90fca34 100644 --- a/packages/backend/src/config/app.ts +++ b/packages/backend/src/config/app.ts @@ -203,7 +203,7 @@ export const Config = { ), localCacheDuration: envInt('LOCAL_CACHE_DURATION_MS', 15_000), excludedWalletAddressPatterns: envRegExPatterns( - 'excluded_Wallet_Address_Patterns' + 'EXCLUDED_WALLET_ADDRESS_PATTERNS' ) } diff --git a/packages/backend/src/open_payments/wallet_address/service.test.ts b/packages/backend/src/open_payments/wallet_address/service.test.ts index b2b7245010..63387df2b8 100644 --- a/packages/backend/src/open_payments/wallet_address/service.test.ts +++ b/packages/backend/src/open_payments/wallet_address/service.test.ts @@ -38,7 +38,8 @@ describe('Open Payments Wallet Address Service', (): void => { beforeAll(async (): Promise => { deps = initIocContainer({ ...Config, - localCacheDuration: 0 + localCacheDuration: 0, + excludedWalletAddressPatterns: [/favicon\.ico$/] }) config = await deps.use('config') appContainer = await createTestApp(deps) @@ -96,11 +97,13 @@ describe('Open Payments Wallet Address Service', (): void => { }) test.each` - url | description - ${'not a url'} | ${'without a valid url'} - ${'http://alice.me/pay'} | ${'with a non-https url'} - ${'https://alice.me'} | ${'with a url without a path'} - ${'https://alice.me/'} | ${'with a url without a path'} + url | description + ${'not a url'} | ${'without a valid url'} + ${'http://alice.me/pay'} | ${'with a non-https url'} + ${'https://alice.me'} | ${'with a url without a path'} + ${'https://alice.me/'} | ${'with a url without a path'} + ${'https://alice.me/favicon.ico'} | ${'with a url using regex config'} + ${'https://alice.me/john/favicon.ico'} | ${'with a url with a path and regex config'} `( 'Wallet address cannot be created $description ($url)', async ({ url }): Promise => { @@ -452,7 +455,7 @@ describe('Open Payments Wallet Address Service', (): void => { ) }) - describe('Get Or Poll Wallet Addres By Url', (): void => { + describe('Get Or Poll Wallet Address By Url', (): void => { describe('existing wallet address', (): void => { test('can retrieve wallet address by url', async (): Promise => { const walletAddress = await createWalletAddress(deps) @@ -487,6 +490,27 @@ describe('Open Payments Wallet Address Service', (): void => { ) ) + test( + 'do not create wallet address not found event for invalid pattern', + withConfigOverride( + () => config, + { walletAddressLookupTimeoutMs: 0 }, + async (): Promise => { + const walletAddressUrl = `https://${faker.internet.domainName()}/.well-known/pay/favicon.ico` + await expect( + walletAddressService.getOrPollByUrl(walletAddressUrl) + ).resolves.toBeUndefined() + + const walletAddressNotFoundEvents = await WalletAddressEvent.query( + knex + ).where({ + type: WalletAddressEventType.WalletAddressNotFound + }) + expect(walletAddressNotFoundEvents.length).toEqual(0) + } + ) + ) + test( 'polls for wallet address', withConfigOverride( diff --git a/packages/backend/src/open_payments/wallet_address/service.ts b/packages/backend/src/open_payments/wallet_address/service.ts index 87b77b3c53..d43213e9fc 100644 --- a/packages/backend/src/open_payments/wallet_address/service.ts +++ b/packages/backend/src/open_payments/wallet_address/service.ts @@ -359,7 +359,7 @@ async function getWalletAddressPage( pagination?: Pagination, sortOrder?: SortOrder ): Promise { - return await WalletAddress.query(deps.knex) + return WalletAddress.query(deps.knex) .getPage(pagination, sortOrder) .withGraphFetched('asset') }