@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 +6 -0
- package/dist/index.d.ts +9 -1
- package/dist/index.js +50 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -61
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/test/file-delete-scope.test.mjs +64 -0
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;
|