From 21a9094ea7a0e962363d81d14de7a903980c12c9 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 2 Sep 2024 11:45:06 +0200 Subject: [PATCH] Enable stateless CSRF protection for forms, logins and logouts --- .../7.2/config/packages/framework.yaml | 5 ++ .../stimulus-bundle/2.20/assets/bootstrap.js | 2 + .../2.20/assets/controllers.json | 4 ++ .../controllers/csrf_protection_controller.js | 60 +++++++++++++++++++ .../assets/controllers/hello_controller.js | 16 +++++ symfony/stimulus-bundle/2.20/manifest.json | 41 +++++++++++++ symfony/ux-turbo/2.20/manifest.json | 14 +++++ 7 files changed, 142 insertions(+) create mode 100644 symfony/stimulus-bundle/2.20/assets/bootstrap.js create mode 100644 symfony/stimulus-bundle/2.20/assets/controllers.json create mode 100644 symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js create mode 100644 symfony/stimulus-bundle/2.20/assets/controllers/hello_controller.js create mode 100644 symfony/stimulus-bundle/2.20/manifest.json create mode 100644 symfony/ux-turbo/2.20/manifest.json diff --git a/symfony/framework-bundle/7.2/config/packages/framework.yaml b/symfony/framework-bundle/7.2/config/packages/framework.yaml index 7e1ee1f1e..b4c42bd18 100644 --- a/symfony/framework-bundle/7.2/config/packages/framework.yaml +++ b/symfony/framework-bundle/7.2/config/packages/framework.yaml @@ -8,6 +8,11 @@ framework: #esi: true #fragments: true + # Enable stateless CSRF protection for forms and logins/logouts + form: { csrf_protection: { token_id: submit } } + csrf_protection: + stateless_token_ids: [submit, authenticate, logout] + when@test: framework: test: true diff --git a/symfony/stimulus-bundle/2.20/assets/bootstrap.js b/symfony/stimulus-bundle/2.20/assets/bootstrap.js new file mode 100644 index 000000000..2689398a6 --- /dev/null +++ b/symfony/stimulus-bundle/2.20/assets/bootstrap.js @@ -0,0 +1,2 @@ +// register any custom, 3rd party controllers here +// app.register('some_controller_name', SomeImportedController); diff --git a/symfony/stimulus-bundle/2.20/assets/controllers.json b/symfony/stimulus-bundle/2.20/assets/controllers.json new file mode 100644 index 000000000..a1c6e90cf --- /dev/null +++ b/symfony/stimulus-bundle/2.20/assets/controllers.json @@ -0,0 +1,4 @@ +{ + "controllers": [], + "entrypoints": [] +} diff --git a/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js b/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js new file mode 100644 index 000000000..6d42e5c9f --- /dev/null +++ b/symfony/stimulus-bundle/2.20/assets/controllers/csrf_protection_controller.js @@ -0,0 +1,60 @@ +var nameCheck = /^[-_a-zA-Z0-9]{4,22}$/; +var tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/; + +// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager +document.addEventListener('submit', function (event) { + var csrfField = event.target.querySelector('input[data-controller="csrf-protection"]'); + + if (!csrfField) { + return; + } + + var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + var csrfToken = csrfField.value; + + if (!csrfCookie && nameCheck.test(csrfToken)) { + csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken); + csrfField.value = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18)))); + } + + if (csrfCookie && tokenCheck.test(csrfToken)) { + var cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict'; + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +}); + +// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie +// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked +document.addEventListener('turbo:submit-start', function (event) { + var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]'); + + if (!csrfField) { + return; + } + + var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + event.detail.formSubmission.fetchRequest.headers[csrfCookie] = csrfField.value; + } +}); + +// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted +document.addEventListener('turbo:submit-end', function (event) { + var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]'); + + if (!csrfField) { + return; + } + + var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value'); + + if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) { + var cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0'; + + document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie; + } +}); + +/* stimulusFetch: 'lazy' */ +export default 'csrf-protection-controller'; diff --git a/symfony/stimulus-bundle/2.20/assets/controllers/hello_controller.js b/symfony/stimulus-bundle/2.20/assets/controllers/hello_controller.js new file mode 100644 index 000000000..e847027bd --- /dev/null +++ b/symfony/stimulus-bundle/2.20/assets/controllers/hello_controller.js @@ -0,0 +1,16 @@ +import { Controller } from '@hotwired/stimulus'; + +/* + * This is an example Stimulus controller! + * + * Any element with a data-controller="hello" attribute will cause + * this controller to be executed. The name "hello" comes from the filename: + * hello_controller.js -> "hello" + * + * Delete this file or adapt it for your use! + */ +export default class extends Controller { + connect() { + this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; + } +} diff --git a/symfony/stimulus-bundle/2.20/manifest.json b/symfony/stimulus-bundle/2.20/manifest.json new file mode 100644 index 000000000..428949575 --- /dev/null +++ b/symfony/stimulus-bundle/2.20/manifest.json @@ -0,0 +1,41 @@ +{ + "bundles": { + "Symfony\\UX\\StimulusBundle\\StimulusBundle": ["all"] + }, + "copy-from-recipe": { + "assets/": "assets/" + }, + "aliases": ["stimulus", "stimulus-bundle"], + "conflict": { + "symfony/framework-bundle": "<7.2", + "symfony/security-csrf": "<7.2", + "symfony/webpack-encore-bundle": "<2.0", + "symfony/flex": "<1.20.0 || >=2.0.0,<2.3.0" + }, + "add-lines": [ + { + "file": "webpack.config.js", + "content": "\n // enables the Symfony UX Stimulus bridge (used in assets/bootstrap.js)\n .enableStimulusBridge('./assets/controllers.json')", + "position": "after_target", + "target": ".splitEntryChunks()" + }, + { + "file": "assets/app.js", + "content": "import './bootstrap.js';", + "position": "top", + "warn_if_missing": true + }, + { + "file": "assets/bootstrap.js", + "content": "import { startStimulusApp } from '@symfony/stimulus-bridge';\n\n// Registers Stimulus controllers from controllers.json and in the controllers/ directory\nexport const app = startStimulusApp(require.context(\n '@symfony/stimulus-bridge/lazy-controller-loader!./controllers',\n true,\n /\\.[jt]sx?$/\n));", + "position": "top", + "requires": "symfony/webpack-encore-bundle" + }, + { + "file": "assets/bootstrap.js", + "content": "import { startStimulusApp } from '@symfony/stimulus-bundle';\n\nconst app = startStimulusApp();", + "position": "top", + "requires": "symfony/asset-mapper" + } + ] +} diff --git a/symfony/ux-turbo/2.20/manifest.json b/symfony/ux-turbo/2.20/manifest.json new file mode 100644 index 000000000..48149c60a --- /dev/null +++ b/symfony/ux-turbo/2.20/manifest.json @@ -0,0 +1,14 @@ +{ + "conflict": { + "symfony/framework-bundle": "<7.2", + "symfony/security-csrf": "<7.2" + }, + "add-lines": [ + { + "file": "config/packages/framework.yaml", + "position": "after_target", + "target": " csrf_protection:", + "content": " check_header: true" + } + ] +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy