diff --git a/pages/security.js b/pages/security.js index f7be58f..409230c 100644 --- a/pages/security.js +++ b/pages/security.js @@ -73,7 +73,7 @@ const summary = [ area: 'Identity, access & tenancy', anchor: 'access', status: 'partial', - note: 'Org RBAC + row-level tenant isolation today; SSO / SAML / SCIM not yet.', + note: 'Org RBAC, row-level tenant isolation, and TOTP step-up protect platform administration; SSO / SAML / SCIM is not included.', }, { area: 'Release integrity', @@ -943,6 +943,41 @@ export default function SecurityPage() { isolated per organization rather than shared across tenants.

+

+ Authenticator-app two-factor authentication is + available to every account and enforced for + platform administration. The platform-admin console + always requires both the server-side administrator + allowlist and a current two-factor session. + When a signed-in session needs this additional + assurance, Cloud asks for one current 6-digit code + and returns the user to the protected page after it + is accepted. +

+

+ Enrollment also produces one-time recovery codes. + If an authenticator is lost, a recovery code removes + the inaccessible factors. Factor removal invalidates + active sessions, so Cloud explicitly returns through + sign-in and then to Security & 2FA to enroll a + replacement. Recovery never grants the protected + session by itself. +

+

+ The signed-in account menu keeps identity and + organization context visible. It provides Security + & 2FA, organization switching when an account + belongs to more than one workspace, and an explicit + sign-out action. The{' '} + + account-security guide + {' '} + covers enrollment, step-up, recovery, and account or + workspace switching. +

What is not{' '} yet available:{' '} diff --git a/tests/securityMfa.test.js b/tests/securityMfa.test.js new file mode 100644 index 0000000..733cb22 --- /dev/null +++ b/tests/securityMfa.test.js @@ -0,0 +1,18 @@ +import assert from 'node:assert/strict' +import fs from 'node:fs' +import path from 'node:path' +import test from 'node:test' +import { fileURLToPath } from 'node:url' + +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..') +const security = fs.readFileSync(path.join(root, 'pages/security.js'), 'utf8') + +test('trust center describes the platform-admin MFA and account-menu contract', () => { + assert.match(security, /two-factor authentication is[\s\S]*enforced for[\s\S]*platform administration/i) + assert.match(security, /platform-admin console[\s\S]*server-side administrator[\s\S]*current two-factor session/i) + assert.match(security, /one current 6-digit code[\s\S]*returns the user to the protected page/i) + assert.match(security, /recovery code[\s\S]*invalidates[\s\S]*returns through[\s\S]*sign-in[\s\S]*Security[\s\S]*never grants[\s\S]*protected[\s\S]*session/i) + assert.match(security, /organization switching[\s\S]*explicit[\s\S]*sign-out action/i) + assert.match(security, /docs\.openadapt\.ai\/guides\/account-security/) + assert.doesNotMatch(security, /default policy also requires two-factor[\s\S]*organization owners/i) +})