reze: Add Node.js password generator with memorable and random modes

Public password generator with no auth and no npm dependencies.

- Server-side generation using crypto.randomInt; two levels: diceware-style
  memorable passphrases (42abacus-91abdomen) and fully random characters.
- Configurable word list (EFF Large / EFF Short), digits per word, separators,
  capitalization, length, character class toggles, custom symbols and
  ambiguous-character exclusion.
- Light/dark theme matching druid520/site, entropy estimate and strength bar,
  clipboard copy, options persisted in localStorage.
- Dockerfile for Dokploy plus a Woodpecker deploy hook on trunk.
This commit is contained in:
2026-10-21 09:01:42 +00:00
parent aa56937a20
commit 58f585b691
19 changed files with 10974 additions and 0 deletions
+185
View File
@@ -0,0 +1,185 @@
# reze-test
A small, dependency-free password generator. It has two modes:
- **Memorable** — diceware-style passphrases shaped like `[num][word]-[num][word]-…`, e.g. `42abacus-91abdomen`.
- **Random** — fully random characters from a configurable character set.
Everything is generated server-side with Node's CSPRNG (`crypto.randomInt`). There is no
account, no login, no database, and nothing you generate is stored or logged.
## Features
- Two randomization levels: memorable passphrases and fully random passwords.
- EFF word lists (7776-word and 1296-word) selectable per request.
- Configurable digits per word, digit separator, group separator and capitalization.
- Configurable length, character classes, custom symbol set, ambiguous-character exclusion
and "at least one of each selected set".
- Generate up to 10 passwords at once, each with an entropy estimate and a strength bar.
- 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.
- No npm dependencies at all — Node.js built-ins only.
## Requirements
- Node.js 22 or newer (ESM, `"type": "module"`).
## Run locally
```sh
node server.js
```
The server listens on `0.0.0.0:3000` by default. Override the port with `PORT`:
```sh
PORT=8080 node server.js
```
Open <http://localhost:3000>.
Or via the POSIX sh helper, which defaults `PORT` to 3000:
```sh
sh scripts/run.sh
PORT=8080 sh scripts/run.sh
```
## Tests
```sh
node --test
```
## API
All API responses are JSON. Every response carries the security headers
`X-Content-Type-Options: nosniff`, `Referrer-Policy: no-referrer`,
`X-Frame-Options: DENY` and a strict `Content-Security-Policy`.
### `GET /healthz`
Liveness probe used by the Docker `HEALTHCHECK`.
```sh
curl -s localhost:3000/healthz
```
```json
{"status":"ok"}
```
### `GET /api/meta`
Describes the available word lists.
```sh
curl -s localhost:3000/api/meta
```
```json
{"lists":[{"id":"eff-large","name":"EFF Large Word List","size":7776},{"id":"eff-short","name":"EFF Short Word List (2.0)","size":1296}]}
```
### `POST /api/generate`
Body is a JSON object. `mode` selects the algorithm; `count` (1–10, default 1) is the
number of passwords to return. Unknown modes, malformed JSON and out-of-range options
are rejected with `400` and a JSON `{"error": "…"}` body. Request bodies are limited to
64 KB.
Memorable options: `words` (2–10), `digits` (0–4), `digitSeparator` (`''` or `'-'`),
`separator` (`'-'`, `'_'`, `'.'`, `' '` or `''`), `capitalization`
(`'none'`, `'title'`, `'upper'`), `list` (`'eff-large'`, `'eff-short'`).
```sh
curl -s -X POST localhost:3000/api/generate \
-H 'Content-Type: application/json' \
-d '{"mode":"memorable","words":2,"digits":2,"separator":"-","digitSeparator":"","capitalization":"title","list":"eff-large","count":2}'
```
```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}
```
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`.
```sh
curl -s -X POST localhost:3000/api/generate \
-H 'Content-Type: application/json' \
-d '{"mode":"random","length":24,"useLower":true,"useUpper":true,"useDigits":true,"useSymbols":true,"requireEach":true,"count":1}'
```
```json
{"mode":"random","count":1,"passwords":[{"value":"3V#P@bQ2C4c3;=igw}E(EpnG","mode":"random","entropy":154.6}],"entropy":154.6}
```
### Entropy
- Random mode: `length * log2(poolSize)`, where `poolSize` is the size of the union of all
enabled character classes.
- Memorable mode: `words * log2(listSize) + words * digits * log2(10)`. Separators and the
capitalization scheme add no entropy.
### Other paths
Unknown `/api/*` paths return a JSON `404`. Unknown page paths return
`public/404.html` with HTTP `404`. Only `GET` and `HEAD` are accepted for static files.
## Word lists
| id | list | size |
| --- | --- | --- |
| `eff-large` | EFF Large Word List | 7776 |
| `eff-short` | EFF Short Word List (2.0) | 1296 |
Both files live in `public/words/` and are loaded once at startup.
- Source: [Electronic Frontier Foundation](https://www.eff.org/dice) — the
[EFF Diceware wordlists](https://www.eff.org/dice-roll-the-dice).
- Licence: Creative Commons Attribution 3.0 Unported (CC BY 3.0),
<https://www.eff.org/copyright>.
The lists contain a few hyphenated entries (`drop-down`, `felt-tip`, `t-shirt`, `yo-yo`),
which are used verbatim.
## Environment variables
| variable | default | description |
| --- | --- | --- |
| `PORT` | `3000` | TCP port the HTTP server binds to on `0.0.0.0`. |
| `NODE_ENV` | — | Set to `production` in the image. |
## Docker
```sh
docker build -t reze-test .
docker run --rm -p 3000:3000 reze-test
```
Or with compose:
```sh
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`.
## Deployment (Dokploy)
- Repository: `druid520/reze-test` on Gitea (git.kg.boson.party).
- Branch: `trunk`.
- Build type: Dockerfile (or "Nixpacks" — the app needs nothing but Node).
- Environment variable: `PORT=3000`.
- Exposed port: `3000`.
- CI hook: add the repository secret `DEPLOY_URL` (the Dokploy deploy webhook URL).
`.woodpecker.yml` posts to it on every push to `trunk` so Dokploy rebuilds and
redeploys the service.
## License
MIT for the code. The bundled word lists are CC BY 3.0, © Electronic Frontier Foundation.