Skip to content

Commit b0a2379

Browse files
author
Rajat Saxena
committed
Fixed signature expiring before upload complete; File size limit check for resumable endpoint; renamed env vars;
1 parent c0e0791 commit b0a2379

File tree

10 files changed

+71
-57
lines changed

10 files changed

+71
-57
lines changed

apps/api/src/config/constants.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ export const maxStorageAllowedNotSubscribed = process.env
1919
.MAX_STORAGE_ALLOWED_NOT_SUBSCRIBED
2020
? +process.env.MAX_STORAGE_ALLOWED_NOT_SUBSCRIBED
2121
: 1073741824; // 1GB
22-
export const PRESIGNED_URL_VALIDITY_MINUTES = 5;
23-
export const PRESIGNED_URL_LENGTH = 100;
22+
export const SIGNATURE_VALIDITY_MINUTES = +(
23+
process.env.SIGNATURE_VALIDITY_MINUTES || "1440"
24+
); // 1 day
25+
export const SIGNATURE_LENGTH = 100;
2426
export const MEDIA_ID_LENGTH = 40;
2527
export const APIKEY_RESTRICTION_REFERRER = "referrer";
2628
export const APIKEY_RESTRICTION_IP = "ipaddress";
@@ -62,8 +64,3 @@ export const CDN_MAX_AGE = process.env.CDN_MAX_AGE
6264

6365
export const ENDPOINT = USE_CLOUDFRONT ? CLOUDFRONT_ENDPOINT : S3_ENDPOINT;
6466
export const HOSTNAME_OVERRIDE = process.env.HOSTNAME_OVERRIDE || ""; // Useful for hosting via Docker
65-
66-
// Tus upload config
67-
export const TUS_UPLOAD_EXPIRATION_HOURS = parseInt(
68-
process.env.TUS_UPLOAD_EXPIRATION_HOURS || "48",
69-
);

apps/api/src/media/handlers.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import Joi from "joi";
22
import {
3-
maxFileUploadSizeNotSubscribed,
4-
maxFileUploadSizeSubscribed,
53
maxStorageAllowedNotSubscribed,
64
maxStorageAllowedSubscribed,
75
} from "../config/constants";
@@ -17,6 +15,7 @@ import mediaService from "./service";
1715
import { getMediaCount as getCount, getTotalSpace } from "./queries";
1816
import { getSubscriptionStatus } from "@medialit/models";
1917
import { getSignatureFromReq } from "../signature/utils";
18+
import getMaxFileUploadSize from "./utils/get-max-file-upload-size";
2019

2120
function validateUploadOptions(req: Request): Joi.ValidationResult {
2221
const uploadSchema = Joi.object({
@@ -28,12 +27,6 @@ function validateUploadOptions(req: Request): Joi.ValidationResult {
2827
return uploadSchema.validate({ caption, access, group });
2928
}
3029

31-
function getMaxFileUploadSize(req: any): number {
32-
return getSubscriptionStatus(req.user)
33-
? maxFileUploadSizeSubscribed
34-
: maxFileUploadSizeNotSubscribed;
35-
}
36-
3730
export async function uploadMedia(
3831
req: any,
3932
res: any,

apps/api/src/media/queries.ts

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import mongoose from "mongoose";
12
import { numberOfRecordsPerPage } from "../config/constants";
23
import GetPageProps from "./GetPageProps";
34
import MediaModel, { MediaWithUserId } from "./model";
@@ -32,31 +33,27 @@ export async function getTotalSpace({
3233
userId,
3334
apikey,
3435
}: {
35-
userId: string;
36+
userId: mongoose.Types.ObjectId;
3637
apikey?: string;
3738
}): Promise<number> {
38-
const result = await MediaModel
39-
// calculate sum of size of all media files
40-
.aggregate([
41-
{
42-
$match: {
43-
userId,
44-
apikey,
45-
},
46-
},
47-
{
48-
$group: {
49-
_id: null,
50-
totalSize: { $sum: "$size" },
51-
},
39+
const query = apikey ? { userId, apikey } : { userId };
40+
const result = await MediaModel.aggregate([
41+
{
42+
$match: query,
43+
},
44+
{
45+
$group: {
46+
_id: null,
47+
totalSize: { $sum: "$size" },
5248
},
53-
{
54-
$project: {
55-
_id: 0,
56-
totalSize: 1,
57-
},
49+
},
50+
{
51+
$project: {
52+
_id: 0,
53+
totalSize: 1,
5854
},
59-
]);
55+
},
56+
]);
6057

6158
if (result.length === 0) {
6259
return 0;

apps/api/src/media/storage-middleware.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { maxStorageAllowedSubscribed } from "../config/constants";
33
import { getSubscriptionStatus, User } from "@medialit/models";
44
import mediaQueries from "./queries";
55
import { NOT_ENOUGH_STORAGE } from "../config/strings";
6+
import mongoose from "mongoose";
67

78
export default async function storageValidation(
89
req: any,
@@ -26,10 +27,10 @@ export default async function storageValidation(
2627

2728
export async function hasEnoughStorage(
2829
size: number,
29-
user: User,
30+
user: User & { _id: mongoose.Types.ObjectId },
3031
): Promise<boolean> {
3132
const totalSpaceOccupied = await mediaQueries.getTotalSpace({
32-
userId: user.id,
33+
userId: user._id,
3334
});
3435
const maxStorageAllowed = getSubscriptionStatus(user)
3536
? maxStorageAllowedSubscribed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {
2+
maxFileUploadSizeNotSubscribed,
3+
maxFileUploadSizeSubscribed,
4+
} from "../../config/constants";
5+
import { getSubscriptionStatus } from "@medialit/models";
6+
7+
export default function getMaxFileUploadSize(req: any): number {
8+
return getSubscriptionStatus(req.user)
9+
? maxFileUploadSizeSubscribed
10+
: maxFileUploadSizeNotSubscribed;
11+
}

apps/api/src/signature/model.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import mongoose from "mongoose";
22
import {
3-
PRESIGNED_URL_LENGTH,
4-
PRESIGNED_URL_VALIDITY_MINUTES,
3+
SIGNATURE_LENGTH,
4+
SIGNATURE_VALIDITY_MINUTES,
55
} from "../config/constants";
66
import { getUniqueId } from "@medialit/utils";
77

@@ -21,15 +21,14 @@ const PreSignedUrlSchema = new mongoose.Schema<PreSignedUrl>(
2121
signature: {
2222
type: String,
2323
required: true,
24-
default: () => getUniqueId(PRESIGNED_URL_LENGTH),
24+
default: () => getUniqueId(SIGNATURE_LENGTH),
2525
},
2626
validTill: {
2727
type: Date,
2828
required: true,
2929
default: () =>
3030
new Date(
31-
new Date().getTime() +
32-
PRESIGNED_URL_VALIDITY_MINUTES * 60000,
31+
new Date().getTime() + SIGNATURE_VALIDITY_MINUTES * 60000,
3332
),
3433
},
3534
group: String,

apps/api/src/tus/finalize.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
import { readFileSync, createReadStream, rmdirSync, existsSync } from "fs";
1+
import {
2+
createReadStream,
3+
existsSync,
4+
copyFileSync,
5+
promises as fsPromises,
6+
} from "fs";
27
import path from "path";
38
import thumbnail from "@medialit/thumbnail";
49
import mongoose from "mongoose";
@@ -79,9 +84,7 @@ export default async function finalizeUpload(uploadId: string) {
7984

8085
const mainFilePath = `${temporaryFolderForWork}/main.${fileExtension}`;
8186

82-
//Copy file from tus store to working directory
83-
const tusFileContent = readFileSync(tusFilePath);
84-
require("fs").writeFileSync(mainFilePath, tusFileContent);
87+
copyFileSync(tusFilePath, mainFilePath);
8588

8689
// Apply WebP conversion if needed
8790
if (useWebP && imagePattern.test(metadata.mimeType)) {
@@ -124,7 +127,7 @@ export default async function finalizeUpload(uploadId: string) {
124127
logger.error({ err }, err.message);
125128
}
126129

127-
rmdirSync(temporaryFolderForWork, { recursive: true });
130+
await fsPromises.rm(temporaryFolderForWork, { recursive: true });
128131

129132
const mediaObject: MediaWithUserId = {
130133
fileName: `main.${fileExtension}`,
@@ -181,26 +184,27 @@ const generateAndUploadThumbnail = async ({
181184
tags: string;
182185
}): Promise<boolean> => {
183186
const thumbPath = `${workingDirectory}/thumb.webp`;
187+
let isGenerated = false;
184188

185-
let isThumbGenerated = false;
186189
if (imagePatternForThumbnailGeneration.test(mimetype)) {
187190
await thumbnail.forImage(originalFilePath, thumbPath);
188-
isThumbGenerated = true;
191+
isGenerated = true;
189192
}
190193
if (videoPattern.test(mimetype)) {
191194
await thumbnail.forVideo(originalFilePath, thumbPath);
192-
isThumbGenerated = true;
195+
isGenerated = true;
193196
}
194197

195-
if (isThumbGenerated) {
198+
if (isGenerated) {
196199
await putObject({
197200
Key: key,
198201
Body: createReadStream(thumbPath),
199202
ContentType: "image/webp",
200203
ACL: USE_CLOUDFRONT ? "private" : "public-read",
201204
Tagging: tags,
202205
});
206+
await fsPromises.rm(thumbPath);
203207
}
204208

205-
return isThumbGenerated;
209+
return isGenerated;
206210
};

apps/api/src/tus/queries.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import { TUS_UPLOAD_EXPIRATION_HOURS } from "../config/constants";
1+
import { SIGNATURE_VALIDITY_MINUTES } from "../config/constants";
22
import TusUploadModel, { TusUpload } from "./model";
33

44
type TusUploadDocument = any;
55

66
export async function createTusUpload(
77
data: Omit<TusUpload, "uploadOffset" | "isComplete">,
88
): Promise<TusUploadDocument> {
9-
// const uploadId = new mongoose.Types.ObjectId().toString();
109
const expiresAt = new Date();
11-
expiresAt.setHours(expiresAt.getHours() + TUS_UPLOAD_EXPIRATION_HOURS);
10+
const signatureValidityHours = SIGNATURE_VALIDITY_MINUTES / 60;
11+
expiresAt.setHours(expiresAt.getHours() + signatureValidityHours);
1212

1313
const tusUploadData: TusUpload = {
1414
uploadId: data.uploadId,

apps/api/src/tus/tus-server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import logger from "../services/log";
55
import finalizeUpload from "./finalize";
66
import * as preSignedUrlService from "../signature/service";
77
import {
8+
FILE_SIZE_EXCEEDED,
89
NOT_ENOUGH_STORAGE,
910
PRESIGNED_URL_INVALID,
1011
UNAUTHORISED,
@@ -14,6 +15,7 @@ import { getApiKeyUsingKeyId } from "../apikey/queries";
1415
import { getUser } from "../user/queries";
1516
import { hasEnoughStorage } from "../media/storage-middleware";
1617
import { createTusUpload, updateTusUploadOffset } from "./queries";
18+
import getMaxFileUploadSize from "../media/utils/get-max-file-upload-size";
1719

1820
const store = new FileStore({
1921
directory: `${tempFileDirForUploads}/tus-uploads`,
@@ -41,6 +43,13 @@ export const server = new Server({
4143
const { user, apikey } = req;
4244

4345
try {
46+
const allowedFileSize = getMaxFileUploadSize(req);
47+
if (upload.size > allowedFileSize) {
48+
throw {
49+
status_code: 403,
50+
body: `${FILE_SIZE_EXCEEDED}. Allowed: ${allowedFileSize} bytes`,
51+
};
52+
}
4453
if (!(await hasEnoughStorage(upload.size, user))) {
4554
throw {
4655
status_code: 403,

apps/api/src/user/queries.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { SubscriptionStatus, User } from "@medialit/models";
22
import UserModel from "./model";
3+
import mongoose from "mongoose";
34

4-
export async function getUser(id: string): Promise<User | null> {
5+
export async function getUser(
6+
id: string,
7+
): Promise<(User & { _id: mongoose.Types.ObjectId }) | null> {
58
return UserModel.findById(id);
69
}
710

0 commit comments

Comments
 (0)