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.
- package/.claude/settings.local.json +6 -1
- package/lib/controllers.js +22 -7
- package/lib/gemini-chat.js +452 -426
- package/lib/nonce-store.js +4 -4
- package/lib/pdf-handler.js +0 -1
- package/lib/topic-access.js +96 -0
- package/library.js +59 -5
- package/package.json +1 -1
- package/plugin.json +4 -0
- package/static/lib/main.js +2 -73
- package/static/viewer-app.js +18 -62
- package/static/viewer.html +257 -55
|
@@ -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
|
}
|
package/lib/controllers.js
CHANGED
|
@@ -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
|
-
|
|
64
|
-
return { allowed:
|
|
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
|
-
|
|
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) {
|