You're Invited: Meet the Socket team at BSidesSF and RSAC - April 27 - May 1.RSVP →

@egodigital/express-controllers

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@egodigital/express-controllers - npm Package Compare versions

Comparing version

to
4.0.0

# Change Log (@egodigital/express-controllers)
## 4.0.0
* implemented `@Swagger` decorator
* bug fixes
* code cleanups and improvements
## 3.1.0

@@ -4,0 +10,0 @@

@@ -19,106 +19,12 @@ /**

import * as joi from 'joi';
import { AuthorizeHandler, AuthorizeFailedHandler } from './authorize';
import { InitControllersSwaggerOptionsValue } from './swagger';
/**
* A handler, that is invoked, if authorization failed.
*
* @param {AuthorizeFailedHandlerContext<TRequest>} context The context.
*/
export declare type AuthorizeFailedHandler<TRequest extends express.Request = express.Request> = (context: AuthorizeFailedHandlerContext<TRequest>) => any;
/**
* The context for a handler, that is invoked, if authorization failed.
*/
export interface AuthorizeFailedHandlerContext<TRequest extends express.Request = express.Request> {
/**
* The reason from authorization handler.
*/
reason?: any;
/**
* The current HTTP request context.
*/
request: TRequest;
/**
* The list of resources to check.
*/
resources: string[];
/**
* The current HTTP response context.
*/
response: express.Response;
/**
* The result of the underlying handler.
*/
result: AuthorizeHandlerResult;
/**
* The list of roles to check.
*/
roles: string[];
}
/**
* An authorization handler.
*
* @param {AuthorizeHandlerContext<TRequest>} context The context.
*
* @return {AuthorizeHandlerResult|PromiseLike<AuthorizeHandlerResult>} The result.
*/
export declare type AuthorizeHandler<TRequest extends express.Request = express.Request> = (context: AuthorizeHandlerContext<TRequest>) => AuthorizeHandlerResult | PromiseLike<AuthorizeHandlerResult>;
/**
* The (execution) context of an authorization handler.
*/
export interface AuthorizeHandlerContext<TRequest extends express.Request = express.Request> {
/**
* Gets or sets an optional object or value,
* which describes why the authorization failed.
*
* This value if submitted to the 'failed handler'.
*/
reason?: any;
/**
* The current HTTP request context.
*/
request: TRequest;
/**
* The list of resources to check.
*/
resources: string[];
/**
* The current HTTP response context.
*/
response: express.Response;
/**
* The list of roles to check.
*/
roles: string[];
}
/**
* The result of an authorization handler.
*/
export declare type AuthorizeHandlerResult = boolean | void | null | undefined | string;
/**
* Options for an @Authorize decorator.
*/
export interface AuthorizeOptions {
/**
* The custom authorization handler.
*/
authorize?: AuthorizeHandler;
/**
* The custom handler, if authorization failed.
*/
onAuthorizeFailed?: AuthorizeFailedHandler;
/**
* One or more resource names.
*/
resources?: string | string[];
/**
* One or more role names.
*/
roles?: string | string[];
}
/**
* Describes a controller.
*/
export interface Controller {
export interface Controller<TApp extends any = ExpressApp> {
/**
* The underlying Express host or router.
* The underlying app instance.
*/
readonly __app: ExpressApp;
readonly __app: TApp;
/**

@@ -170,2 +76,22 @@ * The controller-wide authorization handler.

/**
* A function that provides the name for a controller class.
*
* @param {string} file The underyling class file (relative path).
* @param {string} fullPath The underyling class file (full path).
*
* @return {string} The class name.
*/
export declare type ControllerClassNameProvider = (file: string, fullPath: string) => string;
/**
* A function that provides the arguments for the constructor of a controller class.
*
* @param {ExpressApp} app The underlying Express app / the router.
* @param {string} routePath The route path.
* @param {express.Router} router The underlying Express for the controller instance.
* @param {string} file The underlying file.
*
* @return {ArrayLike<any>} The list of constructors.
*/
export declare type ControllerClassConstructorArgsProvider = (app: ExpressApp, routePath: string, router: express.Router, file: string) => ArrayLike<any>;
/**
* Options for a controller route.

@@ -229,2 +155,10 @@ */

/**
* The custom name for the controller class or a function that provides it. Default 'Controller'
*/
controllerClass?: ControllerClassNameProvider | string;
/**
* The custom list of arguments for a controller class contructor or the function that provides it.
*/
controllerConstructorArgs?: ControllerClassConstructorArgsProvider | ArrayLike<any>;
/**
* The custom current work directory. Default: '{PROCESS}/controllers'

@@ -237,2 +171,6 @@ */

files?: string | string[];
/**
* Swagger options.
*/
swagger?: InitControllersSwaggerOptionsValue;
}

@@ -375,4 +313,4 @@ /**

*/
export declare abstract class ControllerBase implements Controller {
readonly __app: ExpressApp;
export declare abstract class ControllerBase<TApp extends any = ExpressApp> implements Controller<TApp> {
readonly __app: TApp;
readonly __rootPath: string;

@@ -384,3 +322,3 @@ readonly __router: express.Router;

*
* @param {ExpressApp} __app The underlying Express host or router.
* @param {TApp} __app The underlying app instance.
* @param {string} __rootPath The root path.

@@ -390,3 +328,3 @@ * @param {express.Router} __router The router.

*/
constructor(__app: ExpressApp, __rootPath: string, __router: express.Router, __file: string);
constructor(__app: TApp, __rootPath: string, __router: express.Router, __file: string);
/** @inheritdoc */

@@ -396,59 +334,2 @@ __dbg(message?: any, tag?: string): void;

/**
* Sets up a controller method for authorization.
*
* @param {string|string[]} resources One or more resource names.
* @param {AuthorizeFailedHandler} [onAuthorizeFailed] Custom handler, that is invoked if authorization failes.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(resources: string | string[], onAuthorizeFailed?: AuthorizeFailedHandler): DecoratorFunction;
/**
* Sets up a controller method for authorization.
*
* @param {string|string[]} roles One or more role names.
* @param {string|string[]} resources One or more resource names.
* @param {AuthorizeFailedHandler} [onAuthorizeFailed] Custom handler, that is invoked if authorization failes.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(roles: string | string[], resources: string | string[], onAuthorizeFailed?: AuthorizeFailedHandler): DecoratorFunction;
/**
* Sets up a controller method for authorization.
*
* @param {AuthorizeHandler} authorize The custom authorization handler.
* @param {string|string[]} [resources] One or more resource names.
* @param {AuthorizeFailedHandler} [onAuthorizeFailed] Custom handler, that is invoked if authorization failes.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(authorize: AuthorizeHandler, resources?: string | string[], onAuthorizeFailed?: AuthorizeFailedHandler): DecoratorFunction;
/**
* Sets up a controller method for authorization.
*
* @param {AuthorizeHandler} authorize The custom authorization handler.
* @param {AuthorizeFailedHandler} onAuthorizeFailed Custom handler, that is invoked if authorization failes.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(authorize: AuthorizeHandler, onAuthorizeFailed: AuthorizeFailedHandler): DecoratorFunction;
/**
* Sets up a controller method for authorization.
*
* @param {AuthorizeHandler} authorize The custom authorization handler.
* @param {string|string[]} roles One or more role names.
* @param {string|string[]} resources One or more resource names.
* @param {AuthorizeFailedHandler} [onAuthorizeFailed] Custom handler, that is invoked if authorization failes.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(authorize: AuthorizeHandler, roles: string | string[], resources: string | string[], onAuthorizeFailed?: AuthorizeFailedHandler): DecoratorFunction;
/**
* Sets up a controller method for authorization.
*
* @param {AuthorizeOptions} [opts] The custom options.
*
* @return {DecoratorFunction} The decorator function.
*/
export declare function Authorize(opts?: AuthorizeOptions): DecoratorFunction;
/**
* Sets up a controller method for a CONNECT request.

@@ -809,14 +690,2 @@ *

/**
* Returns the global handler, that is invoked if authorization of a request fails.
*
* @return {AuthorizeFailedHandler} The handler.
*/
export declare function getAuthorizeFailedHandler(): AuthorizeFailedHandler;
/**
* Returns the global handler, that authorized requests.
*
* @return {AuthorizeHandler} The handler.
*/
export declare function getAuthorizeHandler(): AuthorizeHandler;
/**
* Returns the global handler, if an object validation fails.

@@ -840,14 +709,2 @@ *

/**
* Sets the global handler, which is invoked if a request authorization fails.
*
* @param {AuthorizeFailedHandler|undefined|null} newHandler The new handler.
*/
export declare function setAuthorizeFailedHandler(newHandler: AuthorizeFailedHandler | undefined | null): void;
/**
* Sets the global handler, which authorizes requests.
*
* @param {AuthorizeHandler|undefined|null} newHandler The new handler.
*/
export declare function setAuthorizeHandler(newHandler: AuthorizeHandler | undefined | null): void;
/**
* Sets the global handler, which checks if an object validation fails.

@@ -864,1 +721,3 @@ *

export declare function setRequestErrorHandler(newHandler: RequestErrorHandler | undefined | null): void;
export * from './authorize';
export * from './swagger';

@@ -18,2 +18,5 @@ "use strict";

*/
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
Object.defineProperty(exports, "__esModule", { value: true });

@@ -24,2 +27,4 @@ const _ = require("lodash");

const path = require("path");
const authorize_1 = require("./authorize");
const swagger_1 = require("./swagger");
const utils_1 = require("./utils");

@@ -54,7 +59,6 @@ /**

})(ObjectValidationFailedReason = exports.ObjectValidationFailedReason || (exports.ObjectValidationFailedReason = {}));
const AUTHORIZER_OPTIONS = Symbol('AUTHORIZER_OPTIONS');
const DEFAULT_CONTROLLER_CLASS_NAME = 'Controller';
const INITIALIZE_ROUTE = Symbol('INITIALIZE_ROUTE');
let authorizationHandler;
let authorizationFailedHandler;
let objValidateFailedHandler;
const METHOD_LIST = Symbol('METHOD_LIST');
let reqErrorHandler;

@@ -71,3 +75,3 @@ const REQUEST_ERROR_HANDLER = Symbol('REQUEST_ERROR_HANDLER');

*
* @param {ExpressApp} __app The underlying Express host or router.
* @param {TApp} __app The underlying app instance.
* @param {string} __rootPath The root path.

@@ -101,8 +105,2 @@ * @param {express.Router} __router The router.

exports.ControllerBase = ControllerBase;
function Authorize(...args) {
return function (controllerConstructor, name, descriptor) {
descriptor.value[AUTHORIZER_OPTIONS] = toAuthorizeOptions(args);
};
}
exports.Authorize = Authorize;
function CONNECT(...args) {

@@ -163,20 +161,2 @@ return function (controllerConstructor, name, descriptor) {

/**
* Returns the global handler, that is invoked if authorization of a request fails.
*
* @return {AuthorizeFailedHandler} The handler.
*/
function getAuthorizeFailedHandler() {
return authorizationFailedHandler;
}
exports.getAuthorizeFailedHandler = getAuthorizeFailedHandler;
/**
* Returns the global handler, that authorized requests.
*
* @return {AuthorizeHandler} The handler.
*/
function getAuthorizeHandler() {
return authorizationHandler;
}
exports.getAuthorizeHandler = getAuthorizeHandler;
/**
* Returns the global handler, if an object validation fails.

@@ -238,2 +218,32 @@ *

cwd = path.resolve(cwd);
// controller class name
let controllerClassNameProvider;
if (!_.isNil(opts.controllerClass)) {
if (_.isFunction(opts.controllerClass)) {
controllerClassNameProvider = opts.controllerClass;
}
else {
controllerClassNameProvider = () => opts.controllerClass;
}
}
if (_.isNil(controllerClassNameProvider)) {
// default
controllerClassNameProvider = () => DEFAULT_CONTROLLER_CLASS_NAME;
}
// constructor arguments for controller class
let controllerConstructorArgsProvider;
if (!_.isNil(opts.controllerConstructorArgs)) {
if (_.isFunction(opts.controllerConstructorArgs)) {
controllerConstructorArgsProvider = opts.controllerConstructorArgs;
}
else {
controllerConstructorArgsProvider = () => opts.controllerConstructorArgs;
}
}
if (_.isNil(controllerConstructorArgsProvider)) {
// default
controllerConstructorArgsProvider = function () {
return arguments;
};
}
const FILE_PATTERNS = utils_1.asArray(opts.files)

@@ -257,4 +267,5 @@ .map(fp => utils_1.toStringSafe(fp))

return !path.basename(f)
.startsWith('_'); // files with leading undrscores are ignored
.startsWith('_'); // files with leading underscores are ignored
}).sort((x, y) => {
// first sort by directory name
const COMP_0 = utils_1.compareValuesBy(x, y, f => {

@@ -266,3 +277,14 @@ return utils_1.normalizeString(path.dirname(f));

}
utils_1.compareValuesBy(x, y, f => {
const COMP_1 = utils_1.compareValuesBy(x, y, (f) => {
switch (path.basename(f)) {
case 'index':
return Number.MIN_SAFE_INTEGER; // 'index' file is always first
}
return Number.MAX_SAFE_INTEGER;
});
if (0 !== COMP_1) {
return COMP_1;
}
// then by file name
return utils_1.compareValuesBy(x, y, f => {
return utils_1.normalizeString(path.basename(f));

@@ -272,8 +294,11 @@ });

const ROUTERS = {};
const SWAGGER_INFOS = [];
for (const F of FILES) {
const CONTROLLER_MODULE_FILE = path.basename(F, path.extname(F));
const CONTROLLER_MODULE = require(path.join(path.dirname(F), CONTROLLER_MODULE_FILE));
const CONTROLLER_CLASS = CONTROLLER_MODULE.Controller;
const ROOT_PATH = normalizeRoutePath(path.relative(cwd, path.dirname(F) + '/' + ('index' === CONTROLLER_MODULE_FILE ? '' : CONTROLLER_MODULE_FILE)));
// custom class name
let controllerClassName = controllerClassNameProvider(ROOT_PATH, F);
const CONTROLLER_CLASS = CONTROLLER_MODULE[controllerClassName];
if (CONTROLLER_CLASS) {
const ROOT_PATH = normalizeRoutePath(path.relative(cwd, path.dirname(F) + '/' + ('index' === CONTROLLER_MODULE_FILE ? '' : CONTROLLER_MODULE_FILE)));
if (_.isNil(ROUTERS[ROOT_PATH])) {

@@ -285,3 +310,17 @@ ROUTERS[ROOT_PATH] = express.Router();

const ROUTER = ROUTERS[ROOT_PATH];
const CONTROLLER = new CONTROLLER_CLASS(opts.app, ROOT_PATH, ROUTER, F);
// constructor arguments
let controllerConstructorArgs = controllerConstructorArgsProvider(opts.app, ROOT_PATH, ROUTER, F);
if (_.isNil(controllerConstructorArgs)) {
controllerConstructorArgs = []; // default
}
if (!_.isArray(controllerConstructorArgs)) {
// convert to array
const ARR = controllerConstructorArgs;
controllerConstructorArgs = [];
for (let i = 0; i < ARR.length; i) {
controllerConstructorArgs.push(ARR[i]);
}
}
// s. https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
const CONTROLLER = new (Function.prototype.bind.apply(CONTROLLER_CLASS, [CONTROLLER_CLASS].concat(controllerConstructorArgs)));
const ROUTER_MIDDLEWARES = utils_1.asArray(CONTROLLER.__use)

@@ -304,3 +343,3 @@ .map(rmw => wrapHandlerForController(CONTROLLER, rmw, false));

case 'index':
return 0; // index is always first
return Number.MIN_SAFE_INTEGER; // 'index' method is always first
}

@@ -318,2 +357,8 @@ return Number.MAX_SAFE_INTEGER;

for (const MN of METHOD_NAMES) {
const SWAGGER = CONTROLLER[MN][swagger_1.SWAGGER_INFO];
if (!_.isNil(SWAGGER)) {
SWAGGER.methods = utils_1.asArray(CONTROLLER[MN][METHOD_LIST]);
SWAGGER.routePath = ROOT_PATH;
SWAGGER_INFOS.push(SWAGGER);
}
CONTROLLER[MN][INITIALIZE_ROUTE](CONTROLLER);

@@ -323,23 +368,6 @@ }

}
swagger_1.setupSwaggerUI(opts.app, opts.swagger, SWAGGER_INFOS);
}
exports.initControllers = initControllers;
/**
* Sets the global handler, which is invoked if a request authorization fails.
*
* @param {AuthorizeFailedHandler|undefined|null} newHandler The new handler.
*/
function setAuthorizeFailedHandler(newHandler) {
authorizationFailedHandler = newHandler;
}
exports.setAuthorizeFailedHandler = setAuthorizeFailedHandler;
/**
* Sets the global handler, which authorizes requests.
*
* @param {AuthorizeHandler|undefined|null} newHandler The new handler.
*/
function setAuthorizeHandler(newHandler) {
authorizationHandler = newHandler;
}
exports.setAuthorizeHandler = setAuthorizeHandler;
/**
* Sets the global handler, which checks if an object validation fails.

@@ -362,70 +390,2 @@ *

exports.setRequestErrorHandler = setRequestErrorHandler;
function createRouteAuthorizer(controller, method) {
return async (req, res, next) => {
const OPTS = method[AUTHORIZER_OPTIONS];
if (!_.isNil(OPTS)) {
let authorizer = OPTS.authorize;
if (_.isNil(authorizer)) {
authorizer = controller.__authorize; // of controller
}
if (_.isNil(authorizer)) {
authorizer = authorizationHandler; // global
}
if (!_.isNil(authorizer)) {
const AUTHORIZE_CTX = {
reason: undefined,
request: req,
response: res,
resources: utils_1.asArray(OPTS.resources)
.map(r => utils_1.toStringSafe(r))
.filter(r => '' !== r.trim()),
roles: utils_1.asArray(OPTS.roles)
.map(r => utils_1.toStringSafe(r))
.filter(r => '' !== r.trim())
};
let authorizeResult = await Promise.resolve(authorizer(AUTHORIZE_CTX));
if (_.isNil(authorizeResult)) {
authorizeResult = false;
}
let isAuthorized;
if (_.isString(authorizeResult)) {
isAuthorized = '' === authorizeResult.trim();
}
else {
isAuthorized = utils_1.toBooleanSafe(authorizeResult);
}
if (!isAuthorized) {
let failedHandler = OPTS.onAuthorizeFailed;
if (_.isNil(failedHandler)) {
failedHandler = controller.__authorizeFailed; // of controller
}
if (_.isNil(failedHandler)) {
failedHandler = authorizationFailedHandler; // global
}
if (_.isNil(failedHandler)) {
// default
failedHandler = async (ctx) => {
if (_.isString(ctx.result)) {
return ctx.response.status(403)
.send(ctx.result.trim());
}
return ctx.response.status(403)
.send();
};
}
const AUTHORIZE_FAILED_CTX = {
reason: AUTHORIZE_CTX.reason,
request: req,
response: res,
resources: AUTHORIZE_CTX.resources,
result: authorizeResult,
roles: AUTHORIZE_CTX.roles,
};
return Promise.resolve(failedHandler(AUTHORIZE_FAILED_CTX));
}
}
}
return next(); // authorized or no handler defined
};
}
function createRouteInitializer(name, descriptor, opts, ifFunction, ifArray, prepare) {

@@ -506,2 +466,11 @@ if (_.isNil(opts)) {

const VALUE = descriptor.value;
// method list (for Swagger UI, e.g.)
{
if (!_.isArray(VALUE[METHOD_LIST])) {
VALUE[METHOD_LIST] = [];
}
if (VALUE[METHOD_LIST].indexOf(method) < 0) {
VALUE[METHOD_LIST].push(method);
}
}
createRouteInitializer(name, descriptor, opts, (controller, path, handler) => {

@@ -513,3 +482,3 @@ controller.__router[method]

.concat([
createRouteAuthorizer(controller, VALUE),
authorize_1.createRouteAuthorizer(controller, VALUE),
].map(a => wrapHandlerForController(controller, a, false)))

@@ -527,3 +496,3 @@ .concat(utils_1.asArray(descriptor.value[REQUEST_VALIDATORS])

.concat([
createRouteAuthorizer(controller, VALUE),
authorize_1.createRouteAuthorizer(controller, VALUE),
].map(a => wrapHandlerForController(controller, a, false)))

@@ -644,67 +613,2 @@ .concat(utils_1.asArray(descriptor.value[REQUEST_VALIDATORS])

}
function toAuthorizeOptions(args) {
let opts = {};
if (args.length) {
const FIRST_ARG = args[0];
if (_.isObjectLike(FIRST_ARG) &&
!_.isArray(FIRST_ARG) &&
!_.isString(FIRST_ARG)) {
// [0] opts: AuthorizeOptions
opts = FIRST_ARG;
}
else {
if (_.isFunction(FIRST_ARG)) {
// [0] authorize: AuthorizeHandler
opts = {
authorize: FIRST_ARG,
};
if (args.length > 1) {
if (_.isFunction(args[1])) {
// [1] onAuthorizeFailed: AuthorizeFailedHandler
opts.onAuthorizeFailed = args[1];
}
else {
if (_.isFunction(args[2])) {
// [1] resources: string | string[];
// [2] onAuthorizeFailed: AuthorizeFailedHandler
opts.resources = args[1];
opts.onAuthorizeFailed = args[2];
}
else {
// [1] roles: string | string[];
// [2] resources: string | string[];
// [3] onAuthorizeFailed?: AuthorizeFailedHandler
opts.roles = args[1];
opts.resources = args[2];
opts.onAuthorizeFailed = args[3];
}
}
}
}
else {
if (args.length < 2) {
// [0] resources: string | string[]
opts.resources = args[0];
}
else {
if (_.isFunction(args[1])) {
// [0] resources: string | string[]
// [1] onAuthorizeFailed?: AuthorizeFailedHandler
opts.resources = args[0];
opts.onAuthorizeFailed = args[1];
}
else {
// [0] roles: string | string[]
// [1] resources: string | string[]
// [2] onAuthorizeFailed?: AuthorizeFailedHandler
opts.roles = args[0];
opts.resources = args[1];
opts.onAuthorizeFailed = args[2];
}
}
}
}
}
return opts;
}
function toControllerRouteOptions(args) {

@@ -823,2 +727,4 @@ let opts;

}
__export(require("./authorize"));
__export(require("./swagger"));
//# sourceMappingURL=index.js.map
{
"name": "@egodigital/express-controllers",
"version": "3.1.1",
"version": "4.0.0",
"description": "Sets up controllers for Express framework.",

@@ -42,8 +42,12 @@ "author": "e.GO Digital GmbH, Aachen, Germany",

"@types/joi": "^14.3.3",
"@types/js-yaml": "^3.12.1",
"@types/lodash": "^4.14.136",
"@types/node": "^10.14.12",
"@types/node": "^10.14.13",
"@types/swagger-ui-express": "^3.0.1",
"express": "^4.17.1",
"fast-glob": "^2.2.7",
"joi": "^14.3.1",
"lodash": "^4.17.14"
"js-yaml": "^3.13.1",
"lodash": "^4.17.14",
"swagger-ui-express": "^4.0.7"
},

@@ -50,0 +54,0 @@ "devDependencies": {