add anywhere csrf protection

This commit is contained in:
root 2026-01-12 18:31:04 +03:00
parent c55264cf42
commit 24ea8deeec
5 changed files with 102 additions and 8 deletions

View File

@ -1,7 +1,7 @@
{# app/Views/macros/forms.twig #}
{% macro form_open(action, attributes = '') %}
<form action="{{ action }}" method="post" {{ attributes|raw }}>
{# Выводим глобальную переменную csrf_token #}
{# Добавляем data-ajax="true" для автоматической CSRF защиты #}
<form action="{{ action }}" method="post" data-ajax="true" {{ attributes|raw }}>
{{ csrf_field()|raw }}
{% endmacro %}

View File

@ -59,8 +59,8 @@
{% endif %}
{# Форма принятия/отклонения #}
<form action="/invitation/accept/{{ token }}" method="POST">
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>">
<form action="/invitation/accept/{{ token }}" method="POST" data-ajax="true">
{{ csrf_field()|raw }}
<input type="hidden" name="action" value="accept">
<div class="d-flex gap-3">

View File

@ -47,8 +47,8 @@
{% endif %}
{# Форма регистрации #}
<form action="/invitation/complete/{{ token }}" method="POST">
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>">
<form action="/invitation/complete/{{ token }}" method="POST" data-ajax="true">
{{ csrf_field()|raw }}
<div class="mb-3">
<label for="completeName" class="form-label">Ваше имя</label>

View File

@ -11,8 +11,8 @@
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="inviteUserForm" action="/organizations/users/{{ organization_id }}/invite" method="POST">
<input type="hidden" name="<?= csrf_token() ?>" value="<?= csrf_hash() ?>">
<form id="inviteUserForm" action="/organizations/users/{{ organization_id }}/invite" method="POST" data-ajax="true">
{{ csrf_field()|raw }}
<div class="mb-3">
<label for="inviteEmail" class="form-label">Email адрес</label>

View File

@ -93,4 +93,98 @@ document.addEventListener("DOMContentLoaded", function () {
document.cookie = cookieString;
}
})();
(function() {
'use strict';
/**
* Получение CSRF токена из мета-тега
*/
function getCsrfToken() {
const meta = document.querySelector('meta[name="csrf-token"]');
return meta ? meta.getAttribute('content') : '';
}
/**
* Обновление CSRF токена в мета-теге
*/
function updateCsrfToken(token, hash) {
const tokenMeta = document.querySelector('meta[name="csrf-token"]');
const hashMeta = document.querySelector('meta[name="csrf-hash"]');
if (tokenMeta) tokenMeta.setAttribute('content', token);
if (hashMeta) hashMeta.setAttribute('content', hash);
}
// Перехват fetch()
const originalFetch = window.fetch;
window.fetch = function(url, options = {}) {
// Добавляем CSRF токен в заголовки
if (!options.headers) {
options.headers = {};
}
// Если headers это объект - добавляем токен
if (options.headers instanceof Headers) {
options.headers.set('X-CSRF-TOKEN', getCsrfToken());
} else if (typeof options.headers === 'object') {
options.headers['X-CSRF-TOKEN'] = getCsrfToken();
}
// Выполняем запрос
return originalFetch(url, options).then(response => {
// Проверяем, пришёл ли новый токен в ответе
const newToken = response.headers.get('X-CSRF-TOKEN');
const newHash = response.headers.get('X-CSRF-HASH');
if (newToken && newHash) {
updateCsrfToken(newToken, newHash);
}
return response;
});
};
// Перехват XMLHttpRequest (для jQuery и других библиотек)
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this._csrfToken = getCsrfToken();
return originalOpen.apply(this, arguments);
};
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(body) {
// Добавляем CSRF токен в заголовки
this.setRequestHeader('X-CSRF-TOKEN', this._csrfToken || getCsrfToken());
// Слушаем ответ для обновления токена
this.addEventListener('load', function() {
const newToken = this.getResponseHeader('X-CSRF-TOKEN');
const newHash = this.getResponseHeader('X-CSRF-HASH');
if (newToken && newHash) {
updateCsrfToken(newToken, newHash);
}
});
return originalSend.apply(this, arguments);
};
// Автоматическое обновление CSRF токена в AJAX формах перед отправкой
document.addEventListener('submit', function(e) {
const form = e.target;
if (form.dataset.ajax === 'true') {
const tokenInput = form.querySelector('input[name="csrf_token"]');
if (tokenInput) {
tokenInput.value = getCsrfToken();
}
}
});
// Обновление CSRF токена в скрытых полях при загрузке страницы (только для AJAX форм)
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form[data-ajax="true"]');
forms.forEach(function(form) {
const tokenInput = form.querySelector('input[name="csrf_token"]');
if (tokenInput) {
tokenInput.value = getCsrfToken();
}
});
});
})();