diff --git a/docs/account-journeys.md b/docs/account-journeys.md index 860bd3a..d7d3950 100644 --- a/docs/account-journeys.md +++ b/docs/account-journeys.md @@ -9,9 +9,11 @@ headless capability alone does not mean a journey is usable or verified live. ## Common interaction rules -- The header states the verified portal identity, or “Not signed in to this - portal.” A valid portal session shows Log out; an absent/expired session shows - Sign in. Never infer identity from URL parameters or an existing provider tab. +- The header is titled NetKingdom Identity. It states “Signed in as” the + verified identity, or “Not signed in.” A valid account-site session shows + Log out; an absent or expired session shows Sign in. A one-time code raises + the security level of the NetKingdom sign-in and is not another sign-in. + Never infer identity from URL parameters or an existing provider tab. - The portal cannot observe every application or shared-provider session. Explain this once in sign-out confirmation or expandable identity-switch help, not as competing login/logout actions everywhere. “Use another account” remains @@ -34,7 +36,7 @@ headless capability alone does not mean a journey is usable or verified live. | ID / intent | Success | Failure and recovery | Current support / acceptance | |---|---|---|---| -| U01 — Know whether I am signed in | Header and the home page name the verified portal identity, or say the portal session is absent. An application may keep its own session | Expired/unknown cookie shows signed-out state; a query does not invent a session; sign in again | Implemented; automated anonymous/expired/member/operator tests, including the home login-state section | +| U01 — Know whether I am signed in | Header and the home page say “Signed in as” the verified identity, or that no account-site session exists. An application may keep its own session. A one-time code is a higher security level, not another sign-in | Expired/unknown cookie shows signed-out state; a query does not invent a session; sign in again | Implemented; automated anonymous/expired/member/operator tests, including the home identity section | | U02 — Sign in to my company application | Personal login lands in the intended tenant and application | Wrong credentials stay on provider; denied membership leads to account help with identity switching | Recovery deployed previously; actual fresh-user acceptance waiting on OTP | | U03 — Accept an invitation | Confirm intended tenant/role, accept once, then see next setup step | Expired/used/wrong-person invitation explains next step; admin reissues without duplicates | Service/browser routes exist; live delivery and full browser acceptance pending | | U04 — Set or recover my password | Use actual login name, complete single-use setup, return to sign-in | Missing mail or expired link offers admin-assisted new setup link | Password setup reported successful; login name and sign-in address now named at handoff and in the user entry (2026-09-23 run, USER-WP-0035-T01); email delivery unresolved (USER-WP-0035-T02) | diff --git a/src/user_engine/authentication_policy.py b/src/user_engine/authentication_policy.py index 078654b..1f2318e 100644 --- a/src/user_engine/authentication_policy.py +++ b/src/user_engine/authentication_policy.py @@ -19,7 +19,7 @@ def page(csrf, result=None): failure = result.get('failure') if failure: messages = { - 'fresh_platform_mfa_required': 'Verify your identity with a fresh MFA sign-in before viewing or changing policy.', + 'fresh_platform_mfa_required': 'Raise the security level with a one-time code before viewing or changing policy.', 'preview_expired_or_changed': 'The review expired, changed or belongs to another session. Check the current policy and review again.', 'policy_changed_review_again': 'Policy changed after this review. Check the current policy and review again.', 'reference_already_used': 'This reference is already recorded. Check the history; use a new reference for a new change.', diff --git a/src/user_engine/web.py b/src/user_engine/web.py index 5e609a7..2fe5ede 100644 --- a/src/user_engine/web.py +++ b/src/user_engine/web.py @@ -226,9 +226,9 @@ class PortalApplication: actor = None self._set_account_navigation(environ, actor) identity = ( - f'
This portal is signed in as {escape(actor.preferred_username or actor.subject)}.
' + f'Signed in as {escape(actor.preferred_username or actor.subject)}.
' '' - if actor else 'You are not signed in to this portal. Sign in to verify your identity and access.
' + if actor else 'You are not signed in. Open the account site with your NetKingdom identity.
' ) return self._html(start_response, self._page_html( "Sign-in help", 'Your shared NetKingdom sign-in may still be active. Signing in may reuse that account.
' + 'Your shared NetKingdom sign-in may still be active. Opening the account site again may reuse that identity.
' + self._identity_switch_help(), ), correlation_id) if path == "/logout" and method == "GET": @@ -255,11 +255,11 @@ class PortalApplication: self._set_account_navigation(environ, actor) token = self._csrf_token(environ) return self._html(start_response, self._page_html( - "Log out", 'This ends your portal session. Your shared NetKingdom sign-in stays active.
' + "Log out", 'This ends the session on the account site. Your NetKingdom sign-in stays active.
' '', ), correlation_id) if path == "/logout" and method == "POST": @@ -1317,15 +1317,15 @@ class PortalApplication: def _operation_capabilities(self) -> str: capabilities = ( - ("Portal sign-in", self.oidc_client is not None, "An existing portal session does not prove a fresh provider sign-in works."), + ("Account-site session", self.oidc_client is not None, "An existing account-site session does not prove a fresh NetKingdom sign-in works."), ("Tenant identity management", self.provisioning is not None, "Use tenant administration for login setup or tenant access recovery. Shared identity and factor recovery belong to the sign-in service."), ("Tenant lifecycle", self.tenant_management is not None, "Review the authority's returned version after a change."), ("Notification delivery", self.outbox_delivery is not None, "Inspect the delivery record below. If email cannot be received, use the tenant's assisted password setup process."), ) - rows = "".join(f'| Service | Known state | Recovery step |
|---|
Authenticator recovery is unavailable in this portal.
') + 'Review authentication policy. A configured adapter is not a health check.
Authenticator recovery is unavailable on this account site.
') + 'Review authentication policy. A configured adapter is not a health check.
') def _require_setup_access(self, tenant: str, user_id: str) -> None: account = self.service.store.tenant_account(tenant, user_id) @@ -1880,14 +1880,14 @@ class PortalApplication:Use this page for help with your password and authenticator app.
An authenticator app generates a short-lived code to enter after your password. -This portal cannot currently confirm whether an authenticator is enabled for your account.
+An authenticator app generates a short-lived code. Entering that code raises the security level of your NetKingdom sign-in. It is not another sign-in. +This account site cannot currently confirm whether an authenticator is enabled for your account.
""" + handoff + """Opening the setup page does not activate two-step verification. To cancel unfinished setup, open All Tokens, select the pending token, and choose Delete. A confirmed authenticator cannot be removed or replaced from this password-only management session; ask your administrator to use audited authenticator recovery.
Use the newest code for the correct account and check that your device sets its time automatically. @@ -2197,19 +2197,25 @@ Use the login name they provide; it may differ from your display name.
str: + level = ( + "A one-time code raises the security level of a NetKingdom sign-in. " + "It is not another sign-in.
" + ) if actor is None: return ( - 'You are not signed in to this portal.
You are not signed in.
" + f"{level}Signed in to this portal as {name}.
" - "This is the portal session. An application can keep its own session.
" + 'Signed in as {name}.
" + "This session is for the account site. An application can keep its own session.
" + f"{level}" f"{escape(verification)}
" - 'After the password is set, sign in at {escape(self.login_url)}. ' - "This portal does not deliver the setup link; pass it and the login name on yourself.
" + "This account site does not deliver the setup link; pass it and the login name on yourself." f'' "Return to tenant administration
", ) @@ -2372,7 +2378,7 @@ Use the login name they provide; it may differ from your display name. None: if actor is None: - _ACCOUNT_NAVIGATION.set('Not signed in to this portal
') + _ACCOUNT_NAVIGATION.set('Not signed in
') return links = 'HomeMy accountSign-in security' if "platform-operator" in actor.roles: @@ -2384,13 +2390,13 @@ Use the login name they provide; it may differ from your display name.Log out' identity = escape(actor.preferred_username or actor.subject) - _ACCOUNT_NAVIGATION.set(f'Signed in to this portal as {identity}
') + _ACCOUNT_NAVIGATION.set(f'Signed in as {identity}
') @staticmethod def _page_html(title: str, body: str) -> str: return f""" -