Skip to content

Commit

Permalink
feat(operation): validate security schemas on requests
Browse files Browse the repository at this point in the history
  • Loading branch information
aleksandryackovlev committed Feb 18, 2020
1 parent aa4738a commit 60d468f
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 32 deletions.
124 changes: 94 additions & 30 deletions src/operation.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import jsf, { JSONSchema } from 'json-schema-faker';
import { OpenAPIV3 } from 'openapi-types';
import express from 'express';
import { has, get } from 'lodash';

import faker from 'faker';
import { pathToRegexp } from 'path-to-regexp';
Expand Down Expand Up @@ -33,51 +34,49 @@ class Operation {

operation: OpenAPIV3.OperationObject;

securitySchemes: { [key: string]: OpenAPIV3.SecuritySchemeObject } | null;

constructor({
method,
path,
operation,
securitySchemes,
}: {
path: string;
method: string;
operation: OpenAPIV3.OperationObject;
securitySchemes?: { [key: string]: OpenAPIV3.SecuritySchemeObject };
}) {
const pathPattern = path.replace(/\{([^/}]+)\}/g, (p1: string, p2: string): string => `:${p2}`);

this.method = method.toUpperCase();
this.operation = operation;
this.securitySchemes = securitySchemes || null;

this.pathRegexp = pathToRegexp(pathPattern);
}

getResponseSchema(): JSONSchema | null {
if (this.operation && this.operation.responses) {
const { responses } = this.operation;
if (
responses['200'] &&
!isReferenceObject(responses['200']) &&
responses['200'].content &&
responses['200'].content['application/json'] &&
responses['200'].content['application/json'].schema
) {
const response = responses['200'].content['application/json'];
const { schema, example, examples } = response;

if (schema && !isReferenceObject(schema)) {
const resultSchema: JSONSchema = schema as JSONSchema;

if (example) {
resultSchema.example = example;
}

if (examples) {
resultSchema.examples = examples;
}

return resultSchema;
if (has(this.operation, ['responses', '200', 'content', 'application/json', 'schema'])) {
const { schema, example, examples } = get(this.operation, [
'responses',
'200',
'content',
'application/json',
]);

if (schema && !isReferenceObject(schema)) {
const resultSchema: JSONSchema = schema as JSONSchema;

if (example) {
resultSchema.example = example;
}

if (examples) {
resultSchema.examples = examples;
}

return null;
return resultSchema;
}

return null;
Expand All @@ -86,16 +85,81 @@ class Operation {
return null;
}

// validateRequest() {
// }
getSecurityRequirements(): OpenAPIV3.SecurityRequirementObject[] {
const requirements: OpenAPIV3.SecurityRequirementObject[] = this.operation.security || [];

return requirements;
}

isRequestAuthorized(req: express.Request): boolean {
const securityRequirements = this.getSecurityRequirements();
if (
securityRequirements.some((schemes) => {
if (schemes && this.securitySchemes) {
return Object.keys(schemes).some((scheme) => {
if (this.securitySchemes && this.securitySchemes[scheme]) {
const securityScheme = this.securitySchemes[scheme];
switch (securityScheme.type) {
case 'apiKey':
if (securityScheme.in === 'header') {
return req.header(securityScheme.name) === undefined;
}

if (securityScheme.in === 'query') {
return req.query[securityScheme.name] === undefined;
}

if (securityScheme.in === 'cookie') {
return req.cookies[securityScheme.name] === undefined;
}

return false;

case 'http': {
const authHeader = req.header('Authorization');
if (!authHeader) {
return true;
}

return securityScheme.scheme === 'basic'
? !authHeader.startsWith('Basic')
: !authHeader.startsWith('Bearer');
}

case 'oauth2': {
const authHeader = req.header('Authorization');
if (!authHeader) {
return true;
}

return !authHeader.startsWith('Bearer');
}

default:
return false;
}
}

return false;
});
}

return false;
})
) {
return false;
}

// isRequestAuthorized(): boolean {
// return true;
// }
return true;
}

generateResponse(req: express.Request, res: express.Response): express.Response {
const responseSchema = this.getResponseSchema();

if (!this.isRequestAuthorized(req)) {
return res.status(401).json({ message: 'Unauthorized request' });
}

return res.json(responseSchema ? jsf.generate(responseSchema) : {});
}
}
Expand Down
4 changes: 2 additions & 2 deletions test/integration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const request = supertest(app);

describe('middleware', () => {
it("should return fields' example values if they are set", async () => {
const response = await request.get('/api/pet/2');
const response = await request.get('/api/pet/2').set('api_key', 'someKey');

expect(response.status).toBe(200);
expect(response.body).toHaveProperty('name', 'doggie');
Expand Down Expand Up @@ -35,7 +35,7 @@ describe('middleware', () => {
});

it('should return values of the given faker type if they are specified', async () => {
const response = await request.post('/api/pet/2');
const response = await request.post('/api/pet/2').set('Authorization', 'Bearer key');

expect(response.status).toBe(200);

Expand Down

0 comments on commit 60d468f

Please sign in to comment.