Six fixes, each with a test: 1. Character pools are built as arrays of code points, so astral characters (emoji) are never split into lone surrogates. Entropy counts code points. 2. Boolean options are parsed strictly: true/false, 1/0, "true"/"false", "1"/"0", "yes"/"no", "on"/"off" (case-insensitive). Anything else is a 400 naming the option instead of a silent truthy fallback. 3. customSymbols is validated: at most 64 code points, duplicates dropped, control characters and whitespace rejected with a 400. 4. Random mode entropy with requireEach now counts only the passwords that contain a character of every enabled class (inclusion-exclusion). The value is an upper bound and is labelled as such in the API (entropyApproximate/entropyNote) and in the UI. 5. docker-compose.yml uses expose instead of ports, so it no longer collides with Dokploy's own port 3000. 6. SIGTERM/SIGINT shut the server down gracefully: close the listener, let in-flight requests finish, force-exit after 5s. Measured at ~60ms. The server now exports createServer() so the API can be tested over HTTP without binding a fixed port.
122 lines
4.3 KiB
HTML
122 lines
4.3 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>reze-test</title>
|
|
<meta name="description" content="Password generator: diceware-style passphrases or fully random characters. Nothing is stored.">
|
|
<link rel="stylesheet" href="/style.css">
|
|
<script src="/theme.js"></script>
|
|
</head>
|
|
<body>
|
|
<main>
|
|
<div class="topbar"><button id="theme-toggle" class="theme-toggle" type="button" hidden>Dark mode</button></div>
|
|
<h1>reze-test</h1>
|
|
<p class="lead">Password generator. Diceware-style passphrases or fully random characters — generated locally on the server with a CSPRNG. Nothing you generate is stored.</p>
|
|
|
|
<h2>Mode</h2>
|
|
<div class="radios">
|
|
<label class="radio"><input type="radio" name="mode" value="memorable" checked> Memorable</label>
|
|
<label class="radio"><input type="radio" name="mode" value="random"> Random</label>
|
|
</div>
|
|
|
|
<section id="panel-memorable">
|
|
<h2>Passphrase</h2>
|
|
|
|
<div class="field">
|
|
<label for="words">Words per passphrase</label>
|
|
<input type="number" id="words" min="2" max="10" step="1" value="4">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="digits">Digits per word</label>
|
|
<input type="number" id="digits" min="0" max="4" step="1" value="2">
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="digitSeparator">Digit separator</label>
|
|
<select id="digitSeparator">
|
|
<option value="" selected>none</option>
|
|
<option value="-">- (hyphen)</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="separator">Group separator</label>
|
|
<select id="separator">
|
|
<option value="-">- (hyphen)</option>
|
|
<option value="_">_ (underscore)</option>
|
|
<option value=".">. (dot)</option>
|
|
<option value=" ">space</option>
|
|
<option value="">none</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="capitalization">Capitalization</label>
|
|
<select id="capitalization">
|
|
<option value="none" selected>none</option>
|
|
<option value="title">Title case</option>
|
|
<option value="upper">UPPERCASE</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="list">Word set</label>
|
|
<select id="list">
|
|
<option value="eff-large">EFF Large Word List</option>
|
|
<option value="eff-short">EFF Short Word List (2.0)</option>
|
|
</select>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="panel-random" hidden>
|
|
<h2>Random</h2>
|
|
|
|
<div class="field">
|
|
<label for="length">Length</label>
|
|
<div class="range-row">
|
|
<input type="range" id="length" min="8" max="64" step="1" value="20">
|
|
<output id="length-out" for="length">20</output>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="checks">
|
|
<label class="check"><input type="checkbox" id="useLower" checked> lowercase</label>
|
|
<label class="check"><input type="checkbox" id="useUpper" checked> uppercase</label>
|
|
<label class="check"><input type="checkbox" id="useDigits" checked> digits</label>
|
|
<label class="check"><input type="checkbox" id="useSymbols"> symbols</label>
|
|
<label class="check"><input type="checkbox" id="excludeAmbiguous"> exclude ambiguous characters</label>
|
|
<label class="check"><input type="checkbox" id="requireEach" checked> require at least one of each selected set</label>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="customSymbols">Custom symbols</label>
|
|
<input type="text" id="customSymbols" value="" placeholder="!@#$%^&*()-_=+[]{};:,.<>?" autocomplete="off" spellcheck="false">
|
|
</div>
|
|
</section>
|
|
|
|
<h2>Output</h2>
|
|
|
|
<p class="hint">Entropy is exact for memorable passphrases. In random mode it is an upper
|
|
bound when “require at least one of each selected set” is on, because the
|
|
generator samples the valid passwords non-uniformly.</p>
|
|
|
|
<div class="field">
|
|
<label for="count">How many passwords</label>
|
|
<input type="number" id="count" min="1" max="10" step="1" value="1">
|
|
</div>
|
|
|
|
<div class="actions">
|
|
<button id="generate" class="button primary" type="button">Generate</button>
|
|
<button id="regenerate" class="button" type="button">Regenerate</button>
|
|
</div>
|
|
|
|
<p id="error" class="error-line" hidden></p>
|
|
|
|
<div id="results"></div>
|
|
</main>
|
|
<script src="/app.js"></script>
|
|
</body>
|
|
</html>
|