Drop 'unsafe-inline' from the Content-Security-Policy (closes #530)
check / check (push) Waiting to run
check / check (push) Waiting to run
The delete prompts on the webhook page were inline onsubmit handlers. Each delete form now carries its question in a data-confirm attribute, which one submit listener in static/js/app.js asks. The three style attributes became Tailwind classes, with tailwind.css regenerated, and the x-cloak rule moved to style.css. The policy now sends script-src 'self' and style-src 'self'. The browser test now answers a target's delete prompt no and then yes, and also loads the sign-in page, the webhook list, the settings and profile pages and the 404 error page under the policy. The README states the policy as sent. Model: opus-5-5
This commit is contained in:
@@ -60,8 +60,8 @@ const (
|
||||
// TestAlpineRunsUnderTheSecurityPolicy loads the webhook page and the
|
||||
// event log in a headless browser, served by the real router and so
|
||||
// under the real Content-Security-Policy, and checks that the pages'
|
||||
// Alpine.js directives and the copy control work, and that a target's
|
||||
// row shows its delivery counts.
|
||||
// Alpine.js directives, the copy control and the delete prompts work,
|
||||
// and that a target's row shows its delivery counts.
|
||||
func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -74,6 +74,10 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
||||
userID, _ := env.seedUser(t, "browser", "browser-password")
|
||||
webhook, older, event, target := seedBrowserWebhook(t, env, userID)
|
||||
|
||||
// The sign-in page is loaded before the browser signs in, since a
|
||||
// signed-in visit to it is sent on to another page.
|
||||
checkOtherPages(ctx, t, srv.URL+"/pages/login")
|
||||
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx, setCookies(srv.URL, env.authCookies(t, userID, "browser")),
|
||||
))
|
||||
@@ -99,6 +103,11 @@ func TestAlpineRunsUnderTheSecurityPolicy(t *testing.T) {
|
||||
checkEventLog(ctx, t, page+"/events", event.ID, older.ID, target.Name)
|
||||
checkMobileMenu(ctx, t, page)
|
||||
checkPhoneWidth(ctx, t, page, page+"/events", target.Name)
|
||||
checkOtherPages(ctx, t,
|
||||
srv.URL+"/hooks", srv.URL+"/settings", srv.URL+"/user/browser",
|
||||
// A path that does not exist, which gets the 404 error page.
|
||||
srv.URL+"/no-such-page")
|
||||
checkDeletePrompt(ctx, t, page, target)
|
||||
|
||||
assert.Empty(t, problems(), "the browser reported problems")
|
||||
}
|
||||
@@ -1388,3 +1397,75 @@ func checkPhoneWidth(
|
||||
assert.False(t, sideways, "the event log scrolls sideways on a phone")
|
||||
assert.Empty(t, cutOff, "the event log cuts these off on a phone")
|
||||
}
|
||||
|
||||
// checkOtherPages loads the pages at urls, which no other check opens,
|
||||
// so that anything the browser reports going wrong on them fails the
|
||||
// test too.
|
||||
func checkOtherPages(ctx context.Context, t *testing.T, urls ...string) {
|
||||
t.Helper()
|
||||
|
||||
for _, url := range urls {
|
||||
require.NoError(t, chromedp.Run(ctx, loadPage(url)))
|
||||
}
|
||||
}
|
||||
|
||||
// answerPrompt returns a script that stands in for the user at the
|
||||
// browser's prompt: it makes window.confirm record the question in
|
||||
// window.asked and give answer. It also records in window.sent whether
|
||||
// a form is sent once its submit event is handled, from a listener on
|
||||
// window, which the event reaches after the listener static/js/app.js
|
||||
// adds to document.
|
||||
func answerPrompt(answer bool) string {
|
||||
return fmt.Sprintf(`
|
||||
window.asked = "";
|
||||
window.sent = false;
|
||||
window.confirm = (question) => {
|
||||
window.asked = question;
|
||||
return %t;
|
||||
};
|
||||
window.addEventListener("submit", (event) => {
|
||||
window.sent = !event.defaultPrevented;
|
||||
});`, answer)
|
||||
}
|
||||
|
||||
// checkDeletePrompt loads a webhook page and clicks Delete beside
|
||||
// target. It checks that the browser asks first, naming the target,
|
||||
// that answering no leaves the form unsent, and that answering yes
|
||||
// sends it and the target is deleted.
|
||||
func checkDeletePrompt(
|
||||
ctx context.Context, t *testing.T, url string, target *database.Target,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
form := `form[action$="/targets/` + target.ID + `/delete"]`
|
||||
question := `Delete target "` + target.Name + `"?` + "\n\n" +
|
||||
"Nothing more is delivered to it. " +
|
||||
"Its past deliveries stay in the event log."
|
||||
|
||||
var (
|
||||
asked string
|
||||
sent bool
|
||||
listed bool
|
||||
)
|
||||
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx, loadPage(url), chromedp.Evaluate(answerPrompt(false), nil),
|
||||
))
|
||||
click(ctx, t, form+" button")
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx,
|
||||
chromedp.Evaluate(`window.asked`, &asked),
|
||||
chromedp.Evaluate(`window.sent`, &sent),
|
||||
))
|
||||
assert.Equal(t, question, asked, "Delete does not ask about the target")
|
||||
assert.False(t, sent, "answering no still sends the delete form")
|
||||
|
||||
require.NoError(t, chromedp.Run(
|
||||
ctx, chromedp.Evaluate(answerPrompt(true), nil),
|
||||
))
|
||||
clickAndLoad(ctx, t, form+" button")
|
||||
require.NoError(t, chromedp.Run(ctx, chromedp.Evaluate(
|
||||
`document.querySelector('`+form+`') !== null`, &listed,
|
||||
)))
|
||||
assert.False(t, listed, "answering yes does not delete the target")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user