@cedarjs/uploads 8.0.0-canary.3402 → 8.0.0-canary.3403
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/README.md +5 -0
- package/dist/contentTypes.d.ts +74 -0
- package/dist/contentTypes.d.ts.map +1 -0
- package/dist/contentTypes.js +78 -0
- package/dist/detectContentType.d.ts +168 -0
- package/dist/detectContentType.d.ts.map +1 -0
- package/dist/detectContentType.js +443 -0
- package/dist/fastify/plugin.d.ts +16 -0
- package/dist/fastify/plugin.d.ts.map +1 -1
- package/dist/fastify/plugin.js +32 -7
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +32 -0
- package/dist/keys.d.ts +3 -1
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +2 -1
- package/dist/profiles.d.ts +8 -2
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +5 -4
- package/dist/providers/s3.d.ts.map +1 -1
- package/dist/providers/s3.js +16 -0
- package/dist/services.d.ts +24 -6
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +45 -10
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/verifyStoredContentType.d.ts +19 -0
- package/dist/verifyStoredContentType.d.ts.map +1 -0
- package/dist/verifyStoredContentType.js +41 -0
- package/dist/webhooks/s3.d.ts +17 -8
- package/dist/webhooks/s3.d.ts.map +1 -1
- package/dist/webhooks/s3.js +22 -3
- package/package.json +7 -7
package/dist/profiles.d.ts
CHANGED
|
@@ -15,7 +15,11 @@ export interface UploadProfile {
|
|
|
15
15
|
target: string;
|
|
16
16
|
/**
|
|
17
17
|
* MIME types the profile accepts. Exact types (`image/png`) and wildcard
|
|
18
|
-
* subtypes (`image/*`) are supported
|
|
18
|
+
* subtypes (`image/*`) are supported, and either spelling of an aliased
|
|
19
|
+
* type (`image/jpeg` or `image/jpg`) matches both. When files are checked
|
|
20
|
+
* by their bytes (`verifyContentType`, on by default), types that can carry
|
|
21
|
+
* script, such as `image/svg+xml` or `text/html`, are only accepted when
|
|
22
|
+
* listed exactly; see `SCRIPTABLE_CONTENT_TYPES`.
|
|
19
23
|
*/
|
|
20
24
|
allowedMimeTypes: string[];
|
|
21
25
|
/** Largest file the profile accepts, in bytes. */
|
|
@@ -43,7 +47,9 @@ export declare function resolveProfile(profiles: UploadProfiles, name: string):
|
|
|
43
47
|
/**
|
|
44
48
|
* Whether `mimeType` matches one of `allowed`. Matching is case-insensitive
|
|
45
49
|
* and ignores parameters such as `; charset=utf-8`. An entry of `image/*`
|
|
46
|
-
* matches every `image/` subtype.
|
|
50
|
+
* matches every `image/` subtype. Aliases in `CONTENT_TYPE_ALIASES` are the
|
|
51
|
+
* same type, so `image/jpg` matches an entry of `image/jpeg` and the other
|
|
52
|
+
* way round.
|
|
47
53
|
*/
|
|
48
54
|
export declare function isMimeTypeAllowed(allowed: string[], mimeType: string): boolean;
|
|
49
55
|
//# sourceMappingURL=profiles.d.ts.map
|
package/dist/profiles.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAKxD;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAc,CAAA;AAE3C,2CAA2C;AAC3C,eAAO,MAAM,+BAA+B,EAAE,oBAA2B,CAAA;AAEzE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,MAAM,EAAE,MAAM,CAAA;IACd;;;;;;;OAOG;IACH,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAA;IACnB,0EAA0E;IAC1E,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;AAE1D,MAAM,WAAW,qBAAsB,SAAQ,aAAa;IAC1D,IAAI,EAAE,MAAM,CAAA;CACb;AAiCD;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,cAAc,EAAE,QAAQ,EAAE,CAAC,GAAG,CAAC,CAM7E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,MAAM,GACX,qBAAqB,CAavB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,WAoBpE"}
|
package/dist/profiles.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { canonicalContentType } from "./contentTypes.js";
|
|
1
2
|
import { UploadError } from "./errors.js";
|
|
2
3
|
const DB_MAX_FILE_SIZE = 1024 * 1024;
|
|
3
4
|
const DEFAULT_UPLOAD_TOKEN_EXPIRES_IN = "5m";
|
|
@@ -44,8 +45,8 @@ function resolveProfile(profiles, name) {
|
|
|
44
45
|
return { ...profile, name };
|
|
45
46
|
}
|
|
46
47
|
function isMimeTypeAllowed(allowed, mimeType) {
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
48
|
+
const type = canonicalContentType(mimeType);
|
|
49
|
+
if (!type) {
|
|
49
50
|
return false;
|
|
50
51
|
}
|
|
51
52
|
return allowed.some((entry) => {
|
|
@@ -54,9 +55,9 @@ function isMimeTypeAllowed(allowed, mimeType) {
|
|
|
54
55
|
return true;
|
|
55
56
|
}
|
|
56
57
|
if (pattern.endsWith("/*")) {
|
|
57
|
-
return
|
|
58
|
+
return type.startsWith(pattern.slice(0, -1));
|
|
58
59
|
}
|
|
59
|
-
return pattern ===
|
|
60
|
+
return canonicalContentType(pattern) === type;
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
export {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/providers/s3.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAmBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,eAAe,
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/providers/s3.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAGlD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sEAAsE;IACtE,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,kEAAkE;IAClE,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAmBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,eAAe,CA4IzE"}
|
package/dist/providers/s3.js
CHANGED
|
@@ -45,6 +45,22 @@ function createS3Provider(opts) {
|
|
|
45
45
|
}
|
|
46
46
|
return Buffer.from(await res.Body.transformToByteArray());
|
|
47
47
|
},
|
|
48
|
+
async readRange(key, { offset, length }) {
|
|
49
|
+
if (length <= 0) {
|
|
50
|
+
return Buffer.alloc(0);
|
|
51
|
+
}
|
|
52
|
+
const res = await client.send(
|
|
53
|
+
new GetObjectCommand({
|
|
54
|
+
Bucket: bucket,
|
|
55
|
+
Key: fullKey(key),
|
|
56
|
+
Range: `bytes=${offset}-${offset + length - 1}`
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
if (!res.Body) {
|
|
60
|
+
return Buffer.alloc(0);
|
|
61
|
+
}
|
|
62
|
+
return Buffer.from(await res.Body.transformToByteArray());
|
|
63
|
+
},
|
|
48
64
|
async readStream(key) {
|
|
49
65
|
const res = await client.send(
|
|
50
66
|
new GetObjectCommand({ Bucket: bucket, Key: fullKey(key) })
|
package/dist/services.d.ts
CHANGED
|
@@ -40,6 +40,16 @@ export interface CreatePresignedUploadOptions {
|
|
|
40
40
|
/** The validated token, as attached to context by `@requireUploadToken`. */
|
|
41
41
|
tokenPayload: UploadTokenPayload;
|
|
42
42
|
input: CreatePresignedUploadInput;
|
|
43
|
+
/**
|
|
44
|
+
* Holds the declared type to the rules `acceptedContentType()` applies to
|
|
45
|
+
* a type it can't check by bytes: no wildcard types, and types that can
|
|
46
|
+
* carry script (`SCRIPTABLE_CONTENT_TYPES`) only when the profile lists
|
|
47
|
+
* them exactly. Pair it with `verifyContentType` on `confirmUpload` (and
|
|
48
|
+
* the S3 webhook), which checks the bytes against the declared type.
|
|
49
|
+
* Defaults to `true`; `false` checks the declared type with
|
|
50
|
+
* `isMimeTypeAllowed()` only.
|
|
51
|
+
*/
|
|
52
|
+
verifyContentType?: boolean;
|
|
43
53
|
}
|
|
44
54
|
export interface PresignedUploadResponse {
|
|
45
55
|
uploadId: string;
|
|
@@ -52,19 +62,27 @@ export interface PresignedUploadResponse {
|
|
|
52
62
|
* a presigned URL the client PUTs the bytes to. This is what the generated
|
|
53
63
|
* `createPresignedUploadUrl` resolver calls.
|
|
54
64
|
*/
|
|
55
|
-
export declare function createPresignedUpload({ db, targets, tokenPayload, input, }: CreatePresignedUploadOptions): Promise<PresignedUploadResponse>;
|
|
65
|
+
export declare function createPresignedUpload({ db, targets, tokenPayload, input, verifyContentType, }: CreatePresignedUploadOptions): Promise<PresignedUploadResponse>;
|
|
56
66
|
export interface ConfirmUploadOptions {
|
|
57
67
|
db: UploadDatabase;
|
|
58
68
|
targets: StorageTargets;
|
|
59
69
|
uploadId: string;
|
|
60
70
|
currentUser: UploadActor | null | undefined;
|
|
71
|
+
/**
|
|
72
|
+
* Checks the object's bytes against the type the client declared for it
|
|
73
|
+
* when it asked for the upload URL, with a ranged read of its head (and of
|
|
74
|
+
* its tail for ZIPs), and records the canonical spelling of that type. A
|
|
75
|
+
* mismatch fails the row and deletes the object. Defaults to `true`.
|
|
76
|
+
*/
|
|
77
|
+
verifyContentType?: boolean;
|
|
61
78
|
}
|
|
62
79
|
/**
|
|
63
80
|
* Client-side confirmation of a direct upload. Verifies that the object
|
|
64
|
-
* exists in storage
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* the
|
|
81
|
+
* exists in storage, that its size matches what the token authorized, and
|
|
82
|
+
* (with `verifyContentType`, the default) that its bytes are the type the
|
|
83
|
+
* client declared, then flips the row `pending` to `completed`. On a
|
|
84
|
+
* mismatch the row is claimed `failed` and the object deleted. Only the user
|
|
85
|
+
* (and organization) the upload was issued to can confirm it.
|
|
68
86
|
*/
|
|
69
|
-
export declare function confirmUpload({ db, targets, uploadId, currentUser, }: ConfirmUploadOptions): Promise<UploadRecord>;
|
|
87
|
+
export declare function confirmUpload({ db, targets, uploadId, currentUser, verifyContentType, }: ConfirmUploadOptions): Promise<UploadRecord>;
|
|
70
88
|
//# sourceMappingURL=services.d.ts.map
|
package/dist/services.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"services.d.ts","sourceRoot":"","sources":["../src/services.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,KAAK,EAGV,cAAc,EACd,cAAc,EACd,YAAY,EACb,MAAM,YAAY,CAAA;AAEnB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAG1D,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,cAAc,CAAA;IACxB,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,8DAA8D;IAC9D,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAA;IAC3C,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAA;IACb,gBAAgB,EAAE,MAAM,EAAE,CAAA;IAC1B,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,QAAQ,EACR,OAAO,EAAE,WAAW,EACpB,MAAM,EACN,WAAW,EACX,cAAc,GACf,EAAE,uBAAuB,GAAG,mBAAmB,CA+B/C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;CAC/B;AAED,MAAM,WAAW,4BAA4B;IAC3C,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,4EAA4E;IAC5E,YAAY,EAAE,kBAAkB,CAAA;IAChC,KAAK,EAAE,0BAA0B,CAAA;IACjC;;;;;;;;OAQG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAUD;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,EAC1C,EAAE,EACF,OAAO,EACP,YAAY,EACZ,KAAK,EACL,iBAAwB,GACzB,EAAE,4BAA4B,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAiDjE;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,WAAW,GAAG,IAAI,GAAG,SAAS,CAAA;IAC3C;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAqBD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,EAClC,EAAE,EACF,OAAO,EACP,QAAQ,EACR,WAAW,EACX,iBAAwB,GACzB,EAAE,oBAAoB,GAAG,OAAO,CAAC,YAAY,CAAC,CA4F9C"}
|
package/dist/services.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isClaimedContentTypeAllowed } from "./detectContentType.js";
|
|
1
2
|
import { UploadError } from "./errors.js";
|
|
2
3
|
import { generateStorageKey } from "./keys.js";
|
|
3
4
|
import {
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
import { resolveProfile } from "./profiles.js";
|
|
9
10
|
import { resolveTarget } from "./targets.js";
|
|
10
11
|
import { createUploadToken } from "./uploadToken.js";
|
|
12
|
+
import { verifyStoredContentType } from "./verifyStoredContentType.js";
|
|
11
13
|
function issueUploadToken({
|
|
12
14
|
profiles,
|
|
13
15
|
profile: profileName,
|
|
@@ -54,10 +56,20 @@ async function createPresignedUpload({
|
|
|
54
56
|
db,
|
|
55
57
|
targets,
|
|
56
58
|
tokenPayload,
|
|
57
|
-
input
|
|
59
|
+
input,
|
|
60
|
+
verifyContentType = true
|
|
58
61
|
}) {
|
|
59
62
|
const size = toBigInt(input.size, "file size");
|
|
60
63
|
assertTokenAllowsFile(tokenPayload, { contentType: input.contentType, size });
|
|
64
|
+
if (verifyContentType && !isClaimedContentTypeAllowed(
|
|
65
|
+
tokenPayload.allowedMimeTypes,
|
|
66
|
+
input.contentType
|
|
67
|
+
)) {
|
|
68
|
+
throw new UploadError(
|
|
69
|
+
"MIME_TYPE_NOT_ALLOWED",
|
|
70
|
+
`File type '${input.contentType}' is not allowed for upload profile '${tokenPayload.profile}'. Types that can carry script must be listed exactly, not through a wildcard.`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
61
73
|
const target = resolveTarget(targets, tokenPayload.target);
|
|
62
74
|
const storageKey = generateStorageKey(input.contentType);
|
|
63
75
|
const upload = await createPendingUpload({
|
|
@@ -80,11 +92,21 @@ async function createPresignedUpload({
|
|
|
80
92
|
}
|
|
81
93
|
return { uploadId: upload.id, ...presigned };
|
|
82
94
|
}
|
|
95
|
+
async function failUpload(db, target, upload) {
|
|
96
|
+
const { count } = await db.upload.updateMany({
|
|
97
|
+
where: { id: upload.id, status: "pending" },
|
|
98
|
+
data: { status: "failed" }
|
|
99
|
+
});
|
|
100
|
+
if (count === 1) {
|
|
101
|
+
await target.delete(upload.storageKey);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
83
104
|
async function confirmUpload({
|
|
84
105
|
db,
|
|
85
106
|
targets,
|
|
86
107
|
uploadId,
|
|
87
|
-
currentUser
|
|
108
|
+
currentUser,
|
|
109
|
+
verifyContentType = true
|
|
88
110
|
}) {
|
|
89
111
|
const upload = await db.upload.findFirst({
|
|
90
112
|
where: { id: uploadId },
|
|
@@ -102,22 +124,35 @@ async function confirmUpload({
|
|
|
102
124
|
throw new UploadError("NOT_IN_STORAGE", "Upload not found in storage.");
|
|
103
125
|
}
|
|
104
126
|
const actualSize = await target.getObjectSize(upload.storageKey);
|
|
127
|
+
const storedUpload = { ...upload, storageKey: upload.storageKey };
|
|
105
128
|
if (actualSize === null || BigInt(actualSize) !== upload.size) {
|
|
106
|
-
|
|
107
|
-
where: { id: uploadId, status: "pending" },
|
|
108
|
-
data: { status: "failed" }
|
|
109
|
-
});
|
|
110
|
-
if (count2 === 1) {
|
|
111
|
-
await target.delete(upload.storageKey);
|
|
112
|
-
}
|
|
129
|
+
await failUpload(db, target, storedUpload);
|
|
113
130
|
throw new UploadError(
|
|
114
131
|
"SIZE_MISMATCH",
|
|
115
132
|
`Uploaded object is ${actualSize ?? "of unknown"} bytes but ${upload.size} bytes were authorized.`
|
|
116
133
|
);
|
|
117
134
|
}
|
|
135
|
+
let mimeType = upload.mimeType;
|
|
136
|
+
if (verifyContentType) {
|
|
137
|
+
const verified = await verifyStoredContentType(target, upload.storageKey, {
|
|
138
|
+
size: actualSize,
|
|
139
|
+
declaredType: upload.mimeType
|
|
140
|
+
});
|
|
141
|
+
if (!verified) {
|
|
142
|
+
await failUpload(db, target, storedUpload);
|
|
143
|
+
throw new UploadError(
|
|
144
|
+
"MIME_TYPE_NOT_ALLOWED",
|
|
145
|
+
`Uploaded file is not of the type '${upload.mimeType}' it was declared as, so it was deleted.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
mimeType = verified;
|
|
149
|
+
}
|
|
118
150
|
const { count } = await db.upload.updateMany({
|
|
119
151
|
where: { id: uploadId, status: "pending" },
|
|
120
|
-
data: {
|
|
152
|
+
data: {
|
|
153
|
+
status: "completed",
|
|
154
|
+
...mimeType !== upload.mimeType ? { mimeType } : {}
|
|
155
|
+
}
|
|
121
156
|
});
|
|
122
157
|
const confirmed = await db.upload.findFirst({
|
|
123
158
|
where: { id: uploadId },
|
package/dist/types.d.ts
CHANGED
|
@@ -63,6 +63,7 @@ export interface UploadWhere {
|
|
|
63
63
|
export interface UploadUpdateData {
|
|
64
64
|
status?: UploadStatus;
|
|
65
65
|
size?: bigint;
|
|
66
|
+
mimeType?: string;
|
|
66
67
|
storageKey?: string | null;
|
|
67
68
|
}
|
|
68
69
|
/**
|
|
@@ -157,6 +158,15 @@ export interface StorageProvider {
|
|
|
157
158
|
}): Promise<void>;
|
|
158
159
|
/** Read the whole object into memory. */
|
|
159
160
|
read(key: string): Promise<Buffer>;
|
|
161
|
+
/**
|
|
162
|
+
* Read `length` bytes starting at `offset` (fewer when the object ends
|
|
163
|
+
* first). Optional; checking a direct upload's content type falls back to
|
|
164
|
+
* `read()` when a provider does not implement it.
|
|
165
|
+
*/
|
|
166
|
+
readRange?(key: string, range: {
|
|
167
|
+
offset: number;
|
|
168
|
+
length: number;
|
|
169
|
+
}): Promise<Buffer>;
|
|
160
170
|
/**
|
|
161
171
|
* Stream the object. Optional; the serve route falls back to `read()` when
|
|
162
172
|
* a provider does not implement it.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { StorageProvider } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks an object that was uploaded straight to storage against the type
|
|
4
|
+
* the client declared for it when it asked for the upload URL. The declared
|
|
5
|
+
* type was checked against the upload profile then, and is the
|
|
6
|
+
* `Content-Type` the object was stored with, so the bytes must be that type
|
|
7
|
+
* (or an alias of it). Reads the first `DETECT_CONTENT_TYPE_BYTES`, plus the
|
|
8
|
+
* last `DETECT_CONTENT_TYPE_TAIL_BYTES` when the head is a ZIP, plus the
|
|
9
|
+
* start of the ZIP's central directory when that begins before the tail
|
|
10
|
+
* (at most `MAX_ZIP_DIRECTORY_BYTES`).
|
|
11
|
+
*
|
|
12
|
+
* Returns the canonical type to record, or `null` when the bytes aren't the
|
|
13
|
+
* declared type.
|
|
14
|
+
*/
|
|
15
|
+
export declare function verifyStoredContentType(target: StorageProvider, key: string, { size, declaredType }: {
|
|
16
|
+
size: number;
|
|
17
|
+
declaredType: string;
|
|
18
|
+
}): Promise<string | null>;
|
|
19
|
+
//# sourceMappingURL=verifyStoredContentType.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifyStoredContentType.d.ts","sourceRoot":"","sources":["../src/verifyStoredContentType.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAmBjD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,eAAe,EACvB,GAAG,EAAE,MAAM,EACX,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAC7D,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAsCxB"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
acceptedContentType,
|
|
3
|
+
DETECT_CONTENT_TYPE_BYTES,
|
|
4
|
+
DETECT_CONTENT_TYPE_TAIL_BYTES,
|
|
5
|
+
findZipCentralDirectory
|
|
6
|
+
} from "./detectContentType.js";
|
|
7
|
+
const ZIP_LOCAL_HEADER = [80, 75, 3, 4];
|
|
8
|
+
async function readRange(target, key, offset, length) {
|
|
9
|
+
if (target.readRange) {
|
|
10
|
+
return target.readRange(key, { offset, length });
|
|
11
|
+
}
|
|
12
|
+
return (await target.read(key)).subarray(offset, offset + length);
|
|
13
|
+
}
|
|
14
|
+
async function verifyStoredContentType(target, key, { size, declaredType }) {
|
|
15
|
+
const headLength = Math.min(size, DETECT_CONTENT_TYPE_BYTES);
|
|
16
|
+
const head = await readRange(target, key, 0, headLength);
|
|
17
|
+
let tail;
|
|
18
|
+
const isZip = ZIP_LOCAL_HEADER.every((byte, i) => head[i] === byte);
|
|
19
|
+
if (isZip && size > head.length) {
|
|
20
|
+
const tailLength = Math.min(size, DETECT_CONTENT_TYPE_TAIL_BYTES);
|
|
21
|
+
const tailStart = size - tailLength;
|
|
22
|
+
tail = await readRange(target, key, tailStart, tailLength);
|
|
23
|
+
const directory = findZipCentralDirectory(tail);
|
|
24
|
+
if (directory && directory.offset < tailStart && directory.offset + directory.size > tailStart) {
|
|
25
|
+
const missing = await readRange(
|
|
26
|
+
target,
|
|
27
|
+
key,
|
|
28
|
+
directory.offset,
|
|
29
|
+
tailStart - directory.offset
|
|
30
|
+
);
|
|
31
|
+
const joined = new Uint8Array(missing.length + tail.length);
|
|
32
|
+
joined.set(missing);
|
|
33
|
+
joined.set(tail, missing.length);
|
|
34
|
+
tail = joined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return acceptedContentType(head, [declaredType], declaredType, { tail });
|
|
38
|
+
}
|
|
39
|
+
export {
|
|
40
|
+
verifyStoredContentType
|
|
41
|
+
};
|
package/dist/webhooks/s3.d.ts
CHANGED
|
@@ -17,21 +17,30 @@ export interface S3Event {
|
|
|
17
17
|
Records: S3EventRecord[];
|
|
18
18
|
}
|
|
19
19
|
export type S3EventOutcome = 'completed' | 'failed' | 'ignored' | 'not-found' | 'no-target';
|
|
20
|
+
export interface ProcessS3EventRecordOptions {
|
|
21
|
+
db: UploadDatabase;
|
|
22
|
+
targets: StorageTargets;
|
|
23
|
+
/**
|
|
24
|
+
* Checks the object's bytes against the type declared when the upload URL
|
|
25
|
+
* was issued, as `confirmUpload` does. Defaults to `true`.
|
|
26
|
+
*/
|
|
27
|
+
verifyContentType?: boolean;
|
|
28
|
+
}
|
|
20
29
|
/**
|
|
21
30
|
* Settles the `pending` row for one `s3:ObjectCreated:*` record. The row is
|
|
22
31
|
* matched on target plus storage key, the object's size is compared with
|
|
23
|
-
* the size authorized at presign time
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
32
|
+
* the size authorized at presign time and, with `verifyContentType` (the
|
|
33
|
+
* default), its bytes with the type declared then, and the row is flipped
|
|
34
|
+
* `pending` to `completed` (or claimed `failed` and the object deleted on a
|
|
35
|
+
* mismatch). Both transitions are conditional, so a duplicate notification
|
|
36
|
+
* or a row the cleanup job already claimed is left alone.
|
|
27
37
|
*/
|
|
28
|
-
export declare function processS3EventRecord(record: S3EventRecord, { db, targets }:
|
|
29
|
-
db: UploadDatabase;
|
|
30
|
-
targets: StorageTargets;
|
|
31
|
-
}): Promise<S3EventOutcome>;
|
|
38
|
+
export declare function processS3EventRecord(record: S3EventRecord, { db, targets, verifyContentType }: ProcessS3EventRecordOptions): Promise<S3EventOutcome>;
|
|
32
39
|
export interface S3WebhookOptions {
|
|
33
40
|
db: UploadDatabase;
|
|
34
41
|
targets: StorageTargets;
|
|
42
|
+
/** See `ProcessS3EventRecordOptions`. Defaults to `true`. */
|
|
43
|
+
verifyContentType?: boolean;
|
|
35
44
|
/** The trusted SNS topic. Messages from any other topic are rejected. */
|
|
36
45
|
topicArn: string;
|
|
37
46
|
fetchCertificate?: FetchCertificate;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/webhooks/s3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/webhooks/s3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AASjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAA;AAEhD,uEAAuE;AACvE,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,EAAE,EAAE;QACF,MAAM,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,CAAA;QACxB,MAAM,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAA;KACvC,CAAA;CACF;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,aAAa,EAAE,CAAA;CACzB;AAsBD,MAAM,MAAM,cAAc,GACxB,WAAW,GAAG,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAA;AAkChE,MAAM,WAAW,2BAA2B;IAC1C,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,aAAa,EACrB,EAAE,EAAE,EAAE,OAAO,EAAE,iBAAwB,EAAE,EAAE,2BAA2B,GACrE,OAAO,CAAC,cAAc,CAAC,CA2EzB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,cAAc,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,6DAA6D;IAC7D,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,cAAc,GAAG,0BAA0B,GAAG,yBAAyB,CAAA;IAC7E,QAAQ,EAAE,cAAc,EAAE,CAAA;CAC3B;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CA+D1B"}
|
package/dist/webhooks/s3.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UploadError } from "../errors.js";
|
|
2
|
+
import { verifyStoredContentType } from "../verifyStoredContentType.js";
|
|
2
3
|
import {
|
|
3
4
|
assertTrustedSnsUrl,
|
|
4
5
|
isSnsMessage,
|
|
@@ -28,7 +29,7 @@ function matchTarget(targets, bucket, key) {
|
|
|
28
29
|
}
|
|
29
30
|
return best ? { name: best.name, storageKey: key.slice(best.keyPrefix.length) } : null;
|
|
30
31
|
}
|
|
31
|
-
async function processS3EventRecord(record, { db, targets }) {
|
|
32
|
+
async function processS3EventRecord(record, { db, targets, verifyContentType = true }) {
|
|
32
33
|
if (!record.eventName.startsWith("ObjectCreated")) {
|
|
33
34
|
return "ignored";
|
|
34
35
|
}
|
|
@@ -51,7 +52,7 @@ async function processS3EventRecord(record, { db, targets }) {
|
|
|
51
52
|
const target = targets[match.name];
|
|
52
53
|
const reportedSize = record.s3.object.size;
|
|
53
54
|
const actualSize = typeof reportedSize === "number" ? reportedSize : await target.getObjectSize(match.storageKey);
|
|
54
|
-
|
|
55
|
+
const fail = async () => {
|
|
55
56
|
const { count: count2 } = await db.upload.updateMany({
|
|
56
57
|
where: { id: upload.id, status: "pending" },
|
|
57
58
|
data: { status: "failed" }
|
|
@@ -60,10 +61,28 @@ async function processS3EventRecord(record, { db, targets }) {
|
|
|
60
61
|
await target.delete(match.storageKey);
|
|
61
62
|
}
|
|
62
63
|
return "failed";
|
|
64
|
+
};
|
|
65
|
+
if (actualSize === null || BigInt(actualSize) !== upload.size) {
|
|
66
|
+
return fail();
|
|
67
|
+
}
|
|
68
|
+
let mimeType = upload.mimeType;
|
|
69
|
+
if (verifyContentType) {
|
|
70
|
+
const verified = await verifyStoredContentType(target, match.storageKey, {
|
|
71
|
+
size: actualSize,
|
|
72
|
+
declaredType: upload.mimeType
|
|
73
|
+
});
|
|
74
|
+
if (!verified) {
|
|
75
|
+
return fail();
|
|
76
|
+
}
|
|
77
|
+
mimeType = verified;
|
|
63
78
|
}
|
|
64
79
|
const { count } = await db.upload.updateMany({
|
|
65
80
|
where: { id: upload.id, status: "pending" },
|
|
66
|
-
data: {
|
|
81
|
+
data: {
|
|
82
|
+
status: "completed",
|
|
83
|
+
size: BigInt(actualSize),
|
|
84
|
+
...mimeType !== upload.mimeType ? { mimeType } : {}
|
|
85
|
+
}
|
|
67
86
|
});
|
|
68
87
|
return count === 1 ? "completed" : "ignored";
|
|
69
88
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cedarjs/uploads",
|
|
3
|
-
"version": "8.0.0-canary.
|
|
3
|
+
"version": "8.0.0-canary.3403",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/cedarjs/cedar.git",
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"test:watch": "vitest watch"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@cedarjs/api": "8.0.0-canary.
|
|
47
|
-
"@cedarjs/context": "8.0.0-canary.
|
|
48
|
-
"@cedarjs/graphql-server": "8.0.0-canary.
|
|
46
|
+
"@cedarjs/api": "8.0.0-canary.3403",
|
|
47
|
+
"@cedarjs/context": "8.0.0-canary.3403",
|
|
48
|
+
"@cedarjs/graphql-server": "8.0.0-canary.3403",
|
|
49
49
|
"@fastify/multipart": "9.4.0",
|
|
50
50
|
"graphql": "16.14.2",
|
|
51
51
|
"mime-types": "2.1.35"
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"@arethetypeswrong/cli": "0.18.5",
|
|
56
56
|
"@aws-sdk/client-s3": "3.1148.0",
|
|
57
57
|
"@aws-sdk/s3-request-presigner": "3.1148.0",
|
|
58
|
-
"@cedarjs/framework-tools": "8.0.0-canary.
|
|
59
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
58
|
+
"@cedarjs/framework-tools": "8.0.0-canary.3403",
|
|
59
|
+
"@cedarjs/web": "8.0.0-canary.3403",
|
|
60
60
|
"@prisma/adapter-better-sqlite3": "7.10.0",
|
|
61
61
|
"@prisma/client": "7.10.0",
|
|
62
62
|
"@testing-library/dom": "10.4.2",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
"@apollo/client": "4.3.3",
|
|
91
91
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
92
92
|
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
|
93
|
-
"@cedarjs/web": "8.0.0-canary.
|
|
93
|
+
"@cedarjs/web": "8.0.0-canary.3403",
|
|
94
94
|
"@uppy/aws-s3": "^6.0.0",
|
|
95
95
|
"@uppy/core": "^6.0.0",
|
|
96
96
|
"@uppy/dashboard": "^6.0.0",
|