├── .eslintrc.json ├── .github └── workflows │ ├── build.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── .prettier.config.js ├── LICENSE ├── README.md ├── data ├── server.crt ├── server.key ├── users.htdigest └── users.htpasswd ├── examples ├── basic.js ├── basic_nofile.js ├── digest.js ├── digest_nofile.js ├── events.js ├── https.js └── proxy.js ├── package-lock.json ├── package.json ├── src ├── auth │ ├── base.js │ ├── basic.js │ ├── digest.js │ └── utils.js └── http-auth.js └── test ├── basic-nofile.js ├── basic-skipuser.js ├── basic.js ├── digest-md5-qop.js ├── digest-md5-sess.js ├── digest-nofile.js ├── digest.js ├── https.js ├── proxy.js └── utils.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier", 4 | "eslint:recommended", 5 | "plugin:node/recommended" 6 | ], 7 | "parserOptions": { 8 | // Only ESLint 6.2.0 and later support ES2020. 9 | "ecmaVersion": 2020 10 | }, 11 | "env": { 12 | "node": true, 13 | "mocha": true 14 | }, 15 | "plugins": [ 16 | "prettier" 17 | ], 18 | "rules": { 19 | "prettier/prettier": "error", 20 | "node/exports-style": ["error", "module.exports"], 21 | "node/file-extension-in-import": ["error", "always"], 22 | "node/prefer-global/buffer": ["error", "always"], 23 | "node/prefer-global/console": ["error", "always"], 24 | "node/prefer-global/process": ["error", "always"], 25 | "node/prefer-global/url-search-params": ["error", "always"], 26 | "node/prefer-global/url": ["error", "always"], 27 | "node/prefer-promises/dns": "error", 28 | "node/prefer-promises/fs": "error" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | strategy: 11 | matrix: 12 | node-version: [16.x, 18.x] 13 | 14 | steps: 15 | - uses: actions/checkout@v3 16 | - name: Use Node.js ${{ matrix.node-version }} 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: ${{ matrix.node-version }} 20 | - run: npm install 21 | - run: npm run build --if-present 22 | - run: npm test 23 | env: 24 | CI: true 25 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | - uses: actions/setup-node@v3 14 | with: 15 | node-version: 16 16 | - run: npm ci 17 | - run: npm test 18 | 19 | publish-npm: 20 | needs: build 21 | runs-on: ubuntu-latest 22 | steps: 23 | - uses: actions/checkout@v3 24 | - uses: actions/setup-node@v3 25 | with: 26 | node-version: 16 27 | registry-url: https://registry.npmjs.org/ 28 | - run: npm ci 29 | - run: npm publish 30 | env: 31 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .project 3 | http-auth.iml 4 | .idea 5 | *.swp 6 | .settings 7 | gensrc 8 | _site -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | node_modules 3 | .project 4 | *.iml 5 | .idea 6 | .settings 7 | _site 8 | test 9 | examples 10 | .travis.yml 11 | data 12 | .github 13 | .eslintrc.json 14 | .prettier.config.js -------------------------------------------------------------------------------- /.prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | semi: true, 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Gevorg Harutyunyan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # http-auth 2 | [Node.js](http://nodejs.org/) package for HTTP basic and digest access authentication. 3 | 4 | [![build](https://github.com/gevorg/http-auth/workflows/build/badge.svg)](https://github.com/gevorg/http-auth/actions/workflows/build.yml) 5 | 6 | ## Installation 7 | 8 | Via git (or downloaded tarball): 9 | 10 | ```bash 11 | $ git clone git@github.com:gevorg/http-auth.git 12 | ``` 13 | Via [npm](http://npmjs.org/): 14 | 15 | ```bash 16 | $ npm install http-auth 17 | ``` 18 | 19 | ## Usage 20 | 21 | ```javascript 22 | // HTTP module 23 | const http = require("http"); 24 | 25 | // Authentication module. 26 | const auth = require("http-auth"); 27 | const basic = auth.basic({ 28 | realm: "Simon Area.", 29 | file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass 30 | }); 31 | 32 | // Creating new HTTP server. 33 | http 34 | .createServer( 35 | basic.check((req, res) => { 36 | res.end(`Welcome to private area - ${req.user}!`); 37 | }) 38 | ) 39 | .listen(1337, () => { 40 | // Log URL. 41 | console.log("Server running at http://127.0.0.1:1337/"); 42 | }); 43 | ``` 44 | 45 | Please check [examples directory](./examples) for more. 46 | 47 | ## Configurations 48 | 49 | - `realm` - Authentication realm, by default it is **Users**. 50 | - `file` - File where user details are stored. 51 | - Line format is **{user:pass}** or **{user:passHash}** for basic access. 52 | - Line format is **{user:realm:passHash}** for digest access. 53 | - Using a callback, it needs to return the same line format, example: `file: () => 'adam:adam\neve:eve',` 54 | - `algorithm` - Algorithm that will be used only for **digest** access authentication. 55 | - **MD5** by default. 56 | - **MD5-sess** can be set. 57 | - `qop` - Quality of protection that is used only for **digest** access authentication. 58 | - **auth** is set by default. 59 | - **none** this option is disabling protection. 60 | - `msg401` - Message for failed authentication 401 page. 61 | - `msg407` - Message for failed authentication 407 page. 62 | - `contentType` - Content type for failed authentication page. 63 | - `skipUser` - Set this to **true**, if you don't want req.user to be filled with authentication info. 64 | - `proxy` - Set this to **true**, if you want to use it with [http-proxy](https://github.com/http-party/node-http-proxy). 65 | 66 | ## Running tests 67 | 68 | It uses [mocha](https://mochajs.org/), so just run following command in package directory: 69 | 70 | ```bash 71 | $ npm test 72 | ``` 73 | 74 | ## Questions 75 | 76 | You can also use [stackoverflow](http://stackoverflow.com/questions/tagged/http-auth) to ask questions using **[http-auth](http://stackoverflow.com/tags/http-auth/info)** tag. 77 | 78 | ## Utilities 79 | 80 | - **[htpasswd](https://github.com/gevorg/htpasswd/)** - Node.js package for HTTP Basic Authentication password file utility. 81 | - **[htdigest](https://github.com/gevorg/htdigest/)** - Node.js package for HTTP Digest Authentication password file utility. 82 | 83 | ## Integrations 84 | 85 | Please check [this link](https://github.com/http-auth) for integration packages. 86 | 87 | ## License 88 | 89 | The MIT License (MIT) 90 | -------------------------------------------------------------------------------- /data/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICNTCCAZ4CCQCYrQAmdm6IDDANBgkqhkiG9w0BAQsFADBfMQswCQYDVQQGEwJB 3 | TTEbMBkGA1UECAwSR2V2b3JnIEhhcnV0eXVueWFuMRAwDgYDVQQHDAdZZXJldmFu 4 | MSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYwNDAxMTEy 5 | MjI1WhcNMTkxMjI3MTEyMjI1WjBfMQswCQYDVQQGEwJBTTEbMBkGA1UECAwSR2V2 6 | b3JnIEhhcnV0eXVueWFuMRAwDgYDVQQHDAdZZXJldmFuMSEwHwYDVQQKDBhJbnRl 7 | cm5ldCBXaWRnaXRzIFB0eSBMdGQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGB 8 | AO0vqRx6ixz80sHfOqESXZwulp+LGB7MhchM6hWLYyM8UfBPun/Pl7EFEfij8ArU 9 | q/eDmpsyOOo//vlgaNdXvVa91MHvlQRpyx2H6OUvFwIPDWrFK4CGg2IzDtGW9cIR 10 | Rjv/qpMpcXWUh5bwZFdNwj/vmeXd3XrOz6t2uZB6ideJAgMBAAEwDQYJKoZIhvcN 11 | AQELBQADgYEAvmka7A3yLgMw7+1eDLzCLqQnHvrhZ3DiNafr+Gq4thd7mXBRjH8R 12 | bD7TMoXEGC2Gp+mKnU4bEaszhXUVQrnQOiHoiS+Z0qCPP6rLOm6/4HLZnZbLxbjv 13 | vtyb7u08YxRcqGiYD+l4tVhrLfcqLJ5hcoXJNubNTmM3fR573XFh/pU= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /data/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDtL6kceosc/NLB3zqhEl2cLpafixgezIXITOoVi2MjPFHwT7p/ 3 | z5exBRH4o/AK1Kv3g5qbMjjqP/75YGjXV71WvdTB75UEacsdh+jlLxcCDw1qxSuA 4 | hoNiMw7RlvXCEUY7/6qTKXF1lIeW8GRXTcI/75nl3d16zs+rdrmQeonXiQIDAQAB 5 | AoGAFrTTWbiZjMLfYlP+huD9OTB0lv1356rou84xMrLPYNYrefgIAKhc+0Ni/wyi 6 | tEa718wvht/99t79h94k60ipBW+imUFYv/u32vVTD3vklxKiSX4DnEENUp7W6YEc 7 | evRL0VAD1J2W4PNI5JozpGM/HlPoXjKeAYY1QV4Amn1CRwECQQD7J5wU5pJKKFB8 8 | ZzYJfVgq6DIENjU8nlIKFqhtAI+BIUM8uZX+cYlydpNOI46Bn0e7MrmPLziHhq4g 9 | 5dF91NF5AkEA8cMQT/MvmFa3Shkc81PZvo0FKoMdj9lTO+vS/IDnx5r9qir8S0FE 10 | bL4wJXdvz1bjhC+YO5ORWLeVevWXvW5CkQJAMT6Y+fgjouq4rUvHaqDrEq6ob+75 11 | DUO1rki3CXhcquMMvfFdmaBFZO4e/+k03fGPLQNLIuoNWXVs2wJ0ywlZcQJAIteO 12 | 8UbF9DmDHt2xq2vJNMoUFYVh7EpQ0ZDyDkvxm3W92xlo1NuqJdXfEHc/GO2XyoQl 13 | d2iUCOokU90zKizK0QJBAL9gR9nle0KpkismBJsKUf4USJXEdleAD70C3HmuJ10D 14 | BAZKdXv4vwviUozebenLEntDGYwfrudodTnZftF8Ans= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /data/users.htdigest: -------------------------------------------------------------------------------- 1 | vivi:Simon Area.:fd805820dae2cf2672473464eaa4b414 2 | sona:Simon Area.:86482b02439333102d7c6f374fc43afe 3 | frida:Other Area.:8310c3dcea3a63dd0c76970d682abf0f 4 | -------------------------------------------------------------------------------- /data/users.htpasswd: -------------------------------------------------------------------------------- 1 | Sarah:testpass 2 | John:itismypass 3 | gevorg:{SHA}PkqnsT6LJKamgrEeDff5GQP97e8= 4 | Shanon:noneof 5 | Mike:pass123 6 | vera:UdMhBHagv7sUU 7 | hera:$apr1$fQc7jP4E$cPBB0pK92nO9VxgVvef4k. 8 | titan:$2a$11$lUPbNJdGo//rlK19CBt4EOOa9WZvCHK/H5CDtu7JN9A6ISFI1rdo2 9 | 10 | #comment:commentpass -------------------------------------------------------------------------------- /examples/basic.js: -------------------------------------------------------------------------------- 1 | // HTTP module 2 | const http = require("http"); 3 | 4 | // Authentication module. 5 | const auth = require("../src/http-auth"); 6 | const basic = auth.basic({ 7 | realm: "Simon Area.", 8 | file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass 9 | }); 10 | 11 | // Creating new HTTP server. 12 | http 13 | .createServer( 14 | basic.check((req, res) => { 15 | res.end(`Welcome to private area - ${req.user}!`); 16 | }) 17 | ) 18 | .listen(1337, () => { 19 | // Log URL. 20 | console.log("Server running at http://127.0.0.1:1337/"); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/basic_nofile.js: -------------------------------------------------------------------------------- 1 | // HTTP module 2 | const http = require("http"); 3 | 4 | // Authentication module. 5 | const auth = require("../src/http-auth"); 6 | const basic = auth.basic( 7 | { 8 | realm: "Simon Area." 9 | }, 10 | (username, password, callback) => { 11 | // Custom authentication method. 12 | callback(username === "Tina" && password === "Bullock"); 13 | } 14 | ); 15 | 16 | // Creating new HTTP server. 17 | http 18 | .createServer( 19 | basic.check((req, res) => { 20 | res.end(`Welcome to private area - ${req.user}!`); 21 | }) 22 | ) 23 | .listen(1337, () => { 24 | // Log URL. 25 | console.log("Server running at http://127.0.0.1:1337/"); 26 | }); 27 | -------------------------------------------------------------------------------- /examples/digest.js: -------------------------------------------------------------------------------- 1 | // HTTP module 2 | const http = require("http"); 3 | 4 | // Authentication module. 5 | const auth = require("../src/http-auth"); 6 | const digest = auth.digest({ 7 | realm: "Simon Area.", 8 | file: __dirname + "/../data/users.htdigest" // vivi:anna, sona:testpass 9 | }); 10 | 11 | // Creating new HTTP server. 12 | http 13 | .createServer( 14 | digest.check((req, res) => { 15 | res.end(`Welcome to private area - ${req.user}!`); 16 | }) 17 | ) 18 | .listen(1337, () => { 19 | // Log URL. 20 | console.log("Server running at http://127.0.0.1:1337/"); 21 | }); 22 | -------------------------------------------------------------------------------- /examples/digest_nofile.js: -------------------------------------------------------------------------------- 1 | // Utility module. 2 | const utils = require("../src/auth/utils"); 3 | 4 | // HTTP module 5 | const http = require("http"); 6 | 7 | // Authentication module. 8 | const auth = require("../src/http-auth"); 9 | const digest = auth.digest( 10 | { 11 | realm: "Simon Area." 12 | }, 13 | (username, callback) => { 14 | // Expecting md5(username:realm:password) in callback. 15 | if (username === "simon") { 16 | callback(utils.md5("simon:Simon Area.:smart")); 17 | } else if (username === "tigran") { 18 | callback(utils.md5("tigran:Simon Area.:great")); 19 | } else { 20 | callback(); 21 | } 22 | } 23 | ); 24 | 25 | // Creating new HTTP server. 26 | http 27 | .createServer( 28 | digest.check((req, res) => { 29 | res.end(`Welcome to private area - ${req.user}!`); 30 | }) 31 | ) 32 | .listen(1337, () => { 33 | // Log URL. 34 | console.log("Server running at http://127.0.0.1:1337/"); 35 | }); 36 | -------------------------------------------------------------------------------- /examples/events.js: -------------------------------------------------------------------------------- 1 | // HTTP module 2 | const http = require("http"); 3 | 4 | // Authentication module. 5 | const auth = require("../src/http-auth"); 6 | const basic = auth.basic({ 7 | realm: "Simon Area.", 8 | file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass 9 | }); 10 | 11 | // Adding event listeners. 12 | basic.on("success", result => { 13 | console.log(`User authenticated: ${result.user}`); 14 | }); 15 | 16 | basic.on("fail", result => { 17 | console.log(`User authentication failed: ${result.user}`); 18 | }); 19 | 20 | basic.on("error", error => { 21 | console.log(`Authentication error: ${error.code + " - " + error.message}`); 22 | }); 23 | 24 | // Creating new HTTP server. 25 | http 26 | .createServer( 27 | basic.check((req, res) => { 28 | res.end(`Welcome to private area - ${req.user}!`); 29 | }) 30 | ) 31 | .listen(1337, () => { 32 | // Log URL. 33 | console.log("Server running at http://127.0.0.1:1337/"); 34 | }); 35 | -------------------------------------------------------------------------------- /examples/https.js: -------------------------------------------------------------------------------- 1 | // HTTPS module 2 | const https = require("https"); 3 | 4 | // File system module. 5 | const fs = require("fs"); 6 | 7 | // Authentication module. 8 | const auth = require("../src/http-auth"); 9 | const basic = auth.basic({ 10 | realm: "Simon Area.", 11 | file: __dirname + "/../data/users.htpasswd" // gevorg:gpass, Sarah:testpass 12 | }); 13 | 14 | // HTTPS server options. 15 | const options = { 16 | key: fs.readFileSync(__dirname + "/../data/server.key"), 17 | cert: fs.readFileSync(__dirname + "/../data/server.crt") 18 | }; 19 | 20 | // Starting server. 21 | https 22 | .createServer( 23 | options, 24 | basic.check((req, res) => { 25 | res.end(`Welcome to private area - ${req.user}!`); 26 | }) 27 | ) 28 | .listen(1337, () => { 29 | // Log URL. 30 | console.log("Server running at https://127.0.0.1:1337/"); 31 | }); 32 | -------------------------------------------------------------------------------- /examples/proxy.js: -------------------------------------------------------------------------------- 1 | // HTTP proxy module. 2 | const http = require("http"); 3 | // eslint-disable-next-line node/no-unpublished-require 4 | const httpProxy = require("http-proxy"); 5 | 6 | // Authentication module. 7 | const auth = require("../src/http-auth"); 8 | const basic = auth.basic({ 9 | realm: "Simon Area.", 10 | file: __dirname + "/../data/users.htpasswd", // gevorg:gpass, Sarah:testpass 11 | proxy: true 12 | }); 13 | 14 | // Create your proxy server. 15 | const proxy = httpProxy.createProxyServer({}); 16 | http 17 | .createServer( 18 | basic.check((req, res) => { 19 | proxy.web(req, res, { target: "http://127.0.0.1:1338" }); 20 | }) 21 | ) 22 | .listen(1337); 23 | 24 | // Create your target server. 25 | http 26 | .createServer((req, res) => { 27 | res.end("Request successfully proxied!"); 28 | }) 29 | .listen(1338, () => { 30 | // Log URL. 31 | console.log("Server running at http://127.0.0.1:1338/"); 32 | }); 33 | 34 | // You can test proxy authentication using curl. 35 | // $ curl -x 127.0.0.1:1337 127.0.0.1:1337 -U gevorg 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-auth", 3 | "description": "Node.js package for HTTP basic and digest access authentication.", 4 | "version": "4.2.1", 5 | "author": "Gevorg Harutyunyan (http://github.com/gevorg)", 6 | "maintainers": [ 7 | { 8 | "name": "gevorg", 9 | "email": "gevorg.ha@gmail.com" 10 | } 11 | ], 12 | "homepage": "https://github.com/gevorg/http-auth", 13 | "repository": { 14 | "type": "git", 15 | "url": "http://github.com/gevorg/http-auth.git" 16 | }, 17 | "main": "./src/http-auth.js", 18 | "licenses": [ 19 | { 20 | "type": "MIT", 21 | "url": "http://github.com/gevorg/http-auth/blob/master/LICENSE" 22 | } 23 | ], 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "http://github.com/gevorg/http-auth/issues" 27 | }, 28 | "dependencies": { 29 | "apache-crypt": "^1.1.2", 30 | "apache-md5": "^1.0.6", 31 | "bcryptjs": "^2.4.3 || ^3.0.0", 32 | "uuid": ">=8.3.2 <12" 33 | }, 34 | "devDependencies": { 35 | "chai": "^4.2.0", 36 | "eslint": "^6.8.0", 37 | "eslint-config-prettier": "^6.10.0", 38 | "eslint-plugin-node": "^11.0.0", 39 | "eslint-plugin-prettier": "^3.1.2", 40 | "http-proxy": "^1.18.0", 41 | "mocha": "^10.2.0", 42 | "prettier": "^1.19.1", 43 | "request": "^2.88.0" 44 | }, 45 | "engines": { 46 | "node": ">=8" 47 | }, 48 | "scripts": { 49 | "test": "mocha", 50 | "pretest": "eslint --ignore-path .gitignore ." 51 | }, 52 | "keywords": [ 53 | "http", 54 | "basic", 55 | "digest", 56 | "access", 57 | "authentication" 58 | ] 59 | } 60 | -------------------------------------------------------------------------------- /src/auth/base.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // File system module. 4 | const fs = require("fs"); 5 | 6 | // Event module. 7 | const events = require("events"); 8 | 9 | // Base authentication. 10 | class Base extends events.EventEmitter { 11 | // Constructor. 12 | constructor(options, checker) { 13 | super(); 14 | 15 | if (!options.msg401) { 16 | options.msg401 = "401 Unauthorized"; 17 | } 18 | 19 | if (!options.msg407) { 20 | options.msg407 = "407 Proxy authentication required"; 21 | } 22 | 23 | if (!options.contentType) { 24 | options.contentType = "text/plain"; 25 | } 26 | 27 | if (!options.realm) { 28 | options.realm = "users"; 29 | } 30 | 31 | if (!options.proxy) { 32 | options.proxy = false; 33 | } 34 | 35 | // Assign values. 36 | this.options = options; 37 | this.checker = checker; 38 | 39 | // Loading users from file, if file is set. 40 | this.options.users = []; 41 | 42 | if (!checker && options.file) { 43 | this.loadUsers(); 44 | } 45 | } 46 | 47 | // Processing user line. 48 | // eslint-disable-next-line no-unused-vars 49 | processLine(userLine) { 50 | throw new Error("Not defined!"); 51 | } 52 | 53 | // Parse auth header. 54 | // eslint-disable-next-line no-unused-vars 55 | parseAuthorization(header) { 56 | throw new Error("Not defined!"); 57 | } 58 | 59 | // Find user. 60 | // eslint-disable-next-line no-unused-vars 61 | findUser(req, clientOptions, callback) { 62 | throw new Error("Not defined!"); 63 | } 64 | 65 | // Generates header. 66 | // eslint-disable-next-line no-unused-vars 67 | generateHeader(result) { 68 | throw new Error("Not defined!"); 69 | } 70 | 71 | // Ask for authentication. 72 | ask(res, result) { 73 | let header = this.generateHeader(result); 74 | res.setHeader("Content-Type", this.options.contentType); 75 | 76 | if (this.options.proxy) { 77 | res.setHeader("Proxy-Authenticate", header); 78 | res.writeHead(407); 79 | res.end(this.options.msg407); 80 | } else { 81 | res.setHeader("WWW-Authenticate", header); 82 | res.writeHead(401); 83 | res.end(this.options.msg401); 84 | } 85 | } 86 | 87 | // Checking if user is authenticated. 88 | check(callback) { 89 | return (req, res) => { 90 | this.isAuthenticated(req, result => { 91 | if (result instanceof Error) { 92 | this.emit("error", result, req); 93 | 94 | res.statusCode = 400; 95 | res.end(result.message); 96 | } else if (!result.pass) { 97 | this.emit("fail", result, req); 98 | this.ask(res, result); 99 | } else { 100 | this.emit("success", result, req); 101 | 102 | if (!this.options.skipUser) { 103 | req.user = result.user; 104 | } 105 | 106 | if (callback) { 107 | callback.apply(this, [req, res]); 108 | } 109 | } 110 | }); 111 | }; 112 | } 113 | 114 | // Is authenticated method. 115 | isAuthenticated(req, callback) { 116 | let header = undefined; 117 | if (this.options.proxy) { 118 | header = req.headers["proxy-authorization"]; 119 | } else { 120 | header = req.headers["authorization"]; 121 | } 122 | 123 | // Searching for user. 124 | let searching = false; 125 | 126 | // If header is sent. 127 | if (header) { 128 | let clientOptions = this.parseAuthorization(header); 129 | if (clientOptions) { 130 | searching = true; 131 | this.findUser(req, clientOptions, result => { 132 | callback.apply(this, [result]); 133 | }); 134 | } 135 | } 136 | 137 | // Only if not searching call callback. 138 | if (!searching) { 139 | callback.apply(this, [{}]); 140 | } 141 | } 142 | 143 | // Loading files or using a callback with user details. 144 | loadUsers() { 145 | let content = 146 | typeof this.options.file == "function" 147 | ? this.options.file(this) 148 | : fs.readFileSync(this.options.file, "UTF-8"); 149 | let users = content.replace(/\r\n/g, "\n").split("\n"); 150 | 151 | // Process all users. 152 | users.forEach(u => { 153 | if (u && !u.match(/^\s*#.*/)) { 154 | this.processLine(u); 155 | } 156 | }); 157 | } 158 | } 159 | 160 | // Export base. 161 | module.exports = Base; 162 | -------------------------------------------------------------------------------- /src/auth/basic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Base class. 4 | const Base = require("./base"); 5 | 6 | // Utility module. 7 | const utils = require("./utils"); 8 | 9 | // Importing apache-md5 module. 10 | const md5 = require("apache-md5"); 11 | 12 | // Importing apache-crypt module. 13 | const crypt = require("apache-crypt"); 14 | 15 | // Bcrypt. 16 | const bcrypt = require("bcryptjs"); 17 | 18 | // Crypto. 19 | const crypto = require("crypto"); 20 | 21 | // Reuse. 22 | const basicSchemeRegExp = /^basic\s/i; 23 | 24 | // Define basic auth. 25 | class Basic extends Base { 26 | // Constructor. 27 | constructor(options, checker) { 28 | super(options, checker); 29 | } 30 | 31 | // Verifies if password is correct. 32 | validate(hash, password) { 33 | if (hash.substr(0, 5) === "{SHA}") { 34 | hash = hash.substr(5); 35 | return hash === utils.sha1(password); 36 | } else if (hash.substr(0, 6) === "$apr1$" || hash.substr(0, 3) === "$1$") { 37 | return hash === md5(password, hash); 38 | } else if (hash.substr(0, 4) === "$2y$" || hash.substr(0, 4) === "$2a$") { 39 | return bcrypt.compareSync(password, hash); 40 | } else if (hash === crypt(password, hash)) { 41 | return true; 42 | } else if (hash.length === password.length) { 43 | return crypto.timingSafeEqual 44 | ? crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(password)) 45 | : hash === password; 46 | } 47 | } 48 | 49 | // Processes line from authentication file. 50 | processLine(userLine) { 51 | let lineSplit = userLine.split(":"); 52 | let username = lineSplit.shift(); 53 | let hash = lineSplit.join(":"); 54 | 55 | // Push user. 56 | this.options.users.push({ username: username, hash: hash }); 57 | } 58 | 59 | // Generates request header. 60 | generateHeader() { 61 | return `Basic realm="${this.options.realm}"`; 62 | } 63 | 64 | // Parsing authorization header. 65 | parseAuthorization(header) { 66 | if (basicSchemeRegExp.test(header)) { 67 | let tokens = header.split(" "); 68 | return tokens[1]; 69 | } 70 | } 71 | 72 | // Searching for user. 73 | findUser(req, hash, callback) { 74 | // Decode base64. 75 | let splitHash = utils.decodeBase64(hash).split(":"); 76 | let username = splitHash.shift(); 77 | let password = splitHash.join(":"); 78 | 79 | if (this.checker) { 80 | // Custom auth. 81 | this.checker.apply(this, [ 82 | username, 83 | password, 84 | (result, customUser) => { 85 | let params = undefined; 86 | 87 | if (result instanceof Error) { 88 | params = [result]; 89 | } else { 90 | params = [{ user: customUser || username, pass: !!result }]; 91 | } 92 | 93 | callback.apply(this, params); 94 | }, 95 | req 96 | ]); 97 | } else { 98 | // File based auth. 99 | let pass = false; 100 | 101 | // Loop users to find the matching one. 102 | this.options.users.forEach(user => { 103 | if (user.username === username && this.validate(user.hash, password)) { 104 | pass = true; 105 | } 106 | }); 107 | 108 | // Call final callback. 109 | callback.apply(this, [{ user: username, pass: pass }]); 110 | } 111 | } 112 | } 113 | 114 | // Export basic auth. 115 | module.exports = (options, checker) => { 116 | return new Basic(options, checker); 117 | }; 118 | -------------------------------------------------------------------------------- /src/auth/digest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Base class. 4 | const Base = require("./base"); 5 | 6 | // Utility module. 7 | const utils = require("./utils"); 8 | 9 | // Unique id. 10 | const { v4: uuid } = require("uuid"); 11 | 12 | // Reuse. 13 | const digestSchemeRegExp = /^digest\s(.*)/i; 14 | 15 | // Define digest auth. 16 | class Digest extends Base { 17 | // Constructor. 18 | constructor(options, checker) { 19 | super(options, checker); 20 | 21 | // Array of random strings sent to clients. 22 | this.nonces = []; 23 | 24 | // Algorithm of encryption, could be MD5 or MD5-sess, default is MD5. 25 | if ("MD5-sess" !== options.algorithm) { 26 | this.options.algorithm = "MD5"; 27 | } 28 | 29 | // Quality of protection is by default auth. 30 | if (options.qop === "none") { 31 | this.options.qop = ""; 32 | } else { 33 | this.options.qop = "auth"; 34 | } 35 | } 36 | 37 | // Process user line. 38 | processLine(line) { 39 | let tokens = line.split(":"); 40 | 41 | // We need only users for given realm. 42 | if (this.options.realm === tokens[1]) { 43 | this.options.users.push({ username: tokens[0], hash: tokens[2] }); 44 | } 45 | } 46 | 47 | // Parse authorization heder. 48 | parseAuthorization(header) { 49 | let match = header.match(digestSchemeRegExp); 50 | if (match) { 51 | let opts = {}; 52 | let params = match[1]; 53 | 54 | // Split the parameters by comma. 55 | let tokens = params.split(/,(?=(?:[^"]|"[^"]*")*$)/); 56 | // Parse parameters. 57 | let i = 0; 58 | let len = tokens.length; 59 | 60 | while (i < len) { 61 | // Strip quotes and whitespace. 62 | let param = /(\w+)=["]?([^"]*)["]?$/.exec(tokens[i]); 63 | if (param) { 64 | opts[param[1]] = param[2]; 65 | } 66 | 67 | ++i; 68 | } 69 | 70 | // Return options. 71 | return opts; 72 | } 73 | } 74 | 75 | // Validating hash. 76 | validate(ha2, co, hash) { 77 | let ha1 = hash; 78 | 79 | // Algorithm. 80 | if (co.algorithm === "MD5-sess") { 81 | ha1 = utils.md5(`${ha1}:${co.nonce}:${co.cnonce}`); 82 | } 83 | 84 | let response = undefined; 85 | 86 | // Quality of protection. 87 | if (co.qop) { 88 | response = utils.md5( 89 | `${ha1}:${co.nonce}:${co.nc}:${co.cnonce}:${co.qop}:${ha2}` 90 | ); 91 | } else { 92 | response = utils.md5(`${ha1}:${co.nonce}:${ha2}`); 93 | } 94 | 95 | // If calculated response is equal to client's response. 96 | return response === co.response; 97 | } 98 | 99 | // Searching for user. 100 | findUser(req, co, callback) { 101 | if (this.validateNonce(co.nonce, co.qop, co.nc)) { 102 | let ha2 = utils.md5(`${req.method}:${co.uri}`); 103 | if (this.checker) { 104 | // Custom authentication. 105 | this.checker.apply(this, [ 106 | co.username, 107 | hash => { 108 | let params = undefined; 109 | 110 | if (hash instanceof Error) { 111 | params = [hash]; 112 | } else { 113 | params = [ 114 | { user: co.username, pass: !!this.validate(ha2, co, hash) } 115 | ]; 116 | } 117 | 118 | // Call callback. 119 | callback.apply(this, params); 120 | }, 121 | req 122 | ]); 123 | } else { 124 | let pass = false; 125 | 126 | // File based, loop users to find the matching one. 127 | this.options.users.forEach(user => { 128 | if ( 129 | user.username === co.username && 130 | this.validate(ha2, co, user.hash) 131 | ) { 132 | pass = true; 133 | } 134 | }); 135 | 136 | callback.apply(this, [{ user: co.username, pass: pass }]); 137 | } 138 | } else { 139 | callback.apply(this, [{ stale: true }]); 140 | } 141 | } 142 | 143 | // Remove nonces. 144 | removeNonces(noncesToRemove) { 145 | noncesToRemove.forEach(nonce => { 146 | let index = this.nonces.indexOf(nonce); 147 | if (index != -1) { 148 | this.nonces.splice(index, 1); 149 | } 150 | }); 151 | } 152 | 153 | // Validate nonce. 154 | validateNonce(nonce, qop, nc) { 155 | let found = false; 156 | 157 | // Current time. 158 | let now = Date.now(); 159 | 160 | // Nonces for removal. 161 | let noncesToRemove = []; 162 | 163 | // Request counter is hexadecimal. 164 | let ncNum = Number.parseInt(nc, 16); 165 | 166 | // Searching for not expired ones. 167 | this.nonces.forEach(serverNonce => { 168 | if (serverNonce[1] + 3600000 > now) { 169 | if (serverNonce[0] === nonce) { 170 | if (qop) { 171 | if (ncNum > serverNonce[2]) { 172 | found = true; 173 | serverNonce[2] = ncNum; 174 | } 175 | } else { 176 | found = true; 177 | } 178 | } 179 | } else { 180 | noncesToRemove.push(serverNonce); 181 | } 182 | }); 183 | 184 | // Remove expired nonces. 185 | this.removeNonces(noncesToRemove); 186 | 187 | return found; 188 | } 189 | 190 | // Generates and returns new random nonce. 191 | askNonce() { 192 | let nonce = utils.md5(uuid()); 193 | this.nonces.push([nonce, Date.now(), 0]); 194 | 195 | return nonce; 196 | } 197 | 198 | // Generates request header. 199 | generateHeader(result) { 200 | let nonce = this.askNonce(); 201 | let stale = result.stale ? true : false; 202 | 203 | // Returning it. 204 | return `Digest realm="${this.options.realm}", qop="${this.options.qop}", nonce="${nonce}", algorithm="${this.options.algorithm}", stale="${stale}"`; 205 | } 206 | } 207 | 208 | // Export digest auth. 209 | module.exports = (options, checker) => { 210 | return new Digest(options, checker); 211 | }; 212 | -------------------------------------------------------------------------------- /src/auth/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Importing crypto module. 4 | const crypto = require("crypto"); 5 | const utils = {}; 6 | 7 | // Generates md5 hash of input. 8 | utils.md5 = input => { 9 | let hash = crypto.createHash("MD5"); 10 | hash.update(input); 11 | 12 | return hash.digest("hex"); 13 | }; 14 | 15 | // Generates sha1 hash of input. 16 | utils.sha1 = input => { 17 | let hash = crypto.createHash("sha1"); 18 | hash.update(input); 19 | 20 | return hash.digest("base64"); 21 | }; 22 | 23 | // Encode to base64 string. 24 | utils.base64 = input => { 25 | return Buffer.from(input, "utf8").toString("base64"); 26 | }; 27 | 28 | // Decodes base64 string. 29 | utils.decodeBase64 = input => { 30 | return Buffer.from(input, "base64").toString("utf8"); 31 | }; 32 | 33 | // Export utils. 34 | module.exports = utils; 35 | -------------------------------------------------------------------------------- /src/http-auth.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // Exports. 3 | module.exports = { 4 | // Basic authentication. 5 | basic: (options, checker) => { 6 | return require("./auth/basic")(options, checker); 7 | }, 8 | 9 | // Digest authentication. 10 | digest: (options, checker) => { 11 | return require("./auth/digest")(options, checker); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /test/basic-nofile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Basic auth. 16 | describe("basic", () => { 17 | describe("nofile", () => { 18 | let server = undefined; 19 | 20 | before(function() { 21 | // Configure authentication. 22 | const basic = auth.basic( 23 | { 24 | realm: "Private Area." 25 | }, 26 | (username, password, done) => { 27 | if (username === "gevorg") { 28 | done(new Error("Error comes here")); 29 | } else if (username === "mia" && password === "supergirl") { 30 | done(true); 31 | } else if ( 32 | username === "ColonUser" && 33 | password === "apasswordwith:acolon" 34 | ) { 35 | done(true); 36 | } else { 37 | done(false); 38 | } 39 | } 40 | ); 41 | 42 | // Add error listener. 43 | basic.on("error", () => { 44 | console.log("Error thrown!"); 45 | }); 46 | 47 | // Creating new HTTP server. 48 | server = http.createServer( 49 | basic.check((req, res) => { 50 | res.end(`Welcome to private area - ${req.user}!`); 51 | }) 52 | ); 53 | 54 | // Start server. 55 | server.listen(1337); 56 | }); 57 | 58 | after(() => { 59 | server.close(); 60 | }); 61 | 62 | it("error", done => { 63 | const callback = (error, response, body) => { 64 | expect(body).to.equal("Error comes here"); 65 | done(); 66 | }; 67 | 68 | // Test request. 69 | request.get("http://127.0.0.1:1337", callback).auth("gevorg", "gpass"); 70 | }); 71 | 72 | it("success", done => { 73 | const callback = (error, response, body) => { 74 | expect(body).to.equal("Welcome to private area - mia!"); 75 | done(); 76 | }; 77 | 78 | // Test request. 79 | request.get("http://127.0.0.1:1337", callback).auth("mia", "supergirl"); 80 | }); 81 | 82 | it("wrong password", done => { 83 | const callback = (error, response, body) => { 84 | expect(body).to.equal("401 Unauthorized"); 85 | done(); 86 | }; 87 | 88 | // Test request. 89 | request.get("http://127.0.0.1:1337", callback).auth("mia", "cute"); 90 | }); 91 | 92 | it("wrong user", done => { 93 | const callback = (error, response, body) => { 94 | expect(body).to.equal("401 Unauthorized"); 95 | done(); 96 | }; 97 | 98 | // Test request. 99 | request.get("http://127.0.0.1:1337", callback).auth("Tina", "supergirl"); 100 | }); 101 | 102 | it("password with colon", function(done) { 103 | const callback = (error, response, body) => { 104 | expect(body).to.equal("Welcome to private area - ColonUser!"); 105 | done(); 106 | }; 107 | 108 | // Test request. 109 | request 110 | .get("http://127.0.0.1:1337", callback) 111 | .auth("ColonUser", "apasswordwith:acolon"); 112 | }); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /test/basic-skipuser.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Basic auth. 16 | describe("basic", () => { 17 | let server = undefined; 18 | 19 | before(() => { 20 | // Configure authentication. 21 | const basic = auth.basic({ 22 | realm: "Private Area.", 23 | file: __dirname + "/../data/users.htpasswd", 24 | skipUser: true 25 | }); 26 | 27 | // Creating new HTTP server. 28 | server = http.createServer( 29 | basic.check((req, res) => { 30 | res.end(`Welcome to private area - ${req.user}!`); 31 | }) 32 | ); 33 | 34 | // Start server. 35 | server.listen(1337); 36 | }); 37 | 38 | after(() => { 39 | server.close(); 40 | }); 41 | 42 | it("skip user", done => { 43 | const callback = (error, response, body) => { 44 | expect(body).to.equal("Welcome to private area - undefined!"); 45 | done(); 46 | }; 47 | 48 | // Test request. 49 | request.get("http://127.0.0.1:1337", callback).auth("gevorg", "gpass"); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Basic auth. 16 | describe("basic", () => { 17 | let server = undefined; 18 | 19 | before(() => { 20 | // Configure authentication. 21 | const basic = auth.basic({ 22 | realm: "Private Area.", 23 | file: __dirname + "/../data/users.htpasswd" 24 | }); 25 | 26 | // Creating new HTTP server. 27 | server = http.createServer( 28 | basic.check((req, res) => { 29 | res.end(`Welcome to private area - ${req.user}!`); 30 | }) 31 | ); 32 | 33 | // Start server. 34 | server.listen(1337); 35 | }); 36 | 37 | after(() => { 38 | server.close(); 39 | }); 40 | 41 | it("SHA1", done => { 42 | const callback = (error, response, body) => { 43 | expect(body).to.equal("Welcome to private area - gevorg!"); 44 | done(); 45 | }; 46 | 47 | // Test request. 48 | request.get("http://127.0.0.1:1337", callback).auth("gevorg", "gpass"); 49 | }); 50 | 51 | it("Crypt", done => { 52 | const callback = (error, response, body) => { 53 | expect(body).to.equal("Welcome to private area - vera!"); 54 | done(); 55 | }; 56 | 57 | // Test request. 58 | request.get("http://127.0.0.1:1337", callback).auth("vera", "kruta"); 59 | }); 60 | 61 | it("MD5", done => { 62 | const callback = (error, response, body) => { 63 | expect(body).to.equal("Welcome to private area - hera!"); 64 | done(); 65 | }; 66 | 67 | // Test request. 68 | request.get("http://127.0.0.1:1337", callback).auth("hera", "gnu"); 69 | }); 70 | 71 | it("Bcrypt", done => { 72 | const callback = (error, response, body) => { 73 | expect(body).to.equal("Welcome to private area - titan!"); 74 | done(); 75 | }; 76 | 77 | // Test request. 78 | request.get("http://127.0.0.1:1337", callback).auth("titan", "demo"); 79 | }); 80 | 81 | it("plain", done => { 82 | const callback = (error, response, body) => { 83 | expect(body).to.equal("Welcome to private area - Sarah!"); 84 | done(); 85 | }; 86 | 87 | // Test request. 88 | request.get("http://127.0.0.1:1337", callback).auth("Sarah", "testpass"); 89 | }); 90 | 91 | it("Wrong password", done => { 92 | const callback = (error, response, body) => { 93 | expect(body).to.equal("401 Unauthorized"); 94 | done(); 95 | }; 96 | 97 | // Test request. 98 | request.get("http://127.0.0.1:1337", callback).auth("gevorg", "duck"); 99 | }); 100 | 101 | it("Wrong user", done => { 102 | const callback = (error, response, body) => { 103 | expect(body).to.equal("401 Unauthorized"); 104 | done(); 105 | }; 106 | 107 | // Test request. 108 | request.get("http://127.0.0.1:1337", callback).auth("solomon", "gpass"); 109 | }); 110 | 111 | it("Empty user and password", done => { 112 | const callback = (error, response, body) => { 113 | expect(body).to.equal("401 Unauthorized"); 114 | done(); 115 | }; 116 | 117 | // Test request. 118 | request.get("http://127.0.0.1:1337", callback).auth("", ""); 119 | }); 120 | 121 | it("Commented user", done => { 122 | const callback = (error, response, body) => { 123 | expect(body).to.equal("401 Unauthorized"); 124 | done(); 125 | }; 126 | 127 | // Test request. 128 | request 129 | .get("http://127.0.0.1:1337", callback) 130 | .auth("#comment", "commentpass"); 131 | }); 132 | }); 133 | -------------------------------------------------------------------------------- /test/digest-md5-qop.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Digest auth. 16 | describe("digest", () => { 17 | describe("md5-qop", () => { 18 | let server = undefined; 19 | 20 | before(() => { 21 | // Configure authentication. 22 | const digest = auth.digest({ 23 | qop: "none", 24 | realm: "Simon Area.", 25 | file: __dirname + "/../data/users.htdigest" 26 | }); 27 | 28 | // Creating new HTTP server. 29 | server = http.createServer( 30 | digest.check((req, res) => { 31 | res.end(`Welcome to private area - ${req.user}!`); 32 | }) 33 | ); 34 | 35 | // Start server. 36 | server.listen(1337); 37 | }); 38 | 39 | after(() => { 40 | server.close(); 41 | }); 42 | 43 | it("success", done => { 44 | const callback = (error, response, body) => { 45 | expect(body).to.equal("Welcome to private area - vivi!"); 46 | done(); 47 | }; 48 | 49 | // Test request. 50 | request 51 | .get("http://127.0.0.1:1337", callback) 52 | .auth("vivi", "anna", false); 53 | }); 54 | 55 | it("wrong password", done => { 56 | const callback = (error, response, body) => { 57 | expect(body).to.equal("401 Unauthorized"); 58 | done(); 59 | }; 60 | 61 | // Test request. 62 | request 63 | .get("http://127.0.0.1:1337", callback) 64 | .auth("vivi", "goose", false); 65 | }); 66 | 67 | it("wrong user", done => { 68 | const callback = (error, response, body) => { 69 | expect(body).to.equal("401 Unauthorized"); 70 | done(); 71 | }; 72 | 73 | // Test request. 74 | request 75 | .get("http://127.0.0.1:1337", callback) 76 | .auth("brad", "anna", false); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/digest-md5-sess.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Digest auth. 16 | describe("digest", () => { 17 | describe("md5-sess", () => { 18 | let server = undefined; 19 | 20 | before(() => { 21 | // Configure authentication. 22 | const digest = auth.digest({ 23 | algorithm: "MD5-sess", 24 | realm: "Simon Area.", 25 | file: __dirname + "/../data/users.htdigest" 26 | }); 27 | 28 | // Creating new HTTP server. 29 | server = http.createServer( 30 | digest.check((req, res) => { 31 | res.end(`Welcome to private area - ${req.user}!`); 32 | }) 33 | ); 34 | 35 | // Start server. 36 | server.listen(1337); 37 | }); 38 | 39 | after(() => { 40 | server.close(); 41 | }); 42 | 43 | it("success", done => { 44 | const callback = (error, response, body) => { 45 | expect(body).to.equal("Welcome to private area - vivi!"); 46 | done(); 47 | }; 48 | 49 | // Test request. 50 | request 51 | .get("http://127.0.0.1:1337", callback) 52 | .auth("vivi", "anna", false); 53 | }); 54 | 55 | it("wrong password", done => { 56 | const callback = (error, response, body) => { 57 | expect(body).to.equal("401 Unauthorized"); 58 | done(); 59 | }; 60 | 61 | // Test request. 62 | request 63 | .get("http://127.0.0.1:1337", callback) 64 | .auth("vivi", "goose", false); 65 | }); 66 | 67 | it("wrong user", done => { 68 | const callback = (error, response, body) => { 69 | expect(body).to.equal("401 Unauthorized"); 70 | done(); 71 | }; 72 | 73 | // Test request. 74 | request 75 | .get("http://127.0.0.1:1337", callback) 76 | .auth("brad", "anna", false); 77 | }); 78 | }); 79 | }); 80 | -------------------------------------------------------------------------------- /test/digest-nofile.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Utils. 16 | const utils = require("../src/auth/utils"); 17 | 18 | // Digest auth. 19 | describe("digest", () => { 20 | describe("nofile", () => { 21 | let server = undefined; 22 | 23 | before(() => { 24 | // Configure authentication. 25 | const digest = auth.digest( 26 | { 27 | realm: "Simon Area." 28 | }, 29 | (username, callback) => { 30 | if (username === "simon") { 31 | callback(utils.md5("simon:Simon Area.:smart")); 32 | } else if (username === "gevorg") { 33 | callback(new Error("Error comes here")); 34 | } else { 35 | callback(); 36 | } 37 | } 38 | ); 39 | 40 | // Add error listener. 41 | digest.on("error", () => { 42 | console.log("Error thrown!"); 43 | }); 44 | 45 | // Creating new HTTP server. 46 | server = http.createServer( 47 | digest.check((req, res) => { 48 | res.end(`Welcome to private area - ${req.user}!`); 49 | }) 50 | ); 51 | 52 | // Start server. 53 | server.listen(1337); 54 | }); 55 | 56 | after(() => { 57 | server.close(); 58 | }); 59 | 60 | it("error", done => { 61 | const callback = (error, response, body) => { 62 | expect(body).to.equal("Error comes here"); 63 | done(); 64 | }; 65 | 66 | // Test request. 67 | request 68 | .get("http://127.0.0.1:1337", callback) 69 | .auth("gevorg", "gpass", false); 70 | }); 71 | 72 | it("success", done => { 73 | const callback = (error, response, body) => { 74 | expect(body).to.equal("Welcome to private area - simon!"); 75 | done(); 76 | }; 77 | 78 | // Test request. 79 | request 80 | .get("http://127.0.0.1:1337", callback) 81 | .auth("simon", "smart", false); 82 | }); 83 | 84 | it("comma URI", done => { 85 | const callback = (error, response, body) => { 86 | expect(body).to.equal("Welcome to private area - simon!"); 87 | done(); 88 | }; 89 | 90 | // Test request. 91 | request 92 | .get("http://127.0.0.1:1337/comma,/", callback) 93 | .auth("simon", "smart", false); 94 | }); 95 | 96 | it("wrong password", done => { 97 | const callback = (error, response, body) => { 98 | expect(body).to.equal("401 Unauthorized"); 99 | done(); 100 | }; 101 | 102 | // Test request. 103 | request 104 | .get("http://127.0.0.1:1337", callback) 105 | .auth("simon", "woolf", false); 106 | }); 107 | 108 | it("wrong user", done => { 109 | const callback = (error, response, body) => { 110 | expect(body).to.equal("401 Unauthorized"); 111 | done(); 112 | }; 113 | 114 | // Test request. 115 | request 116 | .get("http://127.0.0.1:1337", callback) 117 | .auth("virgina", "smart", false); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/digest.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Digest auth. 16 | describe("digest", () => { 17 | let server = undefined; 18 | 19 | before(() => { 20 | // Configure authentication. 21 | const digest = auth.digest({ 22 | realm: "Simon Area.", 23 | file: __dirname + "/../data/users.htdigest" 24 | }); 25 | 26 | // Creating new HTTP server. 27 | server = http.createServer( 28 | digest.check((req, res) => { 29 | res.end(`Welcome to private area - ${req.user}!`); 30 | }) 31 | ); 32 | 33 | // Start server. 34 | server.listen(1337); 35 | }); 36 | 37 | after(() => { 38 | server.close(); 39 | }); 40 | 41 | it("success", done => { 42 | const callback = (error, response, body) => { 43 | expect(body).to.equal("Welcome to private area - vivi!"); 44 | done(); 45 | }; 46 | 47 | // Test request. 48 | request.get("http://127.0.0.1:1337", callback).auth("vivi", "anna", false); 49 | }); 50 | 51 | it("special uri", done => { 52 | const callback = (error, response, body) => { 53 | expect(body).to.equal("Welcome to private area - vivi!"); 54 | done(); 55 | }; 56 | 57 | // Test request. 58 | request 59 | .get("http://127.0.0.1:1337/?coffee=rocks", callback) 60 | .auth("vivi", "anna", false); 61 | }); 62 | 63 | it("wrong password", done => { 64 | const callback = (error, response, body) => { 65 | expect(body).to.equal("401 Unauthorized"); 66 | done(); 67 | }; 68 | 69 | // Test request. 70 | request.get("http://127.0.0.1:1337", callback).auth("vivi", "goose", false); 71 | }); 72 | 73 | it("wrong user", done => { 74 | const callback = (error, response, body) => { 75 | expect(body).to.equal("401 Unauthorized"); 76 | done(); 77 | }; 78 | 79 | // Test request. 80 | request.get("http://127.0.0.1:1337", callback).auth("brad", "anna", false); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /test/https.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTPS. 10 | const https = require("https"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // FS. 16 | const fs = require("fs"); 17 | 18 | // HTTPS. 19 | describe("https", () => { 20 | let server = undefined; 21 | 22 | before(() => { 23 | // Configure authentication. 24 | const basic = auth.basic( 25 | { 26 | realm: "Private Area." 27 | }, 28 | (username, password, done) => { 29 | if (username === "gevorg") { 30 | done(new Error("Error comes here")); 31 | } else if (username === "mia" && password === "supergirl") { 32 | done(true); 33 | } else if ( 34 | username === "ColonUser" && 35 | password === "apasswordwith:acolon" 36 | ) { 37 | done(true); 38 | } else { 39 | done(false); 40 | } 41 | } 42 | ); 43 | 44 | // Add error listener. 45 | basic.on("error", () => { 46 | console.log("Error thrown!"); 47 | }); 48 | 49 | // HTTPS server options. 50 | const options = { 51 | key: fs.readFileSync(__dirname + "/../data/server.key"), 52 | cert: fs.readFileSync(__dirname + "/../data/server.crt") 53 | }; 54 | 55 | // Creating new HTTPS server. 56 | server = https.createServer( 57 | options, 58 | basic.check((req, res) => { 59 | res.end(`Welcome to private area - ${req.user}!`); 60 | }) 61 | ); 62 | 63 | // Start server. 64 | server.listen(1337); 65 | }); 66 | 67 | after(() => { 68 | server.close(); 69 | }); 70 | 71 | it("error", done => { 72 | const callback = (error, response, body) => { 73 | expect(body).to.equal("Error comes here"); 74 | done(); 75 | }; 76 | 77 | // Test request. 78 | request 79 | .get({ uri: "https://127.0.0.1:1337", strictSSL: false }, callback) 80 | .auth("gevorg", "gpass"); 81 | }); 82 | 83 | it("success", done => { 84 | const callback = (error, response, body) => { 85 | expect(body).to.equal("Welcome to private area - mia!"); 86 | done(); 87 | }; 88 | 89 | // Test request. 90 | request 91 | .get({ uri: "https://127.0.0.1:1337", strictSSL: false }, callback) 92 | .auth("mia", "supergirl"); 93 | }); 94 | 95 | it("wrong password", done => { 96 | const callback = (error, response, body) => { 97 | expect(body).to.equal("401 Unauthorized"); 98 | done(); 99 | }; 100 | 101 | // Test request. 102 | request 103 | .get({ uri: "https://127.0.0.1:1337", strictSSL: false }, callback) 104 | .auth("mia", "cute"); 105 | }); 106 | 107 | it("wrong user", done => { 108 | const callback = (error, response, body) => { 109 | expect(body).to.equal("401 Unauthorized"); 110 | done(); 111 | }; 112 | 113 | // Test request. 114 | request 115 | .get({ uri: "https://127.0.0.1:1337", strictSSL: false }, callback) 116 | .auth("Tina", "supergirl"); 117 | }); 118 | 119 | it("password with colon", done => { 120 | const callback = (error, response, body) => { 121 | expect(body).to.equal("Welcome to private area - ColonUser!"); 122 | done(); 123 | }; 124 | 125 | // Test request. 126 | request 127 | .get({ uri: "https://127.0.0.1:1337", strictSSL: false }, callback) 128 | .auth("ColonUser", "apasswordwith:acolon"); 129 | }); 130 | }); 131 | -------------------------------------------------------------------------------- /test/proxy.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Request module. 7 | const request = require("request"); 8 | 9 | // HTTP. 10 | const http = require("http"); 11 | 12 | // Source. 13 | const auth = require("../src/http-auth"); 14 | 15 | // Proxy library. 16 | const httpProxy = require("http-proxy"); 17 | 18 | // Proxy. 19 | describe("proxy", () => { 20 | let server = undefined; 21 | let proxyServer = undefined; 22 | 23 | before(() => { 24 | // Configure authentication. 25 | const basic = auth.basic( 26 | { 27 | proxy: true, 28 | realm: "Private Area." 29 | }, 30 | (username, password, done) => { 31 | if (username === "gevorg") { 32 | done(new Error("Error comes here")); 33 | } else if (username === "mia" && password === "supergirl") { 34 | done(true); 35 | } else if ( 36 | username === "ColonUser" && 37 | password === "apasswordwith:acolon" 38 | ) { 39 | done(true); 40 | } else { 41 | done(false); 42 | } 43 | } 44 | ); 45 | 46 | // Add error listener. 47 | basic.on("error", () => { 48 | console.log("Error thrown!"); 49 | }); 50 | 51 | // Setup proxy. 52 | const proxy = httpProxy.createProxyServer({}); 53 | proxyServer = http 54 | .createServer( 55 | basic.check((req, res) => { 56 | proxy.web(req, res, { target: "http://127.0.0.1:1338" }); 57 | }) 58 | ) 59 | .listen(1337); 60 | 61 | // Create your target server. 62 | server = http 63 | .createServer((req, res) => { 64 | res.end("Request successfully proxied!"); 65 | }) 66 | .listen(1338, () => { 67 | // Log URL. 68 | console.log("Server running at http://127.0.0.1:1338/"); 69 | }); 70 | }); 71 | 72 | after(() => { 73 | proxyServer.close(); 74 | server.close(); 75 | }); 76 | 77 | it("error", done => { 78 | const callback = (error, response, body) => { 79 | expect(body).to.equal("Error comes here"); 80 | done(); 81 | }; 82 | 83 | // Test request. 84 | request.get( 85 | { 86 | proxy: "http://gevorg:gpass@127.0.0.1:1337", 87 | uri: "http://127.0.0.1:1337" 88 | }, 89 | callback 90 | ); 91 | }); 92 | 93 | it("success", done => { 94 | const callback = (error, response, body) => { 95 | expect(body).to.equal("Request successfully proxied!"); 96 | done(); 97 | }; 98 | 99 | // Test request. 100 | request.get( 101 | { 102 | proxy: "http://mia:supergirl@127.0.0.1:1337", 103 | uri: "http://127.0.0.1:1337" 104 | }, 105 | callback 106 | ); 107 | }); 108 | 109 | it("wrong password", done => { 110 | const callback = (error, response, body) => { 111 | expect(body).to.equal("407 Proxy authentication required"); 112 | done(); 113 | }; 114 | 115 | // Test request. 116 | request.get( 117 | { proxy: "http://mia:cute@127.0.0.1:1337", uri: "http://127.0.0.1:1337" }, 118 | callback 119 | ); 120 | }); 121 | 122 | it("wrong user", done => { 123 | const callback = (error, response, body) => { 124 | expect(body).to.equal("407 Proxy authentication required"); 125 | done(); 126 | }; 127 | 128 | // Test request. 129 | request.get( 130 | { 131 | proxy: "http://Tina:supergirl@127.0.0.1:1337", 132 | uri: "http://127.0.0.1:1337" 133 | }, 134 | callback 135 | ); 136 | }); 137 | }); 138 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | // Expect module. 4 | const expect = require("chai").expect; 5 | 6 | // Source. 7 | const utils = require("../src/auth/utils"); 8 | 9 | // Utils 10 | describe("utils", () => { 11 | // Tests for md5. 12 | describe("#md5", () => { 13 | it("hash should be correct", () => { 14 | // Source. 15 | const hash = utils.md5("apple of my eye"); 16 | 17 | // Expectation. 18 | expect(hash).to.equal("bda53a1c77224ede7cb14e756c1d0142"); 19 | }); 20 | }); 21 | 22 | // Tests for sha1. 23 | describe("#sha1", () => { 24 | it("hash should be correct", () => { 25 | // Source. 26 | const hash = utils.sha1("forbidden fruit"); 27 | 28 | // Expectation. 29 | expect(hash).to.equal("E1Kr19KXvaYQPcLo2MvSpGjoAYU="); 30 | }); 31 | }); 32 | 33 | // Tests for base64. 34 | describe("#base64", () => { 35 | it("ASCII input", () => { 36 | // Source. 37 | const hash = utils.base64("crocodile"); 38 | 39 | // Expectation. 40 | expect(hash).to.equal("Y3JvY29kaWxl"); 41 | }); 42 | 43 | it("unicode input", () => { 44 | // Source. 45 | const hash = utils.base64("Գևորգ"); 46 | 47 | // Expectation. 48 | expect(hash).to.equal("1LPWh9W41oDVow=="); 49 | }); 50 | }); 51 | 52 | // Tests for decodeBase64. 53 | describe("#decodeBase64", () => { 54 | it("ASCII input", () => { 55 | // Source. 56 | const hash = utils.decodeBase64("c21pbGU="); 57 | 58 | // Expectation. 59 | expect(hash).to.equal("smile"); 60 | }); 61 | 62 | it("unicode input", () => { 63 | // Source. 64 | const hash = utils.decodeBase64("0J3RgyDRgtGLIQ=="); 65 | 66 | // Expectation. 67 | expect(hash).to.equal("Ну ты!"); 68 | }); 69 | }); 70 | }); 71 | --------------------------------------------------------------------------------