Files
breakpilot-compliance/consent-sdk/src/core/ConsentStorage.ts
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

204 lines
4.6 KiB
TypeScript

/**
* ConsentStorage - Lokale Speicherung des Consent-Status
*
* Speichert Consent-Daten im localStorage mit HMAC-Signatur
* zur Manipulationserkennung.
*/
import type { ConsentConfig, ConsentState } from '../types';
const STORAGE_KEY = 'bp_consent';
const STORAGE_VERSION = '1';
/**
* Gespeichertes Format
*/
interface StoredConsent {
version: string;
consent: ConsentState;
signature: string;
}
/**
* ConsentStorage - Persistente Speicherung
*/
export class ConsentStorage {
private config: ConsentConfig;
private storageKey: string;
constructor(config: ConsentConfig) {
this.config = config;
// Pro Site ein separater Key
this.storageKey = `${STORAGE_KEY}_${config.siteId}`;
}
/**
* Consent laden
*/
get(): ConsentState | null {
if (typeof window === 'undefined') {
return null;
}
try {
const raw = localStorage.getItem(this.storageKey);
if (!raw) {
return null;
}
const stored: StoredConsent = JSON.parse(raw);
// Version pruefen
if (stored.version !== STORAGE_VERSION) {
this.log('Storage version mismatch, clearing');
this.clear();
return null;
}
// Signatur pruefen
if (!this.verifySignature(stored.consent, stored.signature)) {
this.log('Invalid signature, clearing');
this.clear();
return null;
}
return stored.consent;
} catch (error) {
this.log('Failed to load consent:', error);
return null;
}
}
/**
* Consent speichern
*/
set(consent: ConsentState): void {
if (typeof window === 'undefined') {
return;
}
try {
const signature = this.generateSignature(consent);
const stored: StoredConsent = {
version: STORAGE_VERSION,
consent,
signature,
};
localStorage.setItem(this.storageKey, JSON.stringify(stored));
// Auch als Cookie setzen (fuer Server-Side Rendering)
this.setCookie(consent);
this.log('Consent saved to storage');
} catch (error) {
this.log('Failed to save consent:', error);
}
}
/**
* Consent loeschen
*/
clear(): void {
if (typeof window === 'undefined') {
return;
}
try {
localStorage.removeItem(this.storageKey);
this.clearCookie();
this.log('Consent cleared from storage');
} catch (error) {
this.log('Failed to clear consent:', error);
}
}
/**
* Pruefen ob Consent existiert
*/
exists(): boolean {
return this.get() !== null;
}
// ===========================================================================
// Cookie Management
// ===========================================================================
/**
* Consent als Cookie setzen
*/
private setCookie(consent: ConsentState): void {
const days = this.config.consent?.rememberDays ?? 365;
const expires = new Date();
expires.setDate(expires.getDate() + days);
// Nur Kategorien als Cookie (fuer SSR)
const cookieValue = JSON.stringify(consent.categories);
const encoded = encodeURIComponent(cookieValue);
document.cookie = [
`${this.storageKey}=${encoded}`,
`expires=${expires.toUTCString()}`,
'path=/',
'SameSite=Lax',
location.protocol === 'https:' ? 'Secure' : '',
]
.filter(Boolean)
.join('; ');
}
/**
* Cookie loeschen
*/
private clearCookie(): void {
document.cookie = `${this.storageKey}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
// ===========================================================================
// Signature (Simple HMAC-like)
// ===========================================================================
/**
* Signatur generieren
*/
private generateSignature(consent: ConsentState): string {
const data = JSON.stringify(consent);
const key = this.config.siteId;
// Einfache Hash-Funktion (fuer Client-Side)
// In Produktion wuerde man SubtleCrypto verwenden
return this.simpleHash(data + key);
}
/**
* Signatur verifizieren
*/
private verifySignature(consent: ConsentState, signature: string): boolean {
const expected = this.generateSignature(consent);
return expected === signature;
}
/**
* Einfache Hash-Funktion (djb2)
*/
private simpleHash(str: string): string {
let hash = 5381;
for (let i = 0; i < str.length; i++) {
hash = (hash * 33) ^ str.charCodeAt(i);
}
return (hash >>> 0).toString(16);
}
/**
* Debug-Logging
*/
private log(...args: unknown[]): void {
if (this.config.debug) {
console.log('[ConsentStorage]', ...args);
}
}
}
export default ConsentStorage;