Skip to content

FLT-181 — Google sign-in on the board, superseding WING-224's edge-only auth

Date: 2026-07-30 Status: Implemented on all four instances. Supersedes: the WING-224 decision recorded in WorkWingman/ops/vikunja/archive/Set-VikunjaOidc.SUPERSEDED.ps1, which rejected configuring Vikunja's own OIDC in favour of authenticating only at the Cloudflare edge.

What was actually wrong

Cloudflare Access authenticates who may reach board.workwingman.ai, with Google as IdP and an eight-email allowlist. It does not authenticate anyone to Vikunja. Vikunja therefore still presented its own login form, and with registration_enabled = true on the greatroom primary the only way past it was to create a second, Vikunja-local account. That is the reported symptom: people were being forced to sign up.

Why edge-only could not close it

WING-224's alternative was anonymous read-only link shares on the mirrored projects. A share token lives in one instance's database. There are four instances — the greatroom primary plus standby replicas on gaming, streaming and bedroom — and each replica is rehydrated independently from Jira, so a token minted on one does not exist in another. The public hostname is served by whichever connector answers, so the share URL 404s unpredictably. Project ids differ per replica too (FLT/WING are 7/8 on the primary, 2/3 on the replicas), so nothing id-shaped survives failover.

OIDC is identical configuration on every replica with no per-instance state, so it does survive.

What WING-224 got right, and what it got wrong

Right: Vikunja's login page and write API become reachable, so registration must be off everywhere. It now is, on all four.

Wrong: "viewers would get real Vikunja identities and write rights". A fresh Vikunja user owns nothing and sees only an empty Inbox — project access is granted separately. The real risk runs the other way: without an explicit grant, an authenticated viewer sees an empty board.

How access is granted

Grant-BoardViewerRead.ps1, scheduled every 15 minutes on each host, grants read-only (users_projects.permission = 0) on the mirrored FLT/WING projects to the emails listed in ~/.fleet-secrets/board-viewers.txt.

  • It must be scheduled, not one-shot: a user row exists only after that person's first Google sign-in, and only on the replica that answered them.
  • It resolves projects by title, never by id, because ids differ per replica.
  • Read-only is load-bearing. Jira is the source of truth and the sync purges and rebuilds mirrored tasks, so a viewer's edit would be silently erased within the sync interval. Write access would be an edit box whose contents quietly vanish.
  • The viewer list is seeded from the Access allowlist but kept as its own file, and the script fails closed without it. Access decides who reaches the host; this decides who reads the board. Keeping them separate means a loosened Access policy cannot silently widen board access.

Known sharp edge

Rehydrate-Vikunja.ps1 -FromDump does DROP DATABASE + restore, which replaces user rows. A pre-FLT-181 dump restores the admin account with issuer = local and re-breaks its Google sign-in. -FromJira, the path the scheduled sync uses, touches tasks only and is unaffected.