nodebb-plugin-pdf-secure 1.2.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,28 @@ const pdfHandler = require('./pdf-handler');
5
5
 
6
6
  const Controllers = module.exports;
7
7
 
8
+ // XOR key for partial encryption (rotates through this pattern)
9
+ const XOR_KEY = [0x5A, 0x3C, 0x7E, 0x1F, 0x9D, 0xB2, 0x4A, 0xE8];
10
+
11
+ // Partial XOR - encrypts first 10KB and every 50th byte after that
12
+ function partialXorEncode(buffer) {
13
+ const data = Buffer.from(buffer);
14
+ const keyLen = XOR_KEY.length;
15
+
16
+ // Encrypt first 10KB fully
17
+ const fullEncryptLen = Math.min(10240, data.length);
18
+ for (let i = 0; i < fullEncryptLen; i++) {
19
+ data[i] = data[i] ^ XOR_KEY[i % keyLen];
20
+ }
21
+
22
+ // Encrypt every 50th byte after that
23
+ for (let i = fullEncryptLen; i < data.length; i += 50) {
24
+ data[i] = data[i] ^ XOR_KEY[i % keyLen];
25
+ }
26
+
27
+ return data;
28
+ }
29
+
8
30
  Controllers.renderAdminPage = function (req, res) {
9
31
  res.render('admin/plugins/pdf-secure', {
10
32
  title: 'PDF Secure Viewer',
@@ -34,14 +56,19 @@ Controllers.servePdfBinary = async function (req, res) {
34
56
  pdfBuffer = await pdfHandler.getSinglePagePdf(data.file);
35
57
  }
36
58
 
59
+ // Apply partial XOR encryption
60
+ const encodedBuffer = partialXorEncode(pdfBuffer);
61
+
37
62
  res.set({
38
63
  'Content-Type': 'application/octet-stream',
39
64
  'Cache-Control': 'no-store, no-cache, must-revalidate, private',
40
65
  'X-Content-Type-Options': 'nosniff',
41
66
  'Content-Disposition': 'inline',
67
+ // Send XOR key as header for client decoding
68
+ 'X-PDF-Key': Buffer.from(XOR_KEY).toString('base64'),
42
69
  });
43
70
 
44
- return res.send(pdfBuffer);
71
+ return res.send(encodedBuffer);
45
72
  } catch (err) {
46
73
  if (err.message === 'File not found') {
47
74
  return res.status(404).json({ error: 'PDF not found' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodebb-plugin-pdf-secure",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Secure PDF viewer plugin for NodeBB - prevents downloading, enables canvas-only rendering with Premium group support",
5
5
  "main": "library.js",
6
6
  "repository": {
@@ -1420,6 +1420,26 @@
1420
1420
  generateThumbnails();
1421
1421
  }
1422
1422
 
1423
+ // Partial XOR decoder - must match backend encoding
1424
+ function partialXorDecode(encodedData, keyBase64) {
1425
+ const key = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
1426
+ const data = new Uint8Array(encodedData);
1427
+ const keyLen = key.length;
1428
+
1429
+ // Decrypt first 10KB fully
1430
+ const fullDecryptLen = Math.min(10240, data.length);
1431
+ for (let i = 0; i < fullDecryptLen; i++) {
1432
+ data[i] = data[i] ^ key[i % keyLen];
1433
+ }
1434
+
1435
+ // Decrypt every 50th byte after that
1436
+ for (let i = fullDecryptLen; i < data.length; i += 50) {
1437
+ data[i] = data[i] ^ key[i % keyLen];
1438
+ }
1439
+
1440
+ return data.buffer;
1441
+ }
1442
+
1423
1443
  // Auto-load PDF if config is present (injected by NodeBB plugin)
1424
1444
  async function autoLoadSecurePDF() {
1425
1445
  if (!window.PDF_SECURE_CONFIG || !window.PDF_SECURE_CONFIG.filename) {
@@ -1457,7 +1477,7 @@
1457
1477
  const nonceData = await nonceRes.json();
1458
1478
  const nonce = nonceData.response.nonce;
1459
1479
 
1460
- // Step 2: Fetch PDF binary
1480
+ // Step 2: Fetch encrypted PDF binary
1461
1481
  const pdfUrl = config.relativePath + '/api/v3/plugins/pdf-secure/pdf-data?nonce=' + encodeURIComponent(nonce);
1462
1482
  const pdfRes = await fetch(pdfUrl, { credentials: 'same-origin' });
1463
1483
 
@@ -1465,12 +1485,33 @@
1465
1485
  throw new Error('PDF yüklenemedi (' + pdfRes.status + ')');
1466
1486
  }
1467
1487
 
1468
- const pdfBuffer = await pdfRes.arrayBuffer();
1469
- console.log('[PDF-Secure] PDF loaded:', pdfBuffer.byteLength, 'bytes');
1488
+ // Get XOR key from header
1489
+ const xorKey = pdfRes.headers.get('X-PDF-Key');
1490
+ const encodedBuffer = await pdfRes.arrayBuffer();
1491
+
1492
+ console.log('[PDF-Secure] Encrypted data received:', encodedBuffer.byteLength, 'bytes');
1493
+
1494
+ // Step 3: Decode XOR encrypted data
1495
+ let pdfBuffer;
1496
+ if (xorKey) {
1497
+ console.log('[PDF-Secure] Decoding XOR encrypted data...');
1498
+ pdfBuffer = partialXorDecode(encodedBuffer, xorKey);
1499
+ } else {
1500
+ // Fallback for backward compatibility
1501
+ pdfBuffer = encodedBuffer;
1502
+ }
1503
+
1504
+ console.log('[PDF-Secure] PDF decoded successfully');
1470
1505
 
1471
- // Load into viewer
1506
+ // Step 4: Load into viewer
1472
1507
  await loadPDFFromBuffer(pdfBuffer);
1473
1508
 
1509
+ // Step 5: Security - clear references to prevent extraction
1510
+ // Nullify the buffer after loading
1511
+ pdfBuffer = null;
1512
+
1513
+ console.log('[PDF-Secure] Buffer references cleared');
1514
+
1474
1515
  } catch (err) {
1475
1516
  console.error('[PDF-Secure] Auto-load error:', err);
1476
1517
  if (dropzone) {
@@ -3102,14 +3143,10 @@
3102
3143
  }
3103
3144
  }, true);
3104
3145
 
3105
- // 2. Block context menu (right-click)
3146
+ // 2. Block context menu (right-click) - EVERYWHERE
3106
3147
  document.addEventListener('contextmenu', function (e) {
3107
- // Allow our custom context menu for annotations
3108
- if (e.target.closest('.annotationLayer') || e.target.closest('.pdfViewer')) {
3109
- // Keep custom context menu behavior
3110
- return;
3111
- }
3112
3148
  e.preventDefault();
3149
+ e.stopPropagation();
3113
3150
  return false;
3114
3151
  }, true);
3115
3152
 
@@ -3131,41 +3168,14 @@
3131
3168
  return false;
3132
3169
  }, true);
3133
3170
 
3134
- // 5. DevTools detection (basic)
3135
- let devToolsOpen = false;
3136
- const threshold = 160;
3137
-
3138
- function checkDevTools() {
3139
- const widthThreshold = window.outerWidth - window.innerWidth > threshold;
3140
- const heightThreshold = window.outerHeight - window.innerHeight > threshold;
3141
-
3142
- if (widthThreshold || heightThreshold) {
3143
- if (!devToolsOpen) {
3144
- devToolsOpen = true;
3145
- console.log('[Security] DevTools detected');
3146
- // Optional: blur content when devtools open
3147
- document.body.classList.add('devtools-open');
3148
- }
3149
- } else {
3150
- if (devToolsOpen) {
3151
- devToolsOpen = false;
3152
- document.body.classList.remove('devtools-open');
3153
- }
3154
- }
3155
- }
3156
-
3157
- // Check periodically
3158
- setInterval(checkDevTools, 1000);
3159
- window.addEventListener('resize', checkDevTools);
3160
-
3161
- // 6. Block Print via window.print override
3171
+ // 5. Block Print via window.print override
3162
3172
  window.print = function () {
3163
3173
  console.log('[Security] Print function blocked');
3164
3174
  alert('Yazdırma bu belgede engellenmiştir.');
3165
3175
  return false;
3166
3176
  };
3167
3177
 
3168
- // 7. Disable beforeprint event
3178
+ // 6. Disable beforeprint event
3169
3179
  window.addEventListener('beforeprint', function (e) {
3170
3180
  e.preventDefault();
3171
3181
  document.body.style.display = 'none';
@@ -3175,7 +3185,7 @@
3175
3185
  document.body.style.display = '';
3176
3186
  });
3177
3187
 
3178
- // 8. Block screenshot keyboard shortcuts
3188
+ // 7. Block screenshot keyboard shortcuts
3179
3189
  document.addEventListener('keyup', function (e) {
3180
3190
  // PrintScreen key
3181
3191
  if (e.key === 'PrintScreen') {
@@ -3184,10 +3194,9 @@
3184
3194
  }
3185
3195
  }, true);
3186
3196
 
3187
- // 9. Visibility change detection (tab switching for screenshots)
3197
+ // 8. Visibility change detection (tab switching for screenshots)
3188
3198
  document.addEventListener('visibilitychange', function () {
3189
3199
  if (document.hidden) {
3190
- // User switched tabs - could be for screenshot tools
3191
3200
  console.log('[Security] Tab hidden');
3192
3201
  }
3193
3202
  });
@@ -3195,30 +3204,6 @@
3195
3204
  console.log('[Security] All protection features initialized');
3196
3205
  })();
3197
3206
  </script>
3198
-
3199
- <!-- Security CSS for DevTools detection -->
3200
- <style>
3201
- .devtools-open #viewerContainer,
3202
- .devtools-open #viewer,
3203
- .devtools-open .pdfViewer {
3204
- filter: blur(20px) !important;
3205
- pointer-events: none !important;
3206
- }
3207
-
3208
- .devtools-open::after {
3209
- content: 'Geliştirici araçları açıkken içerik görüntülenemez.';
3210
- position: fixed;
3211
- top: 50%;
3212
- left: 50%;
3213
- transform: translate(-50%, -50%);
3214
- background: rgba(0, 0, 0, 0.9);
3215
- color: #fff;
3216
- padding: 30px 50px;
3217
- border-radius: 10px;
3218
- font-size: 18px;
3219
- z-index: 99999;
3220
- }
3221
- </style>
3222
3207
  </body>
3223
3208
 
3224
3209
  </html>