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;