Skip to content

Commit

Permalink
Merge pull request #14625 from craftcms/feature/cms-1282-static-cache…
Browse files Browse the repository at this point in the history
…-safe-csrfinput

Static-cache-safe CSRF input
  • Loading branch information
brandonkelly authored Apr 12, 2024
2 parents 0fbf4c1 + 7f53bb1 commit 1f162d3
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG-WIP.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
- Unselected table column options are now sorted alphabetically within element indexes.

### Administration
- Added the `asyncCsrfInputs` config setting. ([#14625](https://github.com/craftcms/cms/pull/14625))
- `resave` commands now support an `--if-invalid` option. ([#14731](https://github.com/craftcms/cms/issues/14731))

### Development
- Added the `language` element query param, which filters the resulting elements based on their sites’ languages. ([#14631](https://github.com/craftcms/cms/discussions/14631))
- GraphQL responses now include full exception details, when Dev Mode is enabled or an admin is signed in with the “Show full exception views when Dev Mode is disabled” preference enabled. ([#14527](https://github.com/craftcms/cms/issues/14527))
- `craft\helpers\Html::csrfInput()` and the `csrfInput` Twig function now support passing an `async` key to the `options` array, overriding the default behavior per the `asyncCsrfInputs` config setting. ([#14625](https://github.com/craftcms/cms/pull/14625))

### Extensibility
- Added `craft\services\Sites::getSitesByLanguage()`.
Expand Down
36 changes: 36 additions & 0 deletions src/config/GeneralConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -1013,6 +1013,24 @@ class GeneralConfig extends BaseConfig
*/
public bool $disableGraphqlTransformDirective = false;


/**
* @var bool Whether CSRF values should be injected via JavaScript for greater cache-ability.
*
* ::: code
* ```php Static Config
* ->asyncCsrfInputs(true)
* ```
* ```shell Environment Override
* CRAFT_ASYNC_CSRF_INPUTS=1
* ```
* :::
*
* @group Security
* @since 4.9.0
*/
public bool $asyncCsrfInputs = false;

/**
* @var bool Whether front-end web requests should support basic HTTP authentication.
*
Expand Down Expand Up @@ -4204,6 +4222,24 @@ public function disableGraphqlTransformDirective(bool $value = true): self
return $this;
}

/**
* Whether CSRF values should be injected via JavaScript for greater cache-ability.
*
* ```php
* ->asyncCsrfInputs(true)
* ```
*
* @param bool $value
* @return self
* @see $asyncCsrfInputs
* @since 4.9.0
*/
public function asyncCsrfInputs(bool $value = true): self
{
$this->asyncCsrfInputs = $value;
return $this;
}

/**
* Whether front-end web requests should support basic HTTP authentication.
*
Expand Down
22 changes: 20 additions & 2 deletions src/helpers/Html.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use craft\elements\Asset;
use craft\errors\InvalidHtmlTagException;
use craft\image\SvgAllowedAttributes;
use craft\web\View;
use enshrined\svgSanitize\Sanitizer;
use Throwable;
use yii\base\Exception;
Expand Down Expand Up @@ -96,7 +97,24 @@ public static function encodeSpaces(string $str): string
public static function csrfInput(array $options = []): string
{
$request = Craft::$app->getRequest();
return static::hiddenInput($request->csrfParam, $request->getCsrfToken(), $options);
$async = (bool)(ArrayHelper::remove($options, 'async') ?? Craft::$app->getConfig()->getGeneral()->asyncCsrfInputs);

if (!$async) {
Craft::$app->getResponse()->setNoCacheHeaders();
return static::hiddenInput($request->csrfParam, $request->getCsrfToken(), $options);
}

Craft::$app->getView()->registerHtml(
Craft::$app->getView()->renderTemplate(
'_special/async-csrf-input',
[
'url' => UrlHelper::actionUrl('users/session-info'),
],
View::TEMPLATE_MODE_CP,
)
);

return static::tag('craft-csrf-input');
}

/**
Expand Down Expand Up @@ -1057,7 +1075,7 @@ public static function encodeInvalidTags(string $html): string
$offset = $tag['end'];
}
}

/**
* Returns the contents of a given SVG file.
*
Expand Down
19 changes: 19 additions & 0 deletions src/templates/_special/async-csrf-input.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<script>
(function() {
fetch('{{ url }}', {
headers: {
'Accept': 'application/json',
}
}).then(response => response.json())
.then(data => {
document.querySelectorAll('craft-csrf-input')
.forEach(element => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = data.csrfTokenName;
input.value = data.csrfTokenValue;
element.replaceWith(input);
});
});
})();
</script>

0 comments on commit 1f162d3

Please sign in to comment.