nodebb-plugin-pdf-secure 1.2.18 → 1.2.20
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/docs/plans/2026-02-24-premium-gate-design.md +52 -0
- package/docs/plans/2026-02-24-premium-gate.md +323 -0
- package/lib/pdf-handler.js +33 -0
- package/library.js +17 -6
- package/package.json +1 -1
- package/static/viewer-app.js +194 -39
- package/static/viewer.html +296 -38
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Premium Gate Design
|
|
2
|
+
|
|
3
|
+
## Problem
|
|
4
|
+
All PDFs are currently fully viewable by all users. We need to restrict non-Premium users to viewing only the first page, with a lock overlay prompting them to upgrade.
|
|
5
|
+
|
|
6
|
+
## Requirements
|
|
7
|
+
- Users in the "Premium" NodeBB group see all PDF pages
|
|
8
|
+
- Users NOT in "Premium" see only the first page
|
|
9
|
+
- Locked pages show a warning overlay with:
|
|
10
|
+
- Lock icon + "Premium required" message
|
|
11
|
+
- Link to forumtest.ieu.app/premium
|
|
12
|
+
- Secondary message about uploading materials to become Premium
|
|
13
|
+
- Admins and Global Moderators always get full access
|
|
14
|
+
|
|
15
|
+
## Approach: Server-Side Control
|
|
16
|
+
|
|
17
|
+
### Server Changes
|
|
18
|
+
|
|
19
|
+
**library.js (viewer route, line ~83):**
|
|
20
|
+
- Replace `const isPremium = true;` with actual group membership check
|
|
21
|
+
- Use `groups.isMember(req.uid, 'Premium')`
|
|
22
|
+
- Admin + Global Mod bypass (already exists for direct access, reuse pattern)
|
|
23
|
+
- Inject `isPremium` and `totalPages` into `window.PDF_SECURE_CONFIG`
|
|
24
|
+
|
|
25
|
+
**lib/pdf-handler.js:**
|
|
26
|
+
- Add `getTotalPages(filename)` function
|
|
27
|
+
- Loads PDF, returns `srcDoc.getPageCount()`
|
|
28
|
+
- Used by viewer route to tell client how many pages exist
|
|
29
|
+
|
|
30
|
+
**lib/controllers.js:**
|
|
31
|
+
- No changes needed - already handles `isPremium` flag from nonce data
|
|
32
|
+
|
|
33
|
+
### Client Changes
|
|
34
|
+
|
|
35
|
+
**static/viewer.html / viewer-app.js:**
|
|
36
|
+
- After PDF loads, if `isPremium === false`:
|
|
37
|
+
- Show only 1 page (server already sends only 1 page)
|
|
38
|
+
- Below the first page, render a lock overlay
|
|
39
|
+
- Overlay content:
|
|
40
|
+
- Lock icon (SVG)
|
|
41
|
+
- "Bu icerigi goruntulemek icin Premium uyelik gereklidir"
|
|
42
|
+
- "Premium Satin Al" button → forumtest.ieu.app/premium
|
|
43
|
+
- "Materyal yukleyerek de Premium olabilirsiniz!" secondary text
|
|
44
|
+
|
|
45
|
+
### Flow
|
|
46
|
+
1. User clicks PDF → viewer route called
|
|
47
|
+
2. Server checks Premium group membership
|
|
48
|
+
3. isPremium flag set in nonce + injected to viewer HTML
|
|
49
|
+
4. If not premium: server sends single-page PDF via getSinglePagePdf()
|
|
50
|
+
5. Client shows the page + lock overlay with upgrade prompt
|
|
51
|
+
6. If premium: server sends full PDF via getFullPdf()
|
|
52
|
+
7. Client shows all pages normally
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
# Premium Gate Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
+
|
|
5
|
+
**Goal:** Restrict non-Premium users to viewing only the first PDF page, with a lock overlay prompting upgrade.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Server checks user's "Premium" group membership, sends single-page PDF for non-premium users. Client renders a lock overlay below the first page showing upgrade prompts. Admin/Global Moderators always get full access.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** NodeBB plugin (Node.js), pdf-lib, PDF.js viewer, LESS/CSS
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
### Task 1: Add getTotalPages to pdf-handler
|
|
14
|
+
|
|
15
|
+
**Files:**
|
|
16
|
+
- Modify: `lib/pdf-handler.js`
|
|
17
|
+
|
|
18
|
+
**Step 1: Add getTotalPages function**
|
|
19
|
+
|
|
20
|
+
Add after `getSinglePagePdf` (after line 89):
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
PdfHandler.getTotalPages = async function (filename) {
|
|
24
|
+
const filePath = PdfHandler.resolveFilePath(filename);
|
|
25
|
+
if (!filePath) {
|
|
26
|
+
throw new Error('Invalid filename');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(filePath)) {
|
|
30
|
+
throw new Error('File not found');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const pdfBytes = await fs.promises.readFile(filePath);
|
|
34
|
+
const srcDoc = await PDFDocument.load(pdfBytes);
|
|
35
|
+
return srcDoc.getPageCount();
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Step 2: Commit**
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
git add lib/pdf-handler.js
|
|
43
|
+
git commit -m "feat: add getTotalPages to pdf-handler"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
### Task 2: Add Premium group check to viewer route
|
|
49
|
+
|
|
50
|
+
**Files:**
|
|
51
|
+
- Modify: `library.js` (lines 63-119, the viewer route)
|
|
52
|
+
|
|
53
|
+
**Step 1: Replace hardcoded isPremium with group check**
|
|
54
|
+
|
|
55
|
+
In `library.js`, replace line 83:
|
|
56
|
+
```javascript
|
|
57
|
+
const isPremium = true;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
With:
|
|
61
|
+
```javascript
|
|
62
|
+
// Check if user is Premium (admins/global mods always premium)
|
|
63
|
+
let isPremium = false;
|
|
64
|
+
if (req.uid) {
|
|
65
|
+
const [isAdmin, isGlobalMod, isPremiumMember] = await Promise.all([
|
|
66
|
+
groups.isMember(req.uid, 'administrators'),
|
|
67
|
+
groups.isMember(req.uid, 'Global Moderators'),
|
|
68
|
+
groups.isMember(req.uid, 'Premium'),
|
|
69
|
+
]);
|
|
70
|
+
isPremium = isAdmin || isGlobalMod || isPremiumMember;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Step 2: Add totalPages to config injection**
|
|
75
|
+
|
|
76
|
+
In `library.js`, inside the viewer route handler, before the nonce generation, add:
|
|
77
|
+
```javascript
|
|
78
|
+
// Get total page count for non-premium lock overlay
|
|
79
|
+
const pdfHandler = require('./lib/pdf-handler');
|
|
80
|
+
let totalPages = 1;
|
|
81
|
+
try {
|
|
82
|
+
totalPages = await pdfHandler.getTotalPages(safeName);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
console.error('[PDF-Secure] Failed to get page count:', e.message);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Note: Move the `pdfHandler` require to the top of the file (line 10 area) instead of inline.
|
|
89
|
+
|
|
90
|
+
**Step 3: Inject isPremium and totalPages into viewer config**
|
|
91
|
+
|
|
92
|
+
In the `window.PDF_SECURE_CONFIG` object (around line 108-114), add two new fields:
|
|
93
|
+
```javascript
|
|
94
|
+
window.PDF_SECURE_CONFIG = {
|
|
95
|
+
filename: ${JSON.stringify(safeName)},
|
|
96
|
+
relativePath: ${JSON.stringify(req.app.get('relative_path') || '')},
|
|
97
|
+
csrfToken: ${JSON.stringify(req.csrfToken ? req.csrfToken() : '')},
|
|
98
|
+
nonce: ${JSON.stringify(nonceData.nonce)},
|
|
99
|
+
dk: ${JSON.stringify(nonceData.xorKey)},
|
|
100
|
+
isPremium: ${JSON.stringify(isPremium)},
|
|
101
|
+
totalPages: ${JSON.stringify(totalPages)}
|
|
102
|
+
};
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Step 4: Make the viewer route handler async**
|
|
106
|
+
|
|
107
|
+
The current route handler at line 64 is a sync callback: `router.get('/plugins/pdf-secure/viewer', (req, res) => {`
|
|
108
|
+
|
|
109
|
+
Change it to async: `router.get('/plugins/pdf-secure/viewer', async (req, res) => {`
|
|
110
|
+
|
|
111
|
+
**Step 5: Commit**
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
git add library.js
|
|
115
|
+
git commit -m "feat: check Premium group membership in viewer route"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### Task 3: Add lock overlay to viewer-app.js
|
|
121
|
+
|
|
122
|
+
**Files:**
|
|
123
|
+
- Modify: `static/viewer-app.js`
|
|
124
|
+
|
|
125
|
+
**Step 1: Add premium lock overlay after PDF loads**
|
|
126
|
+
|
|
127
|
+
In `autoLoadSecurePDF()`, after `await loadPDFFromBuffer(pdfBuffer);` (line 262), add the lock overlay logic. The config is still available at this point (it gets deleted on line 270).
|
|
128
|
+
|
|
129
|
+
Insert before `pdfBuffer = null;` (line 267):
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
// Premium Gate: Show lock overlay for non-premium users
|
|
133
|
+
if (config.isPremium === false && config.totalPages > 1) {
|
|
134
|
+
showPremiumLockOverlay(config.totalPages);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Step 2: Add showPremiumLockOverlay function**
|
|
139
|
+
|
|
140
|
+
Add this function inside the IIFE, before `autoLoadSecurePDF()`:
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
function showPremiumLockOverlay(totalPages) {
|
|
144
|
+
const viewerEl = document.getElementById('viewer');
|
|
145
|
+
if (!viewerEl) return;
|
|
146
|
+
|
|
147
|
+
const overlay = document.createElement('div');
|
|
148
|
+
overlay.id = 'premiumLockOverlay';
|
|
149
|
+
overlay.innerHTML = `
|
|
150
|
+
<div class="premium-lock-icon">
|
|
151
|
+
<svg viewBox="0 0 24 24" width="64" height="64" fill="#ffd700">
|
|
152
|
+
<path d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1s3.1 1.39 3.1 3.1v2z"/>
|
|
153
|
+
</svg>
|
|
154
|
+
</div>
|
|
155
|
+
<div class="premium-lock-pages">
|
|
156
|
+
${totalPages - 1} sayfa daha kilitli
|
|
157
|
+
</div>
|
|
158
|
+
<div class="premium-lock-message">
|
|
159
|
+
Bu icerigi goruntuleye devam etmek icin Premium uyelik gereklidir.
|
|
160
|
+
</div>
|
|
161
|
+
<a href="https://forumtest.ieu.app/premium" target="_blank" class="premium-lock-button">
|
|
162
|
+
Premium Satin Al
|
|
163
|
+
</a>
|
|
164
|
+
<div class="premium-lock-secondary">
|
|
165
|
+
Materyal yukleyerek de Premium olabilirsiniz!
|
|
166
|
+
</div>
|
|
167
|
+
`;
|
|
168
|
+
|
|
169
|
+
viewerEl.appendChild(overlay);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Step 3: Commit**
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
git add static/viewer-app.js
|
|
177
|
+
git commit -m "feat: add premium lock overlay for non-premium users"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
### Task 4: Add lock overlay CSS styles
|
|
183
|
+
|
|
184
|
+
**Files:**
|
|
185
|
+
- Modify: `static/viewer.html` (inline styles section)
|
|
186
|
+
|
|
187
|
+
**Step 1: Add premium lock overlay styles**
|
|
188
|
+
|
|
189
|
+
Add these styles inside the `<style>` tag in viewer.html, before the closing `</style>`:
|
|
190
|
+
|
|
191
|
+
```css
|
|
192
|
+
/* Premium Lock Overlay */
|
|
193
|
+
#premiumLockOverlay {
|
|
194
|
+
display: flex;
|
|
195
|
+
flex-direction: column;
|
|
196
|
+
align-items: center;
|
|
197
|
+
justify-content: center;
|
|
198
|
+
padding: 60px 20px;
|
|
199
|
+
text-align: center;
|
|
200
|
+
background: linear-gradient(180deg, rgba(31,31,31,0.95) 0%, rgba(20,20,20,0.98) 100%);
|
|
201
|
+
border-top: 2px solid rgba(255, 215, 0, 0.3);
|
|
202
|
+
min-height: 400px;
|
|
203
|
+
margin: 0 auto;
|
|
204
|
+
max-width: 100%;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.premium-lock-icon {
|
|
208
|
+
margin-bottom: 20px;
|
|
209
|
+
opacity: 0.9;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.premium-lock-pages {
|
|
213
|
+
font-size: 22px;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
color: #ffd700;
|
|
216
|
+
margin-bottom: 12px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.premium-lock-message {
|
|
220
|
+
font-size: 16px;
|
|
221
|
+
color: #a0a0a0;
|
|
222
|
+
margin-bottom: 28px;
|
|
223
|
+
max-width: 400px;
|
|
224
|
+
line-height: 1.5;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.premium-lock-button {
|
|
228
|
+
display: inline-block;
|
|
229
|
+
padding: 14px 40px;
|
|
230
|
+
background: linear-gradient(135deg, #ffd700 0%, #ffaa00 100%);
|
|
231
|
+
color: #1a1a1a;
|
|
232
|
+
font-size: 16px;
|
|
233
|
+
font-weight: 700;
|
|
234
|
+
border-radius: 8px;
|
|
235
|
+
text-decoration: none;
|
|
236
|
+
transition: transform 0.2s, box-shadow 0.2s;
|
|
237
|
+
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
.premium-lock-button:hover {
|
|
241
|
+
transform: translateY(-2px);
|
|
242
|
+
box-shadow: 0 6px 20px rgba(255, 215, 0, 0.4);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.premium-lock-secondary {
|
|
246
|
+
margin-top: 20px;
|
|
247
|
+
font-size: 14px;
|
|
248
|
+
color: #888;
|
|
249
|
+
font-style: italic;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
@media (max-width: 768px) {
|
|
253
|
+
#premiumLockOverlay {
|
|
254
|
+
padding: 40px 16px;
|
|
255
|
+
min-height: 300px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.premium-lock-pages {
|
|
259
|
+
font-size: 18px;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.premium-lock-message {
|
|
263
|
+
font-size: 14px;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.premium-lock-button {
|
|
267
|
+
padding: 12px 32px;
|
|
268
|
+
font-size: 14px;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Step 2: Commit**
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
git add static/viewer.html
|
|
277
|
+
git commit -m "feat: add premium lock overlay CSS styles"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
### Task 5: Hide sidebar and page count for non-premium users
|
|
283
|
+
|
|
284
|
+
**Files:**
|
|
285
|
+
- Modify: `static/viewer-app.js`
|
|
286
|
+
|
|
287
|
+
**Step 1: Update page count display for non-premium**
|
|
288
|
+
|
|
289
|
+
In the `pagesinit` event handler (around line 360-362), update the page count to show total pages from config instead of the actual loaded pages:
|
|
290
|
+
|
|
291
|
+
Find the `eventBus.on('pagesinit', ...)` handler and wrap the pageCount update:
|
|
292
|
+
|
|
293
|
+
```javascript
|
|
294
|
+
eventBus.on('pagesinit', () => {
|
|
295
|
+
pdfViewer.currentScaleValue = 'page-width';
|
|
296
|
+
// For non-premium, show "1 / totalPages" instead of "1 / 1"
|
|
297
|
+
const savedConfig = window._pdfSecurePremiumInfo;
|
|
298
|
+
if (savedConfig && !savedConfig.isPremium && savedConfig.totalPages > 1) {
|
|
299
|
+
document.getElementById('pageCount').textContent = `/ ${savedConfig.totalPages}`;
|
|
300
|
+
} else {
|
|
301
|
+
document.getElementById('pageCount').textContent = `/ ${pdfViewer.pagesCount}`;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**Step 2: Save premium info before config is deleted**
|
|
307
|
+
|
|
308
|
+
In `autoLoadSecurePDF()`, right before the config delete line (`delete window.PDF_SECURE_CONFIG`), save the premium info:
|
|
309
|
+
|
|
310
|
+
```javascript
|
|
311
|
+
// Save premium info for UI (page count display)
|
|
312
|
+
window._pdfSecurePremiumInfo = {
|
|
313
|
+
isPremium: config.isPremium,
|
|
314
|
+
totalPages: config.totalPages
|
|
315
|
+
};
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
**Step 3: Commit**
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
git add static/viewer-app.js
|
|
322
|
+
git commit -m "feat: show real total page count for non-premium users"
|
|
323
|
+
```
|
package/lib/pdf-handler.js
CHANGED
|
@@ -6,6 +6,7 @@ const { PDFDocument } = require('pdf-lib');
|
|
|
6
6
|
const nconf = require.main.require('nconf');
|
|
7
7
|
|
|
8
8
|
const singlePageCache = new Map();
|
|
9
|
+
const pageCountCache = new Map();
|
|
9
10
|
const CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
|
10
11
|
|
|
11
12
|
// Periodic cleanup of expired cache entries
|
|
@@ -16,6 +17,11 @@ setInterval(() => {
|
|
|
16
17
|
singlePageCache.delete(key);
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
for (const [key, entry] of pageCountCache.entries()) {
|
|
21
|
+
if (now - entry.createdAt > CACHE_TTL) {
|
|
22
|
+
pageCountCache.delete(key);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
19
25
|
}, 10 * 60 * 1000).unref(); // cleanup every 10 minutes
|
|
20
26
|
|
|
21
27
|
const PdfHandler = module.exports;
|
|
@@ -72,6 +78,10 @@ PdfHandler.getSinglePagePdf = async function (filename) {
|
|
|
72
78
|
const existingPdfBytes = await fs.promises.readFile(filePath);
|
|
73
79
|
const srcDoc = await PDFDocument.load(existingPdfBytes);
|
|
74
80
|
|
|
81
|
+
// Cache page count while we have the document loaded (avoids double read)
|
|
82
|
+
const totalPages = srcDoc.getPageCount();
|
|
83
|
+
pageCountCache.set(filename, { count: totalPages, createdAt: Date.now() });
|
|
84
|
+
|
|
75
85
|
const newDoc = await PDFDocument.create();
|
|
76
86
|
const [copiedPage] = await newDoc.copyPages(srcDoc, [0]);
|
|
77
87
|
newDoc.addPage(copiedPage);
|
|
@@ -87,3 +97,26 @@ PdfHandler.getSinglePagePdf = async function (filename) {
|
|
|
87
97
|
|
|
88
98
|
return buffer;
|
|
89
99
|
};
|
|
100
|
+
|
|
101
|
+
PdfHandler.getTotalPages = async function (filename) {
|
|
102
|
+
const cached = pageCountCache.get(filename);
|
|
103
|
+
if (cached && (Date.now() - cached.createdAt < CACHE_TTL)) {
|
|
104
|
+
return cached.count;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const filePath = PdfHandler.resolveFilePath(filename);
|
|
108
|
+
if (!filePath) {
|
|
109
|
+
throw new Error('Invalid filename');
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!fs.existsSync(filePath)) {
|
|
113
|
+
throw new Error('File not found');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const pdfBytes = await fs.promises.readFile(filePath);
|
|
117
|
+
const srcDoc = await PDFDocument.load(pdfBytes);
|
|
118
|
+
const count = srcDoc.getPageCount();
|
|
119
|
+
|
|
120
|
+
pageCountCache.set(filename, { count, createdAt: Date.now() });
|
|
121
|
+
return count;
|
|
122
|
+
};
|
package/library.js
CHANGED
|
@@ -61,7 +61,7 @@ plugin.init = async (params) => {
|
|
|
61
61
|
routeHelpers.setupAdminPageRoute(router, '/admin/plugins/pdf-secure', controllers.renderAdminPage);
|
|
62
62
|
|
|
63
63
|
// Viewer page route (fullscreen Mozilla PDF.js viewer, guests allowed)
|
|
64
|
-
router.get('/plugins/pdf-secure/viewer', (req, res) => {
|
|
64
|
+
router.get('/plugins/pdf-secure/viewer', async (req, res) => {
|
|
65
65
|
const { file } = req.query;
|
|
66
66
|
if (!file) {
|
|
67
67
|
return res.status(400).send('Missing file parameter');
|
|
@@ -78,10 +78,20 @@ plugin.init = async (params) => {
|
|
|
78
78
|
return res.status(500).send('Viewer not available');
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
// Check if user is Premium (admins/global mods always premium)
|
|
82
|
+
let isPremium = false;
|
|
83
|
+
if (req.uid) {
|
|
84
|
+
const [isAdmin, isGlobalMod, isPremiumMember] = await Promise.all([
|
|
85
|
+
groups.isMember(req.uid, 'administrators'),
|
|
86
|
+
groups.isMember(req.uid, 'Global Moderators'),
|
|
87
|
+
groups.isMember(req.uid, 'Premium'),
|
|
88
|
+
]);
|
|
89
|
+
isPremium = isAdmin || isGlobalMod || isPremiumMember;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Always send full PDF (client-side premium gating)
|
|
93
|
+
// isPremium flag is sent to client for UI control only
|
|
94
|
+
const nonceData = nonceStore.generate(req.uid || 0, safeName, true);
|
|
85
95
|
|
|
86
96
|
// Serve the viewer template with comprehensive security headers
|
|
87
97
|
res.set({
|
|
@@ -110,7 +120,8 @@ plugin.init = async (params) => {
|
|
|
110
120
|
relativePath: ${JSON.stringify(req.app.get('relative_path') || '')},
|
|
111
121
|
csrfToken: ${JSON.stringify(req.csrfToken ? req.csrfToken() : '')},
|
|
112
122
|
nonce: ${JSON.stringify(nonceData.nonce)},
|
|
113
|
-
dk: ${JSON.stringify(nonceData.xorKey)}
|
|
123
|
+
dk: ${JSON.stringify(nonceData.xorKey)},
|
|
124
|
+
isPremium: ${JSON.stringify(isPremium)}
|
|
114
125
|
};
|
|
115
126
|
</script>
|
|
116
127
|
</head>`);
|
package/package.json
CHANGED