├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── async ├── index.js └── wrapper.js ├── hasProfanity.js ├── index.js ├── locale ├── ar.js ├── cs.js ├── da.js ├── de.js ├── en.js ├── eo.js ├── es.js ├── fa.js ├── fi.js ├── fr.js ├── hi.js ├── hu.js ├── it.js ├── ja.js ├── ko.js ├── nl.js ├── no.js ├── pl.js ├── pt.js ├── ru.js ├── sv.js ├── th.js ├── tlh.js ├── tr.js └── zh.js ├── non-secure └── index.js ├── package-lock.json ├── package.json ├── test ├── async.test.js ├── hasProfanity.test.js ├── non-secure.test.js └── test.js └── wrapper.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | .vscode/ 4 | 5 | node_modules/ 6 | npm-debug.log 7 | yarn-error.log -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | yarn-error.log 4 | yarn.lock 5 | package-lock.json 6 | 7 | test/ 8 | .travis.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - node 4 | - "8" 5 | - "6" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yuriy Timofeev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nanoid-good 2 | 3 | Guarantees you will not get any obscene words or other profanity in your ids generated by [Nano ID](https://github.com/ai/nanoid). 4 | 5 | This is a drop-in replacement for **Nano ID** which means you can just change the name of imported package from `nanoid` to `nanoid-good`. 6 | 7 | ## How it works 8 | 9 | It checks every generated ID through a vocabulary of obscene words. If any match is found, then ID is generated again and again until it gets clean ID which is returned. 10 | 11 | It finds bad words with mixed registry and words which are hidden in between other letters. 12 | 13 | For example: 14 | 15 | `Uakgb_J5m9g~0JDMpoRnqLJ` 16 | 17 | This one will be considered a bad word because of `poRn` hidden near the end. 18 | 19 | ## Installation 20 | 21 | ```shell 22 | npm install nanoid-good 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```js 28 | var en = require("nanoid-good/locale/en"); // you should add locale of your preferred language 29 | var nanoid = require("nanoid-good").nanoid(en); 30 | var id = nanoid(); //=> "V1StGXR8_Z5jdHi6B~myT" 31 | ``` 32 | 33 | You can also use several locales: 34 | 35 | ```js 36 | var en = require("nanoid-good/locale/en"); 37 | var ru = require("nanoid-good/locale/ru"); 38 | var nanoid = require("nanoid-good").nanoid(en, ru); 39 | ``` 40 | 41 | All additional functions of **Nano ID** are supported too: 42 | 43 | ```js 44 | var en = require("nanoid-good/locale/en"); 45 | var customRandom = require("nanoid-good").customRandom(en); 46 | var customAlphabet = require("nanoid-good").customAlphabet(en); 47 | var nonSecure = require("nanoid-good/non-secure").nanoid(en); 48 | 49 | var generator1 = customRandom("abcdef", 5, randomFunc); 50 | var id1 = generator1(); 51 | 52 | var generator2 = customAlphabet("1234567abcdef", 10); 53 | var id2 = generator2(); 54 | 55 | var id3 = nonSecure(); 56 | ``` 57 | 58 | ## Async 59 | 60 | You can use async versions of `nanoid` functions the same way as you use them in `nanoid`, i.e. by inserting `async` in the path import. 61 | 62 | ```js 63 | var en = require("nanoid-good/locale/en"); 64 | var nanoid = require("nanoid-good/async").nanoid(en); 65 | var customAlphabet = require("nanoid-good/async").customAlphabet(en); 66 | 67 | async function generateIds() { 68 | var id1 = await nanoid(); 69 | var id2 = await customAlphabet("1234567abcdef", 10)(); 70 | } 71 | ``` -------------------------------------------------------------------------------- /async/index.js: -------------------------------------------------------------------------------- 1 | var nanoid = require('nanoid/async'); 2 | var wrapper = require('./wrapper'); 3 | 4 | module.exports = { 5 | nanoid: wrapper(nanoid.nanoid), 6 | customAlphabet: (...locales) => (alphabet, size) => wrapper(nanoid.customAlphabet(alphabet, size))(...locales), 7 | random: nanoid.random 8 | } -------------------------------------------------------------------------------- /async/wrapper.js: -------------------------------------------------------------------------------- 1 | var hasProfanity = require("../hasProfanity"); 2 | 3 | var wrapper = function (fn) { 4 | return function (locales) { 5 | return function () { 6 | var outerArgs = arguments; 7 | return new Promise(function(resolve, reject) { 8 | var retry = function() { fn.apply(this, outerArgs) 9 | .catch(reject) 10 | .then(function(id) { 11 | if (!hasProfanity(locales)(id)) 12 | { 13 | resolve(id); 14 | } 15 | else retry(); 16 | }); 17 | }; 18 | retry(); 19 | }); 20 | } 21 | }; 22 | }; 23 | 24 | module.exports = wrapper; -------------------------------------------------------------------------------- /hasProfanity.js: -------------------------------------------------------------------------------- 1 | function checkByList(input, bad_words) { 2 | for (var i = 0; i < bad_words.length; i++) { 3 | // Number 1 can be interpreted as l or I. 4 | var sanitizedBadWord = bad_words[i].replace('l', 'i') 5 | if (new RegExp(sanitizedBadWord, "gi").test(input)) return true; 6 | } 7 | return false; 8 | } 9 | 10 | var hasProfanityFactory = function () { 11 | var list = [].slice.call(arguments); 12 | if (list.length == 0 || typeof list[0] === 'string') { 13 | console.warn("No locales were added to nanoid-good. Maybe you forgot to add `require('nanoid-good/locale/en')`?") 14 | return false; 15 | } 16 | return function (input) { 17 | var inputWithNumbersReplaced = input 18 | .replace('1', 'i') 19 | .replace('3', 'e') 20 | .replace('5', 's') 21 | .replace('8', 'b') 22 | .replace('0', 'o') 23 | 24 | for (var i = 0; i < list.length; i++) { 25 | if (checkByList(inputWithNumbersReplaced, list[i])) return true; 26 | } 27 | return false; 28 | }; 29 | }; 30 | 31 | module.exports = hasProfanityFactory; -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var nanoid = require('nanoid'); 2 | var wrapper = require('./wrapper'); 3 | 4 | module.exports = { 5 | nanoid: wrapper(nanoid.nanoid), 6 | customAlphabet: (...locales) => (alphabet, size) => wrapper(nanoid.customAlphabet(alphabet, size))(...locales), 7 | customRandom: (...locales) => (alphabet, size, randomFunc) => wrapper(nanoid.customRandom(alphabet, size, randomFunc))(...locales), 8 | urlAlphabet: nanoid.urlAlphabet, 9 | random: nanoid.random 10 | } -------------------------------------------------------------------------------- /locale/ar.js: -------------------------------------------------------------------------------- 1 | module.exports = require("naughty-words/ar.json"); -------------------------------------------------------------------------------- /locale/cs.js: -------------------------------------------------------------------------------- 1 | module.exports = require("naughty-words/cs.json"); -------------------------------------------------------------------------------- /locale/da.js: -------------------------------------------------------------------------------- 1 | module.exports = require("naughty-words/da.json"); -------------------------------------------------------------------------------- /locale/de.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/de.json"); -------------------------------------------------------------------------------- /locale/en.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/en.json"); -------------------------------------------------------------------------------- /locale/eo.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/eo.json"); -------------------------------------------------------------------------------- /locale/es.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/es.json"); -------------------------------------------------------------------------------- /locale/fa.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/fa.json"); -------------------------------------------------------------------------------- /locale/fi.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/fi.json"); -------------------------------------------------------------------------------- /locale/fr.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/fr.json"); -------------------------------------------------------------------------------- /locale/hi.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/hi.json"); -------------------------------------------------------------------------------- /locale/hu.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/hu.json"); -------------------------------------------------------------------------------- /locale/it.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/it.json"); -------------------------------------------------------------------------------- /locale/ja.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/ja.json"); -------------------------------------------------------------------------------- /locale/ko.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/ko.json"); -------------------------------------------------------------------------------- /locale/nl.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/nl.json"); -------------------------------------------------------------------------------- /locale/no.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/no.json"); -------------------------------------------------------------------------------- /locale/pl.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/pl.json"); -------------------------------------------------------------------------------- /locale/pt.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/pt.json"); -------------------------------------------------------------------------------- /locale/ru.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/ru.json"); -------------------------------------------------------------------------------- /locale/sv.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/sv.json"); -------------------------------------------------------------------------------- /locale/th.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/th.json"); -------------------------------------------------------------------------------- /locale/tlh.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/tlh.json"); -------------------------------------------------------------------------------- /locale/tr.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/tr.json"); -------------------------------------------------------------------------------- /locale/zh.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require("naughty-words/zh.json"); -------------------------------------------------------------------------------- /non-secure/index.js: -------------------------------------------------------------------------------- 1 | var nsnanoid = require('nanoid/non-secure'); 2 | var nanoid = require('nanoid'); 3 | var wrapper = require('../wrapper'); 4 | 5 | module.exports = { 6 | nanoid: wrapper(nsnanoid.nanoid), 7 | customAlphabet: (...locales) => (alphabet, size) => wrapper(nsnanoid.customAlphabet(alphabet, size))(...locales), 8 | customRandom: (...locales) => (alphabet, size, randomFunc) => wrapper(nanoid.customRandom(alphabet, size, randomFunc))(...locales), 9 | urlAlphabet: nanoid.urlAlphabet, 10 | random: nanoid.random 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nanoid-good", 3 | "version": "3.1.0", 4 | "description": "Obscene words filter for nanoid", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "watch": "jest --watch", 9 | "start": "node ./index.js" 10 | }, 11 | "keywords": [ 12 | "profanity", 13 | "nanoid" 14 | ], 15 | "author": "y-gagar1n", 16 | "license": "ISC", 17 | "dependencies": { 18 | "nanoid": "^3.1.2", 19 | "naughty-words": "^1.2.0" 20 | }, 21 | "devDependencies": { 22 | "jest": "^23.4.1", 23 | "size-limit": "^0.18.5" 24 | }, 25 | "size-limit": [ 26 | { 27 | "path": "index.js" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /test/async.test.js: -------------------------------------------------------------------------------- 1 | var en = require('../locale/en'); 2 | var nanoidGood = require('../async/').nanoid(en); 3 | var customAlphabet = require('../async/').customAlphabet(en); 4 | 5 | it('default doesnt throw', function () { 6 | return nanoidGood().then(function(id) { 7 | expect(typeof id).toEqual('string'); 8 | }); 9 | }); 10 | 11 | it('default doesnt throw when length specified', function () { 12 | return nanoidGood(10).then(function(id) { 13 | expect(typeof id).toEqual('string'); 14 | }); 15 | }); 16 | 17 | it('customAlphabet doesnt throw', function () { 18 | return customAlphabet("1234567abcdef", 10)().then(function(id) { 19 | expect(typeof id).toEqual('string'); 20 | }); 21 | }); -------------------------------------------------------------------------------- /test/hasProfanity.test.js: -------------------------------------------------------------------------------- 1 | var hasProfanity = require('../hasProfanity')(require('../locale/en')); 2 | 3 | it('returns false on innocent id', function () { 4 | expect(hasProfanity("qwerty")).toEqual(false); 5 | }); 6 | 7 | it('returns true on bad word', function () { 8 | expect(hasProfanity("aSs")).toEqual(true); 9 | }); 10 | 11 | it('returns true on bad word with numbers', function () { 12 | expect(hasProfanity("aS5")).toEqual(true); 13 | expect(hasProfanity("p0rn")).toEqual(true); 14 | expect(hasProfanity("sh1t")).toEqual(true); 15 | expect(hasProfanity("3rotic")).toEqual(true); 16 | expect(hasProfanity("camgir1")).toEqual(true); 17 | expect(hasProfanity("8astard")).toEqual(true); 18 | }); 19 | 20 | it('returns true on bad word in between of larger word', function () { 21 | expect(hasProfanity("abcaSsqwe")).toEqual(true); 22 | expect(hasProfanity("abcaS5qwe")).toEqual(true); 23 | }); 24 | 25 | it('returns true on bad word in non-english', function () { 26 | var hasProfanityDe = require('../hasProfanity')(require('../locale/de')); 27 | expect(hasProfanityDe("arScH")).toEqual(true); 28 | expect(hasProfanity("arScH")).toEqual(false); 29 | }); 30 | 31 | it('can accept several locales', function () { 32 | var hasProfanityEnDe = require('../hasProfanity')(require('../locale/en'), require('../locale/de')); 33 | expect(hasProfanityEnDe("arScH")).toEqual(true); 34 | expect(hasProfanityEnDe("ass")).toEqual(true); 35 | }); -------------------------------------------------------------------------------- /test/non-secure.test.js: -------------------------------------------------------------------------------- 1 | var en = require('../locale/en'); 2 | var nanoidGood = require('../non-secure').nanoid(en) 3 | var customAlphabet = require('../non-secure').customAlphabet(en) 4 | var customRandom = require('../non-secure').customRandom(en) 5 | var urlAlphabet = require('../non-secure').urlAlphabet; 6 | 7 | it('default doesnt throw', function () { 8 | var id = nanoidGood(); 9 | expect(typeof id).toEqual('string') 10 | }); 11 | 12 | it('default doesnt throw when length specified', function () { 13 | var id = nanoidGood(10); 14 | expect(typeof id).toEqual('string') 15 | }); 16 | 17 | it('customAlphabet doesnt throw', function () { 18 | var generator = customAlphabet("1234567abcdef", 10) 19 | var id = generator(); 20 | expect(typeof id).toEqual('string') 21 | }); 22 | 23 | it('urlAlphabet passes through', function () { 24 | var generator = customAlphabet(urlAlphabet, 10) 25 | var id = generator(); 26 | expect(typeof id).toEqual('string') 27 | }); 28 | 29 | it('customRandom doesnt throw', function () { 30 | 31 | var random = function(size) { 32 | var result = [] 33 | for (var i = 0; i < size; i++) result.push(Math.random() * 255); 34 | return result 35 | } 36 | 37 | var id = customRandom("abcdef", 5, random)(); 38 | expect(typeof id).toEqual('string') 39 | }); 40 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var en = require('../locale/en'); 2 | var de = require('../locale/de'); 3 | var nanoidGood = require('../').nanoid(en) 4 | var customAlphabet = require('../').customAlphabet(en) 5 | var customRandom = require('../').customRandom(en) 6 | var urlAlphabet = require('../').urlAlphabet; 7 | 8 | it('default doesnt throw', function () { 9 | var id = nanoidGood(); 10 | expect(typeof id).toEqual('string') 11 | }); 12 | 13 | it('default doesnt throw when length specified', function () { 14 | var id = nanoidGood(10); 15 | expect(typeof id).toEqual('string') 16 | }); 17 | 18 | it('customAlphabet doesnt throw', function () { 19 | var generator = customAlphabet("1234567abcdef", 10) 20 | var id = generator(); 21 | expect(typeof id).toEqual('string') 22 | }); 23 | 24 | it('customAlphabet doesnt throw with multiple languages', function () { 25 | var generator = customAlphabet("1234567abcdef", 10) 26 | var id = generator(); 27 | expect(typeof id).toEqual('string') 28 | }); 29 | 30 | it('urlAlphabet passes through', function () { 31 | var customAlphabetWithLanguages = require('../').customAlphabet(en, de) 32 | var generator = customAlphabetWithLanguages(urlAlphabet, 10) 33 | var id = generator(); 34 | expect(typeof id).toEqual('string') 35 | }); 36 | 37 | it('customRandom doesnt throw', function () { 38 | 39 | var random = function(size) { 40 | var result = [] 41 | for (var i = 0; i < size; i++) result.push(Math.random() * 255); 42 | return result 43 | } 44 | 45 | var id = customRandom("abcdef", 5, random)(); 46 | expect(typeof id).toEqual('string') 47 | }); 48 | -------------------------------------------------------------------------------- /wrapper.js: -------------------------------------------------------------------------------- 1 | var hasProfanity = require("./hasProfanity"); 2 | 3 | var wrapper = function (fn) { 4 | return function (locales) { 5 | return function () { 6 | while (true) { 7 | var id = fn.apply(this, arguments); 8 | if (!hasProfanity(locales)(id)) return id; 9 | } 10 | } 11 | }; 12 | }; 13 | 14 | module.exports = wrapper; --------------------------------------------------------------------------------