-
Notifications
You must be signed in to change notification settings - Fork 90
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
Conversation
Add services for managing grants.
Update environment variables.
Remove unused locations and interval fields and account access.
Using a combination of this branch + #777, I was able to run localenv with AS signature verification enabled:
and successfully create a quote (with backend auto-requesting a grant to read the receiver incoming payment)
|
return existingGrant | ||
} | ||
|
||
const grant = await deps.openPaymentsClient.grant.request({ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
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", |
what sig headers did you use for making it work? You mean the example P2P payment, correct? |
Is |
Yeah, I changed it from the (Postman) default |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
There was a problem hiding this comment.
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
rafiki/packages/open-payments/src/generated/auth-server-types.ts
Lines 250 to 287 in 48cdfa2
"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
.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
)?
There was a problem hiding this comment.
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
rafiki/packages/backend/src/open_payments/payment/outgoing/service.ts
Lines 255 to 256 in a5c36d7
//lock grant | |
await deps.grantReferenceService.lock(grant.grant, deps.knex) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
const grantOptions = { | ||
authServer: paymentPointer.authServer, | ||
accessType: AccessType.IncomingPayment, | ||
accessActions: [AccessAction.ReadAll] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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)
👇
There was a problem hiding this comment.
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.
test.each` | ||
newAuthServer | ||
${false} | ||
${true} | ||
`( | ||
'Grant can be created and fetched (new auth server: $newAuthServer)', |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
https://github.com/interledger/open-payments/blob/b363d33038fe789e5388f04f80ddd06a4fa97093/openapi/resource-server.yaml#L809-L813
There was a problem hiding this comment.
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. id
s 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
There was a problem hiding this comment.
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:
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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?
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']) | ||
}) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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({ |
There was a problem hiding this comment.
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
Changes proposed in this pull request
Context
Checklist
fixes #number