Wheelhouse docs

Enrol a second factor#

You will end up with a time-based one-time password required on your account at every sign-in. Read the warning below before you enrol anyone but yourself.

Before you start#

  • Your own account. Enrolment is on your own account only; nobody can enrol or disable another person's.
  • An authenticator app on a device you will not lose.
  • A licence is not needed — enrolment is on your own account, and it works on an unlicensed router.

Step 1 — Begin enrolment#

Account → Two-factor authentication.

bash
R=https://<router>:8443
curl -sk -X POST -b cookies.txt -H "X-Wheelhouse-CSRF: $CSRF" "$R/api/auth/totp/begin"

The agent returns a fresh secret and its enrolment URI. Scan it, or type the secret in by hand.

Nothing is enabled yet. The secret is held as pending and never gates a login. That is deliberate: a mis-scanned QR code cannot lock anyone out.

Step 2 — Prove you can generate a code#

bash
curl -sk -X POST -b cookies.txt -H "X-Wheelhouse-CSRF: $CSRF" \
  -H 'Content-Type: application/json' -d '{"code":"123456"}' "$R/api/auth/totp/confirm"

Confirming promotes the pending secret into the real one and switches two-factor on. Until a live code proves the app is set up correctly, it stays pending.

Step 3 — Check the router's clock#

A time-based code depends on time. If the router's clock is wrong, every code is wrong.

bash
show ntp

Time synchronisation, and Diagnostics → Time.

Making it mandatory#

Administration → Agent settings has require_totp, which makes a second factor mandatory for every account.

Before you turn it on:

  1. Enrol yourself and confirm it works.
  2. Make sure at least one other admin is enrolled, or you have a break-glass token — nothing creates one by default.
  3. Tell everybody, because the next sign-in will demand it.

Check it worked#

Sign out and back in. The code should be asked for, and the login should be refused without it.

Then check the audit log has your enrolment.

What protects the login besides this#

Three rate limiters, each stopping a different attack:

  • Per source address: eight failures in five minutes.
  • Per account name: 20 failures in 30 minutes — so guessing one password costs attempts rather than addresses, which matters against anyone holding an IPv6 /64.
  • Site-wide: past 200 failures in five minutes every login attempt is slowed by 250 ms. It never refuses, because a real operator has to be able to sign in during an attack.

Passwords are hashed with Argon2id — 64 MiB and three passes over a 16-byte random salt per account.

Undoing it#

Account → Two-factor authentication → Disable, on your own account:

bash
curl -sk -X POST -b cookies.txt -H "X-Wheelhouse-CSRF: $CSRF" "$R/api/auth/totp/disable"

If require_totp is on, turn that off first or the next sign-in demands a factor you no longer have.

See also#


Checked against agent/auth.go · agent/authhttp.go · ui/src/pages/Account.tsx · docs/security.md

Updated 2026-09-02 totp two-factor security