@lacrous/omnicloud 0.2.16 → 0.2.18
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/dist/index.cjs +25 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/server/index.js +36 -8
- package/server/prisma/migrations/5_upload_request_fingerprint/migration.sql +3 -0
- package/server/prisma/schema.prisma +43 -42
package/dist/index.d.cts
CHANGED
|
@@ -930,6 +930,8 @@ interface UploadOperationRecord {
|
|
|
930
930
|
telegramMessageId: number | null;
|
|
931
931
|
sha256: string | null;
|
|
932
932
|
size: number | null;
|
|
933
|
+
/** Digest of the original request (name, folder, target, content). Replays must match it. */
|
|
934
|
+
requestFingerprint: string | null;
|
|
933
935
|
fileId: string | null;
|
|
934
936
|
error: string | null;
|
|
935
937
|
createdAt: Date;
|
|
@@ -943,6 +945,7 @@ interface UploadOperationRepository {
|
|
|
943
945
|
create(input: {
|
|
944
946
|
userId: string;
|
|
945
947
|
operationId: string;
|
|
948
|
+
requestFingerprint?: string | null;
|
|
946
949
|
}): Promise<UploadOperationRecord>;
|
|
947
950
|
/**
|
|
948
951
|
* Atomically moves an operation from `from` to `to` if, and only if, it is
|
package/dist/index.d.ts
CHANGED
|
@@ -930,6 +930,8 @@ interface UploadOperationRecord {
|
|
|
930
930
|
telegramMessageId: number | null;
|
|
931
931
|
sha256: string | null;
|
|
932
932
|
size: number | null;
|
|
933
|
+
/** Digest of the original request (name, folder, target, content). Replays must match it. */
|
|
934
|
+
requestFingerprint: string | null;
|
|
933
935
|
fileId: string | null;
|
|
934
936
|
error: string | null;
|
|
935
937
|
createdAt: Date;
|
|
@@ -943,6 +945,7 @@ interface UploadOperationRepository {
|
|
|
943
945
|
create(input: {
|
|
944
946
|
userId: string;
|
|
945
947
|
operationId: string;
|
|
948
|
+
requestFingerprint?: string | null;
|
|
946
949
|
}): Promise<UploadOperationRecord>;
|
|
947
950
|
/**
|
|
948
951
|
* Atomically moves an operation from `from` to `to` if, and only if, it is
|
package/dist/index.js
CHANGED
|
@@ -1414,6 +1414,20 @@ function sanitizeFileName(name) {
|
|
|
1414
1414
|
return cleaned.slice(0, 255);
|
|
1415
1415
|
}
|
|
1416
1416
|
|
|
1417
|
+
// ../core/src/utils/request-fingerprint.ts
|
|
1418
|
+
import { createHash as createHash4 } from "crypto";
|
|
1419
|
+
function uploadRequestFingerprint(input) {
|
|
1420
|
+
const contentHash = input.spooled?.sha256 ?? sha256Hex(input.data ?? Buffer.alloc(0));
|
|
1421
|
+
const canonical = JSON.stringify({
|
|
1422
|
+
name: sanitizeFileName(input.name),
|
|
1423
|
+
folderId: input.folderId ?? null,
|
|
1424
|
+
replaceFileId: input.replaceFileId ?? null,
|
|
1425
|
+
contentHash,
|
|
1426
|
+
size: input.spooled?.size ?? input.data?.byteLength ?? 0
|
|
1427
|
+
});
|
|
1428
|
+
return createHash4("sha256").update(canonical).digest("hex");
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1417
1431
|
// ../core/src/services/file-service.ts
|
|
1418
1432
|
var FileService = class {
|
|
1419
1433
|
constructor(files, folders, engineFor, activity = noopActivityRecorder, operations = null, log = null) {
|
|
@@ -1452,7 +1466,13 @@ var FileService = class {
|
|
|
1452
1466
|
if (!isValidOperationId(operationId)) {
|
|
1453
1467
|
throw new ValidationError("Invalid upload operation id");
|
|
1454
1468
|
}
|
|
1469
|
+
const fingerprint = uploadRequestFingerprint(input);
|
|
1455
1470
|
let op = await operations.findByOperationId(userId, operationId);
|
|
1471
|
+
if (op?.requestFingerprint && op.requestFingerprint !== fingerprint) {
|
|
1472
|
+
throw new ConflictError(
|
|
1473
|
+
"This upload key was already used for a different file. Use a new key for a new upload."
|
|
1474
|
+
);
|
|
1475
|
+
}
|
|
1456
1476
|
if (op?.status === "COMPLETED" && op.fileId) {
|
|
1457
1477
|
return this.get(userId, op.fileId);
|
|
1458
1478
|
}
|
|
@@ -1461,7 +1481,7 @@ var FileService = class {
|
|
|
1461
1481
|
}
|
|
1462
1482
|
if (!op) {
|
|
1463
1483
|
try {
|
|
1464
|
-
op = await operations.create({ userId, operationId });
|
|
1484
|
+
op = await operations.create({ userId, operationId, requestFingerprint: fingerprint });
|
|
1465
1485
|
} catch (error) {
|
|
1466
1486
|
if (!(error instanceof OperationAlreadyExistsError)) throw error;
|
|
1467
1487
|
const outcome = await this.followWinner(userId, input, operationId, operations);
|
|
@@ -2571,7 +2591,7 @@ var RecentService = class {
|
|
|
2571
2591
|
};
|
|
2572
2592
|
|
|
2573
2593
|
// ../core/src/utils/spool.ts
|
|
2574
|
-
import { createHash as
|
|
2594
|
+
import { createHash as createHash5 } from "crypto";
|
|
2575
2595
|
import { createWriteStream } from "fs";
|
|
2576
2596
|
import { mkdtemp, rm } from "fs/promises";
|
|
2577
2597
|
import { tmpdir } from "os";
|