mirror of
https://github.com/getformwork/formwork.git
synced 2025-01-17 21:49:04 +01:00
Switch to named CSRF tokens
This commit is contained in:
parent
a45ee5d4a3
commit
12dd4abaa7
@ -102,7 +102,7 @@ abstract class AbstractController extends BaseAbstractController
|
||||
'location' => $this->name,
|
||||
'site' => $this->site(),
|
||||
'panel' => $this->panel(),
|
||||
'csrfToken' => $this->csrfToken->get(),
|
||||
'csrfToken' => $this->csrfToken->get($this->panel()->getCsrfTokenName()),
|
||||
'modals' => $this->modals(),
|
||||
'colorScheme' => $this->getColorScheme(),
|
||||
'navigation' => [
|
||||
|
@ -20,9 +20,11 @@ class AuthenticationController extends AbstractController
|
||||
*/
|
||||
public function login(Request $request, CsrfToken $csrfToken, AccessLimiter $accessLimiter): Response
|
||||
{
|
||||
$csrfTokenName = $this->panel()->getCsrfTokenName();
|
||||
|
||||
if ($accessLimiter->hasReachedLimit()) {
|
||||
$minutes = round($this->config->get('system.panel.loginResetTime') / 60);
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($csrfTokenName);
|
||||
return $this->error($this->translate('panel.login.attempt.tooMany', $minutes));
|
||||
}
|
||||
|
||||
@ -33,7 +35,7 @@ class AuthenticationController extends AbstractController
|
||||
}
|
||||
|
||||
// Always generate a new CSRF token
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($csrfTokenName);
|
||||
|
||||
return new Response($this->view('authentication.login', [
|
||||
'title' => $this->translate('panel.login.login'),
|
||||
@ -47,7 +49,7 @@ class AuthenticationController extends AbstractController
|
||||
|
||||
// Ensure no required data is missing
|
||||
if (!$data->hasMultiple(['username', 'password'])) {
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($csrfTokenName);
|
||||
$this->error($this->translate('panel.login.attempt.failed'));
|
||||
}
|
||||
|
||||
@ -61,7 +63,7 @@ class AuthenticationController extends AbstractController
|
||||
$request->session()->set('FORMWORK_USERNAME', $data->get('username'));
|
||||
|
||||
// Regenerate CSRF token
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($csrfTokenName);
|
||||
|
||||
$accessLog = new Log(FileSystem::joinPaths($this->config->get('system.panel.paths.logs'), 'access.json'));
|
||||
$lastAccessRegistry = new Registry(FileSystem::joinPaths($this->config->get('system.panel.paths.logs'), 'lastAccess.json'));
|
||||
@ -79,7 +81,7 @@ class AuthenticationController extends AbstractController
|
||||
return $this->redirect($this->generateRoute('panel.index'));
|
||||
}
|
||||
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($csrfTokenName);
|
||||
return $this->error($this->translate('panel.login.attempt.failed'), [
|
||||
'username' => $data->get('username'),
|
||||
'error' => true,
|
||||
@ -94,7 +96,7 @@ class AuthenticationController extends AbstractController
|
||||
*/
|
||||
public function logout(Request $request, CsrfToken $csrfToken): RedirectResponse
|
||||
{
|
||||
$csrfToken->destroy();
|
||||
$csrfToken->destroy($this->panel()->getCsrfTokenName());
|
||||
$request->session()->remove('FORMWORK_USERNAME');
|
||||
$request->session()->destroy();
|
||||
|
||||
|
@ -26,7 +26,7 @@ class RegisterController extends AbstractController
|
||||
return $this->redirectToReferer();
|
||||
}
|
||||
|
||||
$csrfToken->generate();
|
||||
$csrfToken->generate($this->panel()->getCsrfTokenName());
|
||||
|
||||
$fields = $schemes->get('forms.register')->fields();
|
||||
|
||||
|
@ -15,6 +15,8 @@ use Formwork\Utils\Uri;
|
||||
|
||||
final class Panel
|
||||
{
|
||||
protected const CSRF_TOKEN_NAME = 'panel';
|
||||
|
||||
/**
|
||||
* Assets instance
|
||||
*/
|
||||
@ -178,4 +180,12 @@ final class Panel
|
||||
|
||||
return $translations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get panel CSRF token name
|
||||
*/
|
||||
public function getCsrfTokenName(): string
|
||||
{
|
||||
return self::CSRF_TOKEN_NAME;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ class CsrfToken
|
||||
/**
|
||||
* Session key to store the CSRF token
|
||||
*/
|
||||
protected const SESSION_KEY = 'CSRF_TOKEN';
|
||||
protected const SESSION_KEY_PREFIX = '_formwork_csrf_tokens';
|
||||
|
||||
public function __construct(protected Request $request)
|
||||
{
|
||||
@ -18,34 +18,45 @@ class CsrfToken
|
||||
/**
|
||||
* Generate a new CSRF token
|
||||
*/
|
||||
public function generate(): string
|
||||
public function generate(string $name): string
|
||||
{
|
||||
$token = base64_encode(random_bytes(36));
|
||||
$this->request->session()->set(self::SESSION_KEY, $token);
|
||||
$this->request->session()->set(self::SESSION_KEY_PREFIX . '.' . $name, $token);
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current CSRF token
|
||||
* Check if CSRF token exists
|
||||
*/
|
||||
public function get(): ?string
|
||||
public function has(string $name): bool
|
||||
{
|
||||
return $this->request->session()->get(self::SESSION_KEY);
|
||||
return $this->request->session()->has(self::SESSION_KEY_PREFIX . '.' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CSRF token by name
|
||||
*/
|
||||
public function get(string $name, bool $autoGenerate = false): ?string
|
||||
{
|
||||
if ($autoGenerate && !$this->has($name)) {
|
||||
return $this->generate($name);
|
||||
}
|
||||
return $this->request->session()->get(self::SESSION_KEY_PREFIX . '.' . $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if given CSRF token is valid
|
||||
*/
|
||||
public function validate(string $token): bool
|
||||
public function validate(string $name, string $token): bool
|
||||
{
|
||||
return ($storedToken = $this->get()) && hash_equals($token, $storedToken);
|
||||
return ($storedToken = $this->get($name)) && hash_equals($token, $storedToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove CSRF token from session data
|
||||
*/
|
||||
public function destroy(): void
|
||||
public function destroy(string $name): void
|
||||
{
|
||||
$this->request->session()->remove(self::SESSION_KEY);
|
||||
$this->request->session()->remove(self::SESSION_KEY_PREFIX . '.' . $name);
|
||||
}
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ return [
|
||||
],
|
||||
|
||||
'filters' => [
|
||||
'request.validateSize' => [
|
||||
'panel.request.validateSize' => [
|
||||
'action' => static function (Request $request, Translations $translations, Panel $panel) {
|
||||
// Validate HTTP request Content-Length according to `post_max_size` directive
|
||||
if ($request->contentLength() !== null) {
|
||||
@ -239,14 +239,16 @@ return [
|
||||
}
|
||||
},
|
||||
'methods' => ['POST'],
|
||||
'types' => ['HTTP', 'XHR'],
|
||||
],
|
||||
|
||||
'request.validateCsrf' => [
|
||||
'panel.request.validateCsrf' => [
|
||||
'action' => static function (Request $request, Translations $translations, Panel $panel, CsrfToken $csrfToken) {
|
||||
$token = $request->input()->get('csrf-token');
|
||||
$tokenName = $panel->getCsrfTokenName();
|
||||
$token = (string) $request->input()->get('csrf-token');
|
||||
|
||||
if (!($token !== null && $csrfToken->validate($token))) {
|
||||
$csrfToken->destroy();
|
||||
if (!$csrfToken->validate($tokenName, $token)) {
|
||||
$csrfToken->destroy($tokenName);
|
||||
$request->session()->remove('FORMWORK_USERNAME');
|
||||
|
||||
$panel->notify(
|
||||
|
Loading…
x
Reference in New Issue
Block a user