Add password generator proper and small word list

This commit is contained in:
Quantum 2018-11-26 22:24:31 -05:00
parent ab4b249aa6
commit 7c2bcc3f9b
10 changed files with 2229 additions and 29 deletions

34
package-lock.json generated
View file

@ -523,7 +523,8 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
}, },
"base": { "base": {
"version": "0.11.2", "version": "0.11.2",
@ -681,6 +682,7 @@
"version": "1.1.11", "version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@ -1142,7 +1144,8 @@
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
}, },
"concat-stream": { "concat-stream": {
"version": "1.6.2", "version": "1.6.2",
@ -1227,6 +1230,11 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true "dev": true
}, },
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
},
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -2272,6 +2280,11 @@
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
} }
}, },
"form-serializer": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/form-serializer/-/form-serializer-2.5.0.tgz",
"integrity": "sha1-yuovrLwbzuf2VdkTZwcTPz+staM="
},
"forwarded": { "forwarded": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -2318,7 +2331,8 @@
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
}, },
"fsevents": { "fsevents": {
"version": "1.2.4", "version": "1.2.4",
@ -2929,6 +2943,7 @@
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
"dev": true,
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
"inflight": "^1.0.4", "inflight": "^1.0.4",
@ -3444,6 +3459,7 @@
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": { "requires": {
"once": "^1.3.0", "once": "^1.3.0",
"wrappy": "1" "wrappy": "1"
@ -3452,7 +3468,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
}, },
"internal-ip": { "internal-ip": {
"version": "3.0.1", "version": "3.0.1",
@ -4178,6 +4195,7 @@
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@ -4665,6 +4683,7 @@
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@ -4853,7 +4872,8 @@
"path-is-absolute": { "path-is-absolute": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
}, },
"path-is-inside": { "path-is-inside": {
"version": "1.0.2", "version": "1.0.2",
@ -5583,6 +5603,7 @@
"version": "2.6.2", "version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dev": true,
"requires": { "requires": {
"glob": "^7.0.5" "glob": "^7.0.5"
} }
@ -7323,7 +7344,8 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}, },
"xregexp": { "xregexp": {
"version": "4.0.0", "version": "4.0.0",

View file

@ -28,6 +28,8 @@
"dependencies": { "dependencies": {
"bootstrap": "^4.1.3", "bootstrap": "^4.1.3",
"clipboard": "^2.0.4", "clipboard": "^2.0.4",
"core-js": "^2.5.7",
"form-serializer": "^2.5.0",
"jquery": "^3.3.1", "jquery": "^3.3.1",
"octicons": "^8.1.3", "octicons": "^8.1.3",
"popper.js": "^1.14.5" "popper.js": "^1.14.5"

View file

@ -1,3 +1,4 @@
import './app.scss' import './app.scss'
import './clipboard' import './clipboard'
import './navbar' import './navbar'
import './ui'

View file

@ -31,4 +31,8 @@ body {
#password-bits { #password-bits {
height: 1.5rem; height: 1.5rem;
.bg-warning {
color: $body-color;
}
} }

62
src/generator.js Normal file
View file

@ -0,0 +1,62 @@
import words from './words'
const digits = '0123456789'
const symbols = '`~!@#$%^&*()_+-=,./<>?;:|'
export function getWordList (name) {
if (['small'].includes(name)) {
return words[name]
}
throw new Error(`Invalid word list: ${name}`)
}
export function getWords (list, indices) {
return Array.from(indices).map(index => list[index % list.length])
}
export function capitalize (string) {
return string[0].toUpperCase() + string.slice(1)
}
function pickWords (list, number) {
const array = new Uint16Array(number)
window.crypto.getRandomValues(array)
return getWords(list, array)
}
function pickChar (options) {
const array = new Uint32Array(1)
window.crypto.getRandomValues(array)
return options[array[0] % options.length]
}
export function generate (options) {
let words = pickWords(getWordList(options.list), options.count)
if (options.capitalize) {
words = words.map(capitalize)
}
if (options.symbol) {
words.push(pickChar(symbols))
}
if (options.digit) {
words.push(pickChar(digits))
}
return words.join('')
}
export function lengthBits (list) {
return Math.log2(list.length)
}
export function computeBits (options) {
const wordBits = lengthBits(getWordList(options.list))
const capsBits = options.capitalize ? 1 : 0
const symbolBits = options.symbol ? lengthBits(symbols) : 0
const digitBits = options.digit ? lengthBits(digits) : 0
return wordBits * options.count + capsBits + symbolBits + digitBits
}

View file

@ -32,9 +32,8 @@
<div class="jumbotron py-4"> <div class="jumbotron py-4">
<p class="lead">This is a truly secure password generator that generates easy-to-remember passwords.</p> <p class="lead">This is a truly secure password generator that generates easy-to-remember passwords.</p>
<hr class="my-4"> <hr class="my-4">
<form>
<div class="input-group mb-3"> <div class="input-group mb-3">
<input id="generated-password" class="form-control" type="text" <input id="generated-password" class="form-control form-control-lg text-monospace" type="text"
placeholder="Click the Generate button to generate a new password"> placeholder="Click the Generate button to generate a new password">
<div class="input-group-append"> <div class="input-group-append">
<button class="btn btn-success copy" type="button" id="copy-password" <button class="btn btn-success copy" type="button" id="copy-password"
@ -50,13 +49,14 @@
</div> </div>
</div> </div>
<form id="options-form">
<div class="form-group row"> <div class="form-group row">
<label for="word-list" class="col-sm-2 col-form-label">Word List</label> <label for="word-list" class="col-sm-2 col-form-label">Word List</label>
<div class="col-sm-10"> <div class="col-sm-10">
<select id="word-list" class="form-control custom-select"> <select id="word-list" class="form-control custom-select" name="list">
<option value="small">2048 words (11 bits/word)</option> <option value="small">2048 words (11 bits/word)</option>
<option value="medium">4096 words (12 bits/word)</option> <option value="medium" disabled>4096 words (12 bits/word)</option>
<option value="large">8192 words (13 bits/word)</option> <option value="large" disabled>8192 words (13 bits/word)</option>
</select> </select>
</div> </div>
</div> </div>
@ -64,7 +64,7 @@
<div class="form-group row"> <div class="form-group row">
<label for="word-count" class="col-sm-2 col-form-label">Word Count</label> <label for="word-count" class="col-sm-2 col-form-label">Word Count</label>
<div class="col-sm-10"> <div class="col-sm-10">
<input id="word-count" class="form-control" type="number" value="4"> <input id="word-count" class="form-control" type="number" name="count" value="5" min="1">
</div> </div>
</div> </div>
@ -72,15 +72,15 @@
<div class="pt-0 col-sm-2 col-form-label">Options</div> <div class="pt-0 col-sm-2 col-form-label">Options</div>
<div class="col-sm-10"> <div class="col-sm-10">
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="use-capitalize"> <input class="form-check-input" type="checkbox" id="use-capitalize" name="capitalize">
<label for="use-capitalize">Capitalize the first letter</label> <label for="use-capitalize">Capitalize the first letter</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="use-symbol"> <input class="form-check-input" type="checkbox" id="use-symbol" name="symbol">
<label for="use-symbol">Add a symbol</label> <label for="use-symbol">Add a symbol</label>
</div> </div>
<div class="form-check"> <div class="form-check">
<input class="form-check-input" type="checkbox" id="use-digit"> <input class="form-check-input" type="checkbox" id="use-digit" name="digit">
<label for="use-digit">Add a digit</label> <label for="use-digit">Add a digit</label>
</div> </div>
</div> </div>

50
src/ui.js Normal file
View file

@ -0,0 +1,50 @@
import $ from 'jquery/dist/jquery'
import 'form-serializer/jquery.serialize-object'
import { generate, computeBits } from './generator'
$(() => {
const $options = $('#options-form')
const $output = $('#generated-password')
const $bits = $('#password-bits').find('div')
$('#run-generator').click(() => {
const options = $options.serializeObject()
$output.val(generate(options))
return false
})
const classes = 'bg-danger bg-warning bg-info bg-success'
function bitClass (bits) {
if (bits < 44) {
return 'bg-danger'
} else if (bits < 64) {
return 'bg-warning'
} else if (bits < 80) {
return 'bg-info'
} else {
return 'bg-success'
}
}
function bitRound (value) {
const rounded = Math.round(value)
return rounded === value ? value : `${rounded}`
}
function updateBitMeter () {
const options = $options.serializeObject()
const bits = computeBits(options)
const maxBits = 96
$bits
.removeClass(classes)
.addClass(bitClass(bits))
.text(`${bitRound(bits)} bits`)
.css('width', `${bits / maxBits * 100}%`)
}
$options.find('select, input').change(updateBitMeter)
$options.find('input[type=nubmer]').on('input', updateBitMeter)
updateBitMeter()
})

2050
src/words/2048.js Normal file

File diff suppressed because it is too large Load diff

5
src/words/index.js Normal file
View file

@ -0,0 +1,5 @@
import small from './2048'
console.assert(small.length === 2048)
export default { small }

View file

@ -2,7 +2,11 @@ const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = { module.exports = {
entry: './src/app.js', entry: [
'core-js/fn/array/from',
'core-js/fn/array/includes',
'./src/app.js'
],
mode: process.env.NODE_ENV || 'development', mode: process.env.NODE_ENV || 'development',
output: { output: {
filename: '[name].[contenthash].js', filename: '[name].[contenthash].js',