Scaffold Pwned Passwords clone: React/wouter frontend, Koa/pg backend, import CLI
- Backend: Koa + @koa/router serving the HIBP-style /range/:prefix k-anonymity API, backed by Postgres via pg, with migrate/seed scripts run through tsx. - Frontend: React + wouter, bundled with webpack (dev server + prod build), thin client that hashes passwords locally and only sends the hash prefix. - Import package: CLI to bulk-load a wordlist or hash list into the database, plus a separate script to pull the full dataset from the HIBP range API. Both support parallelism (worker threads for wordlist hashing, concurrency limiter for DB/HTTP fan-out). - Dev tooling: docker compose for a local Postgres, .env.local/.env.example, DATABASE_URL support as an alternative to discrete PG* vars.
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import Router from "@koa/router";
|
||||
import { pool } from "../db.ts";
|
||||
|
||||
const HEX_PREFIX = /^[0-9A-Fa-f]{5}$/;
|
||||
|
||||
export const rangeRouter = new Router();
|
||||
|
||||
// Mirrors the HaveIBeenPwned Pwned Passwords range API:
|
||||
// https://haveibeenpwned.com/API/v3#PwnedPasswords
|
||||
rangeRouter.get("/range/:prefix", async (ctx) => {
|
||||
const { prefix } = ctx.params;
|
||||
|
||||
if (!HEX_PREFIX.test(prefix)) {
|
||||
ctx.status = 400;
|
||||
ctx.body = { error: "prefix must be 5 hex characters" };
|
||||
return;
|
||||
}
|
||||
|
||||
const { rows } = await pool.query<{ suffix: string; count: string }>(
|
||||
"SELECT suffix, count FROM pwned_passwords WHERE prefix = $1",
|
||||
[prefix.toUpperCase()],
|
||||
);
|
||||
|
||||
ctx.type = "text/plain";
|
||||
ctx.body = rows.map((row) => `${row.suffix}:${row.count}`).join("\r\n");
|
||||
});
|
||||
Reference in New Issue
Block a user