-
Notifications
You must be signed in to change notification settings - Fork 89
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
Adding AS types & grant requests support to open-payments client #727
Conversation
packages/open-payments/src/config.ts
Outdated
OPEN_PAYMENTS_RS_OPEN_API_URL: | ||
'https://raw.githubusercontent.com/interledger/open-payments/1e3e118d8b22c5d2942f972e28ebf0f0114d04f5/openapi/resource-server.yaml', | ||
OPEN_PAYMENTS_AS_OPEN_API_URL: | ||
'https://raw.githubusercontent.com/interledger/open-payments/1e3e118d8b22c5d2942f972e28ebf0f0114d04f5/openapi/auth-server.yaml', |
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.
Originally, I was thinking of doing something like
OPEN_PAYMENTS_RS_OPEN_API_URL: | |
'https://raw.githubusercontent.com/interledger/open-payments/1e3e118d8b22c5d2942f972e28ebf0f0114d04f5/openapi/resource-server.yaml', | |
OPEN_PAYMENTS_AS_OPEN_API_URL: | |
'https://raw.githubusercontent.com/interledger/open-payments/1e3e118d8b22c5d2942f972e28ebf0f0114d04f5/openapi/auth-server.yaml', | |
COMMIT_HASH: '1e3e118d8b22c5d2942f972e28ebf0f0114d04f5', | |
OPEN_PAYMENTS_RS_OPEN_API_URL: | |
'https://raw.githubusercontent.com/interledger/open-payments/<commitHash>/openapi/resource-server.yaml', | |
OPEN_PAYMENTS_AS_OPEN_API_URL: | |
'https://raw.githubusercontent.com/interledger/open-payments/<commitHash>/openapi/auth-server.yaml', |
and then replace the <commitHash>
whenever OPEN_PAYMENTS_AS_OPEN_API_URL
was grabbed, but I thought overkill with only two configs
@@ -1,9 +1,36 @@ | |||
import { components, paths as Paths } from './generated/types' | |||
import { |
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.
decided to keep them in a single file, we don't have too many types
@@ -7,5 +7,5 @@ | |||
"declaration": true | |||
}, | |||
"include": ["src/**/*"], | |||
"exclude": ["**/*.test.ts", "src/scripts/*", "src/test/*"] |
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.
we don't have a src/scripts
folder
return { | ||
requestInteractiveGrant: ( | ||
args: RequestGrantArgs<InteractiveGrantRequest> | ||
) => | ||
post( | ||
{ axiosInstance, logger }, | ||
{ url: args.url, body: args.request }, | ||
createInteractiveGrantValidator | ||
), | ||
requestNonInteractiveGrant: ( | ||
args: RequestGrantArgs<NonInteractiveGrantRequest> | ||
) => | ||
post( | ||
{ axiosInstance, logger }, | ||
{ url: args.url, body: args.request }, | ||
createNonInteractiveGrantValidator | ||
) | ||
} |
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 first, I wanted to do something tricky with types & method overloading, where we have one grant.create
method that determined whether an "interactive" or "non-interactive" grant arguments were passed in, and returned with the correct response, but I decided something more explicit is much better to read and use.
const createInteractiveGrantValidator = | ||
openApi.createResponseValidator<InteractiveGrant>({ | ||
path: getASPath('/'), | ||
method: HttpMethod.POST | ||
}) | ||
const createNonInteractiveGrantValidator = | ||
openApi.createResponseValidator<NonInteractiveGrant>({ | ||
path: getASPath('/'), | ||
method: HttpMethod.POST | ||
}) |
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.
unfourtunately, needed both to pass type checking when given as arguments to the request*Grant
methods
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.
If we keep requestInteractiveGrant
/requestNonInteractiveGrant
separate, I think we'll need custom validators that include the openApi.createResponseValidator
as well as a subsequent type guard that the response is the expected InteractiveGrant
or NonInteractiveGrant
. (In which case, we might be able to have a single openApi.createResponseValidator
for both?)
As is, I think the OpenAPI validator would allow a NonInteractiveGrant
response for requestInteractiveGrant
and vice versa.
requestInteractiveGrant( | ||
args: RequestGrantArgs<InteractiveGrantRequest> | ||
): Promise<InteractiveGrant> |
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.
My concern with splitting requestInteractiveGrant
/requestNonInteractiveGrant
is that I don't think we've standardized what grant requests are interactive. And thus, a client might not be expected to know when to use either.
For example, Rafiki has made incoming-payment
-only grants non-interactive, but I don't know that that's required:
@sabineschaller or @njlie or @adrianhopebailie might be able to correct me on this.
A compromise could be to do:
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant> | |
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant | NonInteractiveGrant> |
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.
requestNonInteractiveGrant
seems fine as is, since the request should just fail if the AS requires RO interaction for that 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.
I separated these, since at some point, I thought the client need to know that they need to pass in interact
?
https://github.com/interledger/open-payments/blob/main/openapi/auth-server.yaml#L93-L94
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.
Given our mix of grants for different resources needing interact or not, I've been assuming a client might just always assume a grant request requires interaction, but also be able to handle getting back a NonInteractiveGrant
response.
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 still leaning towards
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant> | |
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant | NonInteractiveGrant> |
and clients can utilize isInteractiveGrant
/isNonInteractiveGrant
.
I got the impression from Adrian that their policy agent AS might be determining what grant requests require interaction (vs having it Open Payments standardized).
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.
Ah, I see what you mean, it's more of a configurable, per-instance thing instead of standard, makes sense.
Going to make changes to assume client will always require interaction, but call might return either or.
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 ok also keeping:
requestNonInteractiveGrant(
args: RequestGrantArgs<NonInteractiveGrantRequest>
): Promise<NonInteractiveGrant>
But maybe requiring the interact
field to be specified isn't unreasonable? 🤔
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.
Yeah even in the demo Postman example, there is an interact field present when requesting the incoming payment grant -> which returns a non-interactive one.
We could run with a single call to make things simple for now. Going to play around w it
const createInteractiveGrantValidator = | ||
openApi.createResponseValidator<InteractiveGrant>({ | ||
path: getASPath('/'), | ||
method: HttpMethod.POST | ||
}) | ||
const createNonInteractiveGrantValidator = | ||
openApi.createResponseValidator<NonInteractiveGrant>({ | ||
path: getASPath('/'), | ||
method: HttpMethod.POST | ||
}) |
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.
If we keep requestInteractiveGrant
/requestNonInteractiveGrant
separate, I think we'll need custom validators that include the openApi.createResponseValidator
as well as a subsequent type guard that the response is the expected InteractiveGrant
or NonInteractiveGrant
. (In which case, we might be able to have a single openApi.createResponseValidator
for both?)
As is, I think the OpenAPI validator would allow a NonInteractiveGrant
response for requestInteractiveGrant
and vice versa.
): Promise<NonInteractiveGrant> | ||
} | ||
|
||
export const createGrantRoutes = (clientDeps: ClientDeps): GrantRoutes => { |
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.
We'll also need the grant continuation route, in order to support the full interactive grant flow.
https://github.com/interledger/open-payments/blob/62c4b4a9875e3adaa21f89f597e88db43016fe0b/openapi/auth-server.yaml#L163-L171
# Conflicts: # packages/open-payments/src/client/index.ts # packages/open-payments/src/client/requests.test.ts # packages/open-payments/src/client/requests.ts # packages/open-payments/src/client/signatures.ts # packages/open-payments/src/index.ts
Going to add grant continuation as a separate PR |
!('access_token' in grant) && | ||
'interact' in grant && | ||
!!(grant as InteractiveGrant).interact |
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.
Could this just be
!('access_token' in grant) && | |
'interact' in grant && | |
!!(grant as InteractiveGrant).interact | |
!!(grant as InteractiveGrant).interact |
I'm assuming the OpenAPI validator would disallow access_token
+ interact
.
ditto below
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.
Yeah I wasn't sure how lenient the validator was with oneOf
in the spec, but this can be less strict
requestInteractiveGrant( | ||
args: RequestGrantArgs<InteractiveGrantRequest> | ||
): Promise<InteractiveGrant> |
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 still leaning towards
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant> | |
requestInteractiveGrant( | |
args: RequestGrantArgs<InteractiveGrantRequest> | |
): Promise<InteractiveGrant | NonInteractiveGrant> |
and clients can utilize isInteractiveGrant
/isNonInteractiveGrant
.
I got the impression from Adrian that their policy agent AS might be determining what grant requests require interaction (vs having it Open Payments standardized).
export const isInteractiveGrant = ( | ||
grant: InteractiveGrant | NonInteractiveGrant | ||
): grant is InteractiveGrant => !!(grant as InteractiveGrant).interact | ||
|
||
export const isNonInteractiveGrant = ( | ||
grant: InteractiveGrant | NonInteractiveGrant | ||
): grant is NonInteractiveGrant => !!(grant as NonInteractiveGrant).access_token |
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.
could live in src/client/grants.ts
or src/type-guard.ts
but decided to leave them here
packages/open-payments/src/types.ts
Outdated
export type GrantRequest = { | ||
access_token: ASOperations['post-request']['requestBody']['content']['application/json']['access_token'] | ||
client: ASOperations['post-request']['requestBody']['content']['application/json']['client'] | ||
interact?: ASOperations['post-request']['requestBody']['content']['application/json']['interact'] |
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.
@wilsonianb single grant request with optional interact
in line with the Open Payments API
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 we'd require interact
#727 (comment)
#727 (comment)
but I'm ok as is. The grant request would just throw if interaction is required and interact
wasn't specified.
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.
Was going back and forth but less errors and throws are better here IMO. Made it required.
packages/open-payments/src/types.ts
Outdated
export type GrantRequest = { | ||
access_token: ASOperations['post-request']['requestBody']['content']['application/json']['access_token'] | ||
client: ASOperations['post-request']['requestBody']['content']['application/json']['client'] | ||
interact?: ASOperations['post-request']['requestBody']['content']['application/json']['interact'] |
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 we'd require interact
#727 (comment)
#727 (comment)
but I'm ok as is. The grant request would just throw if interaction is required and interact
wasn't specified.
} | ||
|
||
export interface GrantRoutes { | ||
requestGrant( |
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.
requestGrant( | |
request( |
or
requestGrant( | |
requestAccess( |
https://datatracker.ietf.org/doc/html/draft-ietf-gnap-core-protocol#section-2
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.
yup, had request
in my follow-up PR, will do it here instead
commit 300! 🎉 |
Changes proposed in this pull request
Context
Checklist
fixes #number