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:
@@ -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.
|
||||
Reference in New Issue
Block a user