Skip to content

Commit 0e71fb0

Browse files
committed
Add integrity check to webresources
Change-type: patch
1 parent e3d9eb7 commit 0e71fb0

File tree

2 files changed

+61
-4
lines changed

2 files changed

+61
-4
lines changed

src/webresource-handler/actions/commitUpload.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import {
1111
import type { Response } from '../../sbvr-api/sbvr-utils.js';
1212
import { api } from '../../sbvr-api/sbvr-utils.js';
1313
import { permissions } from '../../server-glue/module.js';
14+
import { getChecksumParams } from '../index.js';
1415
import { getMultipartUploadHandler } from '../multipartUpload.js';
1516

1617
const commitUploadAction = async ({
1718
request,
1819
tx,
1920
id,
21+
req,
2022
api: applicationApi,
2123
}: ODataActionArgs): Promise<Response> => {
2224
if (typeof id !== 'number') {
@@ -28,11 +30,14 @@ const commitUploadAction = async ({
2830
const multipartUpload = await getOngoingUpload(request, id, tx);
2931
const handler = getMultipartUploadHandler();
3032

33+
// @ts-expect-error - req.headers is there, we just need to tell them
34+
const checksumPayload = getChecksumParams(req.headers);
3135
const webresource = await handler.multipartUpload.commit({
3236
fileKey: multipartUpload.fileKey,
3337
uploadId: multipartUpload.uploadId,
3438
filename: multipartUpload.filename,
3539
providerCommitData: multipartUpload.providerCommitData,
40+
...checksumPayload,
3641
});
3742

3843
await Promise.all([

src/webresource-handler/index.ts

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,40 @@ import type WebresourceModel from './webresource.js';
2222
import { isMultipartUploadAvailable } from './multipartUpload.js';
2323
import { addAction } from '../sbvr-api/actions.js';
2424
import { beginUpload, commitUpload, cancelUpload } from './actions/index.js';
25+
import type { IncomingHttpHeaders } from 'node:http';
2526

2627
export * from './handlers/index.js';
2728

28-
export interface IncomingFile {
29+
// S3 only supports full checksums (on multipart uploads) for these algorithms
30+
// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#ChecksumTypes
31+
export const supportedChecksumAlgorithms = [
32+
'CRC64NVME',
33+
'CRC32',
34+
'CRC32C',
35+
] as const;
36+
export type SupportedChecksumAlgorithm =
37+
(typeof supportedChecksumAlgorithms)[number];
38+
39+
type ChecksumPayload =
40+
| {
41+
checksum?: undefined;
42+
checksumAlgorithm?: undefined;
43+
}
44+
| {
45+
checksum: string;
46+
checksumAlgorithm: SupportedChecksumAlgorithm;
47+
};
48+
49+
export type IncomingFile = {
2950
fieldname: string;
3051
originalname: string;
3152
encoding: string;
3253
mimetype: string;
3354
stream: stream.Readable;
34-
}
55+
} & ChecksumPayload;
56+
57+
export const checksumHeaderName = 'x-pinejs-checksum';
58+
export const checksumAlgorithmHeaderName = 'x-pinejs-checksum-algorithm';
3559

3660
export interface UploadResponse {
3761
size: number;
@@ -57,12 +81,12 @@ export interface BeginMultipartUploadHandlerResponse {
5781
uploadId: string;
5882
}
5983

60-
export interface CommitMultipartUploadPayload {
84+
export type CommitMultipartUploadPayload = {
6185
fileKey: string;
6286
uploadId: string;
6387
filename: string;
6488
providerCommitData?: Record<string, any>;
65-
}
89+
} & ChecksumPayload;
6690

6791
export interface CancelMultipartUploadPayload {
6892
fileKey: string;
@@ -159,6 +183,31 @@ const getRequestUploadValidator = async (
159183
};
160184
};
161185

186+
export const getChecksumParams = (headers: IncomingHttpHeaders) => {
187+
const checksum = headers[checksumHeaderName];
188+
const checksumAlgorithm = headers[checksumAlgorithmHeaderName] as
189+
| SupportedChecksumAlgorithm
190+
| undefined;
191+
192+
if (checksum == null || checksumAlgorithm == null) {
193+
return { checksum: undefined, checksumAlgorithm: undefined };
194+
}
195+
196+
if (typeof checksum !== 'string' || typeof checksumAlgorithm !== 'string') {
197+
throw new errors.BadRequestError(
198+
`Invalid ${checksumHeaderName} or ${checksumAlgorithmHeaderName} header`,
199+
);
200+
}
201+
202+
if (!supportedChecksumAlgorithms.includes(checksumAlgorithm)) {
203+
throw new errors.BadRequestError(
204+
`Invalid ${checksumAlgorithmHeaderName} header value: ${checksumAlgorithm}`,
205+
);
206+
}
207+
208+
return { checksum, checksumAlgorithm };
209+
};
210+
162211
export const getUploaderMiddlware = (
163212
handler: WebResourceHandler,
164213
): Express.RequestHandler => {
@@ -218,14 +267,17 @@ export const getUploaderMiddlware = (
218267
filestream.resume();
219268
return;
220269
}
270+
const checksumPayload = getChecksumParams(req.headers);
221271
const file: IncomingFile = {
222272
originalname: info.filename,
223273
encoding: info.encoding,
224274
mimetype: info.mimeType,
225275
stream: filestream,
226276
fieldname,
277+
...checksumPayload,
227278
};
228279
const result = await handler.handleFile(file);
280+
229281
req.body[fieldname] = {
230282
filename: info.filename,
231283
content_type: info.mimeType,

0 commit comments

Comments
 (0)