Style the NetKingdom sign-out confirmation like the account site.
All checks were successful
Authentication acceptance / acceptance (push) Successful in 1m20s
Authentication acceptance / provider-contract (push) Successful in 14s
Build and Publish Container Image / build-and-push (push) Successful in 44s

Assistant: grok
Assistant-Session: 01a0d25d-d358-7e13-b84a-d007fbb7e34f
This commit is contained in:
tegwick 2026-09-27 00:51:14 +02:00
parent 36f9677bd1
commit 13afaa916d
2 changed files with 23 additions and 4 deletions

View file

@ -37,13 +37,25 @@ type AccountLogoutHandler struct {
const logoutCSRF = "__Host-keycape-logout"
var accountLogoutPage = template.Must(template.New("logout").Parse(`<!doctype html>
<html lang="en"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sign out of NetKingdom</title><main><h1>Sign out of NetKingdom?</h1>
<html lang="en"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sign out of NetKingdom · 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)}
h1{font:clamp(2.2rem,7vw,5.5rem)/.98 Georgia,serif;max-width:13ch}a{color:var(--accent)}
form{display:grid;gap:.8rem;max-width:42rem}
input,button{font:inherit;padding:.65rem}button{background:var(--accent);color:white;border:0;border-radius:.3rem;cursor:pointer}
a:focus-visible,button:focus-visible{outline:3px solid #e59f24;outline-offset:3px}
@media(max-width:640px){body{font-size:16px}}
</style></head><body><header><strong>NetKingdom Identity</strong></header><main>
<h1>Sign out of NetKingdom?</h1>
<p>This ends your shared sign-in session in this browser so you can use another account.
Applications that already have their own sessions may remain signed in.</p>
<form method="post" action="/account/logout"><input type="hidden" name="csrf" value="{{.CSRF}}">
<button type="submit">Sign out of NetKingdom</button></form>
<p><a href="{{.Portal}}">Back to my account</a></p></main></html>`))
<p><a href="{{.Portal}}">Back to my account</a></p>
</main></body></html>`))
func (h *AccountLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-store")
@ -53,7 +65,7 @@ func (h *AccountLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
upstream, _ := url.Parse(h.UpstreamLogoutURL)
portal, _ := url.Parse(h.PortalURL)
destinations := upstream.Scheme + "://" + upstream.Host + " " + portal.Scheme + "://" + portal.Host
w.Header().Set("Content-Security-Policy", "default-src 'none'; form-action 'self' "+destinations+"; frame-ancestors 'none'; base-uri 'none'")
w.Header().Set("Content-Security-Policy", "default-src 'none'; style-src 'unsafe-inline'; form-action 'self' "+destinations+"; frame-ancestors 'none'; base-uri 'none'")
w.Header().Set("X-Content-Type-Options", "nosniff")
switch r.Method {
case http.MethodGet:

View file

@ -31,6 +31,13 @@ func TestSharedLogoutRequiresConfirmationAndFixedReturn(t *testing.T) {
if w.Code != 200 || w.Header().Get("Location") != "" {
t.Fatal("GET must only confirm")
}
page := w.Body.String()
if !strings.Contains(page, "<strong>NetKingdom Identity</strong>") || !strings.Contains(page, "<h1>Sign out of NetKingdom?</h1>") || !strings.Contains(page, "--paper:#f5f1e8") || !strings.Contains(page, "Georgia,serif") {
t.Fatal("logout page left the account-site design")
}
if !strings.Contains(w.Header().Get("Content-Security-Policy"), "style-src 'unsafe-inline'") {
t.Fatal("inline account-site style blocked")
}
if w.Header().Get("Referrer-Policy") != "same-origin" {
t.Fatal("browser POST would lose its Origin")
}