Skip to content

Commit 95077db

Browse files
authored
Merge pull request #161 from codelitdev/rajat1saxena/issue159
Large file uploads via TUS
2 parents 07375b9 + 2d3730f commit 95077db

36 files changed

Lines changed: 1326 additions & 136 deletions

.changeset/good-books-double.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"medialit": minor
3+
---
4+
5+
API key and signature are passed via header instead of request body

.github/workflows/codeql.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: "CodeQL"
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
schedule:
9+
- cron: "51 18 * * 2"
10+
11+
jobs:
12+
analyze:
13+
name: Analyze
14+
runs-on: ubuntu-latest
15+
permissions:
16+
actions: read
17+
contents: read
18+
security-events: write
19+
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
language: [ javascript ]
24+
25+
steps:
26+
- name: Checkout
27+
uses: actions/checkout@v3
28+
29+
- name: Initialize CodeQL
30+
uses: github/codeql-action/init@v2
31+
with:
32+
languages: ${{ matrix.language }}
33+
queries: +security-and-quality
34+
35+
- name: Autobuild
36+
uses: github/codeql-action/autobuild@v2
37+
38+
- name: Perform CodeQL Analysis
39+
uses: github/codeql-action/analyze@v2
40+
with:
41+
category: "/language:${{ matrix.language }}"

apps/api/__tests__/media/handlers.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ describe("Media handlers", () => {
8080
},
8181
body: {},
8282
query: {},
83+
headers: {},
8384
};
8485

8586
const res = {
@@ -97,6 +98,7 @@ describe("Media handlers", () => {
9798
);
9899

99100
const response = await uploadMedia(req, res, () => {});
101+
console.log("Response", response);
100102
assert.strictEqual(response.code, 200);
101103
});
102104
});

apps/api/__tests__/media/storage-middleware.test.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
maxStorageAllowedNotSubscribed,
88
maxStorageAllowedSubscribed,
99
} from "../../src/config/constants";
10+
import { NOT_ENOUGH_STORAGE } from "../../src/config/strings";
1011

1112
describe("storageValidation middleware", () => {
1213
afterEach(() => {
@@ -108,11 +109,8 @@ describe("storageValidation middleware", () => {
108109
};
109110

110111
const response = await storageValidation(req, res, next);
111-
assert.strictEqual(response.code, 400);
112-
assert.strictEqual(
113-
response.data.error,
114-
"You do not have enough storage space in your account to upload this file",
115-
);
112+
assert.strictEqual(response.code, 403);
113+
assert.strictEqual(response.data.error, NOT_ENOUGH_STORAGE);
116114
assert.strictEqual(nextCalled, false);
117115
});
118116

@@ -145,11 +143,8 @@ describe("storageValidation middleware", () => {
145143
};
146144

147145
const response = await storageValidation(req, res, next);
148-
assert.strictEqual(response.code, 400);
149-
assert.strictEqual(
150-
response.data.error,
151-
"You do not have enough storage space in your account to upload this file",
152-
);
146+
assert.strictEqual(response.code, 403);
147+
assert.strictEqual(response.data.error, NOT_ENOUGH_STORAGE);
153148
assert.strictEqual(nextCalled, false);
154149
});
155150

apps/api/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@
3838
"@medialit/models": "workspace:*",
3939
"@medialit/thumbnail": "workspace:*",
4040
"@medialit/utils": "workspace:^0.1.0",
41+
"@tus/file-store": "^2.0.0",
42+
"@tus/server": "^2.3.0",
4143
"aws-sdk": "^2.1692.0",
4244
"cors": "^2.8.5",
4345
"dotenv": "^16.4.7",
44-
"express": "^4.18.2",
46+
"express": "^4.2.0",
4547
"express-fileupload": "^1.3.1",
4648
"joi": "^17.6.0",
4749
"mongoose": "^8.0.1",

apps/api/src/apikey/middleware.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
import {
2-
BAD_REQUEST,
3-
SUBSCRIPTION_NOT_VALID,
4-
UNAUTHORISED,
5-
} from "../config/strings";
1+
import { BAD_REQUEST, UNAUTHORISED } from "../config/strings";
62
import { getApiKeyUsingKeyId } from "./queries";
73
import { getUser } from "../user/queries";
84
import { Apikey } from "@medialit/models";
5+
import logger from "../services/log";
96

107
export default async function apikey(
118
req: any,
129
res: any,
1310
next: (...args: any[]) => void,
1411
) {
15-
const reqKey = req.body.apikey;
12+
const reqKey = req.body?.apikey || req.headers["x-medialit-apikey"];
1613

1714
if (!reqKey) {
15+
logger.error({}, "API key is missing");
1816
return res.status(400).json({ error: BAD_REQUEST });
1917
}
2018

@@ -23,13 +21,6 @@ export default async function apikey(
2321
return res.status(401).json({ error: UNAUTHORISED });
2422
}
2523

26-
// const isSubscriptionValid = await validateSubscription(
27-
// apiKey!.userId.toString(),
28-
// );
29-
// if (!isSubscriptionValid) {
30-
// return res.status(403).json({ error: SUBSCRIPTION_NOT_VALID });
31-
// }
32-
3324
req.user = await getUser(apiKey!.userId.toString());
3425
req.apikey = apiKey.key;
3526

apps/api/src/config/constants.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ export const tempFileDirForUploads = process.env.TEMP_FILE_DIR_FOR_UPLOADS;
66
export const maxFileUploadSizeSubscribed = process.env
77
.MAX_UPLOAD_SIZE_SUBSCRIBED
88
? +process.env.MAX_UPLOAD_SIZE_SUBSCRIBED
9-
: 2147483648;
9+
: 2147483648; // 2GB
1010
export const maxFileUploadSizeNotSubscribed = process.env
1111
.MAX_UPLOAD_SIZE_NOT_SUBSCRIBED
1212
? +process.env.MAX_UPLOAD_SIZE_NOT_SUBSCRIBED
13-
: 52428800;
13+
: 52428800; // 50MB
1414
export const maxStorageAllowedSubscribed = process.env
1515
.MAX_STORAGE_ALLOWED_SUBSCRIBED
1616
? +process.env.MAX_STORAGE_ALLOWED_SUBSCRIBED
17-
: 107374182400;
17+
: 107374182400; // 100GB
1818
export const maxStorageAllowedNotSubscribed = process.env
1919
.MAX_STORAGE_ALLOWED_NOT_SUBSCRIBED
2020
? +process.env.MAX_STORAGE_ALLOWED_NOT_SUBSCRIBED
21-
: 1073741824;
21+
: 1073741824; // 1GB
2222
export const PRESIGNED_URL_VALIDITY_MINUTES = 5;
2323
export const PRESIGNED_URL_LENGTH = 100;
2424
export const MEDIA_ID_LENGTH = 40;
@@ -62,3 +62,8 @@ export const CDN_MAX_AGE = process.env.CDN_MAX_AGE
6262

6363
export const ENDPOINT = USE_CLOUDFRONT ? CLOUDFRONT_ENDPOINT : S3_ENDPOINT;
6464
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/config/strings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export const FILE_IS_REQUIRED = "File is required";
66
export const FILE_SIZE_EXCEEDED = "File size exceeded";
77
export const NOT_FOUND = "Not found";
88
export const PRESIGNED_URL_INVALID = "The link is invalid";
9+
export const NOT_ENOUGH_STORAGE =
10+
"Not enough storage space in your account to upload this file";

apps/api/src/index.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import express from "express";
55
import connectToDatabase from "./config/db";
66
import passport from "passport";
77
import mediaRoutes from "./media/routes";
8-
import presignedUrlRoutes from "./presigning/routes";
8+
import signatureRoutes from "./signature/routes";
99
import mediaSettingsRoutes from "./media-settings/routes";
10+
import tusRoutes from "./tus/routes";
1011
import logger from "./services/log";
1112
import { createUser, findByEmail } from "./user/queries";
12-
import { Apikey, Constants, User } from "@medialit/models";
13+
import { Apikey, User } from "@medialit/models";
1314
import { createApiKey } from "./apikey/queries";
1415
import { spawn } from "child_process";
16+
import { Cleanup } from "./tus/cleanup";
1517

1618
connectToDatabase();
1719
const app = express();
@@ -28,7 +30,8 @@ app.get("/health", (req, res) => {
2830
});
2931

3032
app.use("/settings/media", mediaSettingsRoutes(passport));
31-
app.use("/media/presigned", presignedUrlRoutes);
33+
app.use("/media/signature", signatureRoutes);
34+
app.use("/media", tusRoutes);
3235
app.use("/media", mediaRoutes);
3336

3437
const port = process.env.PORT || 80;
@@ -41,6 +44,14 @@ checkDependencies().then(() => {
4144
app.listen(port, () => {
4245
logger.info(`Medialit server running at ${port}`);
4346
});
47+
48+
// Setup background cleanup job for expired tus uploads
49+
setInterval(
50+
async () => {
51+
await Cleanup();
52+
},
53+
1000 * 60 * 60, // 1 hours
54+
);
4455
});
4556

4657
async function checkDependencies() {

apps/api/src/media/handlers.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import logger from "../services/log";
1515
import { Request } from "express";
1616
import mediaService from "./service";
1717
import { getMediaCount as getCount, getTotalSpace } from "./queries";
18-
import { Constants, getSubscriptionStatus } from "@medialit/models";
18+
import { getSubscriptionStatus } from "@medialit/models";
19+
import { getSignatureFromReq } from "../signature/utils";
1920

2021
function validateUploadOptions(req: Request): Joi.ValidationResult {
2122
const uploadSchema = Joi.object({
@@ -69,7 +70,7 @@ export async function uploadMedia(
6970
access,
7071
caption,
7172
group,
72-
signature: req.query.signature,
73+
signature: getSignatureFromReq(req),
7374
});
7475

7576
const media = await mediaService.getMediaDetails({

0 commit comments

Comments
 (0)