web: Referrer-Policy same-origin; a password shown once emptied on pagehide
test / test (push) Canceled after 0s
test / test (push) Canceled after 0s
D-B3. Fetch Standard § 3.2 "append a request `Origin` header": under Referrer-Policy no-referrer a request that is not GET/HEAD and not CORS - a form POST, a navigation - gets Origin "null". originAllowed() refuses "null" whenever Sec-Fetch-Site is absent, and Fetch Metadata § 3 sends Sec-Fetch-Site only to a potentially trustworthy URL: every form, sign-in included, failed in Safari before 16.4 and in any browser on a plain-HTTP panel at a name. same-origin nulls Origin only for a request not same origin with its URL and still sends no referrer to another site. The comments of the origin check say so. D-B13. noStore keeps the page out of HTTP caches (RFC 9111 § 5.2.2.5); a history mechanism "might honor" it (§ 6), and Pragma in a response "was never specified" (§ 5.4) - it goes. CopyField.Secret marks the password of out-app-created data-secret, and panel.js empties such fields on pagehide (HTML § 7.5.9, "unload a Document", step 10.2: fired whether or not the document is kept for history traversal). Measured 2026-10-15 in Chrome 152 (the desktop app's browser), a local probe page with Cache-Control: no-store and a data-secret field: Back reloads it (pageshow persisted=false, a new load), so this browser keeps no-store pages out of its back/forward cache; the handler, fired on the same page, empties the field. Firefox and Safari not measured. Tests: the header value; the created page has the password data-secret and the login not, no Pragma. Stand (2026-10-15, make e2e, exit 0): new form_post_with_the_browsers_origin - Referrer-Policy: same-origin on a page; a POST carrying the page's own Origin and no Sec-Fetch-Site, what a browser sends now, passes; one carrying Origin: null, what it sent before, is 403. (A first run failed where the e2e client and a view test scraped the old input markup; both read data-secret now.) go vet and go test ./... pass on the server (24 packages). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -405,6 +405,10 @@ type CopyField struct {
|
||||
Value string
|
||||
Rows int
|
||||
Help Text
|
||||
// Secret marks a value shown once: panel.js empties the field when the
|
||||
// page is left, so a page the browser restores from its back/forward
|
||||
// cache does not show it again.
|
||||
Secret bool
|
||||
}
|
||||
|
||||
// Fact is the input of facts (as a slice): a label and its value. Mono sets the
|
||||
|
||||
@@ -270,7 +270,7 @@ func NewOutAppCreated(m Meta, domainID int64, domain, login, password string, fr
|
||||
Title: "New password for", Code: login},
|
||||
Credential: Box{Icon: "ti-eye", Title: "Shown once", End: Plain("Not stored — copy it now"), Variant: BoxCredential},
|
||||
Login: CopyField{Label: "Login", Value: login},
|
||||
Password: CopyField{Label: "Password", Value: password, Help: help},
|
||||
Password: CopyField{Label: "Password", Value: password, Help: help, Secret: true},
|
||||
Done: Foot{Button: &Action{Label: "Done — back to " + domain, Href: DomainHref(domainID), Primary: true}},
|
||||
Connect: Box{Icon: "ti-plug", Title: "Connect with"},
|
||||
Facts: []Fact{
|
||||
|
||||
@@ -400,7 +400,7 @@ func TestOutAppCreatedShowsLoginAndPasswordOnce(t *testing.T) {
|
||||
pageHas(t, "New password", out,
|
||||
`<h1 class="title is-3">New password for <span class="sp-mono">prod-server</span></h1>`,
|
||||
`<div class="box sp-credential">`, `<h2>Shown once</h2>`, `Not stored — copy it now`, `ti-eye`,
|
||||
`<input class="input" readonly value="prod-server">`, `<input class="input" readonly value="kT7v-Qm2x-Lp9d-Ue4s-Hn6b-Rz3w">`,
|
||||
`<input class="input" readonly value="prod-server">`, `<input class="input" readonly value="kT7v-Qm2x-Lp9d-Ue4s-Hn6b-Rz3w" data-secret>`,
|
||||
`The previous password stopped working when this one was generated.`,
|
||||
`href="/outbound/domains/1">Done — back to example.org</a>`,
|
||||
`<h2>Connect with</h2>`, `mail.example.org`, `465 SSL/TLS · 587 STARTTLS`, `*@example.org`,
|
||||
|
||||
@@ -63,6 +63,22 @@
|
||||
input.focus();
|
||||
});
|
||||
|
||||
// --- Secrets shown once ------------------------------------------------
|
||||
// A password shown once is in a field marked data-secret. Cache-Control:
|
||||
// no-store keeps the page out of HTTP caches, but a browser may still keep
|
||||
// the page in its back/forward cache and show it again on Back (RFC 9111
|
||||
// § 6: a history mechanism "might honor" no-store). Emptying the field when
|
||||
// the page is left means a restored page shows nothing to copy. HTML Living
|
||||
// Standard, § 7.5.9 "Unloading documents", "unload a Document", step 10.2:
|
||||
// pagehide is fired whether or not the document is kept for later history
|
||||
// traversal (its salvageable state, the event's persisted).
|
||||
window.addEventListener("pagehide", function () {
|
||||
var secrets = document.querySelectorAll("[data-secret]");
|
||||
for (var i = 0; i < secrets.length; i++) {
|
||||
secrets[i].value = "";
|
||||
}
|
||||
});
|
||||
|
||||
// --- Confirmation on destructive forms --------------------------------
|
||||
// Forms that delete something or invalidate a working credential carry a
|
||||
// data-confirm message. The prompt lives here rather than in an inline
|
||||
|
||||
@@ -155,7 +155,7 @@
|
||||
CopyField): one line, or a textarea when Rows is set — a DKIM key. The
|
||||
button is wired by panel.js through data-copy. */}}
|
||||
{{define "copy_field"}}<div class="field"><label class="label">{{.Label}}</label>
|
||||
<div class="field has-addons"><div class="control is-expanded">{{if .Rows}}<textarea class="textarea" rows="{{.Rows}}" readonly>{{.Value}}</textarea>{{else}}<input class="input" readonly value="{{.Value}}">{{end}}</div><div class="control"><button type="button" class="button" data-copy><i class="ti ti-copy"></i>Copy</button></div></div>
|
||||
<div class="field has-addons"><div class="control is-expanded">{{if .Rows}}<textarea class="textarea" rows="{{.Rows}}" readonly{{if .Secret}} data-secret{{end}}>{{.Value}}</textarea>{{else}}<input class="input" readonly value="{{.Value}}"{{if .Secret}} data-secret{{end}}>{{end}}</div><div class="control"><button type="button" class="button" data-copy><i class="ti ti-copy"></i>Copy</button></div></div>
|
||||
{{if .Help}}<p class="help">{{template "inline" .Help}}</p>
|
||||
{{end}}</div>{{end}}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user