Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
cdimascio committed Jan 17, 2021
2 parents ea97cde + 51806a8 commit 01a0eb3
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 116 deletions.
4 changes: 2 additions & 2 deletions src/framework/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export type Format = {
};

export type Serializer = {
format: string,
format: string;
serialize: (o: unknown) => string;
};

Expand Down Expand Up @@ -436,7 +436,7 @@ export interface OpenApiRequestMetadata {
}

export interface OpenApiRequest extends Request {
openapi?: OpenApiRequestMetadata | {};
openapi?: OpenApiRequestMetadata;
}

export type OpenApiRequestHandler = (
Expand Down
13 changes: 12 additions & 1 deletion src/middlewares/openapi.metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { pathToRegexp } from 'path-to-regexp';
import { Response, NextFunction } from 'express';
import { OpenApiContext } from '../framework/openapi.context';
import {
MethodNotAllowed,
NotFound,
OpenApiRequest,
OpenApiRequestHandler,
OpenApiRequestMetadata,
Expand All @@ -24,6 +26,12 @@ export function applyOpenApiMetadata(
const matched = lookupRoute(req);
if (matched) {
const { expressRoute, openApiRoute, pathParams, schema } = matched;
if (!schema) {
throw new MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
});
}
req.openapi = {
expressRoute: expressRoute,
openApiRoute: openApiRoute,
Expand All @@ -36,7 +44,10 @@ export function applyOpenApiMetadata(
(<any>req.openapi)._responseSchema = (<any>matched)._responseSchema;
}
} else if (openApiContext.isManagedRoute(path)) {
req.openapi = {};
throw new NotFound({
path: req.path,
message: 'not found',
});
}
next();
};
Expand Down
14 changes: 0 additions & 14 deletions src/middlewares/openapi.request.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,7 @@ export class RequestValidator {

const openapi = <OpenApiRequestMetadata>req.openapi;
const path = openapi.expressRoute;
if (!path) {
throw new NotFound({
path: req.path,
message: 'not found',
});
}

const reqSchema = openapi.schema;
if (!reqSchema) {
throw new MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
});
}

// cache middleware by combining method, path, and contentType
const contentType = ContentType.from(req);
const contentTypeKey = contentType.equivalents()[0] ?? 'not_provided';
Expand Down
182 changes: 83 additions & 99 deletions src/middlewares/openapi.security.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,28 +38,6 @@ export function security(
}

const openapi = <OpenApiRequestMetadata>req.openapi;
const expressRoute = openapi.expressRoute;
if (!expressRoute) {
return next(
new NotFound({
path: req.path,
message: 'not found',
}),
);
}

const pathSchema = openapi.schema;
if (!pathSchema) {
// add openapi metadata to make this case more clear
// its not obvious that missig schema means methodNotAllowed
return next(
new MethodNotAllowed({
path: req.path,
message: `${req.method} method not allowed`,
}),
);
}

// use the local security object or fallbac to api doc's security or undefined
const securities: OpenAPIV3.SecurityRequirementObject[] =
openapi.schema.security ?? apiDoc.security;
Expand All @@ -82,54 +60,54 @@ export function security(
securitySchemes,
securityHandlers,
securities,
).executeHandlers(req);
).executeHandlers(req);

// TODO handle AND'd and OR'd security
// This assumes OR only! i.e. at least one security passed authentication
let firstError: SecurityHandlerResult = null;
let firstError: SecurityHandlerResult = null;
let success = false;

function checkErrorWithOr(res) {
return res.forEach(r => {
if (r.success) {
success = true;
} else if (!firstError) {
firstError = r;
}
})
return res.forEach((r) => {
if (r.success) {
success = true;
} else if (!firstError) {
firstError = r;
}
});
}

function checkErrorsWithAnd(res) {
let allSuccess = false;

res.forEach(r => {
if (!r.success) {
allSuccess = false;
if (!firstError) {
firstError = r;
}
} else if (!firstError) {
allSuccess = true;
}
})

if (allSuccess) {
success = true;
let allSuccess = false;

res.forEach((r) => {
if (!r.success) {
allSuccess = false;
if (!firstError) {
firstError = r;
}
} else if (!firstError) {
allSuccess = true;
}
});

if (allSuccess) {
success = true;
}
}

results.forEach(result => {
if (Array.isArray(result) && result.length > 1) {
checkErrorsWithAnd(result);
} else {
checkErrorWithOr(result);
}
results.forEach((result) => {
if (Array.isArray(result) && result.length > 1) {
checkErrorsWithAnd(result);
} else {
checkErrorWithOr(result);
}
});

if (success) {
next();
next();
} else {
throw firstError;
throw firstError;
}
} catch (e) {
const message = e?.error?.message || 'unauthorized';
Expand Down Expand Up @@ -177,50 +155,56 @@ class SecuritySchemes {
// anonumous security
return [{ success: true }];
}
return Promise.all(Object.keys(s).map(async (securityKey) => {
var _a, _b, _c;
try {
const scheme = this.securitySchemes[securityKey];
const handler = (_b = (_a = this.securityHandlers) === null || _a === void 0 ? void 0 : _a[securityKey]) !== null && _b !== void 0 ? _b : fallbackHandler;
const scopesTmp = s[securityKey];
const scopes = Array.isArray(scopesTmp) ? scopesTmp : [];
if (!scheme) {
const message = `components.securitySchemes.${securityKey} does not exist`;
throw new InternalServerError({ message });
}
if (!scheme.hasOwnProperty('type')) {
const message = `components.securitySchemes.${securityKey} must have property 'type'`;
throw new InternalServerError({ message });
}
if (!handler) {
const message = `a security handler for '${securityKey}' does not exist`;
throw new InternalServerError({ message });
}
new AuthValidator(req, scheme, scopes).validate();
// expected handler results are:
// - throw exception,
// - return true,
// - return Promise<true>,
// - return false,
// - return Promise<false>
// everything else should be treated as false
const securityScheme = <OpenAPIV3.SecuritySchemeObject>scheme;
const success = await handler(req, scopes, securityScheme);
if (success === true) {
return { success };
}
else {
throw Error();
return Promise.all(
Object.keys(s).map(async (securityKey) => {
var _a, _b, _c;
try {
const scheme = this.securitySchemes[securityKey];
const handler =
(_b =
(_a = this.securityHandlers) === null || _a === void 0
? void 0
: _a[securityKey]) !== null && _b !== void 0
? _b
: fallbackHandler;
const scopesTmp = s[securityKey];
const scopes = Array.isArray(scopesTmp) ? scopesTmp : [];
if (!scheme) {
const message = `components.securitySchemes.${securityKey} does not exist`;
throw new InternalServerError({ message });
}
if (!scheme.hasOwnProperty('type')) {
const message = `components.securitySchemes.${securityKey} must have property 'type'`;
throw new InternalServerError({ message });
}
if (!handler) {
const message = `a security handler for '${securityKey}' does not exist`;
throw new InternalServerError({ message });
}
new AuthValidator(req, scheme, scopes).validate();
// expected handler results are:
// - throw exception,
// - return true,
// - return Promise<true>,
// - return false,
// - return Promise<false>
// everything else should be treated as false
const securityScheme = <OpenAPIV3.SecuritySchemeObject>scheme;
const success = await handler(req, scopes, securityScheme);
if (success === true) {
return { success };
} else {
throw Error();
}
} catch (e) {
return {
success: false,
status: (_c = e.status) !== null && _c !== void 0 ? _c : 401,
error: e,
};
}
}
catch (e) {
return {
success: false,
status: (_c = e.status) !== null && _c !== void 0 ? _c : 401,
error: e,
};
}
}));
}),
);
});
return Promise.all(promises);
}
Expand Down

0 comments on commit 01a0eb3

Please sign in to comment.