Name the account site NetKingdom Identity.
Say "Signed in as" the identity, and describe a one-time code as a higher security level of the NetKingdom sign-in rather than another sign-in. The account site keeps its own session. Assistant: grok Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
This commit is contained in:
parent
b5c60ab2bd
commit
b987a3de9e
9 changed files with 50 additions and 39 deletions
|
|
@ -226,9 +226,9 @@ class PortalApplication:
|
|||
actor = None
|
||||
self._set_account_navigation(environ, actor)
|
||||
identity = (
|
||||
f'<p>This portal is signed in as <strong>{escape(actor.preferred_username or actor.subject)}</strong>.</p>'
|
||||
f'<p>Signed in as <strong>{escape(actor.preferred_username or actor.subject)}</strong>.</p>'
|
||||
'<p><a class="button" href="/onboarding">View my account and access</a></p>'
|
||||
if actor else '<p>You are not signed in to this portal. Sign in to verify your identity and access.</p>'
|
||||
if actor else '<p>You are not signed in. Open the account site with your NetKingdom identity.</p>'
|
||||
)
|
||||
return self._html(start_response, self._page_html(
|
||||
"Sign-in help", '<h1>Sign-in could not be completed</h1>'
|
||||
|
|
@ -244,8 +244,8 @@ class PortalApplication:
|
|||
return self._redirect(start_response, "/", correlation_id)
|
||||
return self._html(start_response, self._page_html(
|
||||
"Not signed in",
|
||||
'<h1>You are not signed in to this portal.</h1>'
|
||||
'<p>Your shared NetKingdom sign-in may still be active. Signing in may reuse that account.</p>'
|
||||
'<h1>You are not signed in.</h1>'
|
||||
'<p>Your shared NetKingdom sign-in may still be active. Opening the account site again may reuse that identity.</p>'
|
||||
+ 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", '<h1>Log out of this portal?</h1>'
|
||||
'<p>This ends your portal session. Your shared NetKingdom sign-in stays active.</p>'
|
||||
"Log out", '<h1>End this account-site session?</h1>'
|
||||
'<p>This ends the session on the account site. Your NetKingdom sign-in stays active.</p>'
|
||||
'<form method="post" action="/logout">'
|
||||
f'<input type="hidden" name="csrf_token" value="{escape(token)}">'
|
||||
'<button type="submit">Log out of this portal</button>'
|
||||
'<button type="submit">End this session</button>'
|
||||
'<button type="submit" name="scope" value="shared">Continue to NetKingdom sign-out</button></form>',
|
||||
), 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'<tr><td>{escape(name)}</td><td>{"Configured; live health unverified" if configured else "Unavailable in this portal"}</td><td>{escape(help_text)}</td></tr>'
|
||||
rows = "".join(f'<tr><td>{escape(name)}</td><td>{"Configured; live health unverified" if configured else "Unavailable on this account site"}</td><td>{escape(help_text)}</td></tr>'
|
||||
for name, configured, help_text in capabilities)
|
||||
return ('<section><h2>Service capabilities</h2><table><thead><tr><th>Service</th><th>Known state</th><th>Recovery step</th></tr></thead><tbody>'
|
||||
+ rows + '</tbody></table>' + ('<p><a href="/platform/factor-recovery">Recover a lost authenticator</a></p>' if self.factor_recovery else '<p>Authenticator recovery is unavailable in this portal.</p>') + '<p><a href="/platform/authentication-policy">Review authentication policy</a>. A configured adapter is not a health check.</p></section>')
|
||||
+ rows + '</tbody></table>' + ('<p><a href="/platform/factor-recovery">Recover a lost authenticator</a></p>' if self.factor_recovery else '<p>Authenticator recovery is unavailable on this account site.</p>') + '<p><a href="/platform/authentication-policy">Review authentication policy</a>. A configured adapter is not a health check.</p></section>')
|
||||
|
||||
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:
|
|||
<h1>Sign-in security</h1>
|
||||
<p>Use this page for help with your password and authenticator app.</p>
|
||||
<section><h2>Two-step verification</h2>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
""" + handoff + """
|
||||
<details><summary>How to set up an authenticator when setup is available</summary>
|
||||
<ol><li>Open authenticator management and check that it shows your account.</li>
|
||||
<li>Choose Enroll Token, select TOTP, and scan its QR code with your authenticator app.</li>
|
||||
<li>Enter a current code to confirm setup. Wait for the sign-in service to confirm activation.</li>
|
||||
<li>Follow the recovery instructions shown there, then test a new sign-in before closing your current session.</li></ol>
|
||||
<li>Follow the recovery instructions shown there, then confirm the new security level before closing your current session.</li></ol>
|
||||
<p>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.</p></details>
|
||||
<details><summary>A code is rejected, or I have lost my authenticator</summary>
|
||||
<p>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.</p></sect
|
|||
|
||||
@staticmethod
|
||||
def _login_state(actor: Any | None) -> str:
|
||||
level = (
|
||||
"<p>A one-time code raises the security level of a NetKingdom sign-in. "
|
||||
"It is not another sign-in.</p>"
|
||||
)
|
||||
if actor is None:
|
||||
return (
|
||||
'<section aria-labelledby="login-state"><h2 id="login-state">Login state</h2>'
|
||||
"<p>You are not signed in to this portal.</p></section>"
|
||||
'<section aria-labelledby="login-state"><h2 id="login-state">Identity</h2>'
|
||||
"<p>You are not signed in.</p>"
|
||||
f"{level}</section>"
|
||||
)
|
||||
name = escape(actor.preferred_username or actor.subject)
|
||||
verification = "Verified by your identity provider" if actor.assurance else "Verification pending"
|
||||
return (
|
||||
'<section aria-labelledby="login-state"><h2 id="login-state">Login state</h2>'
|
||||
f"<p>Signed in to this portal as <strong>{name}</strong>.</p>"
|
||||
"<p>This is the portal session. An application can keep its own session.</p>"
|
||||
'<section aria-labelledby="login-state"><h2 id="login-state">Identity</h2>'
|
||||
f"<p>Signed in as <strong>{name}</strong>.</p>"
|
||||
"<p>This session is for the account site. An application can keep its own session.</p>"
|
||||
f"{level}"
|
||||
f"<p>{escape(verification)}</p>"
|
||||
'<p><a href="/security">Password and two-step verification help</a></p></section>'
|
||||
'<p><a href="/security">Password and one-time code help</a></p></section>'
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -2361,7 +2367,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
|
|||
f'<p><a class="button" rel="noreferrer" href="{escape(setup_url)}">'
|
||||
"Continue to password setup</a></p>"
|
||||
+ f'<p>After the password is set, sign in at <a href="{escape(self.login_url)}">{escape(self.login_url)}</a>. '
|
||||
"This portal does not deliver the setup link; pass it and the login name on yourself.</p>"
|
||||
"This account site does not deliver the setup link; pass it and the login name on yourself.</p>"
|
||||
f'<p><a href="/admin/{escape(tenant)}">'
|
||||
"Return to tenant administration</a></p>",
|
||||
)
|
||||
|
|
@ -2372,7 +2378,7 @@ Use the login name they provide; it may differ from your display name.</p></sect
|
|||
|
||||
def _set_account_navigation(self, environ: Mapping[str, Any], actor: Any | None) -> None:
|
||||
if actor is None:
|
||||
_ACCOUNT_NAVIGATION.set('<p>Not signed in to this portal</p><nav aria-label="Account navigation"><a href="/">Home</a><a href="/security">Sign-in help</a><a class="button" href="/login">Sign in</a></nav>')
|
||||
_ACCOUNT_NAVIGATION.set('<p>Not signed in</p><nav aria-label="Account navigation"><a href="/">Home</a><a href="/security">Sign-in help</a><a class="button" href="/login">Sign in</a></nav>')
|
||||
return
|
||||
links = '<a href="/">Home</a><a href="/onboarding">My account</a><a href="/security">Sign-in security</a>'
|
||||
if "platform-operator" in actor.roles:
|
||||
|
|
@ -2384,13 +2390,13 @@ Use the login name they provide; it may differ from your display name.</p></sect
|
|||
if csrf:
|
||||
links += '<a href="/logout">Log out</a>'
|
||||
identity = escape(actor.preferred_username or actor.subject)
|
||||
_ACCOUNT_NAVIGATION.set(f'<p>Signed in to this portal as <strong>{identity}</strong></p><nav aria-label="Account navigation">' + links + '</nav>')
|
||||
_ACCOUNT_NAVIGATION.set(f'<p>Signed in as <strong>{identity}</strong></p><nav aria-label="Account navigation">' + links + '</nav>')
|
||||
|
||||
@staticmethod
|
||||
def _page_html(title: str, body: str) -> str:
|
||||
return f"""<!doctype html><html lang="en"><head><meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>{escape(title)} · Railiance</title><style>
|
||||
<title>{escape(title)} · NetKingdom Identity</title><style>
|
||||
:root{{--ink:#17201c;--paper:#f5f1e8;--accent:#195b47;--line:#c8c1b3}}
|
||||
*{{box-sizing:border-box}}body{{margin:0;background:var(--paper);color:var(--ink);font:18px/1.55 system-ui,sans-serif}}
|
||||
header,main{{max-width:68rem;margin:auto;padding:1.25rem}}header{{border-bottom:1px solid var(--line)}}
|
||||
|
|
@ -2401,7 +2407,7 @@ table{{width:100%;border-collapse:collapse;background:#fff}}th,td{{padding:.75re
|
|||
section{{margin:2rem 0}}form{{display:grid;gap:.8rem;max-width:42rem}}label{{display:grid;gap:.25rem}}
|
||||
input,select,button{{font:inherit;padding:.65rem}}button{{background:var(--accent);color:white;border:0;border-radius:.3rem;cursor:pointer}}
|
||||
a:focus-visible,input:focus-visible,select:focus-visible,button:focus-visible{{outline:3px solid #e59f24;outline-offset:3px}}@media(max-width:640px){{body{{font-size:16px}}table{{display:block;overflow-x:auto}}}}
|
||||
</style></head><body><header><strong>Railiance identity</strong>{_ACCOUNT_NAVIGATION.get()}</header><main>{body}</main></body></html>"""
|
||||
</style></head><body><header><strong>NetKingdom Identity</strong>{_ACCOUNT_NAVIGATION.get()}</header><main>{body}</main></body></html>"""
|
||||
|
||||
def _html(
|
||||
self, start_response: StartResponse, body: str, correlation_id: str,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue