howdy/auth/mfa

Optional second-factor configuration. Keep the encryption key outside the auth database; all nodes must use the same stable, 32-byte base64url key. Rotate it with with_decryption_keys and auth.reseal_mfa.

Types

pub opaque type Config

Values

pub const default_trust_seconds: Int

Default remembered-device lifetime: 30 days.

pub fn new(
  issuer: String,
  encryption_key: String,
) -> Result(Config, service.Error)
pub fn with_decryption_keys(
  config: Config,
  keys: List(String),
) -> Result(Config, service.Error)

Keys that still decrypt authenticator secrets but never encrypt new ones, replacing any given before. To rotate without failing a sign-in:

  1. On every node, add the new key here, keeping the old key in new.
  2. On every node, swap them: the new key in new, the old key here.
  3. Run auth.reseal_mfa once, then drop the old key.

The first step lets a node still sealing with the old key read what an updated node seals; skip it only if all nodes restart together.

pub fn with_delivery(
  config: Config,
  deliver: fn(user.User, secret.Secret) -> Result(Nil, Nil),
) -> Config

Deliver to a separately verified channel owned by this user. The application chooses the address/phone; never take the destination from the login request. An email-token login cannot use delivered OTP as its second factor.

pub fn with_device_trust(
  config: Config,
  seconds seconds: Int,
  renew renew: Bool,
) -> Result(Config, service.Error)

How long a remembered device skips the second factor: 30 days by default, at least five minutes and at most a year. With renew, each successful use restarts the lifetime, so only a device left unused this long is forgotten; without it, trust ends this long after the second factor was last verified. Devices already remembered keep the expiry they were issued with.

pub fn with_recovery_codes(
  config: Config,
  count: Int,
) -> Result(Config, service.Error)

Recovery codes issued per enrollment or regeneration: 10 by default, 4 to 32.

✨ Search Document