├── .editorconfig ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── gulpfile.js ├── index.js ├── lib ├── config │ └── dumbPasswords.js ├── helpers │ ├── CaeserCipher.js │ └── radixTree.js └── index.js ├── package.json └── test └── index.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | root = true 5 | 6 | [*] 7 | # Change these settings to your own preference 8 | indent_style = space 9 | indent_size = 2 10 | 11 | # We recommend you to keep these unchanged 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | 17 | [*.md] 18 | trim_trailing_whitespace = false 19 | 20 | # 4 space indentation 21 | [**.{json, htaccess}] 22 | indent_style = space 23 | indent_size = 4 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "ecmaVersion": 5, 4 | "sourceType": "module" 5 | }, 6 | "env": { 7 | "node": true, 8 | "mocha": true 9 | }, 10 | "extends": "eslint:recommended", 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,laravel,bower,justcode,linux,osx,jetbrains,sublimetext,vim,xcode 3 | 4 | ## My 5 | lab/ 6 | 7 | ### Node ### 8 | # Logs 9 | logs 10 | *.log 11 | npm-debug.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules 38 | jspm_packages 39 | 40 | # Optional npm cache directory 41 | .npm 42 | 43 | # Optional REPL history 44 | .node_repl_history 45 | 46 | 47 | ### Laravel ### 48 | vendor/ 49 | node_modules/ 50 | 51 | # Laravel 4 specific 52 | bootstrap/compiled.php 53 | app/storage/ 54 | 55 | # Laravel 5 & Lumen specific 56 | bootstrap/cache/ 57 | .env.*.php 58 | .env.php 59 | .env 60 | 61 | # Rocketeer PHP task runner and deployment package. https://github.com/rocketeers/rocketeer 62 | .rocketeer/ 63 | 64 | 65 | ### Bower ### 66 | bower_components 67 | .bower-cache 68 | .bower-registry 69 | .bower-tmp 70 | 71 | 72 | ### JustCode ### 73 | .JustCode 74 | 75 | ### Linux ### 76 | *~ 77 | 78 | # temporary files which can be created if a process still has a handle open of a deleted file 79 | .fuse_hidden* 80 | 81 | # KDE directory preferences 82 | .directory 83 | 84 | # Linux trash folder which might appear on any partition or disk 85 | .Trash-* 86 | 87 | 88 | ### OSX ### 89 | *.DS_Store 90 | .AppleDouble 91 | .LSOverride 92 | 93 | # Icon must end with two \r 94 | Icon 95 | 96 | 97 | # Thumbnails 98 | ._* 99 | 100 | # Files that might appear in the root of a volume 101 | .DocumentRevisions-V100 102 | .fseventsd 103 | .Spotlight-V100 104 | .TemporaryItems 105 | .Trashes 106 | .VolumeIcon.icns 107 | .com.apple.timemachine.donotpresent 108 | 109 | # Directories potentially created on remote AFP share 110 | .AppleDB 111 | .AppleDesktop 112 | Network Trash Folder 113 | Temporary Items 114 | .apdisk 115 | 116 | 117 | ### JetBrains ### 118 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 119 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 120 | 121 | # User-specific stuff: 122 | .idea/workspace.xml 123 | .idea/tasks.xml 124 | .idea/dictionaries 125 | .idea/vcs.xml 126 | .idea/jsLibraryMappings.xml 127 | 128 | # Sensitive or high-churn files: 129 | .idea/dataSources.ids 130 | .idea/dataSources.xml 131 | .idea/dataSources.local.xml 132 | .idea/sqlDataSources.xml 133 | .idea/dynamic.xml 134 | .idea/uiDesigner.xml 135 | 136 | # Gradle: 137 | .idea/gradle.xml 138 | .idea/libraries 139 | 140 | # Mongo Explorer plugin: 141 | .idea/mongoSettings.xml 142 | 143 | ## File-based project format: 144 | *.iws 145 | 146 | ## Plugin-specific files: 147 | 148 | # IntelliJ 149 | /out/ 150 | 151 | # mpeltonen/sbt-idea plugin 152 | .idea_modules/ 153 | 154 | # JIRA plugin 155 | atlassian-ide-plugin.xml 156 | 157 | # Crashlytics plugin (for Android Studio and IntelliJ) 158 | com_crashlytics_export_strings.xml 159 | crashlytics.properties 160 | crashlytics-build.properties 161 | fabric.properties 162 | 163 | ### JetBrains Patch ### 164 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 165 | 166 | # *.iml 167 | # modules.xml 168 | 169 | 170 | ### SublimeText ### 171 | # cache files for sublime text 172 | *.tmlanguage.cache 173 | *.tmPreferences.cache 174 | *.stTheme.cache 175 | 176 | # workspace files are user-specific 177 | *.sublime-workspace 178 | 179 | # project files should be checked into the repository, unless a significant 180 | # proportion of contributors will probably not be using SublimeText 181 | # *.sublime-project 182 | 183 | # sftp configuration file 184 | sftp-config.json 185 | 186 | # Package control specific files 187 | Package Control.last-run 188 | Package Control.ca-list 189 | Package Control.ca-bundle 190 | Package Control.system-ca-bundle 191 | Package Control.cache/ 192 | Package Control.ca-certs/ 193 | bh_unicode_properties.cache 194 | 195 | # Sublime-github package stores a github token in this file 196 | # https://packagecontrol.io/packages/sublime-github 197 | GitHub.sublime-settings 198 | 199 | 200 | ### Vim ### 201 | # swap 202 | [._]*.s[a-w][a-z] 203 | [._]s[a-w][a-z] 204 | # session 205 | Session.vim 206 | # temporary 207 | .netrwhist 208 | *~ 209 | # auto-generated tag files 210 | tags 211 | 212 | 213 | ### Xcode ### 214 | # Xcode 215 | # 216 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 217 | 218 | ## Build generated 219 | build/ 220 | DerivedData/ 221 | 222 | ## Various settings 223 | *.pbxuser 224 | !default.pbxuser 225 | *.mode1v3 226 | !default.mode1v3 227 | *.mode2v3 228 | !default.mode2v3 229 | *.perspectivev3 230 | !default.perspectivev3 231 | xcuserdata/ 232 | 233 | ## Other 234 | *.moved-aside 235 | *.xccheckout 236 | *.xcscmblueprint 237 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2016 Eugene Mutai 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Coverage Status](https://coveralls.io/repos/github/kn9ts/dumb-passwords/badge.svg?branch=master)](https://coveralls.io/github/kn9ts/dumb-passwords?branch=master) 2 | 3 | ![](http://res.cloudinary.com/dpmk2cnpi/image/upload/v1466589978/dumbPasswords_sxotda.png) 4 | 5 | > #### Guard your users from security problems such as being hacked that start by having dumb passwords 6 | 7 | ### Introduction 8 | 9 | `dumb-passwords` is an NPM module that can be used to verify **the user provided password is 10 | not one of the top 10,000 worst passwords** as analysed by a respectable IT security analyst. Read 11 | about all [ here](https://xato.net/10-000-top-passwords-6d6380716fe0#.473dkcjfm), 12 | [here(wired)](http://www.wired.com/2013/12/web-semantics-the-ten-thousand-worst-passwords/) or 13 | [here(telegram)](http://www.telegraph.co.uk/technology/internet-security/10303159/Most-common-and-hackable-passwords-on-the-internet.html) 14 | 15 | ### Getting Started 16 | 17 | #### Installation 18 | 19 | ```bash 20 | $ npm install dumb-passwords --save 21 | ``` 22 | 23 | #### Usage 24 | 25 | Short example: 26 | 27 | ```js 28 | const dumbPasswords = require('dumb-passwords'); 29 | 30 | const isDumb = dumbPasswords.check('123456'); // true 31 | // or use: 32 | // const isDumb = dumbPasswords.checkPassword('123456'); 33 | ``` 34 | 35 | Embedding it into your [**EXPRESS**](http://expressjs.com/en/4x/api.html#app.post.method) application: 36 | 37 | ```js 38 | 'use strict'; 39 | 40 | const app = require('express')(); 41 | const dumbPasswords = require('dumb-passwords'); 42 | 43 | ... 44 | 45 | app.post('/user/create', (req, res) => { 46 | const userPassword = req.body.userPassword; 47 | 48 | if (dumbPasswords.check(userPassword)) { 49 | const rate = dumbPasswords.rateOfUsage(userPassword); 50 | let message = 'Dear user, that\'s a dumb password!'; 51 | message += ' Why? For every 100,000 user accounts on the internet, '; 52 | message += rate.frequency + ' are "protected" using that same password.'; 53 | message += ' Hacker\'s paradise.'; 54 | 55 | // DO NOT send this back to your user, it's only for demo purposes 56 | res.status(200).send(message); 57 | } else { 58 | // that password is awesome! 59 | // that user SMART! Give them the key to success! 60 | } 61 | }); 62 | 63 | ... 64 | 65 | app.listen(8080, () => { 66 | console.log('Express server listening on on port 8080'); 67 | }); 68 | 69 | // expose app 70 | module.exports = app; 71 | ``` 72 | 73 | 74 | ## API 75 | 76 | #### dumbPasswords.check(string) => true or false 77 | 78 | Check if the string provided, representing the user's proposed submitted password is not one of the 79 | **top 10,000 worst passwords** users use. 80 | 81 | returns `true` if the password is one of them and `false` if the password is not. 82 | 83 | #### dumbPasswords.rateOfUsage(string) => {password, frequency} 84 | 85 | Checks and returns the recorded usage frequency of the related password per 100,000 user passwords. 86 | 87 | ```js 88 | dumbPasswords.rateOfUsage('superman') // { password: 'superman', frequency: 2523 } 89 | ``` 90 | 91 | ### License 92 | 93 | ##### [MIT](https://mit-license.org/) © [Eugene Mutai](https://github.com/kn9ts) | [Kevin Gathuku](https://github.com/kevgathuku) | [Jeremy Kithome](https://github.com/andela-jkithome) 94 | 95 | *__DISCLAIMER:__* _All opinions aired in this repo are ours and do not reflect any company or organisation any contributor is involved with._ 96 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const os = require('os'); 4 | const gulp = require('gulp'); 5 | const mocha = require('gulp-mocha'); 6 | const istanbul = require('gulp-istanbul'); 7 | const coveralls = require('gulp-coveralls'); 8 | const eslint = require('gulp-eslint'); 9 | const runSequence = require('run-sequence'); 10 | 11 | if (!('COVERALLS_SERVICE_NAME' in process.env)) { 12 | process.env.COVERALLS_SERVICE_NAME = `${os.hostname()}.${os.platform()}-${os.release()}`; 13 | } 14 | process.env.COVERALLS_REPO_TOKEN = 'gjhGzgDSI9fNMQxEM4RavtZaIUqfqO1QH'; 15 | 16 | const filesToLint = [ 17 | 'gulpfile.js', 18 | 'lib/**/*.js', 19 | '!lib/config/**', 20 | '!node_modules/**' 21 | ]; 22 | 23 | gulp.task('lint', () => gulp.src(filesToLint) 24 | .pipe(eslint()) 25 | .pipe(eslint.format()) 26 | .pipe(eslint.failAfterError())); 27 | 28 | gulp.task('coverage', () => gulp 29 | .src(['!node_modules/**', '!lib/config/**', 'lib/**/*.js']) 30 | .pipe(istanbul({ includeUntested: true })) 31 | .pipe(istanbul.hookRequire())); 32 | 33 | gulp.task('test:backend', () => gulp.src(['test/**/*.js']) 34 | .pipe(mocha({ reporter: 'spec' })) 35 | .once('error', err => { 36 | throw err; 37 | }) 38 | .pipe(istanbul.writeReports({ 39 | dir: './coverage', 40 | reporters: ['html', 'lcov', 'text', 'json'] 41 | }))); 42 | 43 | gulp.task('coveralls', () => gulp.src('coverage/lcov.info').pipe(coveralls())); 44 | 45 | gulp.task('test', callback => { 46 | runSequence('lint', 'coverage', 'test:backend', callback); 47 | }); 48 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const dumbPasswords = require('./lib'); 4 | module.exports = dumbPasswords; 5 | -------------------------------------------------------------------------------- /lib/helpers/CaeserCipher.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function CaeserCipher(integer) { 4 | this.constant = integer; 5 | } 6 | 7 | CaeserCipher.prototype.encryptString = function(stringProvided) { 8 | var newEncryptedString = ''; 9 | for (var x = 0, len = stringProvided.length; x < len; x++) { 10 | var letter = stringProvided[x]; 11 | var encrypted = false; 12 | var characterCode = letter.charCodeAt(0); 13 | // only encrypt the alphabetical characters, not the spaces 14 | if (characterCode >= 65 && characterCode <= 122) { 15 | // where the magic happens 16 | letter = this.encrypt(letter); 17 | encrypted = true; 18 | } 19 | 20 | if (encrypted) { 21 | newEncryptedString += String.fromCharCode(letter); 22 | continue; 23 | } 24 | 25 | newEncryptedString += letter; 26 | } 27 | return newEncryptedString; 28 | }; 29 | 30 | CaeserCipher.prototype.encrypt = function(letter) { 31 | // if this.constant is larger than 26 do a modulus on it 32 | if (this.constant > 26) { 33 | this.constant = this.constant % 26; 34 | } 35 | 36 | // get the encryptedCharacter's position between 0 - 25 37 | var pos = letter.charCodeAt(0) - 97; 38 | var crypticPosition = (pos + this.constant) % 26; 39 | return 97 + crypticPosition; 40 | }; 41 | 42 | module.exports = CaeserCipher; 43 | -------------------------------------------------------------------------------- /lib/helpers/radixTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function RadixTree(dumpPasswords) { 4 | var thisClass = this; 5 | thisClass.nodes = {}; 6 | thisClass.dumpPasswords = dumpPasswords; 7 | thisClass.dumpPasswords.forEach(function(password) { 8 | thisClass.addNode(password.hashedPassword); 9 | }); 10 | } 11 | 12 | RadixTree.prototype.addNode = function(word) { 13 | this.splitWordToArrayOfLetters(word) 14 | .reduce(function(node, character, i, a) { 15 | if (!node[character]) { 16 | node[character] = {}; 17 | } 18 | if (i === a.length - 1) { 19 | node[character].isword = true; 20 | } 21 | return node[character]; 22 | }, this.nodes); 23 | return this; 24 | }; 25 | 26 | RadixTree.prototype.searchForNodes = function(word) { 27 | var found = false; 28 | var iterObject = function(nodes, initialValue) { 29 | return Object.keys(nodes).reduce(function(previousKey, key) { 30 | if (key === 'isword' && previousKey === word) { 31 | found = true; 32 | } 33 | typeof nodes[key] === 'object' && iterObject(nodes[key], previousKey + key); 34 | return previousKey; 35 | }, initialValue); 36 | }; 37 | iterObject(this.nodes, ''); 38 | return found; 39 | }; 40 | 41 | RadixTree.prototype.splitWordToArrayOfLetters = function(word) { 42 | return word.toLowerCase().split(''); 43 | }; 44 | 45 | module.exports = RadixTree; 46 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var dumbPasswords = require('./config/dumbPasswords'); 4 | var RadixTree = require('./helpers/radixTree'); 5 | var CaeserCipher = require('../lib/helpers/CaeserCipher'); 6 | 7 | var passwordTree = new RadixTree(dumbPasswords); 8 | var caeserCypher = new CaeserCipher(5); 9 | 10 | var checkPassword = function(userPasswordInputString) { 11 | var userPassword = userPasswordInputString.toLowerCase(); 12 | var userHashedPassword = caeserCypher.encryptString(userPassword); 13 | 14 | return passwordTree.searchForNodes(userHashedPassword); 15 | }; 16 | 17 | exports.checkPassword = checkPassword; 18 | exports.check = checkPassword; 19 | 20 | exports.rateOfUsage = function(userPasswordInputString) { 21 | var userPassword = userPasswordInputString.toLowerCase(); 22 | var userHashedPassword = caeserCypher.encryptString(userPassword); 23 | 24 | var passwordExists = passwordTree.searchForNodes(userHashedPassword); 25 | if (passwordExists) { 26 | var result = dumbPasswords.find(function(password) { 27 | return password.hashedPassword === userHashedPassword; 28 | }); 29 | result.password = userPassword; 30 | // delete result.hashedPassword; 31 | return result; 32 | } 33 | 34 | return { 35 | password: userPasswordInputString, 36 | frequency: 0, 37 | message: 'The password is not part of the list' 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dumb-passwords", 3 | "version": "0.2.1", 4 | "description": "Guard your users from security problems that start by having dumb passwords", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp test", 8 | "istanbul": "istanbul cover _mocha -- --recursive", 9 | "lint": "eslint --fix ./lib index.js", 10 | "mocha": "mocha" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/kn9ts/dumb-passwords.git" 15 | }, 16 | "keywords": [ 17 | "stupid", 18 | "dumb", 19 | "password", 20 | "stupid passwords", 21 | "dumb passwords", 22 | "common", 23 | "common passwords", 24 | "security", 25 | "user", 26 | "user password", 27 | "risks", 28 | "top 10000 worst passwords" 29 | ], 30 | "author": "Eugene Mutai (http://twitter.com/kn9ts)", 31 | "license": "MIT", 32 | "bugs": { 33 | "email": "eugenemutai@gmail.com", 34 | "url": "https://github.com/kn9ts/dumb-passwords/issues" 35 | }, 36 | "homepage": "https://github.com/kn9ts/dumb-passwords#readme", 37 | "devDependencies": { 38 | "chai": "^3.5.0", 39 | "eslint": "^2.10.2", 40 | "gulp": "^3.9.1", 41 | "gulp-coveralls": "^0.1.4", 42 | "gulp-eslint": "^2.0.0", 43 | "gulp-istanbul": "^0.10.4", 44 | "gulp-mocha": "^2.2.0", 45 | "istanbul": "^0.4.3", 46 | "mocha": "^2.5.3", 47 | "run-sequence": "^1.2.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const assert = chai.assert; 5 | const dumbPassword = require('../lib'); 6 | 7 | describe('dumbPassword.check()', () => { 8 | it('should pass for a stupid password', () => { 9 | assert.isTrue(dumbPassword.check('password')); 10 | assert.isTrue(dumbPassword.check('12345678')); 11 | }); 12 | 13 | it('should pass for a stupid password regardless of case', () => { 14 | assert.isTrue(dumbPassword.check('PASSWORD')); 15 | assert.isTrue(dumbPassword.check('XxXxXx')); 16 | }); 17 | 18 | it('should pass for a stupid password using check method', () => { 19 | assert.isTrue(dumbPassword.check('password')); 20 | assert.isTrue(dumbPassword.check('superman')); 21 | }); 22 | 23 | it('should not pass for a non stupid password', () => { 24 | assert.isFalse(dumbPassword.check('Pass990ver')); 25 | assert.isFalse(dumbPassword.check('sTraigh8#@u')); 26 | }); 27 | 28 | it('should not pass for a non stupid password using checkPassword method', () => { 29 | assert.isFalse(dumbPassword.checkPassword('Pass990ver')); 30 | assert.isFalse(dumbPassword.checkPassword('ummoinnerEmbassava33')); 31 | }); 32 | 33 | it('should not pass for a non stupid password using check method', () => { 34 | assert.isFalse(dumbPassword.check('Pass990ver')); 35 | assert.isFalse(dumbPassword.check('ummoinnerEmbassava33')); 36 | }); 37 | }); 38 | 39 | describe('dumbPassword.rateOfUsage()', () => { 40 | it('should return the rate of usage of a stupid password', () => { 41 | assert.deepEqual(dumbPassword.rateOfUsage('baseball'), { 42 | frequency: 3739, 43 | hashedPassword: 'gfxjgfqq', 44 | password: 'baseball' 45 | }); 46 | assert.isObject(dumbPassword.rateOfUsage('PrInCeSs')); 47 | }); 48 | 49 | it('should return correct response of usage for a non stupid password', () => { 50 | assert.deepEqual(dumbPassword.rateOfUsage('Wanderlust!*3000'), { 51 | frequency: 0, 52 | password: 'Wanderlust!*3000', 53 | message: 'The password is not part of the list' 54 | }); 55 | assert.isObject(dumbPassword.rateOfUsage('SpaceOdessey2001Clarke')); 56 | }); 57 | }); 58 | --------------------------------------------------------------------------------