1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Sign-in building blocks and sign-in policy, without I/O.
//!
//! latchkey hashes passwords, checks TOTP and recovery codes, runs passkey ceremonies, issues
//! tokens, seals secrets, builds cookies, and decides what a sign-in flow does next. It
//! never reads a database, sends mail, reads the clock or draws hidden randomness: every
//! function takes its inputs (including `now` and an explicit [`CryptoRng`]) and returns
//! values. The app stores what it is told to store, sends what it is told to send, and
//! answers its clients.
//!
//! # Modules
//!
//! | Module | What it does |
//! |---|---|
//! | [`password`] | Argon2id hashing and verification, with a dummy hash for unknown accounts |
//! | [`totp`] | TOTP secrets, `otpauth://` URLs, QR codes (feature `qr`), replay-safe verification |
//! | [`recovery`] | Recovery codes: plain text to show once, hashes to store |
//! | `passkey` | `WebAuthn` ceremonies through `webauthn-rs` (feature `passkeys`) |
//! | [`token`] | Session, link, device and CSRF tokens: the token for the client, the hash to store |
//! | [`seal`] | AES-256-GCM for secrets at rest, bound to their owner |
//! | [`http`] | `__Host-` cookies, Origin and CSRF checks, client IP and user agent |
//! | [`account`] | Accounts, emails, factors and the lockout rule |
//! | [`limits`] | Rate limits and the wrong-password throttle as state machines |
//! | [`session`] | Session records, stages and access rules (step-up window) |
//! | [`flow`] | Setup, invite, sign-in, email link and step-up as `advance(state, event, now)` |
//! | [`mail`] | Mail subjects and bodies |
//! | [`audit`] | Audit events |
//!
//! # A sign-in step
//!
//! ```
//! use latchkey::flow::sign_in::{SignIn, SignInEvent};
//! use latchkey::limits::FailureWindow;
//! use latchkey::Config;
//! use time::OffsetDateTime;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let config = Config::builder("owner@example.com", "https://home.example.com").build()?;
//! let mut rng = latchkey::os_rng()?;
//! let now = OffsetDateTime::from_unix_timestamp(1_800_000_000)?;
//!
//! // Someone types an unknown address: latchkey still asks for a (dummy) password check.
//! let step = SignIn::Start.advance(
//! &config,
//! SignInEvent::Password {
//! login: "nobody@example.com".into(),
//! account: None,
//! wrong_passwords: FailureWindow::default(),
//! },
//! now,
//! &mut rng,
//! )?;
//! assert!(matches!(step.state, SignIn::VerifyPassword { account: None, .. }));
//! # Ok(())
//! # }
//! ```
//!
//! # Features
//!
//! | Feature | Default | Adds |
//! |---|---|---|
//! | `os-rng` | yes | [`os_rng`], a generator seeded from the operating system |
//! | `serde` | no | `Serialize`/`Deserialize` on ids, hashes, records and states |
//! | `passkeys` | no | the `passkey` module and passkey sealing (webauthn-rs, OpenSSL); implies `serde` |
//! | `qr` | no | `totp::qr_svg` |
//! | `qr-png` | no | `totp::qr_png` (pulls in `image`) |
//!
//! # Public dependencies
//!
//! Types from these crates appear in the API, so their breaking releases are latchkey's:
//! [`time`] 0.3 (`OffsetDateTime`, `Duration`), `rand_core` 0.10 ([`CryptoRng`], re-exported
//! here), [`uuid`] 1 (the id newtypes convert from and to `Uuid`) and, with `passkeys`,
//! `webauthn-rs` 0.5 (re-exported as `passkey::webauthn_rs`). Other dependencies stay
//! private: URLs are `&str` and [`os_rng`] returns `impl CryptoRng`.
//!
//! # Security
//!
//! - Store only hashes of tokens and recovery codes ([`token::TokenHash`],
//! [`recovery::RecoveryCodeHash`]); seal TOTP secrets and passkeys with a [`seal::DataKey`]
//! kept apart from the [`http::IpTagKey`] and out of the database.
//! - Carry out every [`flow::Effect`], including those of a [`flow::Refusal`]: they count
//! failures, lock accounts, mail new-device notices and write the audit log.
//! - Persist the TOTP step and recovery-code use with a compare-and-set, consume one-time
//! links and passkey ceremonies atomically, and refuse when the compare-and-set loses.
//! - Run the throttle and lockout checks (the first step of each password flow) before
//! hashing, and hash on a blocking thread with limited concurrency.
//! - Never pass a client-chosen user id to [`flow::issue_session`].
//! - Secret types redact `Debug`, have no `Display`, and zero their memory on drop.
//!
//! The crate forbids `unsafe` and denies `unwrap`, `expect`, `panic!` and indexing outside
//! tests.
pub use ;
pub use ;
pub use Error;
pub use StepUpFailures;
pub use ;
pub use CryptoRng;
/// Compiles and runs the README's examples as doctests.
;
/// A `ChaCha12` generator seeded once from the operating system: a convenient
/// [`CryptoRng`] to pass to latchkey. Keep one per thread or behind a lock (store it as
/// `Box<dyn CryptoRng + Send>` if you need to name its type), or create one per request.
///
/// # Errors
///
/// [`Error::Rng`] when the operating system provides no randomness.
pub
pub
/// Entry points for the cargo-fuzz target in `fuzz/`; not a public API.