@7365admin1/core 3.53.1 → 3.53.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @iservice365/core
2
2
 
3
+ ## 3.53.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 972c701: Scope file deletion to the uploader's tenant. New uploads are stamped with the caller's user id server-side; a stamped file can only be deleted by its uploader, someone sharing an organisation with them, or a super admin. Files with no stamp (everything uploaded before this change, and the anonymous resident sign-up upload) behave exactly as before — no backfill, no migration.
8
+
3
9
  ## 3.53.1
4
10
 
5
11
  ### Patch Changes
package/dist/index.d.ts CHANGED
@@ -644,6 +644,13 @@ type TFile = {
644
644
  size?: number;
645
645
  status?: string;
646
646
  createdAt: string;
647
+ /**
648
+ * The signed-in user who uploaded this file, stamped server-side from the
649
+ * session. OPTIONAL on purpose: every row written before this shipped has no
650
+ * stamp, and the delete gate leaves those exactly as they were. See
651
+ * `utils/file-owner.util.ts`.
652
+ */
653
+ createdBy?: string;
647
654
  };
648
655
  declare class MFile implements TFile {
649
656
  _id?: ObjectId;
@@ -652,6 +659,7 @@ declare class MFile implements TFile {
652
659
  size?: number;
653
660
  status?: string;
654
661
  createdAt: string;
662
+ createdBy?: string;
655
663
  constructor(value: TFile);
656
664
  }
657
665
 
@@ -665,7 +673,7 @@ declare function useFileRepo(): {
665
673
  };
666
674
 
667
675
  declare function useFileService(): {
668
- createFile: (value: Express.Multer.File, folder?: string) => Promise<string>;
676
+ createFile: (value: Express.Multer.File, folder?: string, createdBy?: string) => Promise<string>;
669
677
  deleteFile: (id: string) => Promise<string>;
670
678
  deleteDraft: () => void;
671
679
  };
package/dist/index.js CHANGED
@@ -16836,6 +16836,8 @@ var MFile = class {
16836
16836
  this.size = value.size ?? 0;
16837
16837
  this.status = value.status ?? "draft";
16838
16838
  this.createdAt = value.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
16839
+ if (value.createdBy)
16840
+ this.createdBy = value.createdBy.toString();
16839
16841
  }
16840
16842
  };
16841
16843
 
@@ -22532,13 +22534,14 @@ function useFileService() {
22532
22534
  bucket: SPACES_BUCKET,
22533
22535
  forcePathStyle: true
22534
22536
  });
22535
- async function createFile(value, folder = "default") {
22537
+ async function createFile(value, folder = "default", createdBy) {
22536
22538
  const session = import_node_server_utils51.useAtlas.getClient()?.startSession();
22537
22539
  session?.startTransaction();
22538
22540
  const file = {
22539
22541
  name: value.originalname,
22540
22542
  size: value.size ?? (value.buffer ? value.buffer.length : 0),
22541
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
22543
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
22544
+ ...createdBy ? { createdBy } : {}
22542
22545
  };
22543
22546
  try {
22544
22547
  const id = await _createFile(file, session);
@@ -27624,6 +27627,49 @@ function useVerificationController() {
27624
27627
  // src/controllers/file.controller.ts
27625
27628
  var import_node_server_utils59 = require("@7365admin1/node-server-utils");
27626
27629
  var import_joi27 = __toESM(require("joi"));
27630
+
27631
+ // src/utils/file-owner.util.ts
27632
+ function fileOwnerId(file) {
27633
+ return file?.createdBy?.toString?.() ?? "";
27634
+ }
27635
+ function mayDeleteFile(params) {
27636
+ if (!params.ownerId)
27637
+ return true;
27638
+ if (!params.callerId)
27639
+ return false;
27640
+ if (params.callerId === params.ownerId)
27641
+ return true;
27642
+ if (params.callerIsSuperAdmin)
27643
+ return true;
27644
+ const ownerOrgs = new Set((params.ownerOrgIds ?? []).map(String));
27645
+ return (params.callerOrgIds ?? []).some((org) => ownerOrgs.has(String(org)));
27646
+ }
27647
+
27648
+ // src/controllers/file.controller.ts
27649
+ async function requireFileDelete(req, file) {
27650
+ const ownerId = fileOwnerId(file);
27651
+ if (!ownerId)
27652
+ return;
27653
+ const caller = callerId(req);
27654
+ if (!caller)
27655
+ throw new import_node_server_utils59.UnauthorizedError("Not authorized.");
27656
+ if (caller === ownerId)
27657
+ return;
27658
+ const [me, owner] = await Promise.all([
27659
+ resolveInviteActor(caller),
27660
+ resolveInviteActor(ownerId)
27661
+ ]);
27662
+ if (mayDeleteFile({
27663
+ ownerId,
27664
+ callerId: caller,
27665
+ callerIsSuperAdmin: me.isSuperAdmin,
27666
+ callerOrgIds: me.orgIds,
27667
+ ownerOrgIds: owner.orgIds
27668
+ })) {
27669
+ return;
27670
+ }
27671
+ throw new import_node_server_utils59.UnauthorizedError("Not authorized.");
27672
+ }
27627
27673
  function useFileController() {
27628
27674
  const { createFile, deleteFile: _deleteFile } = useFileService();
27629
27675
  const { getFileById: _getFileById } = useFileRepo();
@@ -27633,7 +27679,7 @@ function useFileController() {
27633
27679
  return;
27634
27680
  }
27635
27681
  try {
27636
- const id = await createFile(req.file);
27682
+ const id = await createFile(req.file, "default", callerId(req) || void 0);
27637
27683
  res.status(201).json({ message: "Successfully uploaded file", id });
27638
27684
  return;
27639
27685
  } catch (error) {
@@ -27652,6 +27698,7 @@ function useFileController() {
27652
27698
  return;
27653
27699
  }
27654
27700
  try {
27701
+ await requireFileDelete(req, await _getFileById(_id));
27655
27702
  const message = await _deleteFile(_id);
27656
27703
  res.json({ message });
27657
27704
  return;