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>
213 lines
4.8 KiB
TypeScript
213 lines
4.8 KiB
TypeScript
/**
|
|
* ConsentAPI - Kommunikation mit dem Consent-Backend
|
|
*
|
|
* Sendet Consent-Entscheidungen an das Backend zur
|
|
* revisionssicheren Speicherung.
|
|
*/
|
|
|
|
import type {
|
|
ConsentConfig,
|
|
ConsentState,
|
|
ConsentAPIResponse,
|
|
SiteConfigResponse,
|
|
} from '../types';
|
|
|
|
/**
|
|
* Request-Payload fuer Consent-Speicherung
|
|
*/
|
|
interface SaveConsentRequest {
|
|
siteId: string;
|
|
userId?: string;
|
|
deviceFingerprint: string;
|
|
consent: ConsentState;
|
|
metadata?: {
|
|
userAgent?: string;
|
|
language?: string;
|
|
screenResolution?: string;
|
|
platform?: string;
|
|
appVersion?: string;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* ConsentAPI - Backend-Kommunikation
|
|
*/
|
|
export class ConsentAPI {
|
|
private config: ConsentConfig;
|
|
private baseUrl: string;
|
|
|
|
constructor(config: ConsentConfig) {
|
|
this.config = config;
|
|
this.baseUrl = config.apiEndpoint.replace(/\/$/, '');
|
|
}
|
|
|
|
/**
|
|
* Consent speichern
|
|
*/
|
|
async saveConsent(request: SaveConsentRequest): Promise<ConsentAPIResponse> {
|
|
const payload = {
|
|
...request,
|
|
metadata: {
|
|
userAgent: typeof navigator !== 'undefined' ? navigator.userAgent : '',
|
|
language: typeof navigator !== 'undefined' ? navigator.language : '',
|
|
screenResolution:
|
|
typeof window !== 'undefined'
|
|
? `${window.screen.width}x${window.screen.height}`
|
|
: '',
|
|
platform: 'web',
|
|
...request.metadata,
|
|
},
|
|
};
|
|
|
|
const response = await this.fetch('/consent', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to save consent: ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Consent abrufen
|
|
*/
|
|
async getConsent(
|
|
siteId: string,
|
|
deviceFingerprint: string
|
|
): Promise<ConsentState | null> {
|
|
const params = new URLSearchParams({
|
|
siteId,
|
|
deviceFingerprint,
|
|
});
|
|
|
|
const response = await this.fetch(`/consent?${params}`);
|
|
|
|
if (response.status === 404) {
|
|
return null;
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to get consent: ${response.status}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.consent;
|
|
}
|
|
|
|
/**
|
|
* Consent widerrufen
|
|
*/
|
|
async revokeConsent(consentId: string): Promise<void> {
|
|
const response = await this.fetch(`/consent/${consentId}`, {
|
|
method: 'DELETE',
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to revoke consent: ${response.status}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Site-Konfiguration abrufen
|
|
*/
|
|
async getSiteConfig(siteId: string): Promise<SiteConfigResponse> {
|
|
const response = await this.fetch(`/config/${siteId}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to get site config: ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
/**
|
|
* Consent-Historie exportieren (DSGVO Art. 20)
|
|
*/
|
|
async exportConsent(userId: string): Promise<unknown> {
|
|
const params = new URLSearchParams({ userId });
|
|
const response = await this.fetch(`/consent/export?${params}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`Failed to export consent: ${response.status}`);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Internal Methods
|
|
// ===========================================================================
|
|
|
|
/**
|
|
* Fetch mit Standard-Headers
|
|
*/
|
|
private async fetch(
|
|
path: string,
|
|
options: RequestInit = {}
|
|
): Promise<Response> {
|
|
const url = `${this.baseUrl}${path}`;
|
|
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
Accept: 'application/json',
|
|
...this.getSignatureHeaders(),
|
|
...(options.headers || {}),
|
|
};
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers,
|
|
credentials: 'include',
|
|
});
|
|
|
|
this.log(`${options.method || 'GET'} ${path}:`, response.status);
|
|
return response;
|
|
} catch (error) {
|
|
this.log('Fetch error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Signatur-Headers generieren (HMAC)
|
|
*/
|
|
private getSignatureHeaders(): Record<string, string> {
|
|
const timestamp = Math.floor(Date.now() / 1000).toString();
|
|
|
|
// Einfache Signatur fuer Client-Side
|
|
// In Produktion: Server-seitige Validierung mit echtem HMAC
|
|
const signature = this.simpleHash(`${this.config.siteId}:${timestamp}`);
|
|
|
|
return {
|
|
'X-Consent-Timestamp': timestamp,
|
|
'X-Consent-Signature': `sha256=${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('[ConsentAPI]', ...args);
|
|
}
|
|
}
|
|
}
|
|
|
|
export default ConsentAPI;
|