├── .eslintrc ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src └── index.ts ├── test └── unless.tests.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "rules": { 13 | "@typescript-eslint/no-namespace": "off" 14 | }, 15 | "ignorePatterns": [ 16 | "*.js" 17 | ] 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by http://www.gitignore.io 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Commenting this out is preferred by some people, see 27 | # https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 28 | node_modules 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | 33 | dist/* 34 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2014 José F. Romaniello 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Conditionally skip a middleware when a condition is met. 2 | 3 | ## Install 4 | 5 | npm i express-unless --save 6 | 7 | ## Usage 8 | 9 | With existing middlewares: 10 | 11 | ```javascript 12 | var { unless } = require("express-unless"); 13 | 14 | var static = express.static(__dirname + "/public"); 15 | static.unless = unless; 16 | 17 | app.use(static.unless({ method: "OPTIONS" })); 18 | ``` 19 | 20 | If you are authoring a middleware you can support unless as follow: 21 | 22 | ```javascript 23 | var { unless } = require("express-unless"); 24 | 25 | module.exports = function (middlewareOptions) { 26 | var mymid = function (req, res, next) {}; 27 | 28 | mymid.unless = unless; 29 | 30 | return mymid; 31 | }; 32 | ``` 33 | 34 | ## Current options 35 | 36 | - `method` it could be an string or an array of strings. If the request method match the middleware will not run. 37 | - `path` it could be an string, a regexp or an array of any of those. It also could be an array of object which is url and methods key-pairs. If the request path or path and method match, the middleware will not run. Check [Examples](#examples) for usage. 38 | - `ext` it could be an string or an array of strings. If the request path ends with one of these extensions the middleware will not run. 39 | - `custom` it must be a function that accepts `req` and returns `true` / `false`. If the function returns true for the given request, the middleware will not run. 40 | - `useOriginalUrl` it should be `true` or `false`, default is `true`. if false, `path` will match against `req.url` instead of `req.originalUrl`. Please refer to [Express API](http://expressjs.com/4x/api.html#request) for the difference between `req.url` and `req.originalUrl`. 41 | 42 | ## Examples 43 | 44 | Require authentication for every request unless the path is index.html. 45 | 46 | ```javascript 47 | app.use( 48 | requiresAuth.unless({ 49 | path: ["/index.html", { url: "/", methods: ["GET", "PUT"] }], 50 | }) 51 | ); 52 | ``` 53 | 54 | Avoid a fstat for request to routes doesnt end with a given extension. 55 | 56 | ```javascript 57 | app.use( 58 | static.unless(function (req) { 59 | var ext = url.parse(req.originalUrl).pathname.substr(-4); 60 | return !~[".jpg", ".html", ".css", ".js"].indexOf(ext); 61 | }) 62 | ); 63 | ``` 64 | 65 | ## License 66 | 67 | MIT 2014 - Jose Romaniello 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-unless", 3 | "description": "Conditionally add a middleware to express with some common patterns.", 4 | "version": "2.1.3", 5 | "license": "MIT", 6 | "repository": { 7 | "url": "git://github.com/jfromaniello/express-unless.git" 8 | }, 9 | "author": { 10 | "name": "José F. Romaniello", 11 | "email": "jfromaniello@gmail.com", 12 | "url": "http://joseoncode.com" 13 | }, 14 | "files": [ 15 | "/dist" 16 | ], 17 | "main": "./dist/index.js", 18 | "types": "./dist/index.d.ts", 19 | "scripts": { 20 | "build": "rm -rf dist && tsc", 21 | "prepare": "npm run build", 22 | "test": "mocha -R spec --require ts-node/register test/**", 23 | "lint": "eslint --fix --ext .ts ./src" 24 | }, 25 | "devDependencies": { 26 | "@types/chai": "^4.3.1", 27 | "@types/express": "^4.17.13", 28 | "@types/mocha": "^9.1.1", 29 | "@typescript-eslint/eslint-plugin": "^5.27.0", 30 | "@typescript-eslint/parser": "^5.27.0", 31 | "chai": "^4.3.6", 32 | "eslint": "^8.16.0", 33 | "express": "^4.18.1", 34 | "mocha": "^10.0.0", 35 | "prettier": "^2.6.2", 36 | "ts-node": "^10.8.0", 37 | "typescript": "^4.7.2" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as URL from 'url'; 3 | 4 | 5 | export type Path = string | RegExp | { url: string | RegExp, method?: string, methods?: string | string[] }; 6 | 7 | export type RequestChecker = (req: express.Request) => boolean; 8 | 9 | export type Params = { 10 | method?: string | string[], 11 | path?: Path | Path[], 12 | ext?: string | string[], 13 | useOriginalUrl?: boolean, 14 | custom?: RequestChecker 15 | } | RequestChecker; 16 | 17 | export function unless(options: Params) { 18 | // eslint-disable-next-line @typescript-eslint/no-this-alias 19 | const middleware = this; 20 | const opts: Params = typeof options === 'function' ? 21 | { custom: options } : 22 | options; 23 | 24 | opts.useOriginalUrl = (typeof opts.useOriginalUrl === 'undefined') ? true : opts.useOriginalUrl; 25 | 26 | const result = async function (req: express.Request, res: express.Response, next: express.NextFunction) { 27 | const url = URL.parse((opts.useOriginalUrl ? req.originalUrl : req.url) || req.url || '', true); 28 | 29 | let skip = false; 30 | 31 | if (opts.custom) { 32 | skip = skip || (await opts.custom(req)); 33 | } 34 | 35 | const paths = toArray(opts.path); 36 | 37 | if (paths) { 38 | skip = skip || paths.some(function (p) { 39 | if (typeof p === 'string' || p instanceof RegExp) { 40 | return isUrlMatch(p, url.pathname); 41 | } else { 42 | return isUrlMatch(p, url.pathname) && isMethodMatch(p.method || p.methods, req.method); 43 | } 44 | }); 45 | } 46 | 47 | 48 | if (typeof opts.ext !== 'undefined') { 49 | const exts = toArray(opts.ext); 50 | skip = skip || exts.some(function (ext) { 51 | return url.pathname.slice(ext.length * -1) === ext; 52 | }); 53 | } 54 | 55 | 56 | if (typeof opts.method !== 'undefined') { 57 | const methods = toArray(opts.method); 58 | skip = skip || methods.indexOf(req.method) > -1; 59 | } 60 | 61 | if (skip) { 62 | return next(); 63 | } 64 | 65 | middleware(req, res, next); 66 | }; 67 | 68 | result.unless = unless; 69 | 70 | return result; 71 | } 72 | 73 | function toArray(elementOrArray: T | T[]): T[] { 74 | return Array.isArray(elementOrArray) ? elementOrArray : [elementOrArray]; 75 | } 76 | 77 | function isUrlMatch(p: string | RegExp | { url: string | RegExp }, url: string) { 78 | if (typeof p === 'string') { 79 | return p === url; 80 | } 81 | 82 | if (p instanceof RegExp) { 83 | return url.match(p) !== null; 84 | } 85 | 86 | if (typeof p === 'object' && p.url) { 87 | return isUrlMatch(p.url, url); 88 | } 89 | 90 | return false; 91 | } 92 | 93 | function isMethodMatch(methods: undefined | string | string[], m: string): boolean { 94 | if (typeof methods === 'undefined') { 95 | return true; 96 | } 97 | return toArray(methods).includes(m); 98 | } 99 | -------------------------------------------------------------------------------- /test/unless.tests.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { unless } from '../src/index'; 3 | import { assert } from 'chai'; 4 | // eslint-disable-next-line @typescript-eslint/no-empty-function 5 | const noop = function () { }; 6 | 7 | function testMiddleware(req, res, next) { 8 | req.called = true; 9 | next(); 10 | } 11 | 12 | testMiddleware.unless = unless; 13 | 14 | describe('express-unless', function () { 15 | 16 | describe('with PATH and method exception', function () { 17 | const mid = testMiddleware.unless({ 18 | path: [ 19 | { 20 | url: '/test', 21 | methods: ['POST', 'GET'] 22 | }, 23 | { 24 | url: '/bar', 25 | method: 'PUT' 26 | }, 27 | { 28 | url: /^\/regex/, 29 | method: 'OPTIONS' 30 | }, 31 | '/foo' 32 | ] 33 | }); 34 | 35 | it('should not call the middleware when path and method match', function () { 36 | let req: any = { 37 | originalUrl: '/test?das=123', 38 | method: 'POST' 39 | }; 40 | 41 | mid(req, {} as any, noop); 42 | assert.notOk(req.called); 43 | 44 | 45 | req = { 46 | originalUrl: '/test?test=123', 47 | method: 'GET' 48 | }; 49 | 50 | mid(req, {} as any, noop); 51 | assert.notOk(req.called); 52 | 53 | req = { 54 | originalUrl: '/bar?test=123', 55 | method: 'PUT' 56 | }; 57 | 58 | mid(req, {} as any, noop); 59 | assert.notOk(req.called); 60 | 61 | req = { 62 | originalUrl: '/regex', 63 | method: 'OPTIONS' 64 | }; 65 | 66 | mid(req, {} as any, noop); 67 | assert.notOk(req.called); 68 | 69 | req = { 70 | originalUrl: '/foo', 71 | method: 'PUT' 72 | }; 73 | 74 | mid(req, {} as any, noop); 75 | assert.notOk(req.called); 76 | }); 77 | 78 | it('should call the middleware when path or method mismatch', function () { 79 | let req: any = { 80 | originalUrl: '/test?test=123', 81 | method: 'PUT' 82 | }; 83 | 84 | mid(req, {} as any, noop); 85 | assert.ok(req.called); 86 | 87 | req = { 88 | originalUrl: '/bar?test=123', 89 | method: 'GET' 90 | }; 91 | 92 | mid(req, {} as any, noop); 93 | assert.ok(req.called); 94 | 95 | req = { 96 | originalUrl: '/regex', 97 | method: 'DELETE' 98 | }; 99 | 100 | mid(req, {} as any, noop); 101 | assert.ok(req.called); 102 | 103 | req = { 104 | originalUrl: '/unless?test=123', 105 | method: 'PUT' 106 | }; 107 | 108 | mid(req, {} as any, noop); 109 | assert.ok(req.called); 110 | }); 111 | }); 112 | 113 | describe('with PATH exception', function () { 114 | const mid = testMiddleware.unless({ 115 | path: ['/test', '/fobo'] 116 | }); 117 | 118 | it('should not call the middleware when one of the path match', function () { 119 | let req: any = { 120 | originalUrl: '/test?das=123' 121 | }; 122 | 123 | mid(req, {} as any, noop); 124 | 125 | assert.notOk(req.called); 126 | 127 | req = { 128 | originalUrl: '/fobo?test=123' 129 | }; 130 | 131 | mid(req, {} as any, noop); 132 | 133 | assert.notOk(req.called); 134 | }); 135 | 136 | it('should call the middleware when the path doesnt match', function () { 137 | const req: any = { 138 | originalUrl: '/foobar/test=123' 139 | }; 140 | 141 | mid(req, {} as any, noop); 142 | 143 | assert.ok(req.called); 144 | }); 145 | }); 146 | 147 | describe('with PATH (regex) exception', function () { 148 | const mid = testMiddleware.unless({ 149 | path: ['/test', /ag$/ig] 150 | }); 151 | 152 | it('should not call the middleware when the regex match', function () { 153 | const req: any = { 154 | originalUrl: '/foboag?test=123' 155 | }; 156 | 157 | const req2: any = { 158 | originalUrl: '/foboag?test=456' 159 | }; 160 | 161 | mid(req, {} as any, noop); 162 | mid(req2, {} as any, noop); 163 | 164 | assert.notOk(req.called); 165 | assert.notOk(req2.called); 166 | }); 167 | 168 | }); 169 | 170 | describe('with PATH (useOriginalUrl) exception', function () { 171 | const mid = testMiddleware.unless({ 172 | path: ['/test', '/fobo'], 173 | useOriginalUrl: false 174 | }); 175 | 176 | it('should not call the middleware when one of the path match ' + 177 | 'req.url instead of req.originalUrl', function () { 178 | let req: any = { 179 | originalUrl: '/orig/test?das=123', 180 | url: '/test?das=123' 181 | }; 182 | 183 | mid(req, {} as any, noop); 184 | 185 | assert.notOk(req.called); 186 | 187 | req = { 188 | originalUrl: '/orig/fobo?test=123', 189 | url: '/fobo?test=123' 190 | }; 191 | 192 | mid(req, {} as any, noop); 193 | 194 | assert.notOk(req.called); 195 | }); 196 | 197 | it('should call the middleware when the path doesnt match ' + 198 | 'req.url even if path matches req.originalUrl', function () { 199 | const req: any = { 200 | originalUrl: '/test/test=123', 201 | url: '/foobar/test=123' 202 | }; 203 | 204 | mid(req, {} as any, noop); 205 | 206 | assert.ok(req.called); 207 | }); 208 | }); 209 | 210 | describe('with EXT exception', function () { 211 | const mid = testMiddleware.unless({ 212 | ext: ['jpg', 'html', 'txt'] 213 | }); 214 | 215 | it('should not call the middleware when the ext match', function () { 216 | const req: any = { 217 | originalUrl: '/foo.html?das=123' 218 | }; 219 | 220 | mid(req, {} as any, noop); 221 | 222 | assert.notOk(req.called); 223 | }); 224 | 225 | it('should call the middleware when the ext doesnt match', function () { 226 | const req: any = { 227 | originalUrl: '/foobar/test=123' 228 | }; 229 | 230 | mid(req, {} as any, noop); 231 | 232 | assert.ok(req.called); 233 | }); 234 | }); 235 | 236 | describe('with METHOD exception', function () { 237 | const mid = testMiddleware.unless({ 238 | method: ['OPTIONS', 'DELETE'] 239 | }); 240 | 241 | it('should not call the middleware when the method match', function () { 242 | const req: any = { 243 | originalUrl: '/foo.html?das=123', 244 | method: 'OPTIONS' 245 | }; 246 | 247 | mid(req, {} as any, noop); 248 | 249 | assert.notOk(req.called); 250 | }); 251 | 252 | it('should call the middleware when the method doesnt match', function () { 253 | const req: any = { 254 | originalUrl: '/foobar/test=123', 255 | method: 'PUT' 256 | }; 257 | 258 | mid(req, {} as any, noop); 259 | 260 | assert.ok(req.called); 261 | }); 262 | }); 263 | 264 | describe('with custom exception', function () { 265 | const mid = testMiddleware.unless(function (req) { 266 | return (req as any).baba; 267 | }); 268 | 269 | it('should not call the middleware when the custom rule match', function () { 270 | const req: any = { 271 | baba: true 272 | }; 273 | 274 | mid(req, {} as any, noop); 275 | 276 | assert.notOk(req.called); 277 | }); 278 | 279 | it('should call the middleware when the custom rule doesnt match', function (done) { 280 | const req: any = { 281 | baba: false 282 | }; 283 | 284 | mid(req, {} as any, () => { 285 | assert.ok(req.called); 286 | done(); 287 | }); 288 | }); 289 | }); 290 | 291 | describe('with async custom exception', function () { 292 | const mid = testMiddleware.unless(function (req) { 293 | return (req as any).baba; 294 | }); 295 | 296 | it('should not call the middleware when the async custom rule match', function (done) { 297 | const req: any = { 298 | baba: true 299 | }; 300 | 301 | mid(req, {} as any, () => { 302 | assert.notOk(req.called); 303 | done(); 304 | }); 305 | }); 306 | 307 | it('should call the middleware when the async custom rule doesnt match', function (done) { 308 | const req: any = { 309 | baba: false 310 | }; 311 | 312 | mid(req, {} as any, () => { 313 | assert.ok(req.called); 314 | done(); 315 | }); 316 | 317 | 318 | }); 319 | }); 320 | 321 | describe('without originalUrl', function () { 322 | const mid = testMiddleware.unless({ 323 | path: ['/test'] 324 | }); 325 | 326 | it('should not call the middleware when one of the path match', function () { 327 | const req: any = { 328 | url: '/test?das=123' 329 | }; 330 | 331 | mid(req, {} as any, noop); 332 | 333 | assert.notOk(req.called); 334 | }); 335 | 336 | it('should call the middleware when the path doesnt match', function () { 337 | const req: any = { 338 | url: '/foobar/test=123' 339 | }; 340 | 341 | mid(req, {} as any, noop); 342 | 343 | assert.ok(req.called); 344 | }); 345 | }); 346 | 347 | describe('chaining', function () { 348 | const mid = testMiddleware 349 | .unless({ path: '/test' }) 350 | .unless({ method: 'GET' }); 351 | 352 | it('should not call the middleware when first unless match', function () { 353 | const req: any = { 354 | url: '/test' 355 | }; 356 | 357 | mid(req, {} as any, noop); 358 | 359 | assert.notOk(req.called); 360 | }); 361 | 362 | it('should not call the middleware when second unless match', function () { 363 | const req: any = { 364 | url: '/safsa', 365 | method: 'GET' 366 | }; 367 | 368 | mid(req, {} as any, noop); 369 | 370 | assert.notOk(req.called); 371 | }); 372 | 373 | it('should call the middleware when none of the conditions are met', function () { 374 | const req: any = { 375 | url: '/foobar/test=123' 376 | }; 377 | 378 | mid(req, {} as any, noop); 379 | 380 | assert.ok(req.called); 381 | }); 382 | }); 383 | 384 | }); 385 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "allowJs": true, 5 | "target": "es6", 6 | "module": "CommonJS", 7 | "declaration": true, 8 | "moduleResolution": "node" 9 | }, 10 | "include": [ 11 | "./src/*" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------