Skip to content

Commit

Permalink
support apikey, bearer, and basic auth
Browse files Browse the repository at this point in the history
  • Loading branch information
Carmine DiMascio committed Oct 11, 2019
1 parent 5e4feb8 commit b9cb4e8
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 80 deletions.
1 change: 1 addition & 0 deletions src/middlewares/openapi.request.validator.ts
Expand Up @@ -40,6 +40,7 @@ export class RequestValidator {
}

// cache middleware by combining method, path, and contentType
// TODO contentType could have value not_provided
const contentType = extractContentType(req);
const key = `${req.method}-${req.path}-${contentType}`;

Expand Down
164 changes: 119 additions & 45 deletions src/middlewares/openapi.security.ts
@@ -1,13 +1,13 @@
import { SecurityHandlers } from '../index';
import { OpenAPIV3 } from '../framework/types';
import { OpenAPIV3, OpenApiRequest } from '../framework/types';
import { validationError } from './util';
import { OpenApiContext } from '../framework/openapi.context';

export function security(
context: OpenApiContext,
securityHandlers: SecurityHandlers,
) {
return (req, res, next) => {
return async (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
Expand All @@ -19,7 +19,7 @@ export function security(
req.openapi.schema.security
);

const path = req.openapi.openApiRoute;
const path: string = req.openapi.openApiRoute;

if (!path || !Array.isArray(securitySchema)) {
return next();
Expand All @@ -28,60 +28,134 @@ export function security(
const securitySchemes =
context.apiDoc.components && context.apiDoc.components.securitySchemes;
if (!securitySchemes) {
// TODO throw error securitySchemes don't exist, but a security is referenced in this model
const message = `security referenced at path ${path}, but not defined in components.securitySchemes`;
return next(validationError(500, path, message));
}

// TODO security could be boolean or promise bool, handle both
const promises = securitySchema.map(s => {
const securityKey = Object.keys(s)[0];
const scheme: any = securitySchemes[securityKey];
const handler = securityHandlers[securityKey];
if (!scheme) {
const message = `components.securitySchemes.${securityKey} does not exist`;
return Promise.reject(validationError(401, path, message));
}
if (!handler) {
const message = `a handler for ${securityKey} does not exist`;
return Promise.reject(validationError(401, path, message));
}
if (scheme.type === 'apiKey') {
// check defined header
if (scheme.in === 'header') {
if (!req.headers[scheme.name.toLowerCase()]) {
return Promise.reject(validationError(401, path, `'${scheme.name}' header required.`));
}
} else if (scheme.in === 'query') {
if (!req.headers[scheme.name]) {
return Promise.reject(validationError(401, path, `query parameter '${scheme.name}' required.`));
}
try {
const securityKey = Object.keys(s)[0];
const scheme: any = securitySchemes[securityKey];
const handler = securityHandlers[securityKey];

if (!scheme) {
const message = `components.securitySchemes.${securityKey} does not exist`;
throw validationError(401, path, message);
}
}
if (['http'].includes(scheme.type)) {
if (!req.headers['authorization']) {
return Promise.reject(validationError(401, path, `'authorization' header required.`));
if (!scheme.type) {
const message = `components.securitySchemes.${securityKey} must have property 'type'`;
throw validationError(401, path, message);
}
if (!handler) {
const message = `a handler for ${securityKey} does not exist`;
throw validationError(401, path, message);
}
}
// TODO handle other security types

// TODO get scopes
const scopes = [];
try {
const { scopes } = new AuthValidator(req, scheme).validate();

return Promise.resolve(handler(req, scopes, securitySchema));
} catch (e) {
return Promise.reject(e);
}
});

return Promise.all(promises)
.then(results => {
const authFailed = results.filter(b => !b).length > 0;
if (authFailed) throw Error();
else next();
})
.catch(e => {
const message = (e && e.message) || 'unauthorized';
const err = validationError(401, path, message);
next(err);
});
try {
const results = await Promise.all(promises);
const authFailed = results.filter(b => !b).length > 0;
if (authFailed) throw validationError(401, path, 'unauthorized');
else next();
} catch (e) {
const message = (e && e.message) || 'unauthorized';
const err = validationError(401, path, message);
next(err);
}
};
}

class AuthValidator {
private req: OpenApiRequest;
private scheme;
private path: string;
private scopes: string[] = [];
constructor(req: OpenApiRequest, scheme) {
this.req = req;
this.scheme = scheme;
this.path = req.openapi.openApiRoute;
}

validate() {
this.validateApiKey();
this.validateHttp();
this.validateOauth2();
this.validateOpenID();
return {
scopes: this.scopes,
};
}

private validateOauth2() {
// TODO get scopes from auth validator
const { req, scheme, path } = this;
if (['oauth2'].includes(scheme.type.toLowerCase())) {
// TODO handle oauth2
}
}

private validateOpenID() {
// TODO get scopes from auth validator
const { req, scheme, path } = this;
if (['openIdConnect'].includes(scheme.type.toLowerCase())) {
// TODO handle openidconnect
}
}

private validateHttp() {
const { req, scheme, path } = this;
if (['http'].includes(scheme.type.toLowerCase())) {
const authHeader =
req.headers['authorization'] &&
req.headers['authorization'].toLowerCase();

if (!authHeader) {
throw validationError(401, path, `Authorization header required.`);
}

const type = scheme.scheme && scheme.scheme.toLowerCase();
if (type === 'bearer' && !authHeader.includes('bearer')) {
throw validationError(
401,
path,
`Authorization header with scheme 'Bearer' required.`,
);
}

if (type === 'basic' && !authHeader.includes('basic')) {
throw validationError(
401,
path,
`Authorization header with scheme 'Basic' required.`,
);
}
}
}

private validateApiKey() {
const { req, scheme, path } = this;
if (scheme.type === 'apiKey') {
if (scheme.in === 'header') {
if (!req.headers[scheme.name.toLowerCase()]) {
throw validationError(401, path, `'${scheme.name}' header required.`);
}
} else if (scheme.in === 'query') {
if (!req.headers[scheme.name]) {
throw validationError(
401,
path,
`query parameter '${scheme.name}' required.`,
);
}
}
}
}
}
20 changes: 20 additions & 0 deletions test/resources/security.yaml
Expand Up @@ -20,6 +20,26 @@ paths:
#'401':
# description: unauthorized

/bearer:
get:
security:
- BearerAuth: []
responses:
'200':
description: OK
'400':
description: Bad Request

/basic:
get:
security:
- BasicAuth: []
responses:
'200':
description: OK
'400':
description: Bad Request

components:
securitySchemes:
BasicAuth:
Expand Down

0 comments on commit b9cb4e8

Please sign in to comment.