Skip to content

Commit ce17e0f

Browse files
authored
feat(backend): redirect to webpage when querying payment pointer in browser (#3298)
* feat(backend): redirect to webpage when querying payment pointer in browser * feat(backend): change redirect code to 302 * feat(backend): make redirect optional * feat(backend): move wallet redirect to middlware * feat(backend): trim redirect ending slashes * feat(backend): add test for trim trailing slashes on redirect page * chore(deps): update axios
1 parent 5c7f1fa commit ce17e0f

File tree

10 files changed

+340
-141
lines changed

10 files changed

+340
-141
lines changed

localenv/mock-account-servicing-entity/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"@remix-run/serve": "^2.15.3",
1818
"@types/node": "^18.7.12",
1919
"@types/uuid": "^9.0.8",
20-
"axios": "^1.7.9",
20+
"axios": "^1.8.2",
2121
"class-variance-authority": "^0.7.1",
2222
"graphql": "^16.8.1",
2323
"json-canonicalize": "^1.0.6",

packages/auth/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"@koa/cors": "^5.0.0",
3636
"@koa/router": "^12.0.2",
3737
"ajv": "^8.12.0",
38-
"axios": "^1.7.9",
38+
"axios": "^1.8.2",
3939
"dotenv": "^16.4.7",
4040
"graphql": "^16.8.1",
4141
"ioredis": "^5.3.2",

packages/backend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373
"@opentelemetry/sdk-node": "^0.52.1",
7474
"@opentelemetry/sdk-trace-node": "^1.25.1",
7575
"ajv": "^8.12.0",
76-
"axios": "1.7.9",
76+
"axios": "1.8.2",
7777
"base64url": "^3.0.1",
7878
"dotenv": "^16.4.7",
7979
"extensible-error": "^1.0.2",

packages/backend/src/app.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ import {
9494
getWalletAddressUrlFromQuote,
9595
getWalletAddressUrlFromRequestBody,
9696
getWalletAddressForSubresource,
97-
getWalletAddressUrlFromPath
97+
getWalletAddressUrlFromPath,
98+
redirectIfBrowserAcceptsHtml
9899
} from './open_payments/wallet_address/middleware'
99100

100101
import { LoggingPlugin } from './graphql/plugin'
@@ -673,6 +674,7 @@ export class App {
673674
router.get(
674675
WALLET_ADDRESS_PATH,
675676
getWalletAddressUrlFromPath,
677+
redirectIfBrowserAcceptsHtml,
676678
createSpspMiddleware(this.config.enableSpspPaymentPointers),
677679
createValidatorMiddleware(
678680
walletAddressServerSpec,

packages/backend/src/config/app.ts

+1
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export const Config = {
192192
'MAX_OUTGOING_PAYMENT_RETRY_ATTEMPTS',
193193
5
194194
),
195+
walletAddressRedirectHtmlPage: process.env.WALLET_ADDRESS_REDIRECT_HTML_PAGE,
195196
localCacheDuration: envInt('LOCAL_CACHE_DURATION_MS', 15_000)
196197
}
197198

packages/backend/src/open_payments/wallet_address/middleware.test.ts

+83-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
getWalletAddressUrlFromIncomingPayment,
66
getWalletAddressUrlFromQuote,
77
getWalletAddressUrlFromOutgoingPayment,
8-
getWalletAddressUrlFromPath
8+
getWalletAddressUrlFromPath,
9+
redirectIfBrowserAcceptsHtml
910
} from './middleware'
1011
import { Config } from '../../config/app'
1112
import { IocContract } from '@adonisjs/fold'
@@ -382,4 +383,85 @@ describe('Wallet Address Middleware', (): void => {
382383
expect(ctx.walletAddress).toEqual(walletAddress)
383384
})
384385
})
386+
387+
describe('redirectWalletAddress', () => {
388+
let ctx: WalletAddressContext
389+
let next: jest.MockedFunction<() => Promise<void>>
390+
const walletAddressPath = 'ilp.wallet/test'
391+
const walletAddressUrl = `https://${walletAddressPath}`
392+
const walletAddressRedirectHtmlPage = 'https://ilp.dev'
393+
394+
beforeEach((): void => {
395+
ctx = createContext({}, {})
396+
397+
next = jest.fn()
398+
})
399+
400+
test('redirects to wallet address url', async (): Promise<void> => {
401+
ctx.container = initIocContainer({
402+
...Config,
403+
walletAddressRedirectHtmlPage
404+
})
405+
ctx.walletAddressUrl = walletAddressUrl
406+
ctx.request.headers.accept = 'text/html'
407+
408+
await expect(
409+
redirectIfBrowserAcceptsHtml(ctx, next)
410+
).resolves.toBeUndefined()
411+
412+
expect(ctx.response.status).toBe(302)
413+
expect(ctx.response.get('Location')).toBe(
414+
`${walletAddressRedirectHtmlPage}/${walletAddressPath}`
415+
)
416+
expect(next).not.toHaveBeenCalled()
417+
})
418+
419+
test('no redirect to wallet address url if env is not set', async (): Promise<void> => {
420+
ctx.container = initIocContainer({
421+
...Config,
422+
walletAddressRedirectHtmlPage: undefined
423+
})
424+
ctx.walletAddressUrl = walletAddressUrl
425+
ctx.request.headers.accept = 'text/html'
426+
427+
await expect(
428+
redirectIfBrowserAcceptsHtml(ctx, next)
429+
).resolves.toBeUndefined()
430+
431+
expect(next).toHaveBeenCalled()
432+
})
433+
434+
test('no redirect to wallet address url if accept is not text/html', async (): Promise<void> => {
435+
ctx.container = initIocContainer({
436+
...Config,
437+
walletAddressRedirectHtmlPage
438+
})
439+
ctx.walletAddressUrl = walletAddressUrl
440+
441+
await expect(
442+
redirectIfBrowserAcceptsHtml(ctx, next)
443+
).resolves.toBeUndefined()
444+
445+
expect(next).toHaveBeenCalled()
446+
})
447+
448+
it('should trim trailing slashes from redirectHtmlPage', async (): Promise<void> => {
449+
ctx.container = initIocContainer({
450+
...Config,
451+
walletAddressRedirectHtmlPage: 'https://ilp.dev/'
452+
})
453+
ctx.walletAddressUrl = `${walletAddressUrl}`
454+
ctx.request.headers.accept = 'text/html'
455+
456+
await expect(
457+
redirectIfBrowserAcceptsHtml(ctx, next)
458+
).resolves.toBeUndefined()
459+
460+
expect(ctx.response.status).toBe(302)
461+
expect(ctx.response.get('Location')).toBe(
462+
`${walletAddressRedirectHtmlPage}/${walletAddressPath}`
463+
)
464+
expect(next).not.toHaveBeenCalled()
465+
})
466+
})
385467
})

packages/backend/src/open_payments/wallet_address/middleware.ts

+24
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,27 @@ export async function getWalletAddressForSubresource(
117117

118118
await next()
119119
}
120+
121+
export async function redirectIfBrowserAcceptsHtml(
122+
ctx: WalletAddressUrlContext,
123+
next: () => Promise<void>
124+
) {
125+
const config = await ctx.container.use('config')
126+
127+
if (
128+
config.walletAddressRedirectHtmlPage &&
129+
ctx.request.header['accept']?.includes('text/html')
130+
) {
131+
const walletAddressPath = ctx.walletAddressUrl.replace('https://', '')
132+
const redirectHtmlPage = config.walletAddressRedirectHtmlPage.replace(
133+
/\/+$/,
134+
''
135+
)
136+
137+
ctx.set('Location', `${redirectHtmlPage}/${walletAddressPath}`)
138+
ctx.status = 302
139+
return
140+
}
141+
142+
await next()
143+
}

packages/frontend/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"@remix-run/node": "^2.15.3",
2121
"@remix-run/react": "^2.15.3",
2222
"@remix-run/serve": "^2.15.3",
23-
"axios": "^1.7.9",
23+
"axios": "^1.8.2",
2424
"class-variance-authority": "^0.7.1",
2525
"graphql": "^16.8.1",
2626
"ilp-packet": "3.1.4-alpha.2",

packages/token-introspection/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
"dependencies": {
2828
"@interledger/open-payments": "6.13.2",
2929
"@interledger/openapi": "2.0.2",
30-
"axios": "^1.7.9",
30+
"axios": "^1.8.2",
3131
"pino": "^8.19.0"
3232
}
3333
}

0 commit comments

Comments
 (0)