-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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(rest): add OAS Enhancer service #4554
feat(rest): add OAS Enhancer service #4554
Conversation
packages/openapi-v3/src/__tests__/unit/enhancers/fixtures/application.ts
Outdated
Show resolved
Hide resolved
* `spec.component.securitySchemes` | ||
*/ | ||
@bind(asSpecEnhancer) | ||
export class SecuritySpecEnhancer implements OASEnhancer { | ||
name = 'security'; | ||
|
||
modifySpec(spec: OpenApiSpec): OpenApiSpec { | ||
const patchSpec = {components: {securitySchemes: SECURITY_SCHEME_SPEC}}; | ||
if (spec?.components?.securitySchemes?.jwt) return spec; |
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 would expect the security spec enhancers to be contributed by authentication strategies instead of being hard-coded in @loopback/rest
module. This code was added as part of the test fixture intentionally.
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.
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.
@raymondfeng I think we agree to not bind a built-in jwt strategy into LB4 app, so the only thing we can provide the the security spec, as what @dougal83 adds here.
How about introduce an option useDefaultSecuritySpec
, sets to true by default so that our explorer shows the button. If people provide their own auth strategy & security spec enhancer , they can turn off the option.
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 would expect the security spec enhancers to be contributed by authentication strategies instead of being hard-coded in
@loopback/rest
module.
+1
How about introduce an option useDefaultSecuritySpec, sets to true by default so that our explorer shows the button. If people provide their own auth strategy & security spec enhancer , they can turn off the option.
As I mentioned in my other comment, it's my understanding that the REST component does not provide any authentication mechanism out of the box, there is no "default security". Therefore the option useDefaultSecuritySpec
does not make sense to me.
IMO, the security spec should be contributed by @loopback/authentication
, because that's the extension adding support for bearer tokens. The enhancer should be clever enough to handle the case when there are multiple authentication extensions and/or security specs involved (e.g. Bearer token, HTTP Basic/Digest auth, etc.) and preserve those additional security specs.
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.
@raymondfeng I think @dougal83 is adding the default authorizor button in explorer.
@jannyHou #4386 Having read the issue/story properly just now... I had made a big mistake on my understanding... I thought that is was generally adding a default to the rest server! 🤦♂ To make amends I guess I'll have to look into explorer and making the change as per the story!
aeb2ea5
to
c788a70
Compare
packages/rest/src/rest.server.ts
Outdated
@@ -717,6 +742,10 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
spec = this.updateSpecFromRequest(spec, requestContext); | |||
} | |||
|
|||
// Apply OAS enhancers to the OpenAPI specification | |||
this.OASEnhancer.spec = spec; | |||
spec = await this.OASEnhancer.applyEnhancerByName('security'); |
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 would suggest rename it to be lb-default-security
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.
@dougal83 Thank you so much for taking care of the spec enhancer story!
The way how the enhancer service gets added looks reasonable to me. A few minor suggestions.
When created the story, I didn't realize applying the enhancer would turn the getApiSpec
into an async function. I tend to keep it sync and apply the default security enhancer somewhere else, like _serveOpenApiSpec
. And I would like to hear more opinions from @bajtos @raymondfeng @hacksparrow who's more familiar with the rest module regarding where to apply the default security enhancer 🙇♀ .
type: 'http', | ||
scheme: 'bearer', | ||
bearerFormat: 'JWT', | ||
}, | ||
}; | ||
|
||
/** | ||
* A spec enhancer to add bearer token OpenAPI security entry to | ||
* A spec enhancer to add jwt bearer token OpenAPI security entry to | ||
* `spec.component.securitySchemes` | ||
*/ | ||
@bind(asSpecEnhancer) | ||
export class SecuritySpecEnhancer implements OASEnhancer { | ||
name = 'security'; |
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.
same as https://github.com/strongloop/loopback-next/pull/4554/files#r376586868, I would rename it to lb-default-security
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.
Thank you @dougal83 for contributing the pull request.
I feel the proposed implementation is going in a wrong direction at high level, see my comment below.
packages/rest/src/rest.server.ts
Outdated
@@ -717,6 +742,10 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
spec = this.updateSpecFromRequest(spec, requestContext); | |||
} | |||
|
|||
// Apply OAS enhancers to the OpenAPI specification | |||
this.OASEnhancer.spec = spec; | |||
spec = await this.OASEnhancer.applyEnhancerByName('security'); |
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 think the REST server should be invoking specific enhancers defined by a name, because that makes it impossible for other extensions to contribute new enhancers.
As I understand the proposed design:
- The REST API invokes all registered API enhancers via
applyAllEnhancers()
. - Extensions like
@loopback/authentication
contribute custom API enhancers to provide additional OpenAPI metadata like the authentication scheme.
The REST component (@loopback/rest
) should not include any security schemas in the OpenAPI spec out of the box, simply because it does not implement any authentication out of the box.
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.
Totally agree, I was being a slave to example in story #4380 Could you provide direction on following:
- Add option to disable all enhancers on rest server? Alternatively option to override by providing array list of enhancers to apply?
- Would I follow similar pattern to authentication strategies say
spec-enhancers
folder alongside or would I seek to embed the enhancers within strategies. If you have the time, perhaps a top level stepwise implementation path would be useful to follow.
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.
Add option to disable all enhancers on rest server? Alternatively option to override by providing array list of enhancers to apply?
I am proposing to leave this part out of the initial implementation and wait until there is user demand for that.
Would I follow similar pattern to authentication strategies say
spec-enhancers
folder alongside or would I seek to embed the enhancers within strategies. If you have the time, perhaps a top level stepwise implementation path would be useful to follow.
IMO, it's not a concern of this pull request how the spec enhancer classes are registered.
I am expecting the @loopback/authentication
extension to contribute a binding with @bind(asSpecEnhancer)
decorator. As I understand the current implementation of applyAllEnhancers
, it will automatically pick up such class.
See my ModelApiBooter spike in #3617 for an example. I really wish we had better documentation about the ExtensionPoint/Extension pattern :-/
Here the app decides to register a component (it's CrudRestComponent in the spike, it would be @loopback/authentication
in your case):
Here the component implements an Extension:
And here is the Extension implementation contributed by the Component to the Application context:
c788a70
to
e633dd9
Compare
@bajtos I've cut this PR down down to a singular purpose. Please let me know if it is back on track. |
This comment has been minimized.
This comment has been minimized.
e633dd9
to
2da2b42
Compare
@dougal83 @bajtos got distracted by the auth migration last week, sorry for replying late. Let me clarify the goal first:
And if we still agree on ^, let's discuss each module's responsibility. I think I somehow misunderstand the expected approaches and my understanding has been "@loopback/rest provides the bearer auth scheme enhancer" and "it should be applied inside the rest server class, like the consolidation enhancer added in #4365" all the time. If this is not the expected behaviour anymore, first sorry for misleading @dougal83 on the implementation... then let's agree on which module provides the enhancer and where to apply it.
If we modify the app template and apply the enhancers in app constructor, then we don't have the concern above. WDYT? |
@jannyHou Thank you for the reply. Don't worry about misleading, it's more likely my misunderstanding. :) From looking at the stories and attempting PRs, it does make sense to have the rest server apply the extensions by default via From comments made I also attempted to extend the AuthStrategies provider to contribute the OASEhancer. Love to hear your thoughts on the idea! |
@dougal83 The PR looks good to me now. Would you please add a test to verify that an enhancer is invoked as part of |
2da2b42
to
f572622
Compare
BREAKING CHANGE: Api specifications are now emitted as a Promise instead of a value object. Calls to getApiSpec function must switch from the old style to new style as follows: 1. Old style ```ts function() { // ... const spec = restApp.restServer.getApiSpec(); // ... } ``` 2. New style ```ts async function() { // ... const spec = await restApp.restServer.getApiSpec(); // ... } ```
add openapi spec enhancer to rest server impl. loopbackio#4380 Signed-off-by: Douglas McConnachie <[email protected]>
f572622
to
7558cee
Compare
@bajtos @raymondfeng Please let me know if further changes are required. |
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.
👏 LGTM Good to apply the security spec in a separate PR. And really appreciate your effort spent on the OAS enhancer stories. I am looking at the other PRs.
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.
The new version looks great 👏 Thank you @dougal83 for the persistence in pushing this change forward, despite long silences on our side ❤️
@@ -229,6 +238,16 @@ export class RestServer extends Context implements Server, HttpServerLike { | |||
this.bind(RestBindings.HANDLER).toDynamicValue(() => this.httpHandler); | |||
} | |||
|
|||
protected _setupOASEnhancerIfNeeded() { | |||
if (this._OASEnhancer != null) return; |
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.
Nitpick: we could use this._OASEnhancer = this.getSync(OAS_ENHANCER_SERVICE, {optional: true});
to check so that the service can be bound to the server.
Add OpenAPI enhancer service in @loopback/rest
Implements #4365
Checklist
npm test
passes on your machine