├── .eslintrc.json ├── .gitignore ├── History.md ├── index.d.ts ├── package.json ├── index.js ├── readme.md └── __tests__ └── koa-ip.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "plugins": ["jest"] 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.DS_Store 10 | *.idea 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | package-lock.json 19 | coverage 20 | -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | ## 2.1.4/2025-11-25 2 | 3 | - update deps 4 | 5 | ## 2.1.3/2023-03-02 6 | 7 | - update deps 8 | 9 | ## 2.1.2/2022-05-15 10 | 11 | - update deps 12 | - update readme 13 | 14 | ## 2.1.1/2022-02-24 15 | 16 | - update deps 17 | 18 | ## 2.1.0/2020-09-04 19 | 20 | - use `request-ip` 21 | - update dependencies 22 | - add TypeScript definition 23 | 24 | ## 2.0.0/2018-12-21 25 | 26 | - merge #1 #2 #6 27 | - update dependencies 28 | - use jest 29 | - breaking change: default return 403 instead of 404 status. 30 | 31 | ## 1.0.0/2017-12-01 32 | 33 | - v1.0.0 for koa@2 34 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Middleware, Context, Next } from 'koa'; 2 | 3 | declare namespace koaIP { 4 | type IPPatternsType = string | RegExp 5 | 6 | interface koaIPOptionsObject { 7 | whitelist?: Array, 8 | blacklist?: Array, 9 | handler?: (ctx: Context, next: Next) => void | Promise 10 | } 11 | 12 | type KoaIPOptions = koaIPOptionsObject | Array | IPPatternsType 13 | 14 | interface koaIP { 15 | (options: KoaIPOptions): Middleware; 16 | } 17 | } 18 | 19 | declare const koaIP: koaIP.koaIP; 20 | export = koaIP; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-ip", 3 | "version": "2.1.4", 4 | "description": "Ip filter middleware for koa, support `whitelist` and `blacklist`.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "jest" 9 | }, 10 | "author": "nswbmw", 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/nswbmw/koa-ip" 15 | }, 16 | "keywords": [ 17 | "koa", 18 | "ip", 19 | "whitelist", 20 | "blacklist" 21 | ], 22 | "dependencies": { 23 | "debug": "4.4.3", 24 | "lodash.isplainobject": "4.0.6", 25 | "request-ip": "3.3.0" 26 | }, 27 | "devDependencies": { 28 | "eslint-config-standard": "17.1.0", 29 | "eslint-plugin-import": "2.32.0", 30 | "eslint-plugin-jest": "29.2.1", 31 | "eslint-plugin-node": "11.1.0", 32 | "eslint-plugin-promise": "7.2.1", 33 | "eslint-plugin-standard": "5.0.0", 34 | "jest": "30.2.0", 35 | "koa": "3.1.1", 36 | "supertest": "7.1.4" 37 | }, 38 | "jest": { 39 | "collectCoverage": true, 40 | "coverageReporters": [ 41 | "json", 42 | "html" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const isPlainObject = require('lodash.isplainobject') 3 | const requestIp = require('request-ip') 4 | const debug = require('debug')('koa-ip') 5 | 6 | module.exports = function (opts) { 7 | assert(opts, 'koa-ip missing opts') 8 | if (!isPlainObject(opts)) { 9 | opts = { whitelist: Array.isArray(opts) ? opts : [opts] } 10 | } 11 | 12 | return async function koaIp (ctx, next) { 13 | const ip = ctx.ip || requestIp.getClientIp(ctx.req) 14 | let pass = true 15 | 16 | if (opts.whitelist && Array.isArray(opts.whitelist)) { 17 | pass = opts.whitelist.some((item) => { 18 | return new RegExp(item).test(ip) 19 | }) 20 | } 21 | 22 | if (pass && opts.blacklist && Array.isArray(opts.blacklist)) { 23 | pass = !opts.blacklist.some((item) => { 24 | return new RegExp(item).test(ip) 25 | }) 26 | } 27 | 28 | // pass 29 | if (pass) { 30 | debug(`${new Date()}: "${ip} -> ✓"`) 31 | return next() 32 | } 33 | 34 | // not pass 35 | if (typeof opts.handler === 'function') { 36 | debug(`${new Date()}: "${ip} -> handler"`) 37 | await opts.handler(ctx, next) 38 | } else { 39 | debug(`${new Date()}: "${ip} -> ×"`) 40 | ctx.throw(403) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## koa-ip 2 | 3 | ![KoaJs Slack](https://img.shields.io/badge/Koa.Js-Slack%20Channel-Slack.svg?longCache=true&style=for-the-badge) 4 | 5 | > koa-ip is a ip filter middleware for koa, support `whitelist` and `blacklist`. 6 | 7 | ### Install 8 | 9 | ```sh 10 | $ npm i koa-ip --save 11 | ``` 12 | 13 | ### Usage 14 | 15 | ```js 16 | ip(String|RegExp) 17 | ip(Array{String|RegExp}) 18 | ip({ 19 | whitelist: Array{String|RegExp}, 20 | blacklist: Array{String|RegExp}, 21 | handler: async (ctx, next) => {} // handle blacklist ip 22 | }) 23 | ``` 24 | 25 | ### Examples 26 | 27 | ```js 28 | const Koa = require('koa') 29 | const ip = require('koa-ip') 30 | 31 | const app = new Koa() 32 | 33 | app.use(ip('192.168.0.*')) // whitelist 34 | // app.use(ip(['192.168.0.*', '8.8.8.[0-3]'])) // whitelist 35 | // app.use(ip({ 36 | // whitelist: ['192.168.0.*', '8.8.8.[0-3]'], 37 | // blacklist: ['144.144.*'] 38 | // })) 39 | 40 | app.listen(3000) 41 | ``` 42 | 43 | #### blacklist handler 44 | 45 | ```js 46 | const app = new Koa() 47 | app.use((ctx, next) => { 48 | ctx.request.ip = '127.0.0.1' 49 | return next() 50 | }) 51 | app.use(ip({ 52 | blacklist: ['127.0.0.*'], 53 | handler: async (ctx, next) => { 54 | ctx.status = 403 55 | } 56 | })) 57 | 58 | app.use((ctx, next) => { 59 | ctx.status = 200 60 | }) 61 | 62 | app.listen(3000) 63 | ``` 64 | 65 | **NB**: If missing blacklist handler, default `ctx.status = 403`. 66 | 67 | More examples see [test](./__tests__/). 68 | 69 | ### Test 70 | 71 | ```sh 72 | $ npm test (coverage 100%) 73 | ``` 74 | 75 | ### License 76 | 77 | MIT 78 | -------------------------------------------------------------------------------- /__tests__/koa-ip.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest') 2 | const Koa = require('koa') 3 | const ip = require('..') 4 | 5 | describe('koa-ip', () => { 6 | it('paramters error', async () => { 7 | const app = new Koa() 8 | let error 9 | try { 10 | app.use(ip()) 11 | } catch (e) { 12 | error = e 13 | } 14 | expect(error.message).toBe('koa-ip missing opts') 15 | }) 16 | 17 | it('string success', async () => { 18 | const app = new Koa() 19 | app.use((ctx, next) => { 20 | ctx.request.ip = '192.168.0.1' 21 | return next() 22 | }) 23 | app.use(ip('192.168.0.*')) 24 | app.use((ctx, next) => { 25 | ctx.status = 200 26 | }) 27 | 28 | await request(app.callback()).get('/').expect(200) 29 | }) 30 | 31 | it('string failed', async () => { 32 | const app = new Koa() 33 | app.use((ctx, next) => { 34 | ctx.request.ip = '192.168.1.1' 35 | return next() 36 | }) 37 | app.use(ip(/^192.168.0.*$/)) 38 | app.use((ctx, next) => { 39 | ctx.status = 200 40 | }) 41 | 42 | await request(app.callback()).get('/').expect(403) 43 | }) 44 | 45 | it('array success', async () => { 46 | const app = new Koa() 47 | app.use((ctx, next) => { 48 | ctx.request.ip = '192.168.1.1' 49 | return next() 50 | }) 51 | app.use(ip([/^192.168.0.*$/, '192.168.1.*'])) 52 | app.use((ctx, next) => { 53 | ctx.status = 200 54 | }) 55 | 56 | await request(app.callback()).get('/').expect(200) 57 | }) 58 | 59 | it('array failed', async () => { 60 | const app = new Koa() 61 | app.use((ctx, next) => { 62 | ctx.request.ip = '192.168.1.1' 63 | return next() 64 | }) 65 | app.use(ip(['192.168.0.*'])) 66 | app.use((ctx, next) => { 67 | ctx.status = 200 68 | }) 69 | 70 | await request(app.callback()).get('/').expect(403) 71 | }) 72 | 73 | it('object success', async () => { 74 | const app = new Koa() 75 | app.use((ctx, next) => { 76 | ctx.request.ip = '192.168.0.1' 77 | return next() 78 | }) 79 | app.use(ip({ 80 | blacklist: ['192.168.0.[2-9]'] 81 | })) 82 | app.use((ctx, next) => { 83 | ctx.status = 200 84 | }) 85 | 86 | await request(app.callback()).get('/').expect(200) 87 | }) 88 | 89 | it('object failed', async () => { 90 | const app = new Koa() 91 | app.use((ctx, next) => { 92 | ctx.request.ip = '192.168.0.1' 93 | return next() 94 | }) 95 | app.use(ip({ 96 | whitelist: ['192.168.0.1'], 97 | blacklist: ['192.168.0.[1-9]'] 98 | })) 99 | app.use((ctx, next) => { 100 | ctx.status = 200 101 | }) 102 | 103 | await request(app.callback()).get('/').expect(403) 104 | }) 105 | 106 | it('blacklist handler', async () => { 107 | const app = new Koa() 108 | app.use((ctx, next) => { 109 | ctx.request.ip = '192.168.0.1' 110 | return next() 111 | }) 112 | app.use(ip({ 113 | blacklist: ['192.168.0.[1-9]'], 114 | handler: async (ctx) => { 115 | ctx.status = 403 116 | ctx.body = 'Forbidden!!!' 117 | } 118 | })) 119 | app.use((ctx, next) => { 120 | ctx.status = 200 121 | }) 122 | 123 | const res = await request(app.callback()).get('/') 124 | expect(res.status).toBe(403) 125 | expect(res.text).toBe('Forbidden!!!') 126 | }) 127 | 128 | it('blacklist handler passthrough', async () => { 129 | const app = new Koa() 130 | app.use((ctx, next) => { 131 | ctx.request.ip = '192.168.0.1' 132 | return next() 133 | }) 134 | app.use(ip({ 135 | blacklist: ['192.168.0.[1-9]'], 136 | handler: async (ctx, next) => { 137 | return next() 138 | } 139 | })) 140 | app.use((ctx, next) => { 141 | ctx.status = 401 142 | ctx.body = 'Please login!!!' 143 | }) 144 | 145 | const res = await request(app.callback()).get('/') 146 | expect(res.status).toBe(401) 147 | expect(res.text).toBe('Please login!!!') 148 | }) 149 | 150 | it('use request-ip', async () => { 151 | const app = new Koa() 152 | app.use((ctx, next) => { 153 | // rewrite scoket.remoteAddress 154 | Object.defineProperty(ctx.req.socket, 'remoteAddress', { 155 | value: null 156 | }) 157 | ctx.req.headers['x-forwarded-for'] = '1.1.1.1' 158 | return next() 159 | }) 160 | app.use(ip(/^1.1.1.*$/)) 161 | app.use((ctx, next) => { 162 | ctx.status = 200 163 | }) 164 | 165 | await request(app.callback()).get('/').expect(200) 166 | }) 167 | }) 168 | --------------------------------------------------------------------------------