nodebb-plugin-pdf-secure2 1.4.3 → 1.5.0

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,7 +5,12 @@
5
5
  "WebFetch(domain:github.com)",
6
6
  "Bash(dir:*)",
7
7
  "Bash(npm pack:*)",
8
- "Bash(tar:*)"
8
+ "Bash(tar:*)",
9
+ "Bash(grep:*)",
10
+ "Bash(find /c/Users/kadir/OneDrive/Masaüstü/Projeler/nodebb-plugin-pdf-secure/nodebb-plugin-pdf-secure -type f \\\\\\(-name *.conf -o -name *.config -o -name *nginx* -o -name *apache* \\\\\\))",
11
+ "Bash(xargs ls:*)",
12
+ "WebSearch",
13
+ "WebFetch(domain:community.nodebb.org)"
9
14
  ]
10
15
  }
11
16
  }
@@ -7,6 +7,7 @@ const pdfHandler = require('./pdf-handler');
7
7
  const geminiChat = require('./gemini-chat');
8
8
  const groups = require.main.require('./src/groups');
9
9
  const db = require.main.require('./src/database');
10
+ const topicAccess = require('./topic-access');
10
11
 
11
12
  const Controllers = module.exports;
12
13
 
@@ -60,8 +61,8 @@ async function checkRateLimit(uid, tier) {
60
61
  await db.sortedSetAdd(key, now, `${now}:${crypto.randomBytes(4).toString('hex')}`);
61
62
  return { allowed: true, used: count + 1, max: rateConfig.max };
62
63
  } catch (err) {
63
- // Graceful degradation: allow request if DB fails
64
- return { allowed: true, used: 0, max: rateConfig.max };
64
+ console.error('[PDF-Secure] Rate limit DB error:', err.message);
65
+ return { allowed: false, used: 0, max: rateConfig.max };
65
66
  }
66
67
  }
67
68
 
@@ -106,7 +107,8 @@ async function checkQuota(uid, tier) {
106
107
  }
107
108
  return { allowed: true, used: totalTokens, max: cfg.max, resetsIn };
108
109
  } catch (err) {
109
- return { allowed: true, used: 0, max: cfg.max, resetsIn: 0 };
110
+ console.error('[PDF-Secure] Quota check DB error:', err.message);
111
+ return { allowed: false, used: 0, max: cfg.max, resetsIn: 0 };
110
112
  }
111
113
  }
112
114
 
@@ -265,7 +267,8 @@ Controllers.handleChat = async function (req, res) {
265
267
  }
266
268
 
267
269
  // Body validation
268
- const { filename, question, history } = req.body;
270
+ const { filename, question, history, tid, detailMode } = req.body;
271
+ const useDetailMode = tier === 'vip' && detailMode === true;
269
272
 
270
273
  if (!filename || typeof filename !== 'string') {
271
274
  return res.status(400).json({ error: 'Missing or invalid filename' });
@@ -275,6 +278,12 @@ Controllers.handleChat = async function (req, res) {
275
278
  return res.status(400).json({ error: 'Invalid filename' });
276
279
  }
277
280
 
281
+ // Topic-level access control
282
+ const accessResult = await topicAccess.validate(req.uid, tid, safeName);
283
+ if (!accessResult.allowed) {
284
+ return res.status(403).json({ error: accessResult.reason || 'Access denied' });
285
+ }
286
+
278
287
  const trimmedQuestion = typeof question === 'string' ? question.trim() : '';
279
288
  if (!trimmedQuestion || trimmedQuestion.length > 2000) {
280
289
  return res.status(400).json({ error: 'Question is required (max 2000 characters)' });
@@ -312,7 +321,7 @@ Controllers.handleChat = async function (req, res) {
312
321
  }
313
322
 
314
323
  try {
315
- const result = await geminiChat.chat(safeName, trimmedQuestion, history || [], tier);
324
+ const result = await geminiChat.chat(safeName, trimmedQuestion, history || [], tier, useDetailMode);
316
325
  // Record actual token usage after successful AI response
317
326
  // Sanitize: clamp to [0, 1000000] to prevent NaN/negative/absurd values
318
327
  const tokensUsed = Math.max(0, Math.min(parseInt(result.tokensUsed, 10) || 0, 1000000));
@@ -385,7 +394,7 @@ Controllers.getSuggestions = async function (req, res) {
385
394
  }
386
395
  suggestionsRateLimit.set(req.uid, now);
387
396
 
388
- const { filename } = req.query;
397
+ const { filename, tid } = req.query;
389
398
  if (!filename || typeof filename !== 'string') {
390
399
  return res.status(400).json({ error: 'Missing filename' });
391
400
  }
@@ -394,8 +403,14 @@ Controllers.getSuggestions = async function (req, res) {
394
403
  return res.status(400).json({ error: 'Invalid filename' });
395
404
  }
396
405
 
406
+ // Topic-level access control
407
+ const accessResult = await topicAccess.validate(req.uid, tid, safeName);
408
+ if (!accessResult.allowed) {
409
+ return res.status(403).json({ error: accessResult.reason || 'Access denied' });
410
+ }
411
+
397
412
  try {
398
- const suggestions = await geminiChat.generateSuggestions(safeName);
413
+ const suggestions = await geminiChat.generateSuggestions(safeName, tier);
399
414
  const quotaUsage = await getQuotaUsage(req.uid, tier);
400
415
  return res.json({ suggestions, quota: quotaUsage });
401
416
  } catch (err) {