Skip to content
This repository has been archived by the owner on Aug 24, 2021. It is now read-only.
/ Express-Rules Public archive

A simple ruler that will can help you sampling the way you apply rules to your routes

Notifications You must be signed in to change notification settings

Paradoxu/Express-Rules

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Express-Rules

A simple ruler that will can help you sampling the way you apply rules to your routes

This is a beta package and should not be used in production!

Usage

Configuring your rules

Preferably create and export a variable with your rules in a new file.

Rules can have the following structure:

{
    global?: Rule & {
		exclude?: string[];
	};
    routes?: {
        [key: string]: Rule;
    };
};
  • global: Global rules can be used to apply rules to all routes, without having to specify them manually.

  • global.exclude - takes an array of routes that will be defined inside the routes, all routes passed in this array will ignore the global rules.

  • routes: Takes an object of Rule, where the key corrisponds to the route name of that level.

What is a level?

The level of a route is how deep your route is inside the yours Rules. Example: route: /client/details/:id In this case client would be a route of first level, details second level and the :id parameter a third level.

An example of Rule for this case would be:

const rules: Rules = {
	global: {
		allowAllIf: () => isAuthenticated === true,
		exclude: ['login']
	},
	routes: {
		client: {
			routes:{
				details:{
					routes: {
						':id': {

						}
					}
				}
			}
		}
	}
}

Each level of the routes key must implement the Rule interface that has the following format:

interface Rule {
	allowReadIf?: (levelValue: string, req: RequestRule) => boolean;
	allowWriteIf?: (levelValue: string, req: RequestRule) => boolean;
	allowAllIf?: (levelValue: string, req: RequestRule) => boolean;
	schema?: Schema;
	routes?: {
		[key: string]: Rule;
	};
}

The optional methods allowReadIf, allowWriteIf and allowAllIf can define the behavior of the ruler when that level is reached and they have cascade effect, which mean that if request is rejected by any previous level, but authorize the request on the current level, the request will be rejected anyway, example:

'client': {
	allowAllIf: () => false,
	routes: {
		':id': {
			allowAllIf: () => true
		},
	}
}

In this example we have the client level that is rejecting any kind of request, but the route client/:id that accepts all, for performance reasons the ruler will stop on the first rejected rule, which means that it won't even reach the handler allowAllIf inside :id. Any of these handler will be called with the parameters: levelValue: string and request: RequestRule, to help you handle the request easy.

  • levelValue: Is a string that has the value of the current level

Example of request: /client/20

'client': {
	// value in this case will be a string equal to 'client'
	allowAllIf: (value) => false,
	routes: {
		':id': {
			// value is a string, but it's not equal to ':id' instead it will have the actual value of the route, which is '20'
			allowAllIf: (value) => true
		},
	}
}
  • request: (RequestRule)[https://github.com/Paradoxu/Express-Rules/blob/master/src/request_rule.ts] it's just a simple wrapper around the original Request retrieved from the original request of express, that can help you to handle the request with simple method like:
    • maxLength(N: number): Return false if the length of the body is higher than N
    • minLength(N: number): Return false if the length of the body is lower than N
    • hasProps(p: string | string[]): Return true if the passed properties exists on the body of this request
    • isRead(): if the request type is a Get, Head or Options
    • isWrite(): if the request type is a Post, Put or Delete

If you want have a better control of the validation of this information, you can also get the original request by calling the method original() on the RequestRule object, which will return the original request generated by express.

You can also define schema like the json schema package and pass this schema as a value to the schema attribute of any Rule Level, once you have defined a schema, you can easily call the method isValid on the RequestRule to verify if the body of that level matches the predefined schema.

To help with intellisense, JS users can define an schema by importing the createSchema function that takes a Schema as parameter.

Usage of schemas

const { expressRules, ruler, createSchema } = require('@paradoxu/express-rules');

const loginSchema = createSchema({
	type: 'object',
	properties: {
		email: {
			type: 'string',
			required: true,
			maxLength: 64,
			pattern: /^\S+@\S+\.\S+$/
		},
		password: {
			type: 'string',
			required: true,
			minLength: 8,
			maxLength: 128
		}
	}
});

expressRules.configure({
	routes: {
		login: {
			schema: loginSchema,
			allowReadIf: (_, req) => req.isValid
		}
	}
});

app.post('/login', ruler, function(req, res) {
	// ... Handle your login request
});

Once you have defined your rules, just call the method configure like expressRules.configure(myRules) in the initialization of your app, and then pass the ruler as a middleware for your routes:

app.post('/client/:id', ruler, (req, res) => {
  // ... handle your request
});

The configure method should be called only once, preferably when you start your app, if you call it more than once in different scripts, each call will override the previous rules.

Attach to express

Since version 0.0.8-beta it is also possible to modify the default express dispatch handler to always call the our ruler before calling the route handle, by using this method it unnecessary to pass the ruler in every route.

The attach method takes the Route function exposed by the express module, example:

const Route = require('express').Route;

expressRules.configure(myRules);
expressRules.attach(Route); // Attach our ruler to `express` prototype

And now you can just implement your routes like:

app.post('/client/:id', (req, res) => {
  // ... handle your request
});

And express will always call our ruler before your handlers. If you have multiple files with different Routes, you just need to call attach once when you start your app.

Attention, this is a test method that my change or be removed in the future!

About

A simple ruler that will can help you sampling the way you apply rules to your routes

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published