Merge pull request #1303 from PicPeak/fix/1300-remove-fragmentation

fix: remove the image-fragmentation surface
This commit is contained in:
Paul Nothaft
2026-09-05 23:37:14 +02:00
committed by GitHub
20 changed files with 9 additions and 281 deletions
-1
View File
@@ -1597,7 +1597,6 @@ module.exports = (router) => {
body('use_canvas_rendering').optional().isBoolean(), body('use_canvas_rendering').optional().isBoolean(),
body('overlay_protection').optional().isBoolean(), body('overlay_protection').optional().isBoolean(),
body('image_quality').optional().isInt({ min: 1, max: 100 }), body('image_quality').optional().isInt({ min: 1, max: 100 }),
body('fragmentation_level').optional().isInt({ min: 1, max: 10 }),
body('password').optional().isString().custom((value) => { body('password').optional().isString().custom((value) => {
if (value === undefined || value === null || value === '') { if (value === undefined || value === null || value === '') {
return true; return true;
-2
View File
@@ -23,7 +23,6 @@ router.get('/settings', adminAuth, requirePermission(['settings.view', 'image_se
'max_image_requests_per_hour', 'max_image_requests_per_hour',
'suspicious_activity_threshold', 'suspicious_activity_threshold',
'enable_canvas_rendering', 'enable_canvas_rendering',
'default_fragmentation_level',
'security_monitoring_enabled', 'security_monitoring_enabled',
'block_suspicious_ips', 'block_suspicious_ips',
'log_security_events_to_db', 'log_security_events_to_db',
@@ -68,7 +67,6 @@ router.put('/settings', adminAuth, requirePermission('image_security.manage'), a
'max_image_requests_per_hour', 'max_image_requests_per_hour',
'suspicious_activity_threshold', 'suspicious_activity_threshold',
'enable_canvas_rendering', 'enable_canvas_rendering',
'default_fragmentation_level',
'security_monitoring_enabled', 'security_monitoring_enabled',
'block_suspicious_ips', 'block_suspicious_ips',
'log_security_events_to_db', 'log_security_events_to_db',
-1
View File
@@ -1227,7 +1227,6 @@ router.get('/:slug/photos', verifyGalleryAccess, resolveGuest, noStoreCache, asy
protection_level: req.event.protection_level || 'standard', protection_level: req.event.protection_level || 'standard',
image_quality: req.event.image_quality || 85, image_quality: req.event.image_quality || 85,
use_canvas_rendering: parseBooleanInput(req.event.use_canvas_rendering, false), use_canvas_rendering: parseBooleanInput(req.event.use_canvas_rendering, false),
fragmentation_level: req.event.fragmentation_level || 3,
overlay_protection: parseBooleanInput(req.event.overlay_protection, true) overlay_protection: parseBooleanInput(req.event.overlay_protection, true)
}; };
+1 -17
View File
@@ -117,8 +117,7 @@ router.get('/:slug/photo/:photoId/view', verifyGalleryAccess, blockHiddenGallery
const protectionSettings = { const protectionSettings = {
protectionLevel: eventProtectionLevel, protectionLevel: eventProtectionLevel,
quality: req.event.image_quality || 85, quality: req.event.image_quality || 85,
addFingerprint: req.event.add_fingerprint !== false, addFingerprint: req.event.add_fingerprint !== false
fragmentImage: eventProtectionLevel === 'maximum'
}; };
// Resolve photo location through the storage backend (managed) or local // Resolve photo location through the storage backend (managed) or local
@@ -151,21 +150,6 @@ router.get('/:slug/photo/:photoId/view', verifyGalleryAccess, blockHiddenGallery
? await withLocalCopy(storageKey, runProcessing) ? await withLocalCopy(storageKey, runProcessing)
: await runProcessing(resolvePhotoFilePath(req.event, photo)); : await runProcessing(resolvePhotoFilePath(req.event, photo));
if (processedImage.type === 'fragmented') {
return res.json({
type: 'fragmented',
fragments: processedImage.fragments.map(f => ({
index: f.index,
row: f.row,
col: f.col,
data: f.buffer.toString('base64'),
position: f.position
})),
dimensions: processedImage.originalDimensions,
fragmentDimensions: processedImage.fragmentDimensions
});
}
finalImage = processedImage; finalImage = processedImage;
} }
+1 -61
View File
@@ -120,8 +120,6 @@ router.get('/:slug/secure/:photoId/:token',
tokenLength: token?.length, tokenLength: token?.length,
hasAuthHeader: Boolean(req.headers.authorization), hasAuthHeader: Boolean(req.headers.authorization),
}); });
const { fragment } = req.query;
// Verify secure token // Verify secure token
const tokenValidation = secureImageService.verifySecureToken( const tokenValidation = secureImageService.verifySecureToken(
token, token,
@@ -212,8 +210,7 @@ router.get('/:slug/secure/:photoId/:token',
const protectionSettings = { const protectionSettings = {
protectionLevel: event.protection_level || 'standard', protectionLevel: event.protection_level || 'standard',
quality: event.image_quality || 85, quality: event.image_quality || 85,
addFingerprint: event.add_fingerprint !== false, addFingerprint: event.add_fingerprint !== false
fragmentImage: event.use_canvas_rendering === true && fragment !== undefined
}; };
let processedImage; let processedImage;
@@ -232,11 +229,6 @@ router.get('/:slug/secure/:photoId/:token',
return res.status(404).json({ error: 'Photo file not found' }); return res.status(404).json({ error: 'Photo file not found' });
} }
// Handle fragmented images
if (processedImage.type === 'fragmented') {
return await handleFragmentedImage(req, res, processedImage, fragment);
}
// Log successful access // Log successful access
await secureImageService.logImageAccess( await secureImageService.logImageAccess(
photoId, photoId,
@@ -267,58 +259,6 @@ router.get('/:slug/secure/:photoId/:token',
} }
); );
/**
* Handle fragmented image delivery
*/
async function handleFragmentedImage(req, res, fragmentedImage, fragmentIndex) {
const { photoId } = req.params;
try {
if (fragmentIndex === undefined) {
// Return fragment metadata
res.json({
type: 'fragmented',
fragments: fragmentedImage.fragments.length,
dimensions: fragmentedImage.originalDimensions,
fragmentDimensions: fragmentedImage.fragmentDimensions
});
return;
}
const index = parseInt(fragmentIndex);
if (isNaN(index) || index < 0 || index >= fragmentedImage.fragments.length) {
return res.status(400).json({ error: 'Invalid fragment index' });
}
const fragment = fragmentedImage.fragments[index];
// Log fragment access
await secureImageService.logImageAccess(
photoId,
req.event.id,
req.clientInfo,
`fragment_${index}`
);
res.set({
'Content-Type': 'image/jpeg',
'Content-Length': fragment.buffer.length,
'X-Fragment-Index': index,
'X-Fragment-Position': JSON.stringify(fragment.position)
});
res.send(fragment.buffer);
} catch (error) {
logger.error('Error serving image fragment', {
error: error.message,
fragmentIndex,
photoId
});
res.status(500).json({ error: 'Failed to serve image fragment' });
}
}
/** /**
* Download protected image with watermark * Download protected image with watermark
*/ */
+3 -56
View File
@@ -175,8 +175,7 @@ class SecureImageService {
quality = 85, quality = 85,
maxWidth = 1920, maxWidth = 1920,
maxHeight = 1080, maxHeight = 1080,
addFingerprint = true, addFingerprint = true
fragmentImage = false
} = options; } = options;
try { try {
@@ -187,7 +186,7 @@ class SecureImageService {
// For standard protection without fingerprinting, return original file // For standard protection without fingerprinting, return original file
// This avoids unnecessary recompression when no protection features are needed // This avoids unnecessary recompression when no protection features are needed
if (protectionLevel === 'standard' && !addFingerprint && !fragmentImage) { if (protectionLevel === 'standard' && !addFingerprint) {
return await fs.readFile(imagePath); return await fs.readFile(imagePath);
} }
@@ -268,14 +267,7 @@ class SecureImageService {
}); });
} }
const buffer = await image.toBuffer(); return await image.toBuffer();
// Fragment image if requested (for canvas reconstruction)
if (fragmentImage && protectionLevel === 'maximum') {
return await this.fragmentImageBuffer(buffer, metadata);
}
return buffer;
} catch (error) { } catch (error) {
logger.error('Error processing protected image:', error); logger.error('Error processing protected image:', error);
// Return original on error // Return original on error
@@ -283,51 +275,6 @@ class SecureImageService {
} }
} }
/**
* Fragment image into multiple pieces for canvas reconstruction
*/
async fragmentImageBuffer(buffer, metadata) {
const { width, height } = metadata;
const fragments = [];
// Create 3x3 grid of fragments
const cols = 3;
const rows = 3;
const fragmentWidth = Math.floor(width / cols);
const fragmentHeight = Math.floor(height / rows);
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
const left = col * fragmentWidth;
const top = row * fragmentHeight;
const fragment = await sharp(buffer)
.extract({
left,
top,
width: fragmentWidth,
height: fragmentHeight
})
.toBuffer();
fragments.push({
index: row * cols + col,
row,
col,
buffer: fragment,
position: { left, top, width: fragmentWidth, height: fragmentHeight }
});
}
}
return {
type: 'fragmented',
fragments,
originalDimensions: { width, height },
fragmentDimensions: { width: fragmentWidth, height: fragmentHeight, cols, rows }
};
}
/** /**
* Log image access for security monitoring * Log image access for security monitoring
*/ */
@@ -12,9 +12,6 @@ interface ProtectedImageProps extends React.CanvasHTMLAttributes<HTMLCanvasEleme
alt: string; alt: string;
protectionLevel?: ProtectionLevel; protectionLevel?: ProtectionLevel;
watermarkText?: string; watermarkText?: string;
fragmentGrid?: boolean;
gridSize?: number;
scrambleFragments?: boolean;
invisibleWatermark?: boolean; invisibleWatermark?: boolean;
onProtectionViolation?: (violationType: string) => void; onProtectionViolation?: (violationType: string) => void;
fallbackSrc?: string; fallbackSrc?: string;
@@ -26,9 +23,6 @@ export const ProtectedImage: React.FC<ProtectedImageProps> = ({
alt, alt,
protectionLevel = 'standard', protectionLevel = 'standard',
watermarkText, watermarkText,
fragmentGrid = false,
gridSize = 4,
scrambleFragments = false,
invisibleWatermark = false, invisibleWatermark = false,
onProtectionViolation, onProtectionViolation,
fallbackSrc, fallbackSrc,
@@ -120,52 +114,6 @@ export const ProtectedImage: React.FC<ProtectedImageProps> = ({
ctx.shadowOffsetY = 0; ctx.shadowOffsetY = 0;
}, []); }, []);
// Fragment and scramble image for maximum protection
const renderFragmentedImage = useCallback((
ctx: CanvasRenderingContext2D,
img: HTMLImageElement,
width: number,
height: number
) => {
const fragmentWidth = width / gridSize;
const fragmentHeight = height / gridSize;
const fragments: Array<{ x: number; y: number; destX: number; destY: number }> = [];
// Create fragment map
for (let row = 0; row < gridSize; row++) {
for (let col = 0; col < gridSize; col++) {
fragments.push({
x: col * fragmentWidth,
y: row * fragmentHeight,
destX: col * fragmentWidth,
destY: row * fragmentHeight,
});
}
}
// Scramble fragments if requested
if (scrambleFragments) {
for (let i = fragments.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const temp = fragments[i].destX;
const tempY = fragments[i].destY;
fragments[i].destX = fragments[j].destX;
fragments[i].destY = fragments[j].destY;
fragments[j].destX = temp;
fragments[j].destY = tempY;
}
}
// Draw fragments
fragments.forEach(fragment => {
ctx.drawImage(
img,
fragment.x, fragment.y, fragmentWidth, fragmentHeight,
fragment.destX, fragment.destY, fragmentWidth, fragmentHeight
);
});
}, [gridSize, scrambleFragments]);
// Main canvas rendering function - wrapped in useCallback to prevent infinite re-renders // Main canvas rendering function - wrapped in useCallback to prevent infinite re-renders
const renderToCanvas = useCallback(() => { const renderToCanvas = useCallback(() => {
if (!canvasRef.current || !imageRef.current) { if (!canvasRef.current || !imageRef.current) {
@@ -199,14 +147,9 @@ export const ProtectedImage: React.FC<ProtectedImageProps> = ({
ctx.globalCompositeOperation = 'source-over'; // Reset composite operation ctx.globalCompositeOperation = 'source-over'; // Reset composite operation
try { try {
if (fragmentGrid && (protectionLevel === 'enhanced' || protectionLevel === 'maximum')) { // Ensure image is valid before drawing
// Render fragmented image if (img.naturalWidth > 0 && img.naturalHeight > 0) {
renderFragmentedImage(ctx, img, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
} else {
// Render normal image - ensure image is valid before drawing
if (img.naturalWidth > 0 && img.naturalHeight > 0) {
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
}
} }
// Apply watermarks // Apply watermarks
@@ -242,7 +185,7 @@ export const ProtectedImage: React.FC<ProtectedImageProps> = ({
reportViolation('canvas_rendering_error'); reportViolation('canvas_rendering_error');
setError(true); setError(true);
} }
}, [fragmentGrid, protectionLevel, renderFragmentedImage, watermarkText, invisibleWatermark, applyInvisibleWatermark, applyVisibleWatermark, reportViolation]); }, [protectionLevel, watermarkText, invisibleWatermark, applyInvisibleWatermark, applyVisibleWatermark, reportViolation]);
// Set up protection event listeners // Set up protection event listeners
useEffect(() => { useEffect(() => {
@@ -128,25 +128,6 @@ describe('ProtectedImage', () => {
expect(mockContext.fillText).toHaveBeenCalled(); expect(mockContext.fillText).toHaveBeenCalled();
}); });
it('handles fragment grid rendering', async () => {
render(
<ProtectedImage
{...defaultProps}
fragmentGrid={true}
gridSize={4}
protectionLevel="enhanced"
/>
);
await waitFor(() => {
const canvas = screen.getByRole('img', { name: 'Test image' });
expect(canvas).toHaveStyle({ opacity: '1' });
});
// Verify multiple drawImage calls for fragments
expect(mockContext.drawImage).toHaveBeenCalled();
});
it('blocks interactions in maximum protection mode', async () => { it('blocks interactions in maximum protection mode', async () => {
const onViolation = vi.fn(); const onViolation = vi.fn();
@@ -230,25 +211,6 @@ describe('ProtectedImage', () => {
expect(mockContext.putImageData).toHaveBeenCalled(); expect(mockContext.putImageData).toHaveBeenCalled();
}); });
it('scrambles fragments when enabled', async () => {
render(
<ProtectedImage
{...defaultProps}
fragmentGrid={true}
scrambleFragments={true}
protectionLevel="maximum"
/>
);
await waitFor(() => {
const canvas = screen.getByRole('img', { name: 'Test image' });
expect(canvas).toHaveStyle({ opacity: '1' });
});
// Fragment scrambling should result in multiple drawImage calls
expect(mockContext.drawImage).toHaveBeenCalled();
});
it('adds random noise in maximum protection', async () => { it('adds random noise in maximum protection', async () => {
render( render(
<ProtectedImage <ProtectedImage
@@ -15,7 +15,6 @@ interface ImageSecuritySettings {
max_image_requests_per_hour: number; max_image_requests_per_hour: number;
suspicious_activity_threshold: number; suspicious_activity_threshold: number;
enable_canvas_rendering: boolean; enable_canvas_rendering: boolean;
default_fragmentation_level: number;
security_monitoring_enabled: boolean; security_monitoring_enabled: boolean;
block_suspicious_ips: boolean; block_suspicious_ips: boolean;
log_security_events_to_db: boolean; log_security_events_to_db: boolean;
@@ -31,7 +30,6 @@ const defaultSettings: ImageSecuritySettings = {
max_image_requests_per_hour: 500, max_image_requests_per_hour: 500,
suspicious_activity_threshold: 10, suspicious_activity_threshold: 10,
enable_canvas_rendering: false, enable_canvas_rendering: false,
default_fragmentation_level: 3,
security_monitoring_enabled: true, security_monitoring_enabled: true,
block_suspicious_ips: true, block_suspicious_ips: true,
log_security_events_to_db: true, log_security_events_to_db: true,
@@ -161,21 +159,6 @@ export const ImageSecurityTab: React.FC = () => {
/> />
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-1">{t('settings.imageSecurity.imageQualityHelp', '1-100, higher = better quality')}</p> <p className="text-xs text-neutral-500 dark:text-neutral-400 mt-1">{t('settings.imageSecurity.imageQualityHelp', '1-100, higher = better quality')}</p>
</div> </div>
<div>
<label className="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1">
{t('settings.imageSecurity.fragmentationLevel', 'Fragmentation Level')}
</label>
<input
type="number"
min="1"
max="10"
value={settings.default_fragmentation_level}
onChange={(e) => handleChange('default_fragmentation_level', parseInt(e.target.value) || 3)}
className="w-full px-3 py-2 border border-neutral-300 dark:border-neutral-600 rounded-lg bg-white dark:bg-neutral-800 text-neutral-900 dark:text-neutral-100 focus:ring-2 focus:ring-primary-500 focus:border-primary-500"
/>
<p className="text-xs text-neutral-500 dark:text-neutral-400 mt-1">{t('settings.imageSecurity.fragmentationLevelHelp', '1-10, higher = more protection')}</p>
</div>
</div> </div>
<div className="space-y-3 pt-2"> <div className="space-y-3 pt-2">
-1
View File
@@ -20,7 +20,6 @@ interface UseImageProtectionOptions {
blockKeyboardShortcuts?: boolean; blockKeyboardShortcuts?: boolean;
detectPrintScreen?: boolean; detectPrintScreen?: boolean;
watermarkText?: string; watermarkText?: string;
fragmentGrid?: boolean;
} }
export const useImageProtection = (options: UseImageProtectionOptions) => { export const useImageProtection = (options: UseImageProtectionOptions) => {
-2
View File
@@ -1852,7 +1852,6 @@
"defaultProtectionHelp": "Diese Einstellungen gelten für alle neuen Veranstaltungen. Einzelne Veranstaltungen können diese Standardwerte überschreiben.", "defaultProtectionHelp": "Diese Einstellungen gelten für alle neuen Veranstaltungen. Einzelne Veranstaltungen können diese Standardwerte überschreiben.",
"protectionLevel": "Standard-Schutzstufe", "protectionLevel": "Standard-Schutzstufe",
"imageQuality": "Standard-Bildqualität", "imageQuality": "Standard-Bildqualität",
"fragmentationLevel": "Fragmentierungsstufe",
"enableDevtools": "DevTools-Erkennung standardmäßig aktivieren", "enableDevtools": "DevTools-Erkennung standardmäßig aktivieren",
"enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)", "enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)",
"rateLimiting": "Ratenbegrenzung", "rateLimiting": "Ratenbegrenzung",
@@ -1869,7 +1868,6 @@
"infoTitle": "Über Bildschutz", "infoTitle": "Über Bildschutz",
"infoText": "Diese Schutzfunktionen helfen, gelegentliches Herunterladen und Kopieren zu verhindern, können aber nicht alle Methoden blockieren. Entschlossene Benutzer finden möglicherweise trotzdem Wege, Bilder zu erfassen. Erwägen Sie die Verwendung von Wasserzeichen und rechtlichen Vereinbarungen für umfassenden Schutz.", "infoText": "Diese Schutzfunktionen helfen, gelegentliches Herunterladen und Kopieren zu verhindern, können aber nicht alle Methoden blockieren. Entschlossene Benutzer finden möglicherweise trotzdem Wege, Bilder zu erfassen. Erwägen Sie die Verwendung von Wasserzeichen und rechtlichen Vereinbarungen für umfassenden Schutz.",
"imageQualityHelp": "1100, höher = bessere Qualität", "imageQualityHelp": "1100, höher = bessere Qualität",
"fragmentationLevelHelp": "110, höher = mehr Schutz",
"suspiciousActivityThresholdHelp": "Verstöße, bevor als verdächtig markiert wird", "suspiciousActivityThresholdHelp": "Verstöße, bevor als verdächtig markiert wird",
"autoBlockThresholdHelp": "Verstöße, bevor die IP automatisch gesperrt wird" "autoBlockThresholdHelp": "Verstöße, bevor die IP automatisch gesperrt wird"
}, },
-2
View File
@@ -1415,7 +1415,6 @@
"defaultProtectionHelp": "These settings apply to all new events. Individual events can override these defaults.", "defaultProtectionHelp": "These settings apply to all new events. Individual events can override these defaults.",
"protectionLevel": "Default Protection Level", "protectionLevel": "Default Protection Level",
"imageQuality": "Default Image Quality", "imageQuality": "Default Image Quality",
"fragmentationLevel": "Fragmentation Level",
"enableDevtools": "Enable DevTools detection by default", "enableDevtools": "Enable DevTools detection by default",
"enableCanvas": "Enable canvas rendering by default (advanced protection)", "enableCanvas": "Enable canvas rendering by default (advanced protection)",
"rateLimiting": "Rate Limiting", "rateLimiting": "Rate Limiting",
@@ -1432,7 +1431,6 @@
"infoTitle": "About Image Protection", "infoTitle": "About Image Protection",
"infoText": "These protection features help prevent casual downloading and copying but cannot block all methods. Determined users may still find ways to capture images. Consider using watermarks and legal agreements for comprehensive protection.", "infoText": "These protection features help prevent casual downloading and copying but cannot block all methods. Determined users may still find ways to capture images. Consider using watermarks and legal agreements for comprehensive protection.",
"imageQualityHelp": "1-100, higher = better quality", "imageQualityHelp": "1-100, higher = better quality",
"fragmentationLevelHelp": "1-10, higher = more protection",
"suspiciousActivityThresholdHelp": "Violations before flagging as suspicious", "suspiciousActivityThresholdHelp": "Violations before flagging as suspicious",
"autoBlockThresholdHelp": "Violations before auto-blocking IP" "autoBlockThresholdHelp": "Violations before auto-blocking IP"
}, },
-1
View File
@@ -941,7 +941,6 @@
"defaultProtectionHelp": "Estos ajustes se aplican a todos los nuevos eventos. Los eventos individuales pueden anular estos valores por defecto.", "defaultProtectionHelp": "Estos ajustes se aplican a todos los nuevos eventos. Los eventos individuales pueden anular estos valores por defecto.",
"protectionLevel": "Nivel de protección por defecto", "protectionLevel": "Nivel de protección por defecto",
"imageQuality": "Calidad de imagen por defecto", "imageQuality": "Calidad de imagen por defecto",
"fragmentationLevel": "Nivel de fragmentación",
"enableDevtools": "Habilitar detección de DevTools por defecto", "enableDevtools": "Habilitar detección de DevTools por defecto",
"enableCanvas": "Habilitar renderizado con canvas por defecto (protección avanzada)", "enableCanvas": "Habilitar renderizado con canvas por defecto (protección avanzada)",
"rateLimiting": "Limitación de velocidad", "rateLimiting": "Limitación de velocidad",
-1
View File
@@ -911,7 +911,6 @@
"defaultProtectionHelp": "Ces paramètres s'appliquent à tous les nouveaux événements. Les événements individuels peuvent remplacer ces valeurs par défaut.", "defaultProtectionHelp": "Ces paramètres s'appliquent à tous les nouveaux événements. Les événements individuels peuvent remplacer ces valeurs par défaut.",
"protectionLevel": "Niveau de protection par défaut", "protectionLevel": "Niveau de protection par défaut",
"imageQuality": "Qualité d'image par défaut", "imageQuality": "Qualité d'image par défaut",
"fragmentationLevel": "Niveau de fragmentation",
"enableDevtools": "Activer la détection des outils DevTools par défaut", "enableDevtools": "Activer la détection des outils DevTools par défaut",
"enableCanvas": "Activer le rendu canvas par défaut (protection avancée)", "enableCanvas": "Activer le rendu canvas par défaut (protection avancée)",
"rateLimiting": "Limitation de débit", "rateLimiting": "Limitation de débit",
-1
View File
@@ -907,7 +907,6 @@
"defaultProtectionHelp": "Deze instellingen zijn van toepassing op alle nieuwe evenementen. Individuele evenementen kunnen deze standaardwaarden overschrijven.", "defaultProtectionHelp": "Deze instellingen zijn van toepassing op alle nieuwe evenementen. Individuele evenementen kunnen deze standaardwaarden overschrijven.",
"protectionLevel": "Standaard beveiligingsniveau", "protectionLevel": "Standaard beveiligingsniveau",
"imageQuality": "Standaard beeldkwaliteit", "imageQuality": "Standaard beeldkwaliteit",
"fragmentationLevel": "Fragmentatieniveau",
"enableDevtools": "Standaard DevTools-detectie inschakelen", "enableDevtools": "Standaard DevTools-detectie inschakelen",
"enableCanvas": "Standaard canvas-rendering inschakelen (geavanceerde beveiliging)", "enableCanvas": "Standaard canvas-rendering inschakelen (geavanceerde beveiliging)",
"rateLimiting": "Snelheidsbeperking", "rateLimiting": "Snelheidsbeperking",
-1
View File
@@ -924,7 +924,6 @@
"defaultProtectionHelp": "Aplicado a novos eventos por padrão. Podem ser substituídas individualmente.", "defaultProtectionHelp": "Aplicado a novos eventos por padrão. Podem ser substituídas individualmente.",
"protectionLevel": "Nível de Proteção Padrão", "protectionLevel": "Nível de Proteção Padrão",
"imageQuality": "Qualidade de Imagem Padrão", "imageQuality": "Qualidade de Imagem Padrão",
"fragmentationLevel": "Nível de Fragmentação",
"enableDevtools": "Ativar detecção de DevTools por padrão", "enableDevtools": "Ativar detecção de DevTools por padrão",
"enableCanvas": "Ativar renderização em Canvas por padrão", "enableCanvas": "Ativar renderização em Canvas por padrão",
"rateLimiting": "Limite de Requisições", "rateLimiting": "Limite de Requisições",
-1
View File
@@ -930,7 +930,6 @@
"defaultProtectionHelp": "Эти настройки применяются ко всем новым событиям. Отдельные события могут их переопределить.", "defaultProtectionHelp": "Эти настройки применяются ко всем новым событиям. Отдельные события могут их переопределить.",
"protectionLevel": "Уровень защиты по умолчанию", "protectionLevel": "Уровень защиты по умолчанию",
"imageQuality": "Качество изображений по умолчанию", "imageQuality": "Качество изображений по умолчанию",
"fragmentationLevel": "Уровень фрагментации",
"enableDevtools": "Включить обнаружение DevTools по умолчанию", "enableDevtools": "Включить обнаружение DevTools по умолчанию",
"enableCanvas": "Включить рендеринг Canvas по умолчанию (расширенная защита)", "enableCanvas": "Включить рендеринг Canvas по умолчанию (расширенная защита)",
"rateLimiting": "Ограничение скорости", "rateLimiting": "Ограничение скорости",
-1
View File
@@ -911,7 +911,6 @@
"defaultProtectionHelp": "Te nastavitve veljajo za vse nove dogodke. Posamezni dogodki lahko te privzete nastavitve prepišejo.", "defaultProtectionHelp": "Te nastavitve veljajo za vse nove dogodke. Posamezni dogodki lahko te privzete nastavitve prepišejo.",
"protectionLevel": "Privzeta raven zaščite", "protectionLevel": "Privzeta raven zaščite",
"imageQuality": "Privzeta kakovost slike", "imageQuality": "Privzeta kakovost slike",
"fragmentationLevel": "Raven fragmentacije",
"enableDevtools": "Privzeto omogoči zaznavanje DevTools", "enableDevtools": "Privzeto omogoči zaznavanje DevTools",
"enableCanvas": "Privzeto omogoči izris prek canvas (napredna zaščita)", "enableCanvas": "Privzeto omogoči izris prek canvas (napredna zaščita)",
"rateLimiting": "Omejevanje hitrosti", "rateLimiting": "Omejevanje hitrosti",
-1
View File
@@ -290,7 +290,6 @@ export interface GalleryData {
image_quality?: number; image_quality?: number;
use_canvas_rendering?: boolean; use_canvas_rendering?: boolean;
enable_devtools_protection?: boolean; enable_devtools_protection?: boolean;
fragmentation_level?: number;
overlay_protection?: boolean; overlay_protection?: boolean;
// Hero logo customization fields // Hero logo customization fields
hero_logo_visible?: boolean | null; hero_logo_visible?: boolean | null;
-15
View File
@@ -49,7 +49,6 @@ export interface ImageProtectionOptions {
blockKeyboardShortcuts?: boolean; blockKeyboardShortcuts?: boolean;
detectPrintScreen?: boolean; detectPrintScreen?: boolean;
watermarkText?: string; watermarkText?: string;
fragmentGrid?: boolean;
} }
export interface ProtectedImageProps { export interface ProtectedImageProps {
@@ -57,9 +56,6 @@ export interface ProtectedImageProps {
alt: string; alt: string;
protectionLevel?: ProtectionLevel; protectionLevel?: ProtectionLevel;
watermarkText?: string; watermarkText?: string;
fragmentGrid?: boolean;
gridSize?: number;
scrambleFragments?: boolean;
invisibleWatermark?: boolean; invisibleWatermark?: boolean;
onProtectionViolation?: (violationType: ViolationType) => void; onProtectionViolation?: (violationType: ViolationType) => void;
fallbackSrc?: string; fallbackSrc?: string;
@@ -83,13 +79,6 @@ export interface WatermarkConfig {
rotation: number; rotation: number;
} }
export interface FragmentConfig {
enabled: boolean;
gridSize: number;
scramble: boolean;
randomSeed?: number;
}
export interface SteganographyConfig { export interface SteganographyConfig {
enabled: boolean; enabled: boolean;
message: string; message: string;
@@ -121,7 +110,6 @@ export interface CanvasProtectionContext {
originalImageData: ImageData; originalImageData: ImageData;
protectedImageData: ImageData; protectedImageData: ImageData;
watermarkApplied: boolean; watermarkApplied: boolean;
fragmentsScrambled: boolean;
} }
export interface PrintScreenDetectionState { export interface PrintScreenDetectionState {
@@ -181,7 +169,6 @@ export interface ProtectionConfig {
rendering: { rendering: {
canvas: { canvas: {
enabled: boolean; enabled: boolean;
fragmentGrid: FragmentConfig;
watermark: WatermarkConfig; watermark: WatermarkConfig;
steganography: SteganographyConfig; steganography: SteganographyConfig;
noiseInjection: boolean; noiseInjection: boolean;
@@ -231,9 +218,7 @@ export type ProtectionProps = {
export type CanvasProtectionProps = ProtectionProps & { export type CanvasProtectionProps = ProtectionProps & {
useCanvasRendering?: boolean; useCanvasRendering?: boolean;
fragmentGrid?: boolean;
watermarkText?: string; watermarkText?: string;
scrambleFragments?: boolean;
invisibleWatermark?: boolean; invisibleWatermark?: boolean;
}; };