--- # quotesdb-wmvy title: Implement 4-word passphrase auth_code generator (must work in WASM/workers-rs) status: completed type: task priority: high created_at: 2026-03-10T23:32:04Z updated_at: 2026-03-10T23:32:12Z blocked_by: - quotesdb-ghzc - quotesdb-58hh --- The `quotesdb` API is built with Axum + Tokio, targeting Cloudflare Workers via `workers-rs`. It serves JSON at `/api/*` endpoints and persists data to Cloudflare D1 (production) or a local SQLite file via Turso (development). Source lives in `src/bin/api/`. Shared types and utilities are in `src/lib.rs` — code placed there must compile for both the host target and `wasm32-unknown-unknown`. Auth codes are 4-word passphrases (e.g. `ocean-table-purple-storm`) assigned to quotes on creation. They are stored plaintext and used to authorise updates and deletes. **TRIAGE 6ed325 resolved:** Use a custom embedded word list (EFF Short Word List 1) with `rand::rngs::OsRng` from `rand 0.10`. OsRng does not use thread-local storage and is safe on wasm32. Entropy on WASM comes from `getrandom 0.4` with the `wasm_js` feature, which calls `crypto.getRandomValues()` — available in both browsers and Cloudflare Workers. Implement a `generate_auth_code() -> String` function in `src/lib.rs` that produces a random 4-word passphrase. Place it in shared lib code so both the API (generation) and UI (display) can reference it. ## 1. Cargo.toml changes (covered by ticket 1f5bb5, listed here for reference) ```toml [dependencies] rand = "0.10" [target.'cfg(target_arch = "wasm32")'.dependencies] # Enables OsRng entropy via Web Crypto API (crypto.getRandomValues()) # Required by both rand (OsRng) and uuid (v4) on wasm32 targets getrandom = { version = "0.4", features = ["wasm_js"] } ``` ## 2. Embed the EFF Short Word List 1 in src/lib.rs The EFF Short Word List 1 contains 1296 common English words designed for memorable passphrases. Source: https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt Generate the Rust const array (run from shell, paste output into src/lib.rs): ```sh curl -s 'https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt' \ | awk '{print $2}' \ | awk 'NR % 8 == 1 {printf " "} {printf "\"%s\", ", $0} NR % 8 == 0 {print ""}' \ | sed '$a\' | tr -d '\n' | sed 's/, $//' ``` Place the word list as a module-level constant: ```rust /// EFF Short Word List 1 — 1296 common English words designed for memorable passphrases. /// Source: const WORDS: &[&str] = &[ "acid", "acorn", "acre", "acts", "afar", "affix", "aged", "agent", "agile", "aging", "agony", "ahead", "aide", "aids", "aim", "ajar", // ... (full 1296-word list, generated via shell command above) ]; ``` ## 3. Implement generate_auth_code() in src/lib.rs ```rust use rand::rngs::OsRng; use rand::seq::SliceRandom; /// Generates a random 4-word passphrase in the format `word-word-word-word`. /// /// Words are drawn from the EFF Short Word List 1 (1296 common English words). /// The passphrase is used as an `auth_code` to authorize quote edits and deletes. /// /// Uses `rand::rngs::OsRng` for entropy, which is safe on both native and /// `wasm32-unknown-unknown` targets. On WASM (Cloudflare Workers), entropy /// is sourced via `crypto.getRandomValues()` through `getrandom`'s `wasm_js` feature. /// /// # Examples /// /// ``` /// let code = quotesdb::generate_auth_code(); /// let words: Vec<&str> = code.split('-').collect(); /// assert_eq!(words.len(), 4); /// assert!(words.iter().all(|w| !w.is_empty())); /// ``` pub fn generate_auth_code() -> String { WORDS .choose_multiple(&mut OsRng, 4) .copied() .collect::>() .join("-") } ``` ## 4. Unit tests (src/lib.rs tests module or src/tests.rs) ```rust #[cfg(test)] mod tests { use super::*; use std::collections::HashSet; /// Verify format: exactly 4 non-empty words from WORDS, joined by hyphens. #[test] fn auth_code_has_four_valid_words() { for _ in 0..100 { let code = generate_auth_code(); let parts: Vec<&str> = code.split('-').collect(); assert_eq!(parts.len(), 4, "expected 4 words, got: {code}"); for word in &parts { assert!(!word.is_empty(), "empty word in code: {code}"); assert!( WORDS.contains(word), "word '{word}' not in word list, code: {code}" ); } } } /// Verify randomness: 20 samples should produce at least 10 distinct codes. #[test] fn auth_codes_are_varied() { let codes: HashSet = (0..20).map(|_| generate_auth_code()).collect(); assert!( codes.len() > 10, "expected >10 unique codes in 20 samples, got {}", codes.len() ); } } ``` - `generate_auth_code()` must live in `src/lib.rs` (shared code, not bin-specific) - Use `rand::rngs::OsRng` — do NOT use `rand::thread_rng()` (thread-local, unsafe on WASM) - Do not use `std::fs`, thread-based RNG, or any crate that requires file-system access - All public items must have rustdoc comments with doc-examples (per project style) - `getrandom = { version = "0.4", features = ["wasm_js"] }` must be in the wasm32 cfg section only, not in `[dependencies]`, to avoid pulling wasm-bindgen into native builds Use `superpowers:test-driven-development` — write the unit tests (step 4) before implementing (step 3). Use `superpowers:verification-before-completion` before closing. Run in order from the `quotesdb/` directory: ```sh cargo fmt cargo check cargo clippy cargo test ``` `feat(quotesdb): implement WASM-compatible 4-word passphrase auth_code generator`