reze: Fix unicode splitting, strict booleans, symbol validation and entropy
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.
This commit is contained in:
@@ -19,6 +19,8 @@ account, no login, no database, and nothing you generate is stored or logged.
|
||||
- One-click copy with a clipboard fallback.
|
||||
- Light/dark theme with a system-theme fallback, persisted in `localStorage`.
|
||||
- Options persisted in `localStorage`; generated passwords are never persisted.
|
||||
- Unicode-safe: character pools are code points, so emoji are never split.
|
||||
- Strict option validation — bad booleans and symbol sets are rejected, not guessed.
|
||||
- No npm dependencies at all — Node.js built-ins only.
|
||||
|
||||
## Requirements
|
||||
@@ -100,12 +102,21 @@ curl -s -X POST localhost:3000/api/generate \
|
||||
```
|
||||
|
||||
```json
|
||||
{"mode":"memorable","count":2,"passwords":[{"value":"07Sample-29Breach","mode":"memorable","entropy":39.1},{"value":"69Reassign-85Probation","mode":"memorable","entropy":39.1}],"entropy":39.1}
|
||||
{"mode":"memorable","count":2,"passwords":[{"value":"07sample-29breach","mode":"memorable","entropy":39.1,"entropyApproximate":false},{"value":"69reassign-85probation","mode":"memorable","entropy":39.1,"entropyApproximate":false}],"entropy":39.1,"entropyApproximate":false,"entropyNote":"exact: every character is drawn uniformly from the enabled sets"}
|
||||
```
|
||||
|
||||
Random options: `length` (8–64), `useLower`, `useUpper`, `useDigits`, `useSymbols`,
|
||||
`customSymbols` (replaces the default symbol set when non-empty), `excludeAmbiguous`
|
||||
(removes `0 O 1 l I |`) and `requireEach`.
|
||||
Random options: `length` (8–64, in code points), `useLower`, `useUpper`, `useDigits`,
|
||||
`useSymbols`, `customSymbols` (replaces the default symbol set when non-empty),
|
||||
`excludeAmbiguous` (removes `0 O 1 l I |`) and `requireEach`.
|
||||
|
||||
The boolean options are parsed strictly: `true`/`false`, `1`/`0`, `"true"`/`"false"`,
|
||||
`"1"`/`"0"`, `"yes"`/`"no"` and `"on"`/`"off"` (case-insensitive) are accepted, anything
|
||||
else is rejected with a `400` that names the option. `"no"` really means *off*, it is not
|
||||
treated as a truthy string.
|
||||
|
||||
`customSymbols` is validated: at most 64 code points, duplicates are dropped, and
|
||||
control characters or whitespace are rejected with a `400`. Characters are handled as
|
||||
code points, so emoji and other astral characters are never split in half.
|
||||
|
||||
```sh
|
||||
curl -s -X POST localhost:3000/api/generate \
|
||||
@@ -114,15 +125,23 @@ curl -s -X POST localhost:3000/api/generate \
|
||||
```
|
||||
|
||||
```json
|
||||
{"mode":"random","count":1,"passwords":[{"value":"3V#P@bQ2C4c3;=igw}E(EpnG","mode":"random","entropy":154.6}],"entropy":154.6}
|
||||
{"mode":"random","count":1,"passwords":[{"value":"3V#P@bQ2C4c3;=igw}E(EpnG","mode":"random","entropy":154.6,"entropyApproximate":true}],"entropy":154.6,"entropyApproximate":true,"entropyNote":"upper bound: the generator samples the valid passwords non-uniformly"}
|
||||
```
|
||||
|
||||
### Entropy
|
||||
|
||||
- Random mode: `length * log2(poolSize)`, where `poolSize` is the size of the union of all
|
||||
enabled character classes.
|
||||
- Random mode without `requireEach`: `length * log2(poolSize)`, where `poolSize` is the
|
||||
number of code points in the union of all enabled character classes.
|
||||
- Random mode with `requireEach`: the number of passwords that contain at least one
|
||||
character of every enabled class (inclusion-exclusion over the classes), converted to
|
||||
bits. Because the generator samples those passwords non-uniformly, the value is an
|
||||
**upper bound**; the response says so via `entropyApproximate` and `entropyNote`, and
|
||||
the UI marks it with a `~` and "approx.".
|
||||
- Memorable mode: `words * log2(listSize) + words * digits * log2(10)`. Separators and the
|
||||
capitalization scheme add no entropy.
|
||||
capitalization scheme add no entropy. This value is exact.
|
||||
|
||||
Every response carries `entropy`, `entropyApproximate` and `entropyNote`, and each
|
||||
password carries its own `entropy` and `entropyApproximate`.
|
||||
|
||||
### Other paths
|
||||
|
||||
@@ -169,6 +188,14 @@ docker compose up --build -d
|
||||
The image is a single stage based on `node:22-alpine`, runs as the non-root `node` user
|
||||
and ships a `HEALTHCHECK` that polls `http://127.0.0.1:3000/healthz` with `wget`.
|
||||
|
||||
`docker-compose.yml` uses `expose` instead of `ports`, so the service is only reachable
|
||||
on the container network — publishing it on the host would collide with Dokploy's own
|
||||
port 3000.
|
||||
|
||||
`SIGTERM` and `SIGINT` shut the server down gracefully: it stops accepting connections,
|
||||
lets the in-flight requests finish and exits, with a 5 second forced-exit fallback. That
|
||||
keeps `docker stop` fast instead of waiting for the full 10 second timeout.
|
||||
|
||||
## Deployment (Dokploy)
|
||||
|
||||
- Repository: `druid520/reze-test` on Gitea (git.kg.boson.party).
|
||||
|
||||
Reference in New Issue
Block a user