Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docs/desktop/connect-to-cloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@ a recording → see it in the dashboard.
## 1. Create or sign in to your workspace

Sign in at [app.openadapt.ai](https://app.openadapt.ai) (Google or magic-link).
Your account resolves to exactly one **organization**; every token and workflow
belongs to it.
Each workspace action is scoped to exactly one active **organization**; every
token and workflow belongs to that organization.

The dashboard account menu shows the signed-in email and active organization.
It also provides **Security & 2FA**, organization switching when the account
belongs to multiple workspaces, and **Sign out**. See
[Account security and privileged access](../guides/account-security.md) for
authenticator setup and protected-session behavior.

## 2. Mint an ingest token

Expand Down
86 changes: 86 additions & 0 deletions docs/guides/account-security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Account security and privileged access

OpenAdapt Cloud separates **sign-in** from **privileged session assurance**.
Google or magic-link sign-in establishes the account session. Actions with
higher impact can additionally require a current 6-digit code from an
authenticator app.

This extra check is called **step-up authentication**. It lets a signed-in user
continue ordinary workspace activity without repeatedly entering a code while
keeping platform administration behind a second factor.

## Set up an authenticator

1. Sign in to [OpenAdapt Cloud](https://app.openadapt.ai).
2. Open the account menu in the dashboard header.
3. Choose **Security & 2FA**.
4. Select **Add authenticator**.
5. Scan the QR code with a TOTP authenticator such as 1Password, Google
Authenticator, or Authy. You can use the displayed setup key instead.
6. Enter the current 6-digit code and select **Verify & enable**.

The Security page lists each verified authenticator and whether authenticator
enrollment is required by account policy. When policy requires enrollment, the
last verified factor cannot be removed.

## Open a protected page

The platform-admin console has two independent server-side gates:

1. the signed-in email must be on the platform-administrator allowlist; and
2. the current session must have passed two-factor verification.

Organization membership alone never grants platform administration. If an
authorized platform administrator opens **Platform admin** from the account
menu without current two-factor assurance, Cloud asks for one current code.
After the code is accepted, Cloud returns to the intended protected page
automatically. It does not start a second enrollment.

Other accounts can opt in from the same Security page.

## Recover after losing an authenticator

Save the one-time recovery codes shown after enrollment. If the authenticator
is no longer available:

1. Open the protected page and choose **Use recovery code** at the verification
prompt.
2. Enter one unused recovery code.
3. Cloud removes the inaccessible authenticator factors. Supabase invalidates
the account's active sessions as part of administrative factor removal, so
Cloud returns you to sign-in explicitly.
4. Sign in again; Cloud returns to **Security & 2FA**.
5. Enroll and verify a new authenticator before continuing to the protected
destination.

A recovery code does not grant a two-factor session or open the protected page.
It restores the account to the enrollment path through normal reauthentication.
Each code works once; if factor removal fails, Cloud restores the code instead
of burning it on an incomplete recovery.

## Switch workspace or account

The account menu always shows the signed-in email and active organization:

- If the account belongs to more than one organization, use **Switch
organization** to change workspace without signing in again.
- Use **Sign out** to end the current user session. Sign in again to use a
different Google or email account.

Changing organizations never changes the authenticated person, and signing out
does not remove an enrolled authenticator.

## Security model

OpenAdapt Cloud uses authenticator assurance levels to distinguish an ordinary
signed-in session from one that has presented a second factor.
Protected routes re-check both identity and assurance on the server; hiding a
link in the browser is not the security boundary.

Two-factor authentication protects the Cloud account and control plane. It does
not replace workflow-level identity checks, policy, effect verification, or the
declared execution and data boundary.

For the rest of the hosted security model, see
[Security and deployment review](security-review.md) and the public
[OpenAdapt Trust Center](https://openadapt.ai/security).
4 changes: 4 additions & 0 deletions docs/guides/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ cannot enter the OpenAdapt-hosted boundary.
Payment runs through Stripe, and the price shown on the website is the exact
price you pay at Checkout.

[Manage account security and privileged access](account-security.md), including
authenticator setup, step-up verification, recovery codes, organization
switching, and sign-out.

<figure markdown="span">
![The OpenAdapt Cloud workspace overview: counts for workflows, hosted runs used, and items needing attention, a Runs stopped before guessing panel listing a halt to review, a recent-runs table, and the workflow list.](../assets/screenshots/dashboard-overview.png){ width="900" }
<figcaption>The managed control plane. The overview counts workflows, hosted runs, and items needing attention, surfaces runs that stopped before guessing for review, and lists recent runs and workflows.</figcaption>
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ nav:
- Connect to a cloud workspace: desktop/connect-to-cloud.md
- Troubleshooting: guides/troubleshooting.md
- Security:
- Account security and privileged access: guides/account-security.md
- Qualification evidence: get-started/what-works-today.md
- Fail-closed regulated execution: concepts/regulated-execution.md
- Settings and policy governance: concepts/settings-governance.md
Expand Down
43 changes: 43 additions & 0 deletions tests/test_account_security_docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Keep Cloud account-security documentation aligned with the product UX."""

from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
GUIDE = (ROOT / "docs" / "guides" / "account-security.md").read_text()
GUIDE_PROSE = " ".join(GUIDE.split())
CONNECT = (ROOT / "docs" / "desktop" / "connect-to-cloud.md").read_text()
CONNECT_PROSE = " ".join(CONNECT.split())
NAV = (ROOT / "mkdocs.yml").read_text()


def test_account_security_guide_explains_sign_in_step_up_and_admin_gates():
assert "separates **sign-in** from **privileged session assurance**" in GUIDE_PROSE
assert "platform-administrator allowlist" in GUIDE_PROSE
assert "current session must have passed two-factor verification" in GUIDE_PROSE
assert "asks for one current code" in GUIDE_PROSE
assert "returns to the intended protected page automatically" in GUIDE_PROSE
assert "does not start a second enrollment" in GUIDE_PROSE
assert "requires two-factor authentication for organization owners" not in GUIDE_PROSE


def test_account_and_organization_switching_are_distinguished():
assert "Switch organization" in GUIDE_PROSE
assert "Use **Sign out** to end the current user session" in GUIDE_PROSE
assert "Changing organizations never changes the authenticated person" in GUIDE_PROSE
assert "exactly one active **organization**" in CONNECT_PROSE


def test_recovery_returns_to_enrollment_without_granting_privileged_access():
assert "choose **Use recovery code**" in GUIDE_PROSE
assert "returns you to sign-in explicitly" in GUIDE_PROSE
assert "Sign in again; Cloud returns to **Security & 2FA**" in GUIDE_PROSE
assert "does not grant a two-factor session or open the protected page" in GUIDE_PROSE
assert "restores the code instead of burning it" in GUIDE_PROSE


def test_account_security_guide_is_in_the_security_navigation():
assert (
"Account security and privileged access: guides/account-security.md"
in NAV
)