mirror of
https://github.com/quantum5/correcthorsebatterystaple.git
synced 2025-04-24 10:11:57 -04:00
Add password generator proper and small word list
This commit is contained in:
parent
ab4b249aa6
commit
7c2bcc3f9b
34
package-lock.json
generated
34
package-lock.json
generated
|
@ -523,7 +523,8 @@
|
|||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"base": {
|
||||
"version": "0.11.2",
|
||||
|
@ -681,6 +682,7 @@
|
|||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
|
@ -1142,7 +1144,8 @@
|
|||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"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": {
|
||||
"version": "1.6.2",
|
||||
|
@ -1227,6 +1230,11 @@
|
|||
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
|
||||
|
@ -2272,6 +2280,11 @@
|
|||
"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": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
|
||||
|
@ -2318,7 +2331,8 @@
|
|||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "1.2.4",
|
||||
|
@ -2929,6 +2943,7 @@
|
|||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
|
@ -3444,6 +3459,7 @@
|
|||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
|
@ -3452,7 +3468,8 @@
|
|||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"internal-ip": {
|
||||
"version": "3.0.1",
|
||||
|
@ -4178,6 +4195,7 @@
|
|||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
@ -4665,6 +4683,7 @@
|
|||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
|
@ -4853,7 +4872,8 @@
|
|||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"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": {
|
||||
"version": "1.0.2",
|
||||
|
@ -5583,6 +5603,7 @@
|
|||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
|
||||
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.0.5"
|
||||
}
|
||||
|
@ -7323,7 +7344,8 @@
|
|||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "4.0.0",
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
"dependencies": {
|
||||
"bootstrap": "^4.1.3",
|
||||
"clipboard": "^2.0.4",
|
||||
"core-js": "^2.5.7",
|
||||
"form-serializer": "^2.5.0",
|
||||
"jquery": "^3.3.1",
|
||||
"octicons": "^8.1.3",
|
||||
"popper.js": "^1.14.5"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import './app.scss'
|
||||
import './clipboard'
|
||||
import './navbar'
|
||||
import './ui'
|
||||
|
|
|
@ -31,4 +31,8 @@ body {
|
|||
|
||||
#password-bits {
|
||||
height: 1.5rem;
|
||||
|
||||
.bg-warning {
|
||||
color: $body-color;
|
||||
}
|
||||
}
|
62
src/generator.js
Normal file
62
src/generator.js
Normal 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
|
||||
}
|
|
@ -32,31 +32,31 @@
|
|||
<div class="jumbotron py-4">
|
||||
<p class="lead">This is a truly secure password generator that generates easy-to-remember passwords.</p>
|
||||
<hr class="my-4">
|
||||
<form>
|
||||
<div class="input-group mb-3">
|
||||
<input id="generated-password" class="form-control" type="text"
|
||||
placeholder="Click the Generate button to generate a new password">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-success copy" type="button" id="copy-password"
|
||||
data-clipboard-target="#generated-password">
|
||||
${require('octicons').clippy.toSVG({ height: 20 })}
|
||||
</button>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<input id="generated-password" class="form-control form-control-lg text-monospace" type="text"
|
||||
placeholder="Click the Generate button to generate a new password">
|
||||
<div class="input-group-append">
|
||||
<button class="btn btn-success copy" type="button" id="copy-password"
|
||||
data-clipboard-target="#generated-password">
|
||||
${require('octicons').clippy.toSVG({ height: 20 })}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="progress my-3" id="password-bits">
|
||||
<div class="progress-bar" role="progressbar" style="width: 44%"
|
||||
aria-valuenow="44" aria-valuemin="0" aria-valuemax="100">44 bits
|
||||
</div>
|
||||
<div class="progress my-3" id="password-bits">
|
||||
<div class="progress-bar" role="progressbar" style="width: 44%"
|
||||
aria-valuenow="44" aria-valuemin="0" aria-valuemax="100">44 bits
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="options-form">
|
||||
<div class="form-group row">
|
||||
<label for="word-list" class="col-sm-2 col-form-label">Word List</label>
|
||||
<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="medium">4096 words (12 bits/word)</option>
|
||||
<option value="large">8192 words (13 bits/word)</option>
|
||||
<option value="medium" disabled>4096 words (12 bits/word)</option>
|
||||
<option value="large" disabled>8192 words (13 bits/word)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -64,7 +64,7 @@
|
|||
<div class="form-group row">
|
||||
<label for="word-count" class="col-sm-2 col-form-label">Word Count</label>
|
||||
<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>
|
||||
|
||||
|
@ -72,15 +72,15 @@
|
|||
<div class="pt-0 col-sm-2 col-form-label">Options</div>
|
||||
<div class="col-sm-10">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
50
src/ui.js
Normal file
50
src/ui.js
Normal 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
2050
src/words/2048.js
Normal file
File diff suppressed because it is too large
Load diff
5
src/words/index.js
Normal file
5
src/words/index.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
import small from './2048'
|
||||
|
||||
console.assert(small.length === 2048)
|
||||
|
||||
export default { small }
|
|
@ -2,7 +2,11 @@ const path = require('path')
|
|||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
|
||||
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',
|
||||
output: {
|
||||
filename: '[name].[contenthash].js',
|
||||
|
|
Loading…
Reference in a new issue