├── .eslintignore ├── .eslintrc.yml ├── .gitignore ├── .travis.yml ├── HISTORY.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── .eslintrc.yml └── test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: standard 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store* 3 | *.log 4 | *.gz 5 | 6 | node_modules 7 | coverage 8 | package-lock.json 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.12" 6 | - "1.8" 7 | - "2.5" 8 | - "3.3" 9 | - "4.9" 10 | - "5.12" 11 | - "6.17" 12 | - "7.10" 13 | - "8.16" 14 | - "9.11" 15 | - "10.15" 16 | - "11.15" 17 | - "12.5" 18 | sudo: false 19 | cache: 20 | directories: 21 | - node_modules 22 | before_install: 23 | - | 24 | # Setup utility functions 25 | function node_version_lt () { 26 | [[ "$(v "$TRAVIS_NODE_VERSION")" -lt "$(v "${1}")" ]] 27 | } 28 | function npm_module_installed () { 29 | npm -lsp ls | grep -Fq "$(pwd)/node_modules/${1}:${1}@" 30 | } 31 | function npm_remove_module_re () { 32 | node -e ' 33 | fs = require("fs"); 34 | p = JSON.parse(fs.readFileSync("package.json", "utf8")); 35 | r = RegExp(process.argv[1]); 36 | for (k in p.devDependencies) { 37 | if (r.test(k)) delete p.devDependencies[k]; 38 | } 39 | fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); 40 | ' "$@" 41 | } 42 | function npm_use_module () { 43 | node -e ' 44 | fs = require("fs"); 45 | p = JSON.parse(fs.readFileSync("package.json", "utf8")); 46 | p.devDependencies[process.argv[1]] = process.argv[2]; 47 | fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); 48 | ' "$@" 49 | } 50 | function v () { 51 | tr '.' '\n' <<< "${1}" \ 52 | | awk '{ printf "%03d", $0 }' \ 53 | | sed 's/^0*//' 54 | } 55 | # Configure npm 56 | - | 57 | # Skip updating shrinkwrap / lock 58 | npm config set shrinkwrap false 59 | # Setup Node.js version-specific dependencies 60 | - | 61 | # Configure eslint for linting 62 | if node_version_lt '8.0'; then npm_remove_module_re '^eslint(-|$)' 63 | fi 64 | - | 65 | # Configure istanbul for coverage 66 | if node_version_lt '0.10'; then npm_remove_module_re '^istanbul$' 67 | fi 68 | - | 69 | # Configure mocha for testing 70 | if node_version_lt '0.10'; then npm_use_module 'mocha' '2.5.3' 71 | elif node_version_lt '4.0' ; then npm_use_module 'mocha' '3.5.3' 72 | elif node_version_lt '6.0' ; then npm_use_module 'mocha' '5.2.0' 73 | fi 74 | # Update Node.js modules 75 | - | 76 | # Prune & rebuild node_modules 77 | if [[ -d node_modules ]]; then 78 | npm prune 79 | npm rebuild 80 | fi 81 | before_scrpt: 82 | - | 83 | # Contents of node_modules 84 | npm -s ls ||: 85 | script: 86 | - | 87 | # Run test script, depending on istanbul install 88 | if npm_module_installed 'istanbul'; then npm run-script test-travis 89 | else npm test 90 | fi 91 | - | 92 | # Run linting, if eslint exists 93 | if npm_module_installed 'eslint'; then npm run-script lint 94 | fi 95 | after_script: 96 | - | 97 | # Upload coverage to coveralls if exists 98 | if [[ -e ./coverage/lcov.info ]]; then 99 | npm install --save-dev coveralls@2 100 | coveralls < ./coverage/lcov.info 101 | fi 102 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 2.1.5 / 2017-08-02 2 | ================== 3 | 4 | * perf: remove only trailing `=` 5 | 6 | 2.1.4 / 2017-03-02 7 | ================== 8 | 9 | * Remove `base64-url` dependency 10 | 11 | 2.1.3 / 2016-10-30 12 | ================== 13 | 14 | * deps: base64-url@1.3.3 15 | 16 | 2.1.2 / 2016-08-15 17 | ================== 18 | 19 | * deps: base64-url@1.3.2 20 | 21 | 2.1.1 / 2016-05-04 22 | ================== 23 | 24 | * deps: base64-url@1.2.2 25 | 26 | 2.1.0 / 2016-01-17 27 | ================== 28 | 29 | * Use `random-bytes` for byte source 30 | 31 | 2.0.0 / 2015-05-08 32 | ================== 33 | 34 | * Use global `Promise` when returning a promise 35 | 36 | 1.1.0 / 2015-02-01 37 | ================== 38 | 39 | * Use `crypto.randomBytes`, if available 40 | * deps: base64-url@1.2.1 41 | 42 | 1.0.3 / 2015-01-31 43 | ================== 44 | 45 | * Fix error branch that would throw 46 | * deps: base64-url@1.2.0 47 | 48 | 1.0.2 / 2015-01-08 49 | ================== 50 | 51 | * Remove dependency on `mz` 52 | 53 | 1.0.1 / 2014-06-18 54 | ================== 55 | 56 | * Remove direct `bluebird` dependency 57 | 58 | 1.0.0 / 2014-06-18 59 | ================== 60 | 61 | * Initial release 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jonathan Ong 4 | Copyright (c) 2015-2017 Douglas Christopher Wilson 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # uid-safe 2 | 3 | [![NPM Version][npm-image]][npm-url] 4 | [![NPM Downloads][downloads-image]][downloads-url] 5 | [![Node.js Version][node-version-image]][node-version-url] 6 | [![Build Status][travis-image]][travis-url] 7 | [![Test Coverage][coveralls-image]][coveralls-url] 8 | 9 | URL and cookie safe UIDs 10 | 11 | Create cryptographically secure UIDs safe for both cookie and URL usage. 12 | This is in contrast to modules such as [uid2](https://www.npmjs.com/package/uid2) 13 | whose UIDs are actually skewed due to the use of `%` and unnecessarily 14 | truncate the UID. Use this if you could still use UIDs with `-` and `_` in 15 | them. 16 | 17 | ## Installation 18 | 19 | ```sh 20 | $ npm install uid-safe 21 | ``` 22 | 23 | ## API 24 | 25 | ```js 26 | var uid = require('uid-safe') 27 | ``` 28 | 29 | ### uid(byteLength, callback) 30 | 31 | Asynchronously create a UID with a specific byte length. Because `base64` 32 | encoding is used underneath, this is not the string length. For example, 33 | to create a UID of length 24, you want a byte length of 18. 34 | 35 | ```js 36 | uid(18, function (err, string) { 37 | if (err) throw err 38 | // do something with the string 39 | }) 40 | ``` 41 | 42 | ### uid(byteLength) 43 | 44 | Asynchronously create a UID with a specific byte length and return a 45 | `Promise`. 46 | 47 | **Note**: To use promises in Node.js _prior to 0.12_, promises must be 48 | "polyfilled" using `global.Promise = require('bluebird')`. 49 | 50 | ```js 51 | uid(18).then(function (string) { 52 | // do something with the string 53 | }) 54 | ``` 55 | 56 | ### uid.sync(byteLength) 57 | 58 | A synchronous version of above. 59 | 60 | ```js 61 | var string = uid.sync(18) 62 | ``` 63 | 64 | ## License 65 | 66 | [MIT](LICENSE) 67 | 68 | [npm-image]: https://img.shields.io/npm/v/uid-safe.svg 69 | [npm-url]: https://npmjs.org/package/uid-safe 70 | [node-version-image]: https://img.shields.io/node/v/uid-safe.svg 71 | [node-version-url]: https://nodejs.org/en/download/ 72 | [travis-image]: https://img.shields.io/travis/crypto-utils/uid-safe/master.svg 73 | [travis-url]: https://travis-ci.org/crypto-utils/uid-safe 74 | [coveralls-image]: https://img.shields.io/coveralls/crypto-utils/uid-safe/master.svg 75 | [coveralls-url]: https://coveralls.io/r/crypto-utils/uid-safe?branch=master 76 | [downloads-image]: https://img.shields.io/npm/dm/uid-safe.svg 77 | [downloads-url]: https://npmjs.org/package/uid-safe 78 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * uid-safe 3 | * Copyright(c) 2014 Jonathan Ong 4 | * Copyright(c) 2015-2017 Douglas Christopher Wilson 5 | * MIT Licensed 6 | */ 7 | 8 | 'use strict' 9 | 10 | /** 11 | * Module dependencies. 12 | * @private 13 | */ 14 | 15 | var randomBytes = require('random-bytes') 16 | 17 | /** 18 | * Module variables. 19 | * @private 20 | */ 21 | 22 | var EQUAL_END_REGEXP = /=+$/ 23 | var PLUS_GLOBAL_REGEXP = /\+/g 24 | var SLASH_GLOBAL_REGEXP = /\//g 25 | 26 | /** 27 | * Module exports. 28 | * @public 29 | */ 30 | 31 | module.exports = uid 32 | module.exports.sync = uidSync 33 | 34 | /** 35 | * Create a unique ID. 36 | * 37 | * @param {number} length 38 | * @param {function} [callback] 39 | * @return {Promise} 40 | * @public 41 | */ 42 | 43 | function uid (length, callback) { 44 | // validate callback is a function, if provided 45 | if (callback !== undefined && typeof callback !== 'function') { 46 | throw new TypeError('argument callback must be a function') 47 | } 48 | 49 | // require the callback without promises 50 | if (!callback && !global.Promise) { 51 | throw new TypeError('argument callback is required') 52 | } 53 | 54 | if (callback) { 55 | // classic callback style 56 | return generateUid(length, callback) 57 | } 58 | 59 | return new Promise(function executor (resolve, reject) { 60 | generateUid(length, function onUid (err, str) { 61 | if (err) return reject(err) 62 | resolve(str) 63 | }) 64 | }) 65 | } 66 | 67 | /** 68 | * Create a unique ID sync. 69 | * 70 | * @param {number} length 71 | * @return {string} 72 | * @public 73 | */ 74 | 75 | function uidSync (length) { 76 | return toString(randomBytes.sync(length)) 77 | } 78 | 79 | /** 80 | * Generate a unique ID string. 81 | * 82 | * @param {number} length 83 | * @param {function} callback 84 | * @private 85 | */ 86 | 87 | function generateUid (length, callback) { 88 | randomBytes(length, function (err, buf) { 89 | if (err) return callback(err) 90 | callback(null, toString(buf)) 91 | }) 92 | } 93 | 94 | /** 95 | * Change a Buffer into a string. 96 | * 97 | * @param {Buffer} buf 98 | * @return {string} 99 | * @private 100 | */ 101 | 102 | function toString (buf) { 103 | return buf.toString('base64') 104 | .replace(EQUAL_END_REGEXP, '') 105 | .replace(PLUS_GLOBAL_REGEXP, '-') 106 | .replace(SLASH_GLOBAL_REGEXP, '_') 107 | } 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uid-safe", 3 | "description": "URL and cookie safe UIDs", 4 | "version": "2.1.5", 5 | "contributors": [ 6 | "Douglas Christopher Wilson ", 7 | "Jonathan Ong (http://jongleberry.com)" 8 | ], 9 | "license": "MIT", 10 | "repository": "crypto-utils/uid-safe", 11 | "dependencies": { 12 | "random-bytes": "~1.0.0" 13 | }, 14 | "devDependencies": { 15 | "bluebird": "3.5.4", 16 | "eslint": "6.2.1", 17 | "eslint-config-standard": "14.0.0", 18 | "eslint-plugin-import": "2.18.2", 19 | "eslint-plugin-node": "9.1.0", 20 | "eslint-plugin-promise": "4.2.1", 21 | "eslint-plugin-standard": "4.0.1", 22 | "istanbul": "0.4.5", 23 | "mocha": "6.1.4" 24 | }, 25 | "files": [ 26 | "LICENSE", 27 | "HISTORY.md", 28 | "README.md", 29 | "index.js" 30 | ], 31 | "engines": { 32 | "node": ">= 0.8" 33 | }, 34 | "scripts": { 35 | "lint": "eslint .", 36 | "test": "mocha --trace-deprecation --reporter spec --bail --check-leaks test/", 37 | "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --trace-deprecation --reporter dot --check-leaks test/", 38 | "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --trace-deprecation --reporter spec --check-leaks test/" 39 | }, 40 | "keywords": [ 41 | "random", 42 | "generator", 43 | "uid", 44 | "safe" 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var uid = require('..') 3 | 4 | var Promise = global.Promise || require('bluebird') 5 | 6 | // Add Promise to mocha's global list 7 | // eslint-disable-next-line no-self-assign 8 | global.Promise = global.Promise 9 | 10 | describe('uid()', function () { 11 | describe('with global Promise', function () { 12 | before(function () { 13 | global.Promise = Promise 14 | }) 15 | 16 | after(function () { 17 | global.Promise = undefined 18 | }) 19 | 20 | it('should return a uid of the correct length', function () { 21 | return uid(18).then(function (val) { 22 | assert.strictEqual(24, Buffer.byteLength(val)) 23 | }) 24 | }) 25 | 26 | it('should not contain +, /, or =', function () { 27 | return uid(100000).then(function (val) { 28 | assert.strictEqual(val.indexOf('+'), -1) 29 | assert.strictEqual(val.indexOf('/'), -1) 30 | assert.strictEqual(val.indexOf('='), -1) 31 | }) 32 | }) 33 | }) 34 | 35 | describe('without global Promise', function () { 36 | before(function () { 37 | global.Promise = undefined 38 | }) 39 | 40 | after(function () { 41 | global.Promise = Promise 42 | }) 43 | 44 | it('should require callback', function () { 45 | assert.throws(function () { 46 | uid(18) 47 | }, /argument callback.*required/) 48 | }) 49 | 50 | it('should error for bad callback', function () { 51 | assert.throws(function () { 52 | uid(18, 'silly') 53 | }, /argument callback.*function/) 54 | }) 55 | 56 | it('should return a uid of the correct length', function (done) { 57 | uid(18, function (err, val) { 58 | if (err) return done(err) 59 | assert.strictEqual(Buffer.byteLength(val), 24) 60 | done() 61 | }) 62 | }) 63 | 64 | it('should not contain +, /, or =', function (done) { 65 | uid(1000000, function (err, val) { 66 | if (err) return done(err) 67 | assert.strictEqual(val.indexOf('+'), -1) 68 | assert.strictEqual(val.indexOf('/'), -1) 69 | assert.strictEqual(val.indexOf('='), -1) 70 | done() 71 | }) 72 | }) 73 | }) 74 | }) 75 | 76 | describe('uid.sync()', function () { 77 | it('should return a uid of the correct length', function () { 78 | var val = uid.sync(18) 79 | assert.strictEqual(Buffer.byteLength(val), 24) 80 | }) 81 | 82 | it('should not contain +, /, or =', function () { 83 | var val = uid.sync(100000) 84 | assert.strictEqual(val.indexOf('+'), -1) 85 | assert.strictEqual(val.indexOf('/'), -1) 86 | assert.strictEqual(val.indexOf('='), -1) 87 | }) 88 | }) 89 | --------------------------------------------------------------------------------