Wheelhouse docs

Enrol a second factor#

You will end up with a time-based one-time password asked for at every sign-in to your account, and a saved list of ten codes that get you back in when the phone is gone.

Before you start#

  • Your own account. Enrolling is done on your own account only — nobody can enrol on somebody else's behalf. An admin can clear another account's second factor, which is a different thing and is covered below.
  • Somewhere to keep ten codes that is not this router: a password manager, or paper.
  • An authenticator app on a device you will not lose.
  • No licence is needed: enrolment changes your account, not the router's configuration.

Step 1 — Begin enrolment#

Open Account — your name at the bottom of the navigation — and choose Set up on the Two-factor authentication panel.

The first step, 1 — scan this, is a QR code drawn on the router itself: nothing about your account leaves it, and it works with no route to the internet. Your app lists the entry as Wheelhouse: <your account>. If you cannot scan it, Cannot scan? Type it in instead shows the secret — time-based, six digits, 30-second step — and the whole enrolment URI. Treat both as a password.

Nothing is switched on yet. The new secret is held as pending and never gates a sign-in, so a mis-scanned code cannot lock anyone out.

Step 2 — Prove you can generate a code#

Under 2 — prove it works, type the Six-digit code your app shows and choose Turn on two-factor. A code that is accepted is what switches two-factor on.

If the code is refused, check the clock on your phone first. Start over with a new secret goes back to step 1.

Step 3 — Save the ten recovery codes#

The enrolment screen then shows 3 — keep these: ten codes, and "This is the only time they are shown". Done stays disabled until you tick "I have saved these codes somewhere I can reach without this router."

Each code signs you in once, in place of a code from the app. Case, spaces and the grouping hyphens are all forgiven when you type one back, and the codes leave out i, l, o, 0 and 1, because they get read off a printout.

Afterwards, the Two-factor authentication panel on Account shows how many are left — 7 of 10 left, amber at three or fewer. Issue new codes replaces the whole set. It asks for an Authenticator code, or an unused recovery code first: a printed list is a credential, and an unlocked browser should not be able to mint one. Anything unspent from the old set stops working the moment the new list appears.

Step 4 — Check the router's clock#

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

Diagnostics → Time tab lists the configured time servers and the sources the router is synchronising with. Time synchronisation sets them up.

Making it mandatory#

Admin → Agent settings, panel Sign-in: turn on Require two-factor authentication ("Accounts without it are asked to set it up at their next sign-in.") and choose Save changes.

It does not lock out the people who have not enrolled yet. An account without a second factor still signs in and lands on Set up two-factor sign-in — the QR code, the code box, and no way past it but finishing or Sign out. The requirement is checked on every request, so turning it on catches sessions that were already open and turning it off releases them. Sign-ins through single sign-on are not asked: for those accounts, the second factor is the identity provider's. The agent refuses to save the requirement when no admin account could sign in to enrol, and says why.

Before you turn it on:

  1. Enrol yourself and confirm it works.
  2. Confirm at least one other admin is enrolled, or that you have a break-glass token — nothing creates one by default. With the requirement on, nobody can turn off their own second factor, so the way back from a lost phone runs through somebody else: an admin, or the console. One admin with two-factor and no second admin is the shape to avoid.
  3. Tell everybody where to put their recovery codes, before the enrolment screen asks them to.
  4. Tell everybody it is coming, because their next sign-in will walk them through it.

If the device is lost#

Three answers, cheapest first.

  1. Spend a recovery code. The browser sign-in screen does not take one yet: its Authenticator code box accepts six digits only. The API does — POST /api/auth/login takes a code in a recovery field, and the answer says how many are left — so today a recovery code is an API client's way back. From a browser, go to 2.
  2. Ask an admin to clear it. Admin → Users, the account's row, Clear two-factor authentication. It removes the secret and every remaining recovery code, signs out the account's sessions, and is recorded in the audit log naming both of you. It does not turn the requirement off, so with it on the account enrols again at its next sign-in. An admin cannot clear their own this way.
  3. The console, when every admin is locked out at once: wheelhouse-rescue totp --user NAME from the rescue boot entry or a signed-in console — Locked out of the UI.

Check it worked#

Sign out and back in. After the password, the sign-in screen asks for an Authenticator code ("Six digits from your authenticator app.") and the button reads Verify and sign in. The sign-in is refused without a valid code.

Then look at System → Audit log tab: your enrolment is "Turned on two-factor authentication", followed by "Issued new recovery codes".

What protects the sign-in besides this#

Three rate limits, 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.
  • Across the router: past 200 failures in five minutes every sign-in 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 → Turn off, on your own account. Turning it off discards the recovery codes with the secret: "Its recovery codes no longer work." While Require two-factor authentication is on, the button is disabled — "Required for every account" — and the agent refuses it too: turn the requirement off first, or you would be left demanding a factor nobody has.

For automation#

Enrolment belongs to an account, not a token, so these calls need a signed-in session: its cookie, and its CSRF token in the X-Wheelhouse-CSRF header.

StepRequest
BeginPOST /api/auth/totp/begin — returns the pending secret and its enrolment URI
ProvePOST /api/auth/totp/confirm with {"code":"123456"} — returns the recovery codes
New codesPOST /api/auth/totp/recovery with an authenticator code or an unused recovery code
Turn offPOST /api/auth/totp/disable — refused with 403 while the requirement is on

See also#

Updated 2026-09-18 totp two-factor security