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

feat(backend): request grant to query incoming payment receiver #779

Merged
merged 12 commits into from
Dec 6, 2022

Conversation

wilsonianb
Copy link
Contributor

@wilsonianb wilsonianb commented Nov 23, 2022

Changes proposed in this pull request

  • The receiver service now uses a stored grant if one exists or requests a new grant when querying a remote incoming payment.

Context

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Documentation added
  • Make sure that all checks pass

@github-actions github-actions bot added pkg: auth Changes in the GNAP auth package. pkg: backend Changes in the backend package. pkg: open-payments type: localenv Local playground type: source Changes business logic type: tests Testing related labels Nov 23, 2022
@wilsonianb wilsonianb marked this pull request as ready for review November 23, 2022 02:05
@wilsonianb
Copy link
Contributor Author

Using a combination of this branch + #777, I was able to run localenv with AS signature verification enabled:

BYPASS_SIGNATURE_VALIDATION: "true"

and successfully create a quote (with backend auto-requesting a grant to read the receiver incoming payment)

@wilsonianb wilsonianb requested review from njlie, mkurapov and sabineschaller and removed request for njlie November 23, 2022 02:08
return existingGrant
}

const grant = await deps.openPaymentsClient.grant.request({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it worth (get or creating and) locking the auth server's db row here to avoid simultaneously sending redundant grant requests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case for when we request the receiver on the outgoing payment lifecycle hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking if (multiple instances of) the backend were handling two quotes with the same receiver at the same time, should it avoid performing multiple grant requests?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty edge-casey IMO, not sure if worth adding just yet

@@ -69,3 +70,6 @@ services:
- ./seed.peer.yml:/workspace/seed.peer.yml
depends_on:
- peer-backend
networks:
local_rafiki:
external: true
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added this after noticing 👇 while testing:

$ docker compose -f infrastructure/local/peer-docker-compose.yml ps
service "peer-auth" refers to undefined network rafiki: invalid compose project

However, I am now sometimes getting the following when starting the localenv:

network local_rafiki declared as external, but could not be found

I wonder if that can be avoided by splitting these into separate/subsequent docker compose commands:

rafiki/package.json

Lines 20 to 21 in 632bdfb

"localenv": "docker compose -f ./infrastructure/local/docker-compose.yml -f ./infrastructure/local/peer-docker-compose.yml",
"localenv:build": "docker compose -f ./infrastructure/local/docker-compose.yml -f ./infrastructure/local/peer-docker-compose.yml -f ./infrastructure/local/build-override.yml build",

@mkurapov
Copy link
Contributor

mkurapov commented Nov 23, 2022

Using a combination of this branch + #777, I was able to run localenv with AS signature verification enabled:

BYPASS_SIGNATURE_VALIDATION: "true"

and successfully create a quote (with backend auto-requesting a grant to read the receiver incoming payment)

what sig headers did you use for making it work? You mean the example P2P payment, correct?

@wilsonianb
Copy link
Contributor Author

Screenshot from 2022-11-23 12-50-21
This query 👆
The signature headers are unchanged (TODO)
But the signature headers are there for the RS->AS grant request under the hood.

@mkurapov
Copy link
Contributor

Is accessToken just dev-access-token for now? I'm working on updating those seed scripts.

@wilsonianb
Copy link
Contributor Author

Yeah, I changed it from the (Postman) default example-access-token.

packages/backend/src/paymentPointerKey/routes.test.ts Outdated Show resolved Hide resolved
Comment on lines 213 to 227
const grant = await deps.openPaymentsClient.grant.request({
url: paymentPointer.authServer,
request: {
access_token: {
access: [
{
type: grantOptions.accessType,
actions: grantOptions.accessActions
}
]
},
interact: {
start: ['redirect']
}
} as GrantRequest
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const grant = await deps.openPaymentsClient.grant.request({
url: paymentPointer.authServer,
request: {
access_token: {
access: [
{
type: grantOptions.accessType,
actions: grantOptions.accessActions
}
]
},
interact: {
start: ['redirect']
}
} as GrantRequest
const grant = await deps.openPaymentsClient.grant.request({
url: paymentPointer.authServer,
request: {
access_token: {
access: [
{
type: grantOptions.accessType as 'incoming-payment',
actions: grantOptions.accessActions
}
]
},
interact: {
start: ['redirect']
}
}

Should we export accessType from open-payments, along with a mapper? So we don't need the as GrantRequest. Otherwise, I would prefer type asserting only the single thing that needs it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or could these be exported

"access-incoming": {
/** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */
type: "incoming-payment";
/** @description The types of actions the client instance will take at the RS as an array of strings. */
actions: (
| "create"
| "complete"
| "read"
| "read-all"
| "list"
| "list-all"
)[];
/**
* Format: uri
* @description A string identifier indicating a specific resource at the RS.
*/
identifier?: string;
};
/** access-outgoing */
"access-outgoing": {
/** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */
type: "outgoing-payment";
/** @description The types of actions the client instance will take at the RS as an array of strings. */
actions: ("create" | "read" | "read-all" | "list" | "list-all")[];
/**
* Format: uri
* @description A string identifier indicating a specific resource at the RS.
*/
identifier: string;
limits?: external["https://raw.githubusercontent.com/interledger/open-payments/b363d33038fe789e5388f04f80ddd06a4fa97093/openapi/schemas.yaml"]["components"]["schemas"]["limits-outgoing"];
};
/** access-quote */
"access-quote": {
/** @description The type of resource request as a string. This field defines which other fields are allowed in the request object. */
type: "quote";
/** @description The types of actions the client instance will take at the RS as an array of strings. */
actions: ("create" | "read" | "read-all")[];
};

as IncomingPaymentAccess, etc?
Then backend/auth could utilize these types instead having a bunch of pairings of arbitrary AccessType/AccessAction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone with your original suggestion but opened:

import { AuthServer } from '../authServer/model'
import { BaseModel } from '../../shared/baseModel'

export class Grant extends BaseModel {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything we can do here (naming wise) to make it more explicit what the difference between Grant and GrantReference is?

Copy link
Contributor Author

@wilsonianb wilsonianb Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about GrantReference -> ResourceGrant (or (PaymentPointer)SubresourceGrant)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also tempted to backtrack on this: #535 (comment)

We could instead store clientId (soon to be client payment pointer #737) on subresources.
Rename GrantReference as OutgoingPaymentGrants (since it's only really needed to lock on limit checking 👇) and drop the clientId column

//lock grant
await deps.grantReferenceService.lock(grant.grant, deps.knex)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming is the right move since we only store references to outgoing payment grants. I'll have to look into the other PR to understand why we can drop clientId.

Copy link
Contributor Author

@wilsonianb wilsonianb Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment, we store references for grants for incoming payments, quotes, and outgoing payments because it is how we associate clientId with those resources.

packages/backend/src/open_payments/receiver/service.ts Outdated Show resolved Hide resolved
const grantOptions = {
authServer: paymentPointer.authServer,
accessType: AccessType.IncomingPayment,
accessActions: [AccessAction.ReadAll]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it all depends on the AS, but would just Read be "safer" here? Having to create a grant per incoming payment isn't great, but makes it easier on auth servers to restrict access

Copy link
Contributor Author

@wilsonianb wilsonianb Nov 28, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion either way, and I'm unsure which would make auth server locking more of a necessity:
#779 (comment)

👇

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just Read wouldn't work because I'd imagine in most cases, this Rafiki instance wasn't the client creating the incoming payment. It just wants to read it to pay it.

Comment on lines 39 to 44
test.each`
newAuthServer
${false}
${true}
`(
'Grant can be created and fetched (new auth server: $newAuthServer)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

imo might be easier to split into two separate tests to avoid the if (newAuthServer) checks

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I made it better or worse by moving it to the describe.each
aa571e1

authServer: string
}): OpenPaymentsPaymentPointer {
return {
id: this.url,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would returning url: this.url be more clear from an Open Payments API perspective?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

@mkurapov mkurapov Nov 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As in, change id to url in the Open Payments spec?

Exactly, wasn't a comment of the code, more in general. ids for other resources (incoming/outgoing payments) have a different meaning that the id for the payment pointer. NABD, it felt slightly off to me in the spec in the first place

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤔 Want to elaborate on how the id is different for the payment pointer (here or in an open-payments issue)?
Part of me wants to say payment pointers are probably going to be out of scope for Open Payments soon.
Also:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, for example in our case, we call incoming payments by baseUrl/incoming-payment/{{id}}, id being a uuid, a sub resource identifier. You don't necessarily have a similar "baseUrl/payment-pointer" resource, where you could GET a payment pointer using the id path param.

Likewise in backend, when we use paymentPointer.id its usually the model's uuid. I don't think it's worth changing, just might not be always 100% clear, is all.

return existingGrant
}

const grant = await deps.openPaymentsClient.grant.request({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a case for when we request the receiver on the outgoing payment lifecycle hook?

njlie
njlie previously approved these changes Nov 29, 2022
infrastructure/local/peer-docker-compose.yml Outdated Show resolved Hide resolved
Comment on lines +1 to +19
exports.up = function (knex) {
return knex.schema.createTable('grants', function (table) {
table.uuid('id').notNullable().primary()
table.uuid('authServerId').notNullable()
table.foreign('authServerId').references('authServers.id')
table.string('continueId').nullable()
table.string('continueToken').nullable()
table.string('accessToken').nullable().unique()
table.string('accessType').notNullable()
table.specificType('accessActions', 'text[]')

table.timestamp('expiresAt').nullable()

table.timestamp('createdAt').defaultTo(knex.fn.now())
table.timestamp('updatedAt').defaultTo(knex.fn.now())

table.unique(['authServerId', 'accessType', 'accessActions'])
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it sufficient to not store the entire access array of the grant?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is for now since the RS is only requesting read-all incoming payment grants, and the new GrantService only supports creating grants of a single access type.
Do we anticipate the RS requesting multi-type grants that we should consider future-proofing for?

import { AuthServer } from '../authServer/model'
import { BaseModel } from '../../shared/baseModel'

export class Grant extends BaseModel {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renaming is the right move since we only store references to outgoing payment grants. I'll have to look into the other PR to understand why we can drop clientId.

const grantOptions = {
authServer: paymentPointer.authServer,
accessType: AccessType.IncomingPayment,
accessActions: [AccessAction.ReadAll]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think just Read wouldn't work because I'd imagine in most cases, this Rafiki instance wasn't the client creating the incoming payment. It just wants to read it to pay it.

return existingGrant
}

const grant = await deps.openPaymentsClient.grant.request({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty edge-casey IMO, not sure if worth adding just yet

@wilsonianb wilsonianb merged commit a0302b3 into main Dec 6, 2022
@wilsonianb wilsonianb deleted the bw-rs-client branch December 6, 2022 14:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: auth Changes in the GNAP auth package. pkg: backend Changes in the backend package. type: localenv Local playground type: source Changes business logic type: tests Testing related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Request grant at other Open Payments server(s)
4 participants