Skip to main content

geronimo_captcha/
lib.rs

1#![forbid(unsafe_code)]
2
3//! A JavaScript-free CAPTCHA. A speed bump for bulk
4//! automation, layered over server-side rate limiting.
5//! Not a wall.
6//!
7//! A challenge is one to five rounds. Each round
8//! renders its own source image at nine orientations
9//! in a 3x3 grid, exactly one of them upright. The
10//! challenge id is an HMAC-SHA256 tag binding the
11//! length-prefixed correct tile numbers to a nonce
12//! and a timestamp. Verification recomputes the tag
13//! over the client's guesses and compares it in
14//! constant time. No answer is stored server-side.
15//!
16//! Replay and brute-force limiting live entirely
17//! in [`ChallengeRegistry`]. Without one, nothing
18//! limits guesses; a single round falls in nine.
19//!
20//! The [`SampleSet`] is the security boundary.
21//! Supply your own corpus and rotate it.
22//!
23//! ```no_run
24//! use geronimo_captcha::{
25//!     CaptchaManager, ChallengeInMemoryRegistry, GenerationOptions,
26//!     NoiseOptions, SampleFormat, SampleSet, SpriteUri,
27//! };
28//! use std::sync::Arc;
29//!
30//! fn main() -> Result<(), Box<dyn std::error::Error>> {
31//!     let ttl_secs = 60;
32//!     let registry = Arc::new(ChallengeInMemoryRegistry::new(ttl_secs, 3, 10_000)?);
33//!
34//!     let images = std::fs::read_dir("/srv/captcha-corpus")?
35//!         .filter_map(|e| std::fs::read(e.ok()?.path()).ok())
36//!         .collect();
37//!     let samples = SampleSet::new(SampleFormat::Jpeg, images)?;
38//!
39//!     let mgr = CaptchaManager::new(
40//!         "an-example-secret-key-of-32-bytes".to_string(),
41//!         ttl_secs,
42//!         NoiseOptions::default(),
43//!         Some(registry),
44//!         GenerationOptions::default(),
45//!         &samples,
46//!     )?;
47//!
48//!     let challenge = mgr.generate_challenge::<SpriteUri>()?;
49//!
50//!     // One sprite per round, rendered into the form
51//!     let img_srcs: Vec<String> = challenge.sprites.into_iter().map(|s| s.0).collect();
52//!     let challenge_id = challenge.challenge_id;
53//!
54//!     // One index per round, in issue order
55//!     let verified = mgr.verify_challenge(&challenge_id, &[5])?;
56//!
57//!     Ok(())
58//! }
59//! ```
60
61mod challenge;
62mod error;
63mod image;
64mod manager;
65mod registry;
66mod samples;
67mod sprite;
68mod utils;
69
70pub use challenge::{CaptchaChallenge, DecodeLimits, GenerationOptions};
71pub use error::{CaptchaError, RegistryRejection, Result};
72pub use image::{NoiseOptions, NoisePattern};
73pub use manager::CaptchaManager;
74pub use registry::{ChallengeInMemoryRegistry, ChallengeRegistry};
75pub use samples::{SampleFormat, SampleSet};
76pub use sprite::{SpriteBinary, SpriteFormat, SpriteTarget, SpriteUri};
77
78#[cfg(doctest)]
79#[doc = include_str!("../README.md")]
80struct Readme;