Skip to content

Commit

Permalink
initial security integration
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Oct 7, 2019
1 parent 7ff34c0 commit 5a6b330
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 33 deletions.
23 changes: 20 additions & 3 deletions src/framework/openapi.spec.loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export class OpenApiSpecLoader {
framework.initialize({
visitApi(ctx: OpenAPIFrameworkAPIContext) {
const apiDoc = ctx.getApiDoc();
const security = apiDoc.security;
for (const bpa of basePaths) {
const bp = bpa.replace(/\/$/, '');
for (const [path, methods] of Object.entries(apiDoc.paths)) {
Expand All @@ -52,8 +53,12 @@ export class OpenApiSpecLoader {
continue;
}
const schemaParameters = new Set();
(schema.parameters || []).forEach(parameter => schemaParameters.add(parameter));
((methods as any).parameters || []).forEach(parameter => schemaParameters.add(parameter));
(schema.parameters || []).forEach(parameter =>
schemaParameters.add(parameter),
);
((methods as any).parameters || []).forEach(parameter =>
schemaParameters.add(parameter),
);
schema.parameters = Array.from(schemaParameters);
const pathParams = new Set();
for (const param of schema.parameters) {
Expand All @@ -66,12 +71,24 @@ export class OpenApiSpecLoader {
.split('/')
.map(toExpressParams)
.join('/');

// add apply any general defined security
const moddedSchema =
security || schema.security
? {
schema,
security: [
...(security || []),
...(schema.security || []),
],
}
: { ...schema };
routes.push({
expressRoute,
openApiRoute,
method: method.toUpperCase(),
pathParams: Array.from(pathParams),
schema,
schema: moddedSchema,
});
}
}
Expand Down
1 change: 0 additions & 1 deletion src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,6 @@ export interface OpenAPIFrameworkPathContext {
export interface OpenAPIFrameworkVisitor {
visitApi?(context: OpenAPIFrameworkAPIContext): void;
visitPath?(context: OpenAPIFrameworkPathContext): void;
// visitOperation?(context: OpenAPIFrameworkOperationContext): void;
}

export interface OpenApiRequest extends Request {
Expand Down
22 changes: 17 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ono from 'ono';
import * as _ from 'lodash';
import { Application } from 'express';
import { Application, Request } from 'express';
import { OpenApiContext } from './framework/openapi.context';
import { OpenAPIV3, OpenApiRequest } from './framework/types';
import * as middlewares from './middlewares';
Expand All @@ -10,6 +10,13 @@ export interface OpenApiValidatorOpts {
coerceTypes?: boolean;
validateResponses?: boolean;
validateRequests?: boolean;
securityHandlers?: {
[key: string]: (
req: Request,
scopes: [],
schema: OpenAPIV3.SecuritySchemeObject,
) => boolean | Promise<boolean>;
};
multerOpts?: {};
}

Expand Down Expand Up @@ -58,20 +65,25 @@ export class OpenApiValidator {
useDefaults: true,
});

const validateMiddleware = (req, res, next) => {
const requestValidator = (req, res, next) => {
return aoav.validate(req, res, next);
};

const resOav = new middlewares.ResponseValidator(this.context.apiDoc, {
const responseValidator = new middlewares.ResponseValidator(this.context.apiDoc, {
coerceTypes,
});

const securityMiddleware = middlewares.security(this.context);

const components = this.context.apiDoc.components;
const use = [
middlewares.applyOpenApiMetadata(this.context),
middlewares.multipart(this.context, this.options.multerOpts),
];
if (this.options.validateRequests) use.push(validateMiddleware);
if (this.options.validateResponses) use.push(resOav.validate());
// TODO validate security functions exist for each security key
if (components && components.securitySchemes) use.push(securityMiddleware);
if (this.options.validateRequests) use.push(requestValidator);
if (this.options.validateResponses) use.push(responseValidator.validate());

app.use(use);
}
Expand Down
1 change: 1 addition & 0 deletions src/middlewares/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { applyOpenApiMetadata } from './openapi.metadata';
export { RequestValidator } from './openapi.request.validator';
export { ResponseValidator } from './openapi.response.validator';
export { multipart } from './openapi.multipart';
export { security } from './openapi.security';
24 changes: 0 additions & 24 deletions src/middlewares/openapi.response.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,28 +129,4 @@ export class ResponseValidator {
}
return validators;
}

private validateBody(body) {}

private toOpenapiValidationError(error: Ajv.ErrorObject) {
const validationError = {
path: `instance${error.dataPath}`,
errorCode: `${error.keyword}.openapi.responseValidation`,
message: error.message,
};

validationError.path = validationError.path.replace(
/^instance\.(?:response\.)?/,
'',
);

validationError.message =
validationError.path + ' ' + validationError.message;

if (validationError.path === 'response') {
delete validationError.path;
}

return validationError;
}
}
22 changes: 22 additions & 0 deletions src/middlewares/openapi.security.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { OpenApiContext } from '../framework/openapi.context';
// import { validationError } from './util';

export function security(openApiContext: OpenApiContext) {
return (req, res, next) => {
if (!req.openapi) {
// this path was not found in open api and
// this path is not defined under an openapi base path
// skip it
return next();
}

const security = req.openapi.schema.security;
if (!security) {
return next();
}

console.log('found security ', security, req.openapi);
// run security handlers
next();
};
}
34 changes: 34 additions & 0 deletions test/resources/security.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
openapi: '3.0.2'
info:
version: 1.0.0
title: requestBodies $ref
description: requestBodies $ref Test

servers:
- url: /v1/

paths:
/api_key:
get:
security:
- ApiKeyAuth: []
responses:
'200':
description: OK
'400':
description: Bad Request
#'401':
# description: unauthorized

components:
securitySchemes:
BasicAuth:
type: http
scheme: basic
BearerAuth:
type: http
scheme: bearer
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
54 changes: 54 additions & 0 deletions test/security.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import * as path from 'path';
import * as express from 'express';
import { expect } from 'chai';
import * as request from 'supertest';
import { createApp } from './common/app';

const packageJson = require('../package.json');

describe(packageJson.name, () => {
let app = null;
let basePath = null;

before(async () => {
// Set up the express app
const apiSpec = path.join('test', 'resources', 'security.yaml');
app = await createApp(
{
apiSpec,
securityHandlers: {
ApiKeyAuth: function(req, scopes, schema) {
console.log('-------in sec handler');
},
},
},
3005,
);
basePath = app.basePath;
console.log(basePath);

app.use(
`${basePath}`,
express
.Router()
.get(`/api_key`, (req, res) => res.json({ logged_in: true })),
);
});

after(() => {
app.server.close();
});

it.only('should return 401 if apikey not valid', async () =>
request(app)
.get(`${basePath}/api_key`)
.send({})
.expect(401)
.then(r => {
console.log(r.body);
expect(r.body.errors).to.be.an('array');
expect(r.body.errors).to.have.length(1);

// TODO add test
}));
});

0 comments on commit 5a6b330

Please sign in to comment.