Реализация управления согласиями на обработку данных на сайте
Управление согласиями (Consent Management) — система, которая фиксирует, хранит и обрабатывает юридически значимые согласия пользователей на обработку персональных данных в соответствии с GDPR, 152-ФЗ и другими регуляторными требованиями.
Типы согласий
| Тип | Примеры | Обязательность |
|---|---|---|
| Обработка ПДн | Регистрация, форма заказа | Обязательно |
| Маркетинговые коммуникации | Email-рассылка, SMS | По запросу |
| Профилирование | Рекомендации, аналитика | По запросу |
| Передача третьим лицам | Партнёры, рекламные сети | По запросу |
| Cookies (неосновные) | Аналитика, ремаркетинг | По запросу |
Структура базы данных
-- Типы согласий
CREATE TABLE consent_types (
id SERIAL PRIMARY KEY,
code VARCHAR(50) UNIQUE NOT NULL, -- 'marketing_email', 'data_processing'
title VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
version VARCHAR(20) NOT NULL, -- '1.2' — при изменении политики
is_required BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Записи о согласиях
CREATE TABLE user_consents (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id) ON DELETE CASCADE,
consent_type_id INT REFERENCES consent_types(id),
status VARCHAR(20) NOT NULL, -- 'granted', 'denied', 'withdrawn'
version_accepted VARCHAR(20) NOT NULL,
ip_address INET,
user_agent TEXT,
source VARCHAR(100), -- 'registration_form', 'account_settings'
granted_at TIMESTAMPTZ,
withdrawn_at TIMESTAMPTZ,
expires_at TIMESTAMPTZ, -- некоторые согласия имеют срок
UNIQUE (user_id, consent_type_id, version_accepted)
);
Сервис управления согласиями (Laravel)
class ConsentService
{
public function grant(User $user, string $consentCode, string $source): UserConsent
{
$consentType = ConsentType::where('code', $consentCode)->firstOrFail();
return UserConsent::updateOrCreate(
[
'user_id' => $user->id,
'consent_type_id' => $consentType->id,
'version_accepted' => $consentType->version,
],
[
'status' => 'granted',
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
'source' => $source,
'granted_at' => now(),
'withdrawn_at' => null,
]
);
}
public function withdraw(User $user, string $consentCode): void
{
$consentType = ConsentType::where('code', $consentCode)->firstOrFail();
UserConsent::where('user_id', $user->id)
->where('consent_type_id', $consentType->id)
->where('status', 'granted')
->update([
'status' => 'withdrawn',
'withdrawn_at' => now(),
]);
// Действия при отзыве: отписать от рассылки, удалить профиль отслеживания
event(new ConsentWithdrawn($user, $consentCode));
}
public function hasConsent(User $user, string $consentCode): bool
{
$consentType = ConsentType::where('code', $consentCode)->first();
if (!$consentType) return false;
return UserConsent::where('user_id', $user->id)
->where('consent_type_id', $consentType->id)
->where('status', 'granted')
->where('version_accepted', $consentType->version)
->exists();
}
}
Форма согласия при регистрации
{{-- resources/views/auth/register.blade.php --}}
<form method="POST" action="/register">
@csrf
{{-- Обязательное согласие --}}
<label class="required-consent">
<input type="checkbox" name="consents[]" value="data_processing" required>
Я согласен с
<a href="/privacy-policy" target="_blank">политикой конфиденциальности</a>
и даю согласие на обработку персональных данных
</label>
{{-- Опциональное --}}
<label>
<input type="checkbox" name="consents[]" value="marketing_email">
Я хочу получать информацию об акциях и новостях на email
</label>
</form>
// В RegisterController
protected function create(array $data): User
{
$user = User::create([...]);
$consentService = app(ConsentService::class);
// Всегда даём согласие на базовую обработку
$consentService->grant($user, 'data_processing', 'registration_form');
// Опциональные — только если выбраны
if (in_array('marketing_email', $data['consents'] ?? [])) {
$consentService->grant($user, 'marketing_email', 'registration_form');
}
return $user;
}
Личный кабинет: управление согласиями
// ConsentSettings.tsx
export function ConsentSettings() {
const { data: consents, mutate } = useSWR('/api/user/consents');
const toggleConsent = async (code: string, currentStatus: boolean) => {
await fetch(`/api/user/consents/${code}`, {
method: 'PATCH',
body: JSON.stringify({ granted: !currentStatus }),
});
mutate();
};
return (
<div>
<h2>Управление согласиями</h2>
{consents?.map(consent => (
<div key={consent.code}>
<div>
<strong>{consent.title}</strong>
<p>{consent.description}</p>
{consent.granted_at && (
<small>
Дано: {formatDate(consent.granted_at)}
{consent.withdrawn_at && `, отозвано: ${formatDate(consent.withdrawn_at)}`}
</small>
)}
</div>
{!consent.is_required && (
<Toggle
checked={consent.status === 'granted'}
onChange={() => toggleConsent(consent.code, consent.status === 'granted')}
/>
)}
</div>
))}
</div>
);
}
Re-consent при изменении политики
При изменении версии политики конфиденциальности пользователи с устаревшим согласием должны дать новое:
// Middleware: проверить актуальность обязательных согласий
class RequireFreshConsent
{
public function handle(Request $request, Closure $next)
{
$user = $request->user();
if (!$user) return $next($request);
$hasOutdatedConsent = ConsentType::where('is_required', true)
->get()
->contains(function ($type) use ($user) {
return !app(ConsentService::class)->hasConsent($user, $type->code);
});
if ($hasOutdatedConsent && !$request->is('consent*', 'logout*')) {
return redirect()->route('consent.update');
}
return $next($request);
}
}
Экспорт и удаление данных (право на забвение)
// Правая на доступ к данным (GDPR Art. 15)
class ExportUserDataCommand extends Command
{
public function handle(): void
{
$user = User::findOrFail($this->argument('user_id'));
$export = [
'personal_data' => $user->only(['name', 'email', 'phone', 'created_at']),
'consents' => $user->consents()->with('type')->get(),
'orders' => $user->orders()->get(),
'activity_log' => AuditLog::where('user_id', $user->id)->get(),
];
Storage::put("exports/user_{$user->id}.json", json_encode($export, JSON_PRETTY_PRINT));
}
}
Срок реализации
- Базовая модель согласий + форма регистрации: 3–4 дня
- Личный кабинет управления + re-consent: 3–4 дня
- API экспорта/удаления данных: 2–3 дня







