plugin-file-preview-auth 1.2.9 → 1.3.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.
@@ -377,15 +377,97 @@ class PluginFilePreviewAuthServer extends import_server.Plugin {
377
377
  }
378
378
  ctx.throw(404, "Attachment not found for this preview file");
379
379
  }
380
+ getParentCollections(fileCollectionName) {
381
+ const parents = [];
382
+ for (const collection of this.db.collections.values()) {
383
+ if (collection.name === fileCollectionName) continue;
384
+ for (const field of collection.fields.values()) {
385
+ const options = field.options || {};
386
+ if (field.type === "belongsToMany" && options.target === fileCollectionName && options.through) {
387
+ parents.push({
388
+ collectionName: collection.name,
389
+ throughTable: options.through,
390
+ otherKey: options.otherKey || "attachmentId",
391
+ foreignKey: options.foreignKey || `${collection.model.name.toLowerCase()}Id`
392
+ });
393
+ }
394
+ }
395
+ }
396
+ return parents;
397
+ }
398
+ async checkParentCollectionAccess(attachmentId, fileCollectionName, currentRoles, ctx) {
399
+ var _a, _b, _c;
400
+ const parents = this.getParentCollections(fileCollectionName);
401
+ if (parents.length === 0) {
402
+ const canView = this.app.acl.can({
403
+ roles: currentRoles,
404
+ resource: fileCollectionName,
405
+ action: "view"
406
+ });
407
+ return !!canView;
408
+ }
409
+ for (const parent of parents) {
410
+ const canView = this.app.acl.can({
411
+ roles: currentRoles,
412
+ resource: parent.collectionName,
413
+ action: "view"
414
+ });
415
+ if (!canView) continue;
416
+ try {
417
+ const throughCollection = this.db.getCollection(parent.throughTable);
418
+ if (throughCollection) {
419
+ const links = await throughCollection.repository.find({
420
+ filter: { [parent.otherKey]: attachmentId }
421
+ });
422
+ if (links.length > 0) {
423
+ const parentIds = links.map((l) => l.get(parent["foreignKey"])).filter(Boolean);
424
+ if (parentIds.length > 0) {
425
+ let dataScopeFilter = ((_a = canView.params) == null ? void 0 : _a.filter) || {};
426
+ if (ctx && ctx.app.environment) {
427
+ dataScopeFilter = ctx.app.environment.renderJsonTemplate(dataScopeFilter, {
428
+ $user: ((_b = ctx.state.currentUser) == null ? void 0 : _b.toJSON) ? ctx.state.currentUser.toJSON() : ctx.state.currentUser,
429
+ $nRole: ctx.state.currentRole
430
+ });
431
+ }
432
+ const parentCollection = this.db.getCollection(parent.collectionName);
433
+ const pk = ((_c = parentCollection == null ? void 0 : parentCollection.model) == null ? void 0 : _c.primaryKeyAttribute) || "id";
434
+ const count = await parentCollection.repository.count({
435
+ filter: {
436
+ $and: [
437
+ { [pk]: { $in: parentIds } },
438
+ dataScopeFilter
439
+ ]
440
+ }
441
+ });
442
+ if (count > 0) return true;
443
+ }
444
+ }
445
+ }
446
+ } catch (error) {
447
+ this.log.warn(`[FilePreviewAuth] Failed to query through table "${parent.throughTable}":`, error.message);
448
+ }
449
+ }
450
+ return false;
451
+ }
380
452
  async assertCanAccessAttachment(ctx, attachment) {
453
+ var _a, _b;
381
454
  const currentUser = this.assertAuthenticated(ctx);
382
455
  const createdById = getAttachmentValue(attachment, "createdById");
383
456
  const currentRoles = ctx.state.currentRoles || [];
384
457
  const userRoles = currentUser.roles || [];
385
458
  const isOwner = createdById != null && String(createdById) === String(currentUser.id);
386
459
  const isAdmin = currentRoles.includes("root") || currentRoles.includes("admin") || userRoles.some((role) => role === "root" || role === "admin" || (role == null ? void 0 : role.name) === "root" || (role == null ? void 0 : role.name) === "admin");
387
- if (!isOwner && !isAdmin) {
388
- ctx.throw(403, "Permission denied: you cannot view other users' files");
460
+ if (isOwner || isAdmin) {
461
+ return;
462
+ }
463
+ const attachmentId = getAttachmentValue(attachment, "id");
464
+ if (!attachmentId) {
465
+ ctx.throw(403, "Permission denied: virtual attachment cannot be accessed");
466
+ }
467
+ const collectionName = ((_b = (_a = attachment.constructor) == null ? void 0 : _a.collection) == null ? void 0 : _b.name) || getAttachmentValue(attachment, "collectionName") || "attachments";
468
+ const hasParentAccess = await this.checkParentCollectionAccess(attachmentId, collectionName, currentRoles, ctx);
469
+ if (!hasParentAccess) {
470
+ ctx.throw(403, "Permission denied: you do not have permission to access this attachment");
389
471
  }
390
472
  }
391
473
  assertAuthenticated(ctx) {
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "description": "Preview PDF, image, and text files with Bearer token authentication via blob URLs.",
7
7
  "description.vi-VN": "Xem trước file PDF, hình ảnh và văn bản với xác thực Bearer token qua blob URL.",
8
8
  "description.zh-CN": "通过 Bearer 令牌认证和 Blob URL 预览 PDF、图片和文本文件。",
9
- "version": "1.2.9",
9
+ "version": "1.3.0",
10
10
  "main": "dist/server/index.js",
11
11
  "files": [
12
12
  "dist",