Skip to content

Commit

Permalink
feat: add operation security
Browse files Browse the repository at this point in the history
Signed-off-by: jannyHou <[email protected]>
  • Loading branch information
jannyHou committed Sep 12, 2019
1 parent 5f9b027 commit ee75b64
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 18 deletions.
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ You can see the details in
`swagger-ui` module is built with authorization component, which will show up by
adding the security schema and operation security spec in the OpenAPI spec.

You can check the swagger
You can check the OpenAPI
[doc](https://swagger.io/docs/specification/authentication/bearer-authentication/)
to learn how to add it, see section "Describing Bearer Authentication".

Expand Down Expand Up @@ -151,6 +151,57 @@ is injected in the request:

![me](/imgs/me.png)

### Operation Level Security Policy

You can also specify security policy for a single endpoint, by adding the
security spec in the operation decorator, like:

```ts
// Define your operation level security policy
const SECURITY_SPEC_OPERATION = {
[{basicAuth: []}];
}

// Also add the corresponding security schema into `components.securitySchemas`
const SECURITY_SCHEMA_SPEC = {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
basicAuth: {
type: 'http',
scheme: 'basic',
},
},
};

// Add security policy in the operation decorator
@get('/users/{userId}', {
security: SECURITY_SPEC_OPERATION,
responses: RESPONSE_SPEC,
})
async findById(@param.path.string('userId') userId: string): Promise<User> {
// Print out the header, you will see the Basic auth string in the header
console.log(`header ${inspect(this.request.headers)}`);
// business logic
}
```
The "Authorize" dialog will show up your new entry as:
![operation-level-security](/imgs/operation-level-security.png)
Provide the username and password to login:
![set-basic-auth](/imgs/set-basic-auth.png)
Try endpoint `GET users/{userId}`, the printed header contains the basic auth
string:
![basic-auth-header](/imgs/basic-auth-header.png)
### Follow-up Stories
As you could find in the `security-spec.ts` file, security related spec is
Expand Down
Binary file added imgs/basic-auth-header.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/operation-level-security.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added imgs/set-basic-auth.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 15 additions & 1 deletion packages/shopping/src/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import {BootMixin} from '@loopback/boot';
import {ApplicationConfig, BindingKey} from '@loopback/core';
import {RepositoryMixin} from '@loopback/repository';
import {RestApplication} from '@loopback/rest';
import {RestApplication, RestBindings} from '@loopback/rest';
import {ServiceMixin} from '@loopback/service-proxy';
import {MyAuthenticationSequence} from './sequence';
import {
Expand All @@ -29,6 +29,7 @@ import {
import {PasswordHasherBindings} from './keys';
import {BcryptHasher} from './services/hash.password.bcryptjs';
import {JWTAuthenticationStrategy} from './authentication-strategies/jwt-strategy';
import {SECURITY_SCHEMA_SPEC, SECURITY_SPEC} from './utils/security-spec';

/**
* Information from package.json
Expand Down Expand Up @@ -99,4 +100,17 @@ export class ShoppingApplication extends BootMixin(

this.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService);
}

async start(): Promise<void> {
await super.start();
const oaiSpec = this.getSync(RestBindings.API_SPEC);
this.api(
Object.assign(oaiSpec, {
components: {
...SECURITY_SCHEMA_SPEC,
},
security: SECURITY_SPEC,
}),
);
}
}
18 changes: 16 additions & 2 deletions packages/shopping/src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@

import {repository} from '@loopback/repository';
import {validateCredentials} from '../services/validator';
import {post, param, get, requestBody, HttpErrors} from '@loopback/rest';
import {
post,
param,
get,
requestBody,
HttpErrors,
RestBindings,
Request,
} from '@loopback/rest';
import {User, Product} from '../models';
import {UserRepository} from '../repositories';
import {RecommenderService} from '../services/recommender.service';
import {inject} from '@loopback/core';
import {inject, Context} from '@loopback/core';
import {
authenticate,
UserProfile,
Expand All @@ -23,13 +31,15 @@ import {
} from './specs/user-controller.specs';
import {Credentials} from '../repositories/user.repository';
import {PasswordHasher} from '../services/hash.password.bcryptjs';
import {inspect} from 'util';

import {
TokenServiceBindings,
PasswordHasherBindings,
UserServiceBindings,
} from '../keys';
import * as _ from 'lodash';
import {SECURITY_SPEC_OPERATION} from '../utils/security-spec';

export class UserController {
constructor(
Expand All @@ -42,6 +52,8 @@ export class UserController {
public jwtService: TokenService,
@inject(UserServiceBindings.USER_SERVICE)
public userService: UserService<User, Credentials>,
@inject(RestBindings.Http.CONTEXT) public ctx: Context,
@inject(RestBindings.Http.REQUEST) private request: Request,
) {}

@post('/users', {
Expand Down Expand Up @@ -83,6 +95,7 @@ export class UserController {
}

@get('/users/{userId}', {
security: SECURITY_SPEC_OPERATION,
responses: {
'200': {
description: 'User',
Expand All @@ -97,6 +110,7 @@ export class UserController {
},
})
async findById(@param.path.string('userId') userId: string): Promise<User> {
console.log(`header ${inspect(this.request.headers)}`);
return this.userRepository.findById(userId, {
fields: {password: false},
});
Expand Down
5 changes: 0 additions & 5 deletions packages/shopping/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,12 @@
import {ShoppingApplication} from './application';
import {ApplicationConfig} from '@loopback/core';
import {RestBindings} from '@loopback/rest';
import {addSecuritychema} from './utils/security-spec';
export {ShoppingApplication, PackageInfo, PackageKey} from './application';

export async function main(options?: ApplicationConfig) {
const app = new ShoppingApplication(options);

await app.boot();
let oaiSchema = app.getSync(RestBindings.API_SPEC);
addSecuritychema(oaiSchema);
console.log(oaiSchema.components!.securitySchemes);
app.bind(RestBindings.API_SPEC).to(oaiSchema);
await app.start();

const url = app.restServer.url;
Expand Down
15 changes: 6 additions & 9 deletions packages/shopping/src/utils/security-spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
import {OpenApiSpec} from '@loopback/rest';

export const SECURITY_SPEC = [{bearerAuth: []}];
export const BEARER_SECURITY_SCHEMA_SPEC = {
export const SECURITY_SPEC_OPERATION = [{basicAuth: []}];
export const SECURITY_SCHEMA_SPEC = {
securitySchemes: {
bearerAuth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
},
basicAuth: {
type: 'http',
scheme: 'basic',
},
},
};

export function addSecuritychema(schema: OpenApiSpec) {
schema.components = schema.components || {};
Object.assign(schema.components, BEARER_SECURITY_SCHEMA_SPEC);
Object.assign(schema, {security: SECURITY_SPEC});
}

0 comments on commit ee75b64

Please sign in to comment.