Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: GET resource lists, use op client #2122

Merged
20 changes: 14 additions & 6 deletions openapi/resource-server.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ paths:
methods:
- type: ilp
ilpAddress: g.ilp.iwuyge987y.98y08y
sharedSecret: 1c7eaXa4rd2fFOBl1iydvCT1tV5TbM3RW1WLCafu_JA'401'
sharedSecret: 1c7eaXa4rd2fFOBl1iydvCT1tV5TbM3RW1WLCafu_JA
'401':
$ref: '#/components/responses/401'
'403':
$ref: '#/components/responses/403'
Expand Down Expand Up @@ -818,7 +819,7 @@ components:
title: Wallet Address
type: object
description: A **wallet address** resource is the root of the API and contains the public details of the financial account represented by the Wallet Address that is also the service endpoint URL.
additionalProperties: false
additionalProperties: true
examples:
- id: 'https://ilp.rafiki.money/alice'
publicName: Alice
Expand Down Expand Up @@ -974,9 +975,16 @@ components:
value: '0'
assetCode: USD
assetScale: 2
- authServer: 'https://auth.rafiki.money'
properties:
receivedAmount:
$ref: ./schemas.yaml#/components/schemas/amount
authServer:
type: string
format: uri
description: The URL of the authorization server endpoint for getting grants and access tokens for this wallet address.
required:
- authServer
unresolvedProperites: false
outgoing-payment:
title: Outgoing Payment
Expand Down Expand Up @@ -1283,7 +1291,6 @@ components:
type: integer
minimum: 1
maximum: 100
default: 10
name: first
in: query
description: The number of items to return after the cursor.
Expand All @@ -1292,7 +1299,6 @@ components:
type: integer
minimum: 1
maximum: 100
default: 10
name: last
in: query
description: The number of items to return before the cursor.
Expand Down Expand Up @@ -1329,14 +1335,16 @@ components:
in: header
schema:
type: string
example: 'Signature: sig1=:EWJgAONk3D6542Scj8g51rYeMHw96cH2XiCMxcyL511wyemGcw==:'
examples:
- 'Signature: sig1=:EWJgAONk3D6542Scj8g51rYeMHw96cH2XiCMxcyL511wyemGcw==:'
description: 'The signature generated based on the Signature-Input, using the signing algorithm specified in the "alg" field of the JWK.'
optional-signature-input:
name: Signature-Input
in: header
schema:
type: string
example: 'Signature-Input: sig1=("@method" "@target-uri" "content-digest" "content-length" "content-type");created=1618884473;keyid="gnap-rsa"'
examples:
- 'Signature-Input: sig1=("@method" "@target-uri" "content-digest" "content-length" "content-type");created=1618884473;keyid="gnap-rsa"'
description: 'The Signature-Input field is a Dictionary structured field containing the metadata for one or more message signatures generated from components within the HTTP message. Each member describes a single message signature. The member''s key is the label that uniquely identifies the message signature within the context of the HTTP message. The member''s value is the serialization of the covered components Inner List plus all signature metadata parameters identified by the label. The following components MUST be included: - "@method" - "@target-uri" - "authorization". When the message contains a request body, the covered components MUST also include the following: - "content-digest" The keyid parameter of the signature MUST be set to the kid value of the JWK. See [ietf-httpbis-message-signatures](https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-message-signatures#section-4.1) for more details.'
security:
- GNAP: []
4 changes: 2 additions & 2 deletions packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@
"@graphql-tools/load": "^8.0.0",
"@graphql-tools/schema": "^10.0.0",
"@interledger/http-signature-utils": "1.1.0",
"@interledger/open-payments": "5.2.2",
"@interledger/openapi": "1.2.0",
"@interledger/open-payments": "5.3.1",
"@interledger/openapi": "1.2.1",
"@interledger/pay": "0.4.0-alpha.9",
"@interledger/stream-receiver": "^0.3.3-alpha.3",
"@koa/router": "^12.0.0",
Expand Down
61 changes: 26 additions & 35 deletions packages/backend/src/open_payments/receiver/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import assert from 'assert'
import { Receiver } from './model'
import { Grant } from '../grant/model'
import { IncomingPaymentState } from '../payment/incoming/model'
import { PublicIncomingPayment } from '@interledger/open-payments/dist/types'
import { mockPublicIncomingPayment } from '@interledger/open-payments/dist/test/helpers'

describe('Receiver Service', (): void => {
let deps: IocContract<AppServices>
Expand Down Expand Up @@ -123,10 +125,7 @@ describe('Receiver Service', (): void => {
`('remote ($description)', ({ existingGrant }): void => {
let walletAddress: OpenPaymentsWalletAddress
let incomingPayment: OpenPaymentsIncomingPaymentWithPaymentMethods
let publicIncomingPayment: {
authServer: string
receivedAmount: OpenPaymentsIncomingPaymentWithPaymentMethods['receivedAmount']
}
let publicIncomingPayment: PublicIncomingPayment
const authServer = faker.internet.url({ appendSlash: false })
const INCOMING_PAYMENT_PATH = 'incoming-payments'
const grantOptions = {
Expand Down Expand Up @@ -172,8 +171,6 @@ describe('Receiver Service', (): void => {
}
}

let publicIncomingPaymentMock: () => Promise<Response>

beforeEach(async (): Promise<void> => {
walletAddress = mockWalletAddress({
authServer
Expand All @@ -182,10 +179,10 @@ describe('Receiver Service', (): void => {
id: `${walletAddress.id}/incoming-payments/${uuid()}`,
walletAddress: walletAddress.id
})
publicIncomingPayment = {
publicIncomingPayment = mockPublicIncomingPayment({
authServer,
receivedAmount: incomingPayment.receivedAmount
}
})
if (existingGrant) {
await expect(
grantService.create({
Expand All @@ -199,12 +196,6 @@ describe('Receiver Service', (): void => {
managementId: '8f69de01-5bf9-4603-91ed-eeca101081f1'
})
}
publicIncomingPaymentMock = () =>
Promise.resolve({
ok: true,
status: 200,
json: async () => publicIncomingPayment
} as Response)
jest
.spyOn(walletAddressService, 'getByUrl')
.mockResolvedValueOnce(undefined)
Expand All @@ -219,13 +210,14 @@ describe('Receiver Service', (): void => {
${false} | ${''}
${true} | ${'- after rotating access token'}
`('resolves incoming payment $description', async ({ rotate }) => {
const fetchSpy = jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
const clientRequestGrantSpy = jest
.spyOn(openPaymentsClient.grant, 'request')
.mockResolvedValueOnce(grant)

const clientGetPublicIncomingPaymentSpy = jest
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)

const clientGetIncomingPaymentSpy = jest
.spyOn(openPaymentsClient.incomingPayment, 'get')
.mockResolvedValueOnce(incomingPayment)
Expand Down Expand Up @@ -270,13 +262,15 @@ describe('Receiver Service', (): void => {
]
}
})
expect(fetchSpy).toHaveBeenCalledTimes(1)
if (!existingGrant) {
expect(clientRequestGrantSpy).toHaveBeenCalledWith(
{ url: authServer },
grantRequest
)
}
expect(clientGetPublicIncomingPaymentSpy).toHaveBeenCalledWith({
url: incomingPayment.id
})
expect(clientGetIncomingPaymentSpy).toHaveBeenCalledWith({
url: incomingPayment.id,
accessToken:
Expand All @@ -293,12 +287,9 @@ describe('Receiver Service', (): void => {
})

test('returns undefined for invalid remote public incoming payment', async (): Promise<void> => {
const fetchSpy = jest.spyOn(global, 'fetch').mockImplementation(() =>
Promise.resolve({
ok: false,
status: 400
} as Response)
)
const clientGetPublicIncomingPaymentSpy = jest
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockRejectedValueOnce({ status: 404 })

await expect(
receiverService.get(
Expand All @@ -307,7 +298,7 @@ describe('Receiver Service', (): void => {
}/${INCOMING_PAYMENT_PATH}/${uuid()}`
)
).resolves.toBeUndefined()
expect(fetchSpy).toHaveBeenCalledTimes(1)
expect(clientGetPublicIncomingPaymentSpy).toHaveBeenCalledTimes(1)
})

if (existingGrant) {
Expand All @@ -325,8 +316,8 @@ describe('Receiver Service', (): void => {
expired: true
} as Grant)
jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)
const clientRequestGrantSpy = jest.spyOn(
openPaymentsClient.grant,
'request'
Expand All @@ -345,8 +336,8 @@ describe('Receiver Service', (): void => {
})
await grant?.$query(knex).patch({ expiresAt: new Date() })
jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)
const clientRequestGrantSpy = jest.spyOn(
openPaymentsClient.grant,
'request'
Expand All @@ -360,8 +351,8 @@ describe('Receiver Service', (): void => {
} else {
test('returns undefined for invalid grant', async (): Promise<void> => {
jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)
const clientRequestGrantSpy = jest
.spyOn(openPaymentsClient.grant, 'request')
.mockRejectedValueOnce(new Error('Could not request grant'))
Expand All @@ -377,8 +368,8 @@ describe('Receiver Service', (): void => {

test('returns undefined for interactive grant', async (): Promise<void> => {
jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)
const clientRequestGrantSpy = jest
.spyOn(openPaymentsClient.grant, 'request')
.mockResolvedValueOnce({
Expand All @@ -401,8 +392,8 @@ describe('Receiver Service', (): void => {

test('returns undefined when fetching remote incoming payment throws', async (): Promise<void> => {
jest
.spyOn(global, 'fetch')
.mockImplementation(publicIncomingPaymentMock)
.spyOn(openPaymentsClient.incomingPayment, 'getPublic')
.mockResolvedValueOnce(publicIncomingPayment)
jest
.spyOn(openPaymentsClient.grant, 'request')
.mockResolvedValueOnce(grant)
Expand Down
19 changes: 4 additions & 15 deletions packages/backend/src/open_payments/receiver/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,21 +234,10 @@ async function getIncomingPaymentGrant(
deps: ServiceDependencies,
incomingPaymentUrl: string
): Promise<Grant | undefined> {
// TODO: replace with OP client method
// const publicIncomingPayment =
// await deps.openPaymentsClient.incomingPayment.getPublic(incomingPaymentUrl)
let url: string
if (deps.config.env === 'development') {
const parsedUrl = new URL(incomingPaymentUrl)
url = `http://${parsedUrl.hostname}${parsedUrl.pathname}`
} else {
url = incomingPaymentUrl
}
const publicIncomingPaymentResponse = await fetch(url)
if (!publicIncomingPaymentResponse.ok) {
return undefined
}
const publicIncomingPayment = await publicIncomingPaymentResponse.json()
const publicIncomingPayment =
await deps.openPaymentsClient.incomingPayment.getPublic({
url: incomingPaymentUrl
})
if (!publicIncomingPayment || !publicIncomingPayment.authServer) {
return undefined
}
Expand Down
30 changes: 23 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 20 additions & 2 deletions postman/collections/Interledger.json
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@
"mode": "graphql",
"graphql": {
"query": "mutation CreateWalletAddressKey ($input: CreateWalletAddressKeyInput!) {\n createWalletAddressKey(input: $input) {\n code\n message\n success\n walletAddressKey {\n id\n revoked\n walletAddressId\n createdAt\n jwk {\n alg\n crv\n kid\n kty\n x\n }\n }\n }\n}",
"variables": "{\n \"input\": {\n \"walletAddresId\": \"{{walletAddressId}}\",\n \"jwk\": {\n \"alg\": \"EdDSA\",\n \"crv\": \"Ed25519\",\n \"kid\": \"kid_dad93e7f-f40b-484d-99d2-df12c8523176\",\n \"kty\": \"OKP\", \n \"x\": \"ubqoInifJ5sssIPPnQR1gVPfmoZnJtPhTkyMXNoJF_8\"\n }\n }\n}"
"variables": "{\n \"input\": {\n \"walletAddressId\": \"{{walletAddressId}}\",\n \"jwk\": {\n \"alg\": \"EdDSA\",\n \"crv\": \"Ed25519\",\n \"kid\": \"kid_dad93e7f-f40b-484d-99d2-df12c8523176\",\n \"kty\": \"OKP\", \n \"x\": \"ubqoInifJ5sssIPPnQR1gVPfmoZnJtPhTkyMXNoJF_8\"\n }\n }\n}"
}
},
"url": {
Expand Down Expand Up @@ -2026,10 +2026,28 @@
"script": {
"id": "dc5e8d6a-7834-405f-8198-465bb5795824",
"exec": [
"request.url = request.url",
" .replace(/{{(senderWalletAddress)}}/g, (_, key) => pm.environment.get(key))",
" .replace(/http:\\/\\/localhost:([3,4])000/g, (_, key) =>",
" key === '3'",
" ? 'https://' + pm.environment.get('host3000')",
" : 'https://' + pm.environment.get('host4000')",
" )",
"console.log(request.url)",
"",
"if(pm.environment.get('preRequestHost')){",
" eval(pm.environment.get('preRequestHost'))",
"}",
"eval(pm.environment.get('preRequestSignatures'))"
"eval(pm.environment.get('preRequestSignatures'))",
"",
"pm.request.url.query.idx(2).value = pm.request.url.query.idx(2).value",
" .replace(/{{([A-Za-z]\\w+)}}/g, (_, key) => pm.environment.get(key))",
" .replace(/http:\\/\\/localhost:([3,4])000/g, (_, key) =>",
" key === '3'",
" ? 'https://' + pm.environment.get('host3000')",
" : 'https://' + pm.environment.get('host4000')",
" )",
""
],
"type": "text/javascript"
}
Expand Down