@rivtor/consent
v1.1.0
Published
GDPR Cookie Consent with GeoIP detection and encrypted consent logging
Maintainers
Readme
@rivtor/consent
Production-ready GDPR Cookie Consent with GeoIP detection and encrypted consent logging
Features
- GeoIP Detection: Cloudflare headers and MaxMind GeoIP2 support
- Consent State Machine: no_consent_needed, awaiting_consent, consent_given, consent_revoked
- Encrypted Logging: 5-year retention with encrypted consent records
- Pre-Built Components: GDPR-compliant banner with Radix UI
- Cookie Wall Prevention: Architectural enforcement preventing cookie walls in EU
- Category-Based Consent: Essential, statistics, marketing, preferences
Installation
npm install @rivtor/consent
# or
yarn add @rivtor/consent
# or
pnpm add @rivtor/consentQuick Start
1. Detect Consent Requirement
import { detectConsentRequirement } from '@rivtor/consent/server';
// In your API route or middleware
const result = await detectConsentRequirement(
request.headers.get('x-forwarded-for') || 'unknown',
Object.fromEntries(request.headers)
);
console.log(result);
// {
// country: 'DE',
// isEu: true,
// region: 'eu',
// requiresConsent: true
// }2. Wrap Your App
'use client';
import { VocoConsentBanner } from '@rivtor/consent/react';
import { DEFAULT_CONSENT_CONFIG } from '@rivtor/consent/server';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<VocoConsentBanner
config={DEFAULT_CONSENT_CONFIG}
onConsentChange={(consent) => {
console.log('User consent:', consent);
}}
/>
</body>
</html>
);
}3. Use Consent Hook
'use client';
import { useConsent } from '@rivtor/consent/react';
export function Analytics() {
const { isCategoryAccepted, acceptCategory } = useConsent();
useEffect(() => {
if (isCategoryAccepted('statistics')) {
// Load analytics
initializeAnalytics();
}
}, [isCategoryAccepted]);
return <div>Analytics Component</div>;
}4. Server-Side Validation
import { validateConsent, recordConsent } from '@rivtor/consent/server';
// Validate user consent before processing
const valid = await validateConsent({
userId: 'user-123',
sessionId: 'session-abc',
requiredCategories: ['statistics', 'marketing'],
});
if (!valid.valid) {
return Response.json({ error: 'Consent required' }, { status: 403 });
}
// Record consent decision
await recordConsent({
userId: 'user-123',
sessionId: 'session-abc',
region: 'eu',
state: 'consent_given',
categoriesAccepted: ['essential', 'statistics'],
consentVersion: '1.0',
expiresAt: new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000),
});API Reference
Server Functions
import { consent } from '@rivtor/consent/server';
// Detect consent requirement from IP/headers
const result = await consent.detectConsentRequirement(ip, headers);
// Validate user consent
const valid = await consent.validateConsent({
userId: 'user-123',
sessionId: 'session-abc',
requiredCategories: ['statistics', 'marketing'],
});
// Record consent decision
await consent.recordConsent({
userId: 'user-123',
sessionId: 'session-abc',
region: 'eu',
state: 'consent_given',
categoriesAccepted: ['essential', 'statistics'],
consentVersion: '1.0',
expiresAt: new Date(Date.now() + 5 * 365 * 24 * 60 * 60 * 1000),
});
// Get user consent
const userConsent = await consent.getUserConsent('user-123');
// Revoke consent
await consent.revokeConsent('user-123', 'session-abc');
// Check if consent is needed
const needed = await consent.isConsentNeeded('user-123');
// Get consent statistics
const stats = await consent.getConsentStats(30);Client Functions
import { createConsentManager } from '@rivtor/consent/client';
const manager = createConsentManager(DEFAULT_CONSENT_CONFIG);
// Check if consent is needed
if (manager.isConsentNeeded()) {
// Show banner
}
// Grant consent for categories
await manager.grantConsent(['essential', 'statistics']);
// Revoke consent
await manager.revokeConsent();
// Check specific category
if (manager.isCategoryAccepted('statistics')) {
// Load statistics
}
// Get current consent state
const state = manager.getState();
// Accept all categories
await manager.acceptAll();
// Accept only essential
await manager.acceptEssential();React Components
VocoConsentBanner
<VocoConsentBanner
config={DEFAULT_CONSENT_CONFIG}
onConsentChange={(consent) => console.log('Consent:', consent)}
position="bottom"
style="default"
/>useConsent Hook
const {
state,
isConsentNeeded,
isCategoryAccepted,
acceptAll,
acceptEssential,
revoke,
acceptCategory,
} = useConsent(DEFAULT_CONSENT_CONFIG);Configuration
interface ConsentConfig {
// Consent categories
categories: {
essential: {
required: true;
description: string;
};
statistics: {
cookies: string[];
description: string;
};
marketing: {
cookies: string[];
description: string;
};
preferences: {
cookies: string[];
description: string;
};
};
// Banner settings
banner: {
title: string;
description: string;
acceptAllText: string;
acceptEssentialText: string;
privacyPolicyUrl: string;
showDetails: boolean;
};
// Consent duration
consentDuration: number; // days (default: 1825 = 5 years)
// Current consent version
consentVersion: string;
}Database Schema
CREATE TABLE consent_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID,
session_id TEXT NOT NULL,
ip_hash TEXT,
region TEXT NOT NULL,
state TEXT NOT NULL CHECK (state IN (
'no_consent_needed',
'awaiting_consent',
'consent_given',
'consent_revoked'
)),
categories_accepted TEXT[] NOT NULL DEFAULT '{}',
consent_version TEXT NOT NULL,
consented_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Indexes for efficient queries
CREATE INDEX idx_consent_logs_user_id ON consent_logs(user_id);
CREATE INDEX idx_consent_logs_session_id ON consent_logs(session_id);
CREATE INDEX idx_consent_logs_expires_at ON consent_logs(expires_at);
CREATE INDEX idx_consent_logs_region ON consent_logs(region);Consent Categories
| Category | Required | Description |
|----------|----------|-------------|
| essential | Yes | Required for basic functionality |
| statistics | No | Analytics and performance monitoring |
| marketing | No | Advertising and tracking |
| preferences | No | User preferences and settings |
License
MIT
Made with ❤️ by Rivtor
