Expand description
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;
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, .. }));§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) |
§Security
- Store only hashes of tokens and recovery codes (
token::TokenHash,recovery::RecoveryCodeHash); seal TOTP secrets and passkeys with aseal::DataKeykept apart from thehttp::IpTagKeyand out of the database. - Carry out every
flow::Effect, including those of aflow::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 noDisplay, and zero their memory on drop.
The crate forbids unsafe and denies unwrap, expect, panic! and indexing outside
tests.
Re-exports§
pub use account::Account;pub use account::Email;pub use account::Factors;pub use account::Lockout;pub use account::LockoutPolicy;pub use account::Role;pub use flow::step_up::StepUpFailures;
Modules§
- account
- What the flows need to know about an account, and the lockout rule.
- audit
- Audit events as values, for the app’s append-only log.
- flow
- The sign-in flows as state machines over plain data.
- http
- HTTP helpers over plain header values:
__Host-cookies, the Origin and CSRF checks, and the client’s IP address and user agent. No HTTP crate is involved; pass&strvalues from whichever server library the app uses. - limits
- Rate limits and the wrong-password throttle as small state machines the app owns.
- Mail content as values. The flows emit
Effect::SendMailwith aMail; the app sends it however it likes, withMail::subjectandMail::textor its own templates built from the fields. - passkey
passkeys - Passkeys through
webauthn_rs, with user verification required. - password
- Argon2id password hashing, with a dummy hash for accounts that do not exist.
- recovery
- Single-use recovery codes such as
abcd-efgh-jkmn. - seal
- AES-256-GCM sealing for secrets the app stores, such as TOTP secrets and passkey data.
- session
- Sessions as plain records, and the rules for what a session may do.
- token
- Random bearer tokens: session cookies, one-time links, device cookies and CSRF tokens.
- totp
- TOTP (RFC 6238): SHA-1, 6 digits, 30-second steps, one step of skew either way.
Structs§
- Ceremony
Id - A pending passkey ceremony, for the app to store its state under.
- Config
- Validated settings: the origin and relying party, cookie names, mailed link targets,
lifetimes, the lockout and step-up failure policies and the rate-limit and password
parameters. Build it with
Config::builder. - Config
Builder - Builds a
Config; every setting has the default latchkey 0.2 used. - Passkey
Id - A stored passkey, used to bind its sealed data to its row.
- Session
Id - A session, for listing and revoking sessions; not a secret.
- UserId
- An account. Passkeys carry it as their
WebAuthnuser handle.
Enums§
- Error
- Why latchkey refused a request or could not compute a value.
Traits§
- Crypto
Rng - A marker trait for securely unpredictable infallible RNGs