You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
5.7 KiB
Markdown
163 lines
5.7 KiB
Markdown
+++
|
|
title = "Implement 4-word passphrase auth_code generator (must work in WASM/workers-rs)"
|
|
priority = 7
|
|
status = "done"
|
|
ticket_type = "task"
|
|
dependencies = ["1f5bb5", "6ed325"]
|
|
+++
|
|
|
|
<context>
|
|
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.
|
|
</context>
|
|
|
|
<goal>
|
|
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.
|
|
</goal>
|
|
|
|
<implementation>
|
|
|
|
## 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: <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
|
|
|
|
```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::<Vec<_>>()
|
|
.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<String> = (0..20).map(|_| generate_auth_code()).collect();
|
|
assert!(
|
|
codes.len() > 10,
|
|
"expected >10 unique codes in 20 samples, got {}",
|
|
codes.len()
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
</implementation>
|
|
|
|
<constraints>
|
|
- `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
|
|
</constraints>
|
|
|
|
<skills>
|
|
Use `superpowers:test-driven-development` — write the unit tests (step 4) before implementing (step 3).
|
|
Use `superpowers:verification-before-completion` before closing.
|
|
</skills>
|
|
|
|
<validation>
|
|
Run in order from the `quotesdb/` directory:
|
|
|
|
```sh
|
|
cargo fmt
|
|
cargo check
|
|
cargo clippy
|
|
cargo test
|
|
```
|
|
</validation>
|
|
|
|
<commit>
|
|
`feat(quotesdb): implement WASM-compatible 4-word passphrase auth_code generator`
|
|
</commit>
|