Skip to content

feat: connection longevity + one-click 'Make permanent' 2FA - #3524

Merged
tofikwest merged 7 commits into
mainfrom
feat/browser-connection-make-permanent
Jul 28, 2026
Merged

feat: connection longevity + one-click 'Make permanent' 2FA#3524
tofikwest merged 7 commits into
mainfrom
feat/browser-connection-make-permanent

Conversation

@tofikwest

@tofikwest tofikwest commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What

Implements the Browser Connections design (Claude Designer handoff): make it obvious, per connection, whether a saved vendor sign-in keeps running on its own, and give a one-click way to fix the ones that don't. No schema change.

Connections page (Settings → Browser Connections)

  • Per-row longevity state, read live from the vault:
    • Stays signed in — password + authenticator key (re-signs in unattended)
    • Signed in for now — password, no key (may pause when the vendor next asks for a code) → Make permanent action
    • Signed in — SSO (can't be renewed unattended; occasional re-sign-in)
    • Reconnect needed — paused → Reconnect
    • Each with a plain-language hint.
  • Dismissible banner nudging when connections can be made permanent.
  • 'Make permanent' sheet (form → saving → success): pastes the authenticator setup key with per-vendor guidance + validation. We never change the vendor's 2FA — the user enables it and pastes the one-time setup key; it's stored in 1Password.

Task flow

  • The connection ⋯ menu gains 'Keep signed in (2FA)', reusing the same sheet, so a connection can be made permanent without leaving the task.

Backend

  • New GET /v1/browserbase/profiles/totp-statuses — 2FA status for all password connections in one round-trip (reuses the existing live vault read; no DB flag, so nothing can drift).

Boundary (deliberate)

We do not enable 2FA on the vendor for the user (no MFA auto-enrollment). The flow only helps them store the key we need to generate codes.

Honesty note

'Permanent' holds only when the vendor's 2FA is an authenticator app (TOTP). SMS/email/passkey 2FA can't be automated — SSO and those cases are shown as needing occasional re-sign-in.

Tests

  • API: batch status service (per-connection, degrades an unreadable item, empty when no password connections). 189 browserbase tests green.
  • App: row states + actions (permanent / at-risk / SSO / reconnect / loading / view-only); sheet validation (rejects rotating code + short key) + save/success/failure. 23 settings + 95 task-flow tests green. Typecheck clean; no legacy UI/lucide imports.

Follow-up (deferred, agreed)

The 'Where these connections run' tasks section (needs a tasks↔connections join endpoint).


Summary by cubic

Shows how long each browser connection will keep working and adds a one‑click “Make permanent” flow to store an authenticator setup key so runs can re‑sign in automatically. Validates and normalizes keys on the server and moves TOTP endpoints into a focused controller.

  • New Features

    • Per‑connection longevity with clear hints: Stays signed in / Signed in for now / Signed in (SSO) / Reconnect needed — read live from the vault; never‑verified sessions show as Reconnect. If the 2FA status can’t be read, rows show Status unavailable (with a temporary Checking… placeholder), and a dismissible banner nudges only for confirmed “no key” connections.
    • One‑click “Make permanent” sheet to paste and validate the authenticator setup key; normalizes the Base32 key, saves to 1Password, and shows a success state. We never change vendor 2FA.
    • New GET /v1/browserbase/profiles/totp-statuses returns 2FA status for all password connections in one round‑trip; unreadable items are omitted (treated as unknown), requests use bounded concurrency, and the cache is scoped per org. Statuses refresh after connect/reconnect.
    • Limitation: “Permanent” applies to authenticator app (TOTP) 2FA only; SSO/SMS/email/passkey can’t be automated.
  • Bug Fixes

    • Treat unreadable 2FA status as unknown (not “no key”) to avoid prompting overwrites; rows suppress the upgrade action when status is unavailable.
    • Server‑side TOTP validation and normalization: accepts a Base32 secret or an otpauth:// URI, rejects rotating codes and malformed/short keys, stores a normalized secret, and keeps the parser linear with an input‑length bound (resolves a CodeQL polynomial‑regexp alert).
    • Extract TOTP routes into a BrowserAuthTotpController (no path or contract change) and add controller tests to enforce RBAC and org/profile scoping; Make‑Permanent sheet recovers cleanly if save or refresh fails.

Written for commit 72b3a2c. Summary will update on new commits.

Review in cubic

Make it obvious, per connection, whether a saved vendor sign-in keeps running on
its own — and give a one-click way to fix the ones that don't.

Connections page:
- Each connection now shows a longevity state read live from the vault:
  Stays signed in (password + authenticator), Signed in for now (password, no
  key — may pause when the vendor asks for a code), Signed in (SSO — occasional
  re-sign-in), or Reconnect needed. Each with a plain-language hint.
- A dismissible banner nudges when connections can be made permanent.
- A focused 'Make permanent' sheet stores the authenticator setup key (with
  per-vendor guidance, key validation, and a success state). We never change the
  vendor's 2FA — the user enables it and pastes the one-time setup key.
- New batch endpoint GET /profiles/totp-statuses returns 2FA status for all
  password connections in one round-trip (reuses the live vault read; no schema
  change).

Task flow:
- The connection menu gains 'Keep signed in (2FA)', reusing the same sheet, so a
  connection can be made permanent without leaving the task.

Tests: batch status service (per-connection, degrades unreadable items); row
states + actions; sheet validation and save/success. API + app typecheck clean.
@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jul 28, 2026 8:41pm
comp-framework-editor Ready Ready Preview, Comment Jul 28, 2026 8:41pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Jul 28, 2026 8:41pm

Request Review

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files

Tip: instead of fixing issues one by one fix them all with cubic

Re-trigger cubic

Comment thread apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useTotpStatuses.ts Outdated
Comment thread apps/app/src/app/(app)/[orgId]/tasks/[taskId]/hooks/useTotpStatuses.ts Outdated
Comment thread apps/api/src/browserbase/browser-auth-profiles.controller.ts Outdated
- Scope the 2FA-status SWR cache to the org so switching orgs can't show another
  org's statuses.
- Treat an unreadable/failed status as UNKNOWN, never 'no key': the batch endpoint
  omits connections it couldn't read (instead of reporting false), the hook
  surfaces its error, and rows show 'Status unavailable' with no 'Make permanent'
  prompt — so an outage never invites overwriting an existing key. The at-risk
  banner counts only confirmed 'no key' connections.
- Bound the 1Password fan-out in the batch endpoint (was unbounded per org).
- Refresh statuses after a connect/reconnect so a new row isn't stale.
- Treat a never-verified session as 'reconnect', not 'stays signed in', even with
  a stored key.
- Make the 'Make permanent' sheet resilient: a rejected save (or a failed status
  refresh after a successful save) returns to the form with an error instead of
  hanging on 'Saving…'; store the normalized Base32 key (spaces/hyphens stripped).
- Remove the task-flow 'Keep signed in (2FA)' menu item: ConnectionManageMenu is
  not mounted anywhere and connection management deliberately lives on the
  Connections page, so it was dead code.

Deferred: splitting the browser-auth-profiles controller (already over the file
limit before this change) into a focused module.
…ller

Moves the four TOTP routes (batch status, per-connection get/set/clear) out of
BrowserAuthProfilesController into a dedicated BrowserAuthTotpController, bringing
the profiles controller back under the 300-line limit and grouping the 2FA routes.
Same paths, guards, permissions, and behavior — no route or contract change.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 3 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/browserbase/browser-auth-totp.controller.ts
Comment thread apps/api/src/browserbase/browser-auth-totp.controller.ts
…e TOTP controller

- setProfileTotp now validates and normalizes the key on the server (the client
  check is only UX): it accepts a Base32 secret or an otpauth:// URI and rejects a
  rotating one-time code or junk, so a direct API caller can't silently replace a
  working seed with something that can't generate codes — which would quietly
  break unattended 2FA. Extracted as normalizeTotpSecret with unit tests.
- Add controller-level tests for BrowserAuthTotpController: organization + profile
  ids reach the storage service on all four routes, and reads require
  integration:read while writes require integration:update.
Comment thread apps/api/src/browserbase/browser-totp-secret.ts Fixed
Strip trailing Base32 padding with a plain scan instead of a start-anchorless
regex (which can backtrack polynomially on untrusted input), and bound the input
length up front. Behavior is unchanged for valid keys; adds padding + long-input
tests. Clears the CodeQL polynomial-regexp alert.
@tofikwest
tofikwest merged commit 7345e3c into main Jul 28, 2026
10 of 11 checks passed
@tofikwest
tofikwest deleted the feat/browser-connection-make-permanent branch July 28, 2026 20:39
claudfuen pushed a commit that referenced this pull request Jul 29, 2026
# [3.111.0](v3.110.1...v3.111.0) (2026-07-29)

### Bug Fixes

* **api:** classify the take-over method on an unclear outcome too ([539fdba](539fdba))
* **api:** only classify a switchable passkey when a code method exists ([#3528](#3528)) ([0d79093](0d79093))
* **app:** make a half-finished connect resumable, not a forced full-screen step ([#3525](#3525)) ([b1afcae](b1afcae))
* **cloud-security:** add missing logGroupName to CreateLogGroup remediation ([#3515](#3515)) ([76ae56c](76ae56c))
* harden sign-in classification + align take-over messaging ([#3527](#3527)) ([914cb1e](914cb1e))
* **integrations:** wrap aws add account form in dialog and scroll into view ([#3526](#3526)) ([07e91ae](07e91ae)), closes [#418](#418)
* make the 2FA take-over universal — tailor guidance to what the page asks for ([#3520](#3520)) ([b12f3fe](b12f3fe))
* **policies:** remove archived policies from framework controls after unlinking ([#3513](#3513)) ([d639df2](d639df2))
* **training:** defer completion email and share canonical training IDs ([#3529](#3529)) ([fecb556](fecb556))
* **training:** remove rbac gate from mark-complete endpoint ([#3501](#3501)) ([8c5e98f](8c5e98f)), closes [#3455](#3455)

### Features

* connection longevity + one-click 'Make permanent' 2FA ([#3524](#3524)) ([7345e3c](7345e3c))
* **policies:** add bulk upload for policy migration ([#3514](#3514)) ([1940f06](1940f06))
* **security-questionnaire:** add browser extension ([#3064](#3064)) ([e678421](e678421))
@claudfuen

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.111.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants