diff --git a/docs/desktop/connect-to-cloud.md b/docs/desktop/connect-to-cloud.md index 91fc874..b89e1df 100644 --- a/docs/desktop/connect-to-cloud.md +++ b/docs/desktop/connect-to-cloud.md @@ -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 diff --git a/docs/guides/account-security.md b/docs/guides/account-security.md new file mode 100644 index 0000000..1325e23 --- /dev/null +++ b/docs/guides/account-security.md @@ -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). diff --git a/docs/guides/hosted.md b/docs/guides/hosted.md index da71479..e3fb282 100644 --- a/docs/guides/hosted.md +++ b/docs/guides/hosted.md @@ -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. +
![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" }
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.
diff --git a/mkdocs.yml b/mkdocs.yml index fbccea9..3cd5a45 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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 diff --git a/tests/test_account_security_docs.py b/tests/test_account_security_docs.py new file mode 100644 index 0000000..64f3811 --- /dev/null +++ b/tests/test_account_security_docs.py @@ -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 + )