5.7 KiB
+++ title = "Implement 4-word passphrase auth_code generator (must work in WASM/workers-rs)" priority = 7 status = "done" ticket_type = "task" dependencies = ["1f5bb5", "6ed325"] +++
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.
1. Cargo.toml changes (covered by ticket 1f5bb5, listed here for reference)
[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):
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:
/// EFF Short Word List 1 — 1296 common English words designed for memorable passphrases.
/// Source: <https://www.eff.org/files/2016/09/08/eff_short_wordlist_1.txt>
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
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::<Vec<_>>()
.join("-")
}
4. Unit tests (src/lib.rs tests module or src/tests.rs)
#[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<String> = (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:
cargo fmt
cargo check
cargo clippy
cargo test
`feat(quotesdb): implement WASM-compatible 4-word passphrase auth_code generator`