Skip to main content

Crate latchkey

Crate latchkey 

Source
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

ModuleWhat it does
passwordArgon2id hashing and verification, with a dummy hash for unknown accounts
totpTOTP secrets, otpauth:// URLs, QR codes (feature qr), replay-safe verification
recoveryRecovery codes: plain text to show once, hashes to store
passkeyWebAuthn ceremonies through webauthn-rs (feature passkeys)
tokenSession, link, device and CSRF tokens: the token for the client, the hash to store
sealAES-256-GCM for secrets at rest, bound to their owner
http__Host- cookies, Origin and CSRF checks, client IP and user agent
accountAccounts, emails, factors and the lockout rule
limitsRate limits and the wrong-password throttle as state machines
sessionSession records, stages and access rules (step-up window)
flowSetup, invite, sign-in, email link and step-up as advance(state, event, now)
mailMail subjects and bodies
auditAudit 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

FeatureDefaultAdds
os-rngyesos_rng, a generator seeded from the operating system
serdenoSerialize/Deserialize on ids, hashes, records and states
passkeysnothe passkey module and passkey sealing (webauthn-rs, OpenSSL); implies serde
qrnototp::qr_svg
qr-pngnototp::qr_png (pulls in image)

§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.

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 &str values from whichever server library the app uses.
limits
Rate limits and the wrong-password throttle as small state machines the app owns.
mail
Mail content as values. The flows emit Effect::SendMail with a Mail; the app sends it however it likes, with Mail::subject and Mail::text or its own templates built from the fields.
passkeypasskeys
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§

CeremonyId
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.
ConfigBuilder
Builds a Config; every setting has the default latchkey 0.2 used.
PasskeyId
A stored passkey, used to bind its sealed data to its row.
SessionId
A session, for listing and revoking sessions; not a secret.
UserId
An account. Passkeys carry it as their WebAuthn user handle.

Enums§

Error
Why latchkey refused a request or could not compute a value.

Traits§

CryptoRng
A marker trait for securely unpredictable infallible RNGs

Functions§

os_rngos-rng
A ChaCha12 generator seeded once from the operating system: a convenient CryptoRng to pass to latchkey. Keep one per thread or behind a lock, or create one per request.